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++; /// /// Gets the cached label for the given entity, or creates a new /// (cached) label if it hasn't already been created. The label /// is set on the supplied object. /// /// true iff the label already existed. public bool GetOrAddCachedLabel(ICachedEntity entity) { var id = GetId(entity); if (id == null) throw new InternalError("Attempt to create a null entity for {0}", entity.GetType()); Label existingLabel; if (labelCache.TryGetValue(id, out existingLabel)) { entity.Label = existingLabel; return true; } entity.Label = new Label(NewId()); DefineLabel(entity.Label, id); labelCache[id] = entity.Label; return false; } /// /// 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; } } /// /// Gets the ID belonging to cached entity . /// /// The ID itself is also cached, but unlike the label cache (which is used /// to prevent reextraction/infinite loops), this is a pure performance /// optimization. Moreover, the label cache is injective, which the ID cache /// need not be. /// IId GetId(ICachedEntity entity) { IId id; if (!idCache.TryGetValue(entity, out id)) { id = entity.Id; idCache[entity] = id; } return id; } /// /// 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; } readonly Dictionary labelCache = new Dictionary(); readonly HashSet