using Microsoft.CodeAnalysis;
using Semmle.Extraction.CommentProcessing;
using Semmle.Extraction.Entities;
using Semmle.Util.Logging;
using System;
using System.Collections.Generic;
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 IExtractor Extractor { get; }
///
/// The program database provided by Roslyn.
/// There's one per syntax tree, which makes things awkward.
///
public SemanticModel GetModel(SyntaxNode node)
{
if (cachedModel == null || node.SyntaxTree != cachedModel.SyntaxTree)
{
cachedModel = Compilation.GetSemanticModel(node.SyntaxTree);
}
return cachedModel;
}
private SemanticModel? cachedModel;
///
/// 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;
public void DefineLabel(IEntity entity, TextWriter trapFile, IExtractor extractor)
{
if (writingLabel)
{
// Don't define a label whilst writing a label.
PopulateLater(() => DefineLabel(entity, trapFile, extractor));
}
else
{
try
{
writingLabel = true;
entity.DefineLabel(trapFile, extractor);
}
finally
{
writingLabel = false;
}
}
}
#if DEBUG_LABELS
private void CheckEntityHasUniqueLabel(string id, ICachedEntity entity)
{
if (idLabelCache.ContainsKey(id))
{
ExtractionError("Label collision for " + id, entity.Label.ToString(), Entities.Location.Create(this, entity.ReportingLocation), "", Severity.Warning);
}
else
{
idLabelCache[id] = entity;
}
}
#endif
public Label GetNewLabel() => new Label(GetNewId());
public TEntity CreateEntity(ICachedEntityFactory factory, object cacheKey, TInit init)
where TEntity : ICachedEntity =>
cacheKey is ISymbol s ? CreateEntity(factory, s, init, symbolEntityCache) : CreateEntity(factory, cacheKey, init, objectEntityCache);
public TEntity CreateEntityFromSymbol(ICachedEntityFactory factory, TSymbol init)
where TSymbol : ISymbol
where TEntity : ICachedEntity => 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(ICachedEntityFactory factory, TCacheKey cacheKey, TInit init, IDictionary dictionary)
where TCacheKey : notnull
where TEntity : ICachedEntity
{
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, TrapWriter.Writer, Extractor);
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;
}
}
///
/// Should the given entity be extracted?
/// A second call to this method will always return false,
/// on the assumption that it would have been extracted on the first call.
///
/// This is used to track the extraction of generics, which cannot be extracted
/// in a top-down manner.
///
/// The entity to extract.
/// True only on the first call for a particular entity.
public bool ExtractGenerics(ICachedEntity entity)
{
if (extractedGenerics.Contains(entity.Label))
{
return false;
}
extractedGenerics.Add(entity.Label);
return true;
}
///
/// Creates a fresh label with ID "*", and set it on the
/// supplied object.
///
public void AddFreshLabel(IEntity entity)
{
entity.Label = GetNewLabel();
entity.DefineFreshLabel(TrapWriter.Writer);
}
#if DEBUG_LABELS
private readonly Dictionary idLabelCache = new Dictionary();
#endif
private readonly IDictionary