diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs index 66e45387d876..fe01e0f9b58d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Accessor.cs @@ -63,8 +63,10 @@ public override void Populate(TextWriter trapFile) trapFile.accessors(this, kind, Symbol.Name, parent, unboundAccessor); - foreach (var l in Locations) - trapFile.accessor_location(this, l); + if (Context.ExtractLocation(Symbol)) + { + WriteLocationsToTrap(trapFile.accessor_location, this, Locations); + } Overrides(trapFile); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs index e4799c05507f..0e1b756a37ce 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Attribute.cs @@ -59,11 +59,11 @@ public override void Populate(TextWriter trapFile) { var type = Type.Create(Context, Symbol.AttributeClass); trapFile.attributes(this, kind, type.TypeRef, entity); - trapFile.attribute_location(this, Location); + WriteLocationToTrap(trapFile.attribute_location, this, Location); if (attributeSyntax is not null) { - trapFile.attribute_location(this, Assembly.CreateOutputAssembly(Context)); + WriteLocationToTrap(trapFile.attribute_location, this, Assembly.CreateOutputAssembly(Context)); TypeMention.Create(Context, attributeSyntax.Name, this, type); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedEntity.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedEntity.cs index 96bef9732117..2002fe0f1d7a 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedEntity.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedEntity.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.CodeAnalysis; @@ -52,6 +54,22 @@ public string DebugContents } } + protected static void WriteLocationToTrap(Action writeAction, T1 entity, Location l) + { + if (l is not EmptyLocation) + { + writeAction(entity, l); + } + } + + protected static void WriteLocationsToTrap(Action writeAction, T1 entity, IEnumerable locations) + { + foreach (var loc in locations) + { + WriteLocationToTrap(writeAction, entity, loc); + } + } + public override bool NeedsPopulation { get; } public override int GetHashCode() => Symbol is null ? 0 : Symbol.GetHashCode(); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedSymbol.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedSymbol.cs index c39eb6076b57..92861e97fdd8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedSymbol.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Base/CachedSymbol.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs index 3c4cdcffc0e9..4e63f5535aad 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentBlock.cs @@ -11,7 +11,7 @@ private CommentBlock(Context cx, Comments.CommentBlock init) public override void Populate(TextWriter trapFile) { trapFile.commentblock(this); - trapFile.commentblock_location(this, Context.CreateLocation(Symbol.Location)); + WriteLocationToTrap(trapFile.commentblock_location, this, Context.CreateLocation(Symbol.Location)); Symbol.CommentLines.ForEach((l, child) => trapFile.commentblock_child(this, l, child)); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs index 7638eefce125..13cd002da79e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs @@ -23,7 +23,7 @@ public override void Populate(TextWriter trapFile) { location = Context.CreateLocation(Location); trapFile.commentline(this, Type == CommentLineType.MultilineContinuation ? CommentLineType.Multiline : Type, Text, RawText); - trapFile.commentline_location(this, location); + WriteLocationToTrap(trapFile.commentline_location, this, location); } public override Microsoft.CodeAnalysis.Location? ReportingLocation => location?.Symbol; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs index c3ce2bb6d29e..045277784695 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs @@ -29,7 +29,7 @@ public override void Populate(TextWriter trapFile) ContainingType!.PopulateGenerics(); trapFile.constructors(this, Symbol.ContainingType.Name, ContainingType, (Constructor)OriginalDefinition); - trapFile.constructor_location(this, Location); + WriteLocationToTrap(trapFile.constructor_location, this, Location); if (MakeSynthetic) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs index b6a9c8e8f1b1..3d07c7d42de9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Destructor.cs @@ -15,7 +15,7 @@ public override void Populate(TextWriter trapFile) ContainingType!.PopulateGenerics(); trapFile.destructors(this, $"~{Symbol.ContainingType.Name}", ContainingType, OriginalDefinition(Context, this, Symbol)); - trapFile.destructor_location(this, Location); + WriteLocationToTrap(trapFile.destructor_location, this, Location); } private static new Destructor OriginalDefinition(Context cx, Destructor original, IMethodSymbol symbol) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs index 888e1ba7304e..8828639820be 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Event.cs @@ -51,8 +51,10 @@ public override void Populate(TextWriter trapFile) TypeMention.Create(Context, syntax.ExplicitInterfaceSpecifier!.Name, this, explicitInterface); } - foreach (var l in Locations) - trapFile.event_location(this, l); + if (Context.ExtractLocation(Symbol)) + { + WriteLocationsToTrap(trapFile.event_location, this, Locations); + } foreach (var syntaxType in declSyntaxReferences .OfType() diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs index 1df6be7a2733..254e7c769569 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/EventAccessor.cs @@ -48,8 +48,10 @@ public override void Populate(TextWriter trapFile) trapFile.event_accessors(this, kind, Symbol.Name, parent, unboundAccessor); - foreach (var l in Locations) - trapFile.event_accessor_location(this, l); + if (Context.ExtractLocation(Symbol)) + { + WriteLocationsToTrap(trapFile.event_accessor_location, this, Locations); + } Overrides(trapFile); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs index a5cb6e316f41..93107fc6dab8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs @@ -40,6 +40,7 @@ protected sealed override void Populate(TextWriter trapFile) trapFile.expr_parent_top_level(this, info.Child, info.Parent); else trapFile.expr_parent(this, info.Child, info.Parent); + trapFile.expr_location(this, Location); if (Type.HasValue && !Type.Value.HasObliviousNullability()) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs index 0a91eb57ecdf..61b5c40e6e50 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Field.cs @@ -49,8 +49,7 @@ public override void Populate(TextWriter trapFile) } } - foreach (var l in Locations) - trapFile.field_location(this, l); + WriteLocationsToTrap(trapFile.field_location, this, Locations); if (!IsSourceDeclaration || !Symbol.FromSource()) return; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs index 1235b120253c..e6a188a6ab1e 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Indexer.cs @@ -19,8 +19,10 @@ public override void Populate(TextWriter trapFile) var type = Type.Create(Context, Symbol.Type); trapFile.indexers(this, Symbol.GetName(useMetadataName: true), ContainingType!, type.TypeRef, OriginalDefinition); - foreach (var l in Locations) - trapFile.indexer_location(this, l); + if (Context.ExtractLocation(Symbol)) + { + WriteLocationsToTrap(trapFile.indexer_location, this, Locations); + } var getter = BodyDeclaringSymbol.GetMethod; var setter = BodyDeclaringSymbol.SetMethod; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs index f1c6813a66d6..f16faa7f5303 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalVariable.cs @@ -41,7 +41,7 @@ public void PopulateManual(Expression parent, bool isVar) trapFile.localvars(this, Kinds.VariableKind.None, Symbol.Name, @var, Type.Create(Context, parent.Type).TypeRef, parent); } - trapFile.localvar_location(this, Location); + WriteLocationToTrap(trapFile.localvar_location, this, Location); DefineConstantValue(trapFile); } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/EmptyLocation.cs similarity index 54% rename from csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs rename to csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/EmptyLocation.cs index d12f1ca51e00..f81db35fdbca 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/EmptyLocation.cs @@ -2,11 +2,11 @@ namespace Semmle.Extraction.CSharp.Entities { - public class GeneratedLocation : SourceLocation + public class EmptyLocation : SourceLocation { private readonly File generatedFile; - private GeneratedLocation(Context cx) + private EmptyLocation(Context cx) : base(cx, null) { generatedFile = GeneratedFile.Create(cx); @@ -26,15 +26,16 @@ public override void WriteId(EscapingTextWriter trapFile) public override int GetHashCode() => 98732567; - public override bool Equals(object? obj) => obj is not null && obj.GetType() == typeof(GeneratedLocation); + public override bool Equals(object? obj) => obj is not null && obj.GetType() == typeof(EmptyLocation); - public static GeneratedLocation Create(Context cx) => GeneratedLocationFactory.Instance.CreateEntity(cx, typeof(GeneratedLocation), null); + public static EmptyLocation Create(Context cx) + => EmptyLocationFactory.Instance.CreateEntity(cx, typeof(EmptyLocation), null); - private class GeneratedLocationFactory : CachedEntityFactory + private class EmptyLocationFactory : CachedEntityFactory { - public static GeneratedLocationFactory Instance { get; } = new GeneratedLocationFactory(); + public static EmptyLocationFactory Instance { get; } = new EmptyLocationFactory(); - public override GeneratedLocation Create(Context cx, string? init) => new GeneratedLocation(cx); + public override EmptyLocation Create(Context cx, string? init) => new EmptyLocation(cx); } } } diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs index 12684c304a41..09ba3cc02b22 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/NamespaceDeclaration.cs @@ -35,7 +35,7 @@ public override void Populate(TextWriter trapFile) var ns = Namespace.Create(Context, @namespace); trapFile.namespace_declarations(this, ns); - trapFile.namespace_declaration_location(this, Context.CreateLocation(node.Name.GetLocation())); + WriteLocationToTrap(trapFile.namespace_declaration_location, this, Context.CreateLocation(node.Name.GetLocation())); var visitor = new Populators.TypeOrNamespaceVisitor(Context, trapFile, this); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs index bd3a637a624c..af4cd1a10cef 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/OrdinaryMethod.cs @@ -43,8 +43,10 @@ public override void Populate(TextWriter trapFile) } } - foreach (var l in Locations) - trapFile.method_location(this, l); + if (Context.ExtractLocation(Symbol)) + { + WriteLocationsToTrap(trapFile.method_location, this, Locations); + } PopulateGenerics(trapFile); Overrides(trapFile); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs index 76d518776caa..a5d208fc86fe 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Parameter.cs @@ -116,14 +116,16 @@ public override void Populate(TextWriter trapFile) trapFile.@params(this, Name, type.TypeRef, Ordinal, ParamKind, Parent!, Original); foreach (var l in Symbol.Locations) - trapFile.param_location(this, Context.CreateLocation(l)); + { + WriteLocationToTrap(trapFile.param_location, this, Context.CreateLocation(l)); + } if (!Symbol.Locations.Any() && Symbol.ContainingSymbol is IMethodSymbol ms && ms.Name == WellKnownMemberNames.TopLevelStatementsEntryPointMethodName && ms.ContainingType.Name == WellKnownMemberNames.TopLevelStatementsEntryPointTypeName) { - trapFile.param_location(this, Context.CreateLocation()); + WriteLocationToTrap(trapFile.param_location, this, Context.CreateLocation()); } if (Symbol.HasExplicitDefaultValue && Context.Defines(Symbol)) @@ -247,7 +249,6 @@ public override void Populate(TextWriter trapFile) var typeKey = VarargsType.Create(Context); // !! Maybe originaldefinition is wrong trapFile.@params(this, "", typeKey, Ordinal, Kind.None, Parent!, this); - trapFile.param_location(this, GeneratedLocation.Create(Context)); } protected override int Ordinal => ((Method)Parent!).OriginalDefinition.Symbol.Parameters.Length; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs index da39613e1247..9520e80d2459 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs @@ -13,7 +13,7 @@ public sealed override void Populate(TextWriter trapFile) PopulatePreprocessor(trapFile); trapFile.preprocessor_directive_active(this, Symbol.IsActive); - trapFile.preprocessor_directive_location(this, Context.CreateLocation(ReportingLocation)); + WriteLocationToTrap(trapFile.preprocessor_directive_location, this, Context.CreateLocation(ReportingLocation)); var compilation = Compilation.Create(Context); trapFile.preprocessor_directive_compilation(this, compilation); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs index f8845a667cc7..ccffa1d95118 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Property.cs @@ -69,8 +69,10 @@ public override void Populate(TextWriter trapFile) TypeMention.Create(Context, syntax.ExplicitInterfaceSpecifier!.Name, this, explicitInterface); } - foreach (var l in Locations) - trapFile.property_location(this, l); + if (Context.ExtractLocation(Symbol)) + { + WriteLocationsToTrap(trapFile.property_location, this, Locations); + } if (IsSourceDeclaration && Symbol.FromSource()) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs index 5a840e3b9ef5..a3372726ae31 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/DynamicType.cs @@ -16,7 +16,7 @@ private DynamicType(Context cx, IDynamicTypeSymbol init) public override void Populate(TextWriter trapFile) { trapFile.types(this, Kinds.TypeKind.DYNAMIC, "dynamic"); - trapFile.type_location(this, Location); + WriteLocationToTrap(trapFile.type_location, this, Location); trapFile.has_modifiers(this, Modifier.Create(Context, "public")); trapFile.parent_namespace(this, Namespace.Create(Context, Context.Compilation.GlobalNamespace)); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs index 96c523d5bbdc..d43ec6860313 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs @@ -83,8 +83,7 @@ public override void Populate(TextWriter trapFile) // Class location if (!Symbol.IsGenericType || Symbol.IsReallyUnbound()) { - foreach (var l in Locations) - trapFile.type_location(this, l); + WriteLocationsToTrap(trapFile.type_location, this, Locations); } if (Symbol.IsAnonymousType) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs index d97e4b9dad57..7e7d3df107f4 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TupleType.cs @@ -55,7 +55,7 @@ public override void Populate(TextWriter trapFile) // about what locations are available for a tuple type. // Sometimes it's the source code, and sometimes it's empty. foreach (var l in Symbol.Locations) - trapFile.type_location(this, Context.CreateLocation(l)); + WriteLocationToTrap(trapFile.type_location, this, Context.CreateLocation(l)); } private readonly Lazy tupleElementsLazy; diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs index 25fda9eabe9d..a74a547f87b8 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs @@ -28,7 +28,7 @@ public override void Populate(TextWriter trapFile) foreach (var l in Symbol.Locations) { - trapFile.type_location(this, Context.CreateLocation(l)); + WriteLocationToTrap(trapFile.type_location, this, Context.CreateLocation(l)); } if (IsSourceDeclaration) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs index 141bded87acf..e37d16567e1b 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/UserOperator.cs @@ -26,8 +26,7 @@ public override void Populate(TextWriter trapFile) returnType.TypeRef, (UserOperator)OriginalDefinition); - foreach (var l in Locations) - trapFile.operator_location(this, l); + WriteLocationsToTrap(trapFile.operator_location, this, Locations); if (IsSourceDeclaration) { diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Analyser.cs index 3ea99a0d772c..4c8660c172ab 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Analyser.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Analyser.cs @@ -238,6 +238,9 @@ private void DoAnalyseCompilation() compilationEntity = Entities.Compilation.Create(cx); + // Ensure that the empty location is always created. + Entities.EmptyLocation.Create(cx); + ExtractionContext.CompilationInfos.ForEach(ci => trapWriter.Writer.compilation_info(compilationEntity, ci.key, ci.value)); ReportProgressTaskDone(currentTaskId, assemblyPath, trapWriter.TrapFile, stopwatch.Elapsed, AnalysisAction.Extracted); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs index f231c8238a96..b7160cd1f638 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs @@ -550,6 +550,10 @@ public bool Defines(ISymbol symbol) => !SymbolEqualityComparer.Default.Equals(symbol, symbol.OriginalDefinition) || scope.InScope(symbol); + public bool ExtractLocation(ISymbol symbol) => + SymbolEqualityComparer.Default.Equals(symbol, symbol.OriginalDefinition) && + scope.InScope(symbol); + /// /// Runs the given action , guarding for trap duplication /// based on key . @@ -582,14 +586,14 @@ public void WithDuplicationGuard(Key key, Action a) public Entities.Location CreateLocation() { return SourceTree is null - ? Entities.GeneratedLocation.Create(this) + ? Entities.EmptyLocation.Create(this) : CreateLocation(Microsoft.CodeAnalysis.Location.Create(SourceTree, Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(0, 0))); } public Entities.Location CreateLocation(Microsoft.CodeAnalysis.Location? location) { return (location is null || location.Kind == LocationKind.None) - ? Entities.GeneratedLocation.Create(this) + ? Entities.EmptyLocation.Create(this) : location.IsInSource ? Entities.NonGeneratedSourceLocation.Create(this, location) : Entities.Assembly.Create(this, location); diff --git a/csharp/ql/lib/change-notes/2025-10-02-entity-locations.md b/csharp/ql/lib/change-notes/2025-10-02-entity-locations.md new file mode 100644 index 000000000000..dd13aab6292e --- /dev/null +++ b/csharp/ql/lib/change-notes/2025-10-02-entity-locations.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The extraction of the location for bound generic entities (methods, accessors, indexers, properties, and events) has been optimized. Previously, location information was extracted multiple times for each bound generic. Now, only the location of the unbound generic declaration is extracted during the extraction phase, and the QL library explicitly reuses this location for all bound instances of the same generic. diff --git a/csharp/ql/lib/semmle/code/csharp/Callable.qll b/csharp/ql/lib/semmle/code/csharp/Callable.qll index 7e17f853913a..ef0d0673ce2a 100644 --- a/csharp/ql/lib/semmle/code/csharp/Callable.qll +++ b/csharp/ql/lib/semmle/code/csharp/Callable.qll @@ -265,7 +265,7 @@ class Method extends Callable, Virtualizable, Attributable, @method { result = Virtualizable.super.getAnUltimateImplementor() } - override Location getALocation() { method_location(this, result) } + override Location getALocation() { method_location(this.getUnboundDeclaration(), result) } /** Holds if this method is an extension method. */ predicate isExtensionMethod() { this.getParameter(0).hasExtensionMethodModifier() } diff --git a/csharp/ql/lib/semmle/code/csharp/Event.qll b/csharp/ql/lib/semmle/code/csharp/Event.qll index a70799524787..39e2fdb7894a 100644 --- a/csharp/ql/lib/semmle/code/csharp/Event.qll +++ b/csharp/ql/lib/semmle/code/csharp/Event.qll @@ -68,7 +68,7 @@ class Event extends DeclarationWithAccessors, @event { result = DeclarationWithAccessors.super.getAnUltimateImplementor() } - override Location getALocation() { event_location(this, result) } + override Location getALocation() { event_location(this.getUnboundDeclaration(), result) } override string getAPrimaryQlClass() { result = "Event" } } @@ -99,7 +99,7 @@ class EventAccessor extends Accessor, @event_accessor { override Event getDeclaration() { event_accessors(this, _, _, result, _) } - override Location getALocation() { event_accessor_location(this, result) } + override Location getALocation() { event_accessor_location(this.getUnboundDeclaration(), result) } } /** diff --git a/csharp/ql/lib/semmle/code/csharp/Property.qll b/csharp/ql/lib/semmle/code/csharp/Property.qll index 60ea83c30117..e651639b6313 100644 --- a/csharp/ql/lib/semmle/code/csharp/Property.qll +++ b/csharp/ql/lib/semmle/code/csharp/Property.qll @@ -196,7 +196,7 @@ class Property extends DeclarationWithGetSetAccessors, @property { override PropertyAccess getAnAccess() { result.getTarget() = this } - override Location getALocation() { property_location(this, result) } + override Location getALocation() { property_location(this.getUnboundDeclaration(), result) } override Expr getAnAssignedValue() { result = DeclarationWithGetSetAccessors.super.getAnAssignedValue() @@ -328,7 +328,7 @@ class Indexer extends DeclarationWithGetSetAccessors, Parameterizable, @indexer result = DeclarationWithGetSetAccessors.super.getAnUltimateImplementor() } - override Location getALocation() { indexer_location(this, result) } + override Location getALocation() { indexer_location(this.getUnboundDeclaration(), result) } override string toStringWithTypes() { result = this.getName() + "[" + this.parameterTypesToString() + "]" @@ -408,7 +408,7 @@ class Accessor extends Callable, Modifiable, Attributable, Overridable, @callabl override Accessor getUnboundDeclaration() { accessors(this, _, _, _, result) } - override Location getALocation() { accessor_location(this, result) } + override Location getALocation() { accessor_location(this.getUnboundDeclaration(), result) } override string toString() { result = this.getName() } } diff --git a/csharp/ql/test/library-tests/locations/A.cs b/csharp/ql/test/library-tests/locations/A.cs new file mode 100644 index 000000000000..7f641a0024e8 --- /dev/null +++ b/csharp/ql/test/library-tests/locations/A.cs @@ -0,0 +1,35 @@ +using System; + +public abstract class A +{ + public abstract T Prop { get; } + public abstract T this[int index] { get; set; } + public abstract event EventHandler Event; + public void Apply(T t) { } + public abstract object ToObject(T t); +} + +public class A2 : A +{ + public override string Prop => ""; + + public override string this[int i] + { + get { return ""; } + set { } + } + + public override event EventHandler Event + { + add { } + remove { } + } + + public override object ToObject(string t) => t; + + public void M() + { + A2 other = new(); + other.Apply(""); + } +} diff --git a/csharp/ql/test/library-tests/locations/B.cs b/csharp/ql/test/library-tests/locations/B.cs new file mode 100644 index 000000000000..c4cfa9711162 --- /dev/null +++ b/csharp/ql/test/library-tests/locations/B.cs @@ -0,0 +1,20 @@ +using System; + +public class B : A +{ + public override int Prop => 0; + + public override int this[int i] + { + get { return 0; } + set { } + } + + public override event EventHandler Event + { + add { } + remove { } + } + + public override object ToObject(int t) => t; +} diff --git a/csharp/ql/test/library-tests/locations/C.cs b/csharp/ql/test/library-tests/locations/C.cs new file mode 100644 index 000000000000..a4063646d53f --- /dev/null +++ b/csharp/ql/test/library-tests/locations/C.cs @@ -0,0 +1,12 @@ +using System; + +class C +{ + public void M() + { + B b = new B(); + b.Apply(0); + A2 a2 = new A2(); + a2.Apply(""); + } +} diff --git a/csharp/ql/test/library-tests/locations/locations.expected b/csharp/ql/test/library-tests/locations/locations.expected new file mode 100644 index 000000000000..8b021b115d26 --- /dev/null +++ b/csharp/ql/test/library-tests/locations/locations.expected @@ -0,0 +1,52 @@ +member_locations +| A.cs:3:23:3:26 | A | A.cs:5:23:5:26 | Prop | A.cs:5:23:5:26 | A.cs:5:23:5:26 | +| A.cs:3:23:3:26 | A | A.cs:6:23:6:26 | Item | A.cs:6:23:6:26 | A.cs:6:23:6:26 | +| A.cs:3:23:3:26 | A | A.cs:7:40:7:44 | Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A | A.cs:8:17:8:21 | Apply | A.cs:8:17:8:21 | A.cs:8:17:8:21 | +| A.cs:3:23:3:26 | A | A.cs:9:28:9:35 | ToObject | A.cs:9:28:9:35 | A.cs:9:28:9:35 | +| A.cs:3:23:3:26 | A | A.cs:5:23:5:26 | Prop | A.cs:5:23:5:26 | A.cs:5:23:5:26 | +| A.cs:3:23:3:26 | A | A.cs:6:23:6:26 | Item | A.cs:6:23:6:26 | A.cs:6:23:6:26 | +| A.cs:3:23:3:26 | A | A.cs:7:40:7:44 | Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A | A.cs:8:17:8:21 | Apply | A.cs:8:17:8:21 | A.cs:8:17:8:21 | +| A.cs:3:23:3:26 | A | A.cs:9:28:9:35 | ToObject | A.cs:9:28:9:35 | A.cs:9:28:9:35 | +| A.cs:3:23:3:26 | A`1 | A.cs:5:23:5:26 | Prop | A.cs:5:23:5:26 | A.cs:5:23:5:26 | +| A.cs:3:23:3:26 | A`1 | A.cs:6:23:6:26 | Item | A.cs:6:23:6:26 | A.cs:6:23:6:26 | +| A.cs:3:23:3:26 | A`1 | A.cs:7:40:7:44 | Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A`1 | A.cs:8:17:8:21 | Apply | A.cs:8:17:8:21 | A.cs:8:17:8:21 | +| A.cs:3:23:3:26 | A`1 | A.cs:9:28:9:35 | ToObject | A.cs:9:28:9:35 | A.cs:9:28:9:35 | +| A.cs:12:14:12:15 | A2 | A.cs:14:28:14:31 | Prop | A.cs:14:28:14:31 | A.cs:14:28:14:31 | +| A.cs:12:14:12:15 | A2 | A.cs:16:28:16:31 | Item | A.cs:16:28:16:31 | A.cs:16:28:16:31 | +| A.cs:12:14:12:15 | A2 | A.cs:22:40:22:44 | Event | A.cs:22:40:22:44 | A.cs:22:40:22:44 | +| A.cs:12:14:12:15 | A2 | A.cs:28:28:28:35 | ToObject | A.cs:28:28:28:35 | A.cs:28:28:28:35 | +| A.cs:12:14:12:15 | A2 | A.cs:30:17:30:17 | M | A.cs:30:17:30:17 | A.cs:30:17:30:17 | +| B.cs:3:14:3:14 | B | B.cs:5:25:5:28 | Prop | B.cs:5:25:5:28 | B.cs:5:25:5:28 | +| B.cs:3:14:3:14 | B | B.cs:7:25:7:28 | Item | B.cs:7:25:7:28 | B.cs:7:25:7:28 | +| B.cs:3:14:3:14 | B | B.cs:13:40:13:44 | Event | B.cs:13:40:13:44 | B.cs:13:40:13:44 | +| B.cs:3:14:3:14 | B | B.cs:19:28:19:35 | ToObject | B.cs:19:28:19:35 | B.cs:19:28:19:35 | +| C.cs:3:7:3:7 | C | C.cs:5:17:5:17 | M | C.cs:5:17:5:17 | C.cs:5:17:5:17 | +accessor_location +| A.cs:3:23:3:26 | A | A.cs:5:30:5:32 | get_Prop | A.cs:5:30:5:32 | A.cs:5:30:5:32 | +| A.cs:3:23:3:26 | A | A.cs:6:41:6:43 | get_Item | A.cs:6:41:6:43 | A.cs:6:41:6:43 | +| A.cs:3:23:3:26 | A | A.cs:6:46:6:48 | set_Item | A.cs:6:46:6:48 | A.cs:6:46:6:48 | +| A.cs:3:23:3:26 | A | A.cs:7:40:7:44 | add_Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A | A.cs:7:40:7:44 | remove_Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A | A.cs:5:30:5:32 | get_Prop | A.cs:5:30:5:32 | A.cs:5:30:5:32 | +| A.cs:3:23:3:26 | A | A.cs:6:41:6:43 | get_Item | A.cs:6:41:6:43 | A.cs:6:41:6:43 | +| A.cs:3:23:3:26 | A | A.cs:6:46:6:48 | set_Item | A.cs:6:46:6:48 | A.cs:6:46:6:48 | +| A.cs:3:23:3:26 | A | A.cs:7:40:7:44 | add_Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A | A.cs:7:40:7:44 | remove_Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A`1 | A.cs:5:30:5:32 | get_Prop | A.cs:5:30:5:32 | A.cs:5:30:5:32 | +| A.cs:3:23:3:26 | A`1 | A.cs:6:41:6:43 | get_Item | A.cs:6:41:6:43 | A.cs:6:41:6:43 | +| A.cs:3:23:3:26 | A`1 | A.cs:6:46:6:48 | set_Item | A.cs:6:46:6:48 | A.cs:6:46:6:48 | +| A.cs:3:23:3:26 | A`1 | A.cs:7:40:7:44 | add_Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:3:23:3:26 | A`1 | A.cs:7:40:7:44 | remove_Event | A.cs:7:40:7:44 | A.cs:7:40:7:44 | +| A.cs:12:14:12:15 | A2 | A.cs:14:36:14:37 | get_Prop | A.cs:14:36:14:37 | A.cs:14:36:14:37 | +| A.cs:12:14:12:15 | A2 | A.cs:18:9:18:11 | get_Item | A.cs:18:9:18:11 | A.cs:18:9:18:11 | +| A.cs:12:14:12:15 | A2 | A.cs:19:9:19:11 | set_Item | A.cs:19:9:19:11 | A.cs:19:9:19:11 | +| A.cs:12:14:12:15 | A2 | A.cs:24:9:24:11 | add_Event | A.cs:24:9:24:11 | A.cs:24:9:24:11 | +| A.cs:12:14:12:15 | A2 | A.cs:25:9:25:14 | remove_Event | A.cs:25:9:25:14 | A.cs:25:9:25:14 | +| B.cs:3:14:3:14 | B | B.cs:5:33:5:33 | get_Prop | B.cs:5:33:5:33 | B.cs:5:33:5:33 | +| B.cs:3:14:3:14 | B | B.cs:9:9:9:11 | get_Item | B.cs:9:9:9:11 | B.cs:9:9:9:11 | +| B.cs:3:14:3:14 | B | B.cs:10:9:10:11 | set_Item | B.cs:10:9:10:11 | B.cs:10:9:10:11 | +| B.cs:3:14:3:14 | B | B.cs:15:9:15:11 | add_Event | B.cs:15:9:15:11 | B.cs:15:9:15:11 | +| B.cs:3:14:3:14 | B | B.cs:16:9:16:14 | remove_Event | B.cs:16:9:16:14 | B.cs:16:9:16:14 | diff --git a/csharp/ql/test/library-tests/locations/locations.ql b/csharp/ql/test/library-tests/locations/locations.ql new file mode 100644 index 000000000000..6c1dcc3e385b --- /dev/null +++ b/csharp/ql/test/library-tests/locations/locations.ql @@ -0,0 +1,14 @@ +import csharp + +query predicate member_locations(Type t, Member m, SourceLocation l) { + t = m.getDeclaringType() and + l = m.getLocation() and + not l instanceof EmptyLocation and + not m instanceof Constructor +} + +query predicate accessor_location(Type t, Accessor a, SourceLocation l) { + t = a.getDeclaringType() and + l = a.getLocation() and + not l instanceof EmptyLocation +}