using Microsoft.CodeAnalysis; using System; using System.Diagnostics.CodeAnalysis; using Semmle.Extraction.Entities; using System.Collections.Generic; namespace Semmle.Extraction.CSharp { /// /// State that needs to be available throughout the extraction process. /// There is one Context object per trap output file. /// internal class Context : Extraction.Context { /// /// The program database provided by Roslyn. /// There's one per syntax tree, which makes things awkward. /// public SemanticModel GetModel(SyntaxNode node) { // todo: when this context belongs to a SourceScope, the syntax tree can be retrieved from the scope, and // the node parameter could be removed. Is there any case when we pass in a node that's not from the current // tree? if (cachedModel is null || node.SyntaxTree != cachedModel.SyntaxTree) { cachedModel = Compilation.GetSemanticModel(node.SyntaxTree); } return cachedModel; } private SemanticModel? cachedModel; /// /// The current compilation unit. /// public Compilation Compilation { get; } internal CommentProcessor CommentGenerator { get; } = new CommentProcessor(); public Context(Extraction.Extractor e, Compilation c, TrapWriter trapWriter, IExtractionScope scope, bool addAssemblyTrapPrefix) : base(e, trapWriter, addAssemblyTrapPrefix) { Compilation = c; this.scope = scope; } public bool FromSource => scope is SourceScope; private readonly IExtractionScope scope; public bool IsAssemblyScope => scope is AssemblyScope; private SyntaxTree? SourceTree => scope is SourceScope sc ? sc.SourceTree : null; /// /// Whether the given symbol needs to be defined in this context. /// This is the case if the symbol is contained in the source/assembly, or /// of the symbol is a constructed generic. /// /// The symbol to populate. public bool Defines(ISymbol symbol) => !SymbolEqualityComparer.Default.Equals(symbol, symbol.OriginalDefinition) || scope.InScope(symbol); public override void WithDuplicationGuard(Key key, Action a) { if (IsAssemblyScope) { // No need for a duplication guard when extracting assemblies, // and the duplication guard could lead to method bodies being missed // depending on trap import order. a(); } else { base.WithDuplicationGuard(key, a); } } public override Extraction.Entities.Location CreateLocation() { return SourceTree is null ? GeneratedLocation.Create(this) : CreateLocation(Microsoft.CodeAnalysis.Location.Create(SourceTree, Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(0, 0))); } public override Extraction.Entities.Location CreateLocation(Microsoft.CodeAnalysis.Location? location) { return (location is null || location.Kind == LocationKind.None) ? GeneratedLocation.Create(this) : location.IsInSource ? Entities.NonGeneratedSourceLocation.Create(this, location) : Entities.Assembly.Create(this, location); } /// /// Register a program entity which can be bound to comments. /// /// Extractor context. /// Program entity. /// Location of the entity. public void BindComments(Entity entity, Microsoft.CodeAnalysis.Location? l) { var duplicationGuardKey = GetCurrentTagStackKey(); CommentGenerator.AddElement(entity.Label, duplicationGuardKey, l); } protected override bool IsEntityDuplicationGuarded(IEntity entity, [NotNullWhen(true)] out Extraction.Entities.Location? loc) { if (CreateLocation(entity.ReportingLocation) is Entities.NonGeneratedSourceLocation l) { loc = l; return true; } loc = null; return false; } private readonly HashSet