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 objectEntityCache = new Dictionary(); private readonly IDictionary symbolEntityCache = new Dictionary(10000, SymbolEqualityComparer.IncludeNullability); private readonly HashSet