using Microsoft.CodeAnalysis;
using Semmle.Extraction.Entities;
using Semmle.Util.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
namespace Semmle.Extraction
{
///
/// State that needs to be available throughout the extraction process.
/// There is one Context object per trap output file.
///
public class Context
{
///
/// Access various extraction functions, e.g. logger, trap writer.
///
public Extractor Extractor { get; }
///
/// Access to the trap file.
///
public TrapWriter TrapWriter { get; }
///
/// Holds if assembly information should be prefixed to TRAP labels.
///
public bool ShouldAddAssemblyTrapPrefix { get; }
private int GetNewId() => TrapWriter.IdCounter++;
// A recursion guard against writing to the trap file whilst writing an id to the trap file.
private bool writingLabel = false;
protected void DefineLabel(IEntity entity)
{
if (writingLabel)
{
// Don't define a label whilst writing a label.
PopulateLater(() => DefineLabel(entity));
}
else
{
try
{
writingLabel = true;
entity.DefineLabel(TrapWriter.Writer, Extractor);
}
finally
{
writingLabel = false;
}
}
}
#if DEBUG_LABELS
private void CheckEntityHasUniqueLabel(string id, CachedEntity entity)
{
if (idLabelCache.ContainsKey(id))
{
this.Extractor.Message(new Message("Label collision for " + id, entity.Label.ToString(), CreateLocation(entity.ReportingLocation), "", Severity.Warning));
}
else
{
idLabelCache[id] = entity;
}
}
#endif
protected Label GetNewLabel() => new Label(GetNewId());
internal TEntity CreateEntity(CachedEntityFactory factory, object cacheKey, TInit init)
where TEntity : CachedEntity =>
cacheKey is ISymbol s ? CreateEntity(factory, s, init, symbolEntityCache) : CreateEntity(factory, cacheKey, init, objectEntityCache);
internal TEntity CreateEntityFromSymbol(CachedEntityFactory factory, TSymbol init)
where TSymbol : ISymbol
where TEntity : CachedEntity => CreateEntity(factory, init, init, symbolEntityCache);
///
/// Creates and populates a new entity, or returns the existing one from the cache.
///
/// The entity factory.
/// The key used for caching.
/// The initializer for the entity.
/// The dictionary to use for caching.
/// The new/existing entity.
private TEntity CreateEntity(CachedEntityFactory factory, TCacheKey cacheKey, TInit init, IDictionary dictionary)
where TCacheKey : notnull
where TEntity : CachedEntity
{
if (dictionary.TryGetValue(cacheKey, out var cached))
return (TEntity)cached;
using (StackGuard)
{
var label = GetNewLabel();
var entity = factory.Create(this, init);
entity.Label = label;
dictionary[cacheKey] = entity;
DefineLabel(entity);
if (entity.NeedsPopulation)
Populate(init as ISymbol, entity);
#if DEBUG_LABELS
using var id = new StringWriter();
entity.WriteQuotedId(id);
CheckEntityHasUniqueLabel(id.ToString(), entity);
#endif
return entity;
}
}
///
/// Creates a fresh label with ID "*", and set it on the
/// supplied object.
///
internal void AddFreshLabel(Entity entity)
{
entity.Label = GetNewLabel();
entity.DefineFreshLabel(TrapWriter.Writer);
}
#if DEBUG_LABELS
private readonly Dictionary idLabelCache = new Dictionary();
#endif
private readonly IDictionary