using Microsoft.CodeAnalysis; using System.Linq; using Semmle.Extraction.CommentProcessing; using System.Collections.Generic; using System; using Semmle.Util.Logging; using Semmle.Extraction.Entities; namespace Semmle.Extraction { /// /// State which needs needs to be available throughout the extraction process. /// There is one Context object per trap output file. /// public class Context { /// /// Interface to 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 Model(SyntaxNode node) { if (cachedModel == null || node.SyntaxTree != cachedModel.SyntaxTree) { cachedModel = Compilation.GetSemanticModel(node.SyntaxTree); } return cachedModel; } SemanticModel cachedModel; /// /// Access to the trap file. /// public readonly TrapWriter TrapWriter; int NewId() => 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); } /// /// 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 { var id = entity.Id; #if DEBUG_LABELS CheckEntityHasUniqueLabel(id, entity); #endif label = new Label(NewId()); entity.Label = label; entityLabelCache[entity] = label; DefineLabel(label, id); if (entity.NeedsPopulation) Populate(init as ISymbol, entity); } return entity; } } #if DEBUG_LABELS private void CheckEntityHasUniqueLabel(IId 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 private Entity CreateNonNullEntity(ICachedEntityFactory factory, Type init) where Entity : ICachedEntity { if (objectEntityCache.TryGetValue(init, out var cached)) return (Entity)cached; using (StackGuard) { var label = new Label(NewId()); var entity = factory.Create(this, init); entity.Label = label; objectEntityCache[init] = entity; var id = entity.Id; DefineLabel(label, id); #if DEBUG_LABELS CheckEntityHasUniqueLabel(id, entity); #endif if (entity.NeedsPopulation) Populate(init as ISymbol, entity); 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) { var label = new Label(NewId()); TrapWriter.Emit(new DefineFreshLabelEmitter(label)); entity.Label = label; } #if DEBUG_LABELS readonly Dictionary idLabelCache = new Dictionary(); #endif readonly Dictionary objectEntityCache = new Dictionary(); readonly Dictionary entityLabelCache = new Dictionary(); readonly HashSet