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 readonly IExtractor Extractor; /// /// 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 readonly TrapWriter TrapWriter; int GetNewId() => TrapWriter.IdCounter++; /// /// Creates a new entity using the factory. /// /// The entity factory. /// The initializer for the entity. /// The new/existing entity. public Entity CreateEntity(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity { return init == null ? CreateEntity2(factory, init) : CreateNonNullEntity(factory, init); } // A recursion guard against writing to the trap file whilst writing an id to the trap file. bool WritingLabel = false; public void DefineLabel(IEntity entity, TextWriter trapFile) { if (WritingLabel) { // Don't define a label whilst writing a label. PopulateLater(() => DefineLabel(entity, trapFile)); } else { try { WritingLabel = true; entity.DefineLabel(trapFile); } finally { WritingLabel = false; } } } /// /// Creates a new entity using the factory. /// Uses a different cache to , /// and can store null values. /// /// The entity factory. /// The initializer for the entity. /// The new/existing entity. public Entity CreateEntity2(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity { using (StackGuard) { var entity = factory.Create(this, init); if (entityLabelCache.TryGetValue(entity, out var label)) { entity.Label = label; } else { label = GetNewLabel(); entity.Label = label; entityLabelCache[entity] = label; DefineLabel(entity, TrapWriter.Writer); if (entity.NeedsPopulation) Populate(init as ISymbol, entity); #if DEBUG_LABELS using (var id = new StringWriter()) { entity.WriteId(id); CheckEntityHasUniqueLabel(id.ToString(), entity); } #endif } return entity; } } #if DEBUG_LABELS private void CheckEntityHasUniqueLabel(string id, ICachedEntity entity) { if (idLabelCache.TryGetValue(id, out var originalEntity)) { 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()); private Entity CreateNonNullEntity(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity { if (objectEntityCache.TryGetValue(init, out var cached)) return (Entity)cached; using (StackGuard) { var label = GetNewLabel(); var entity = factory.Create(this, init); entity.Label = label; objectEntityCache[init] = entity; DefineLabel(entity, TrapWriter.Writer); if (entity.NeedsPopulation) Populate(init as ISymbol, entity); #if DEBUG_LABELS using (var id = new StringWriter()) { entity.WriteId(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; } else { 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 readonly Dictionary idLabelCache = new Dictionary(); #endif readonly Dictionary objectEntityCache = new Dictionary(); readonly Dictionary entityLabelCache = new Dictionary(); readonly HashSet