From f2b45b8726a4cf1463da0a7974bd3c58a05af192 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 30 Sep 2025 15:52:18 +0200 Subject: [PATCH 01/10] C#: Add type locations test. --- csharp/ql/test/library-tests/locations/locations.expected | 8 ++++++++ csharp/ql/test/library-tests/locations/locations.ql | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/csharp/ql/test/library-tests/locations/locations.expected b/csharp/ql/test/library-tests/locations/locations.expected index 8b021b115d26..4ce04a705550 100644 --- a/csharp/ql/test/library-tests/locations/locations.expected +++ b/csharp/ql/test/library-tests/locations/locations.expected @@ -50,3 +50,11 @@ accessor_location | 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 | +type_location +| A.cs:3:23:3:26 | A | A.cs:3:23:3:26 | A.cs:3:23:3:26 | +| A.cs:3:23:3:26 | A | A.cs:3:23:3:26 | A.cs:3:23:3:26 | +| A.cs:3:23:3:26 | A`1 | A.cs:3:23:3:26 | A.cs:3:23:3:26 | +| A.cs:3:25:3:25 | T | A.cs:3:25:3:25 | A.cs:3:25:3:25 | +| A.cs:12:14:12:15 | A2 | A.cs:12:14:12:15 | A.cs:12:14:12:15 | +| B.cs:3:14:3:14 | B | B.cs:3:14:3:14 | B.cs:3:14:3:14 | +| C.cs:3:7:3:7 | C | C.cs:3:7:3:7 | C.cs:3:7:3:7 | diff --git a/csharp/ql/test/library-tests/locations/locations.ql b/csharp/ql/test/library-tests/locations/locations.ql index 6c1dcc3e385b..bfadcff2ac89 100644 --- a/csharp/ql/test/library-tests/locations/locations.ql +++ b/csharp/ql/test/library-tests/locations/locations.ql @@ -12,3 +12,7 @@ query predicate accessor_location(Type t, Accessor a, SourceLocation l) { l = a.getLocation() and not l instanceof EmptyLocation } + +query predicate type_location(Type t, SourceLocation l) { + l = t.getLocation() and not l instanceof EmptyLocation +} From 0cd7c37209b610b080085e5cd0dc152802603463 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 30 Sep 2025 15:52:47 +0200 Subject: [PATCH 02/10] C#: Avoid extracting duplicate type locations. --- .../Entities/Types/NamedType.cs | 13 ++++++++----- .../Entities/Types/TypeParameter.cs | 7 +++++-- .../Semmle.Extraction.CSharp/Extractor/Context.cs | 3 +++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs index d43ec6860313..d7eab644eeb9 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/NamedType.cs @@ -111,15 +111,18 @@ public override IEnumerable Locations } } - private static IEnumerable GetLocations(INamedTypeSymbol type) + private IEnumerable GetLocations(INamedTypeSymbol type) { - return type.Locations - .Where(l => l.IsInMetadata) - .Concat(type.DeclaringSyntaxReferences + var metadataLocations = type.Locations + .Where(l => l.IsInMetadata); + var sourceLocations = type.DeclaringSyntaxReferences .Select(loc => loc.GetSyntax()) .OfType() .Select(l => l.FixedLocation()) - ); + .Where(Context.IsLocationInContext); + + return metadataLocations + .Concat(sourceLocations); } public override Microsoft.CodeAnalysis.Location? ReportingLocation => GetLocations(Symbol).BestOrDefault(); diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs index a74a547f87b8..303421d32e76 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Types/TypeParameter.cs @@ -26,9 +26,12 @@ public override void Populate(TextWriter trapFile) var parentNs = Namespace.Create(Context, Symbol.TypeParameterKind == TypeParameterKind.Method ? Context.Compilation.GlobalNamespace : Symbol.ContainingNamespace); trapFile.parent_namespace(this, parentNs); - foreach (var l in Symbol.Locations) + if (Context.ExtractLocation(Symbol)) { - WriteLocationToTrap(trapFile.type_location, this, Context.CreateLocation(l)); + foreach (var l in Symbol.Locations) + { + WriteLocationToTrap(trapFile.type_location, this, Context.CreateLocation(l)); + } } if (IsSourceDeclaration) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs index b7160cd1f638..44a2fcda5c22 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Context.cs @@ -554,6 +554,9 @@ public bool ExtractLocation(ISymbol symbol) => SymbolEqualityComparer.Default.Equals(symbol, symbol.OriginalDefinition) && scope.InScope(symbol); + public bool IsLocationInContext(Location location) => + location.SourceTree == SourceTree; + /// /// Runs the given action , guarding for trap duplication /// based on key . From b5592ad42ff4a3efc541aa44824b1aabe1a3f372 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Thu, 2 Oct 2025 14:40:43 +0200 Subject: [PATCH 03/10] C#: Add partial class example. --- csharp/ql/test/library-tests/locations/Multiple1.cs | 1 + csharp/ql/test/library-tests/locations/Multiple2.cs | 1 + csharp/ql/test/library-tests/locations/locations.expected | 4 ++++ 3 files changed, 6 insertions(+) create mode 100644 csharp/ql/test/library-tests/locations/Multiple1.cs create mode 100644 csharp/ql/test/library-tests/locations/Multiple2.cs diff --git a/csharp/ql/test/library-tests/locations/Multiple1.cs b/csharp/ql/test/library-tests/locations/Multiple1.cs new file mode 100644 index 000000000000..34bec96c0699 --- /dev/null +++ b/csharp/ql/test/library-tests/locations/Multiple1.cs @@ -0,0 +1 @@ +public partial class Multiple { } diff --git a/csharp/ql/test/library-tests/locations/Multiple2.cs b/csharp/ql/test/library-tests/locations/Multiple2.cs new file mode 100644 index 000000000000..34bec96c0699 --- /dev/null +++ b/csharp/ql/test/library-tests/locations/Multiple2.cs @@ -0,0 +1 @@ +public partial class Multiple { } diff --git a/csharp/ql/test/library-tests/locations/locations.expected b/csharp/ql/test/library-tests/locations/locations.expected index 4ce04a705550..502a99805c4d 100644 --- a/csharp/ql/test/library-tests/locations/locations.expected +++ b/csharp/ql/test/library-tests/locations/locations.expected @@ -58,3 +58,7 @@ type_location | A.cs:12:14:12:15 | A2 | A.cs:12:14:12:15 | A.cs:12:14:12:15 | | B.cs:3:14:3:14 | B | B.cs:3:14:3:14 | B.cs:3:14:3:14 | | C.cs:3:7:3:7 | C | C.cs:3:7:3:7 | C.cs:3:7:3:7 | +| Multiple1.cs:1:22:1:29 | Multiple | Multiple1.cs:1:22:1:29 | Multiple1.cs:1:22:1:29 | +| Multiple1.cs:1:22:1:29 | Multiple | Multiple2.cs:1:22:1:29 | Multiple2.cs:1:22:1:29 | +| Multiple2.cs:1:22:1:29 | Multiple | Multiple1.cs:1:22:1:29 | Multiple1.cs:1:22:1:29 | +| Multiple2.cs:1:22:1:29 | Multiple | Multiple2.cs:1:22:1:29 | Multiple2.cs:1:22:1:29 | From 12dc65d170baa338b54c0fb165b6bc3b1006552d Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Oct 2025 11:40:42 +0200 Subject: [PATCH 04/10] C#: For implicit constructors, pick a unique source location as reporting location (if any). --- .../extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs index 045277784695..4fa035446ef5 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Constructor.cs @@ -222,7 +222,8 @@ public override Microsoft.CodeAnalysis.Location? ReportingLocation if (Symbol.IsImplicitlyDeclared) { - return ContainingType!.ReportingLocation; + var best = Symbol.Locations.Where(l => l.IsInSource).BestOrDefault(); + return best ?? ContainingType!.ReportingLocation; } return Symbol.ContainingType.Locations.FirstOrDefault(); From 0ba9b80d089510684d1b75a55814349ad19f339a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Oct 2025 11:43:35 +0200 Subject: [PATCH 05/10] C#: Add some more partial class related tests. --- csharp/ql/test/library-tests/partial/Partial1.expected | 2 ++ .../test/library-tests/partial/PartialConstructors.expected | 4 ++++ csharp/ql/test/library-tests/partial/PartialConstructors.ql | 5 +++++ .../ql/test/library-tests/partial/PartialMultipleFiles1.cs | 1 + .../ql/test/library-tests/partial/PartialMultipleFiles2.cs | 1 + csharp/ql/test/library-tests/partial/PrintAst.expected | 4 ++++ 6 files changed, 17 insertions(+) create mode 100644 csharp/ql/test/library-tests/partial/PartialConstructors.expected create mode 100644 csharp/ql/test/library-tests/partial/PartialConstructors.ql create mode 100644 csharp/ql/test/library-tests/partial/PartialMultipleFiles1.cs create mode 100644 csharp/ql/test/library-tests/partial/PartialMultipleFiles2.cs diff --git a/csharp/ql/test/library-tests/partial/Partial1.expected b/csharp/ql/test/library-tests/partial/Partial1.expected index af7be1359143..55dcaabcea7d 100644 --- a/csharp/ql/test/library-tests/partial/Partial1.expected +++ b/csharp/ql/test/library-tests/partial/Partial1.expected @@ -10,3 +10,5 @@ | Partial.cs:28:9:28:11 | set_Item | | Partial.cs:32:15:32:33 | OnePartPartialClass | | Partial.cs:34:18:34:42 | PartialMethodWithoutBody2 | +| PartialMultipleFiles1.cs:1:22:1:41 | PartialMultipleFiles | +| PartialMultipleFiles2.cs:1:22:1:41 | PartialMultipleFiles | diff --git a/csharp/ql/test/library-tests/partial/PartialConstructors.expected b/csharp/ql/test/library-tests/partial/PartialConstructors.expected new file mode 100644 index 000000000000..01779f1b81ed --- /dev/null +++ b/csharp/ql/test/library-tests/partial/PartialConstructors.expected @@ -0,0 +1,4 @@ +| Partial.cs:1:15:1:26 | TwoPartClass | Partial.cs:1:15:1:26 | {...} | +| Partial.cs:32:15:32:33 | OnePartPartialClass | Partial.cs:32:15:32:33 | {...} | +| Partial.cs:38:7:38:21 | NonPartialClass | Partial.cs:38:7:38:21 | {...} | +| PartialMultipleFiles1.cs:1:22:1:41 | PartialMultipleFiles | PartialMultipleFiles1.cs:1:22:1:41 | {...} | diff --git a/csharp/ql/test/library-tests/partial/PartialConstructors.ql b/csharp/ql/test/library-tests/partial/PartialConstructors.ql new file mode 100644 index 000000000000..049de6290854 --- /dev/null +++ b/csharp/ql/test/library-tests/partial/PartialConstructors.ql @@ -0,0 +1,5 @@ +import csharp + +from Constructor c +where c.getDeclaringType().fromSource() +select c, c.getBody() diff --git a/csharp/ql/test/library-tests/partial/PartialMultipleFiles1.cs b/csharp/ql/test/library-tests/partial/PartialMultipleFiles1.cs new file mode 100644 index 000000000000..6f9471f19ca0 --- /dev/null +++ b/csharp/ql/test/library-tests/partial/PartialMultipleFiles1.cs @@ -0,0 +1 @@ +public partial class PartialMultipleFiles { } diff --git a/csharp/ql/test/library-tests/partial/PartialMultipleFiles2.cs b/csharp/ql/test/library-tests/partial/PartialMultipleFiles2.cs new file mode 100644 index 000000000000..6f9471f19ca0 --- /dev/null +++ b/csharp/ql/test/library-tests/partial/PartialMultipleFiles2.cs @@ -0,0 +1 @@ +public partial class PartialMultipleFiles { } diff --git a/csharp/ql/test/library-tests/partial/PrintAst.expected b/csharp/ql/test/library-tests/partial/PrintAst.expected index 8d9da42fc11c..d97f6fc01f04 100644 --- a/csharp/ql/test/library-tests/partial/PrintAst.expected +++ b/csharp/ql/test/library-tests/partial/PrintAst.expected @@ -89,3 +89,7 @@ Partial.cs: # 42| 0: [Parameter] index # 45| 1: [Parameter] value # 45| 4: [BlockStmt] {...} +PartialMultipleFiles1.cs: +# 1| [Class] PartialMultipleFiles +PartialMultipleFiles2.cs: +# 1| [Class] PartialMultipleFiles From 7b61a5fffa31aaa515294417707eba788e2db0bf Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Fri, 3 Oct 2025 13:28:20 +0200 Subject: [PATCH 06/10] C#: Update some test expected output that uses the location of the implicit constructor. --- .../test/library-tests/csharp8/NullableRefTypes.expected | 8 ++++---- csharp/ql/test/library-tests/csharp9/withExpr.expected | 8 ++++---- .../expressions/ConstructorInitializers.expected | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/csharp/ql/test/library-tests/csharp8/NullableRefTypes.expected b/csharp/ql/test/library-tests/csharp8/NullableRefTypes.expected index 56f2f7e3d602..6ad47e73d01a 100644 --- a/csharp/ql/test/library-tests/csharp8/NullableRefTypes.expected +++ b/csharp/ql/test/library-tests/csharp8/NullableRefTypes.expected @@ -178,8 +178,8 @@ returnTypes | NullableRefTypes.cs:51:12:51:15 | Q | object | | NullableRefTypes.cs:51:12:51:15 | Q | object! | | NullableRefTypes.cs:51:12:51:15 | Q`1 | object! | -| NullableRefTypes.cs:54:11:54:33 | Generic | Void! | -| NullableRefTypes.cs:58:11:58:26 | Generic2 | Void! | +| NullableRefTypes.cs:54:11:54:17 | Generic | Void! | +| NullableRefTypes.cs:58:11:58:18 | Generic2 | Void! | | NullableRefTypes.cs:67:10:67:21 | GenericFn | Void | | NullableRefTypes.cs:67:10:67:21 | GenericFn | Void! | | NullableRefTypes.cs:67:10:67:21 | GenericFn`1 | Void! | @@ -271,8 +271,8 @@ expressionTypes | NullableRefTypes.cs:40:26:40:30 | ref ... | MyClass | | NullableRefTypes.cs:40:30:40:30 | access to local variable b | MyClass? | | NullableRefTypes.cs:51:44:51:47 | null | null | -| NullableRefTypes.cs:54:11:54:33 | call to constructor Object | object | -| NullableRefTypes.cs:58:11:58:26 | call to constructor Object | object | +| NullableRefTypes.cs:54:11:54:17 | call to constructor Object | object | +| NullableRefTypes.cs:58:11:58:18 | call to constructor Object | object | | NullableRefTypes.cs:73:18:73:18 | access to local variable x | MyClass! | | NullableRefTypes.cs:73:18:73:25 | MyClass x = ... | MyClass! | | NullableRefTypes.cs:73:22:73:25 | null | null | diff --git a/csharp/ql/test/library-tests/csharp9/withExpr.expected b/csharp/ql/test/library-tests/csharp9/withExpr.expected index 2d32e7c8bfd4..cf3b9f5eb007 100644 --- a/csharp/ql/test/library-tests/csharp9/withExpr.expected +++ b/csharp/ql/test/library-tests/csharp9/withExpr.expected @@ -4,10 +4,10 @@ withExpr | Record.cs:77:21:77:31 | ... with { ... } | Person1 | Record.cs:77:21:77:22 | access to local variable p1 | Record.cs:77:29:77:31 | { ..., ... } | Person1.$() | | Record.cs:84:16:84:33 | ... with { ... } | R1 | Record.cs:84:16:84:16 | access to local variable b | Record.cs:84:23:84:33 | { ..., ... } | R1.$() | withTarget -| Record.cs:75:18:75:47 | ... with { ... } | Record.cs:27:1:27:57 | $ | Record.cs:27:1:27:57 | Person1 | -| Record.cs:76:18:76:81 | ... with { ... } | Record.cs:29:1:30:35 | $ | Record.cs:29:1:30:35 | Teacher1 | -| Record.cs:77:21:77:31 | ... with { ... } | Record.cs:27:1:27:57 | $ | Record.cs:27:1:27:57 | Person1 | -| Record.cs:84:16:84:33 | ... with { ... } | Record.cs:54:1:54:39 | $ | Record.cs:54:1:54:39 | R1 | +| Record.cs:75:18:75:47 | ... with { ... } | Record.cs:27:1:27:57 | $ | Record.cs:27:15:27:21 | Person1 | +| Record.cs:76:18:76:81 | ... with { ... } | Record.cs:29:1:30:35 | $ | Record.cs:29:15:29:22 | Teacher1 | +| Record.cs:77:21:77:31 | ... with { ... } | Record.cs:27:1:27:57 | $ | Record.cs:27:15:27:21 | Person1 | +| Record.cs:84:16:84:33 | ... with { ... } | Record.cs:54:1:54:39 | $ | Record.cs:54:24:54:25 | R1 | cloneOverrides | Person1.$() | Student1.$() | | Person1.$() | Teacher1.$() | diff --git a/csharp/ql/test/library-tests/expressions/ConstructorInitializers.expected b/csharp/ql/test/library-tests/expressions/ConstructorInitializers.expected index 00e46f6359c0..2e6a5f679ba4 100644 --- a/csharp/ql/test/library-tests/expressions/ConstructorInitializers.expected +++ b/csharp/ql/test/library-tests/expressions/ConstructorInitializers.expected @@ -27,6 +27,6 @@ | file://:0:0:0:0 | TestCreations | expressions.cs:383:18:383:30 | call to constructor Object | file://:0:0:0:0 | Object | | file://:0:0:0:0 | TestUnaryOperator | expressions.cs:292:11:292:27 | call to constructor Object | file://:0:0:0:0 | Object | | file://:0:0:0:0 | TupleExprs | expressions.cs:501:11:501:20 | call to constructor Object | file://:0:0:0:0 | Object | -| file://:0:0:0:0 | X | expressions.cs:108:15:108:18 | call to constructor Object | file://:0:0:0:0 | Object | +| file://:0:0:0:0 | X | expressions.cs:108:15:108:15 | call to constructor Object | file://:0:0:0:0 | Object | | file://:0:0:0:0 | X | expressions.cs:216:18:216:18 | call to constructor Object | file://:0:0:0:0 | Object | -| file://:0:0:0:0 | Y | expressions.cs:104:15:104:21 | call to constructor Object | file://:0:0:0:0 | Object | +| file://:0:0:0:0 | Y | expressions.cs:104:15:104:15 | call to constructor Object | file://:0:0:0:0 | Object | From b362b4657f127af4febf7a92054b5f5b7732b711 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 6 Oct 2025 16:40:13 +0200 Subject: [PATCH 07/10] C#: Add some examples with nested types. --- .../ql/test/library-tests/locations/Base.cs | 6 ++++++ csharp/ql/test/library-tests/locations/Sub.cs | 8 ++++++++ .../locations/locations.expected | 19 +++++++++++++++++++ .../test/library-tests/locations/locations.ql | 5 +++++ 4 files changed, 38 insertions(+) create mode 100644 csharp/ql/test/library-tests/locations/Base.cs create mode 100644 csharp/ql/test/library-tests/locations/Sub.cs diff --git a/csharp/ql/test/library-tests/locations/Base.cs b/csharp/ql/test/library-tests/locations/Base.cs new file mode 100644 index 000000000000..02082e0e6c4b --- /dev/null +++ b/csharp/ql/test/library-tests/locations/Base.cs @@ -0,0 +1,6 @@ +public abstract class Base +{ + public void M() { } + + public class InnerBase { } +} diff --git a/csharp/ql/test/library-tests/locations/Sub.cs b/csharp/ql/test/library-tests/locations/Sub.cs new file mode 100644 index 000000000000..744cf63b7b11 --- /dev/null +++ b/csharp/ql/test/library-tests/locations/Sub.cs @@ -0,0 +1,8 @@ +public class Sub : Base +{ + public void SubM() + { + M(); + var x = new InnerBase(); + } +} diff --git a/csharp/ql/test/library-tests/locations/locations.expected b/csharp/ql/test/library-tests/locations/locations.expected index 502a99805c4d..122921ac1e3a 100644 --- a/csharp/ql/test/library-tests/locations/locations.expected +++ b/csharp/ql/test/library-tests/locations/locations.expected @@ -23,7 +23,12 @@ member_locations | 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 | +| Base.cs:1:23:1:29 | Base | Base.cs:3:17:3:17 | M | Base.cs:3:17:3:17 | Base.cs:3:17:3:17 | +| Base.cs:1:23:1:29 | Base | Base.cs:5:18:5:26 | InnerBase | Base.cs:5:18:5:26 | Base.cs:5:18:5:26 | +| Base.cs:1:23:1:29 | Base`1 | Base.cs:3:17:3:17 | M | Base.cs:3:17:3:17 | Base.cs:3:17:3:17 | +| Base.cs:1:23:1:29 | Base`1 | Base.cs:5:18:5:26 | InnerBase | Base.cs:5:18:5:26 | Base.cs:5:18:5:26 | | 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 | +| Sub.cs:1:14:1:16 | Sub | Sub.cs:3:17:3:20 | SubM | Sub.cs:3:17:3:20 | Sub.cs:3:17:3:20 | 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 | @@ -57,8 +62,22 @@ type_location | A.cs:3:25:3:25 | T | A.cs:3:25:3:25 | A.cs:3:25:3:25 | | A.cs:12:14:12:15 | A2 | A.cs:12:14:12:15 | A.cs:12:14:12:15 | | B.cs:3:14:3:14 | B | B.cs:3:14:3:14 | B.cs:3:14:3:14 | +| Base.cs:1:23:1:29 | Base | Base.cs:1:23:1:29 | Base.cs:1:23:1:29 | +| Base.cs:1:23:1:29 | Base`1 | Base.cs:1:23:1:29 | Base.cs:1:23:1:29 | +| Base.cs:1:28:1:28 | T | Base.cs:1:28:1:28 | Base.cs:1:28:1:28 | +| Base.cs:5:18:5:26 | InnerBase | Base.cs:5:18:5:26 | Base.cs:5:18:5:26 | +| Base.cs:5:18:5:26 | InnerBase | Base.cs:5:18:5:26 | Base.cs:5:18:5:26 | | C.cs:3:7:3:7 | C | C.cs:3:7:3:7 | C.cs:3:7:3:7 | | Multiple1.cs:1:22:1:29 | Multiple | Multiple1.cs:1:22:1:29 | Multiple1.cs:1:22:1:29 | | Multiple1.cs:1:22:1:29 | Multiple | Multiple2.cs:1:22:1:29 | Multiple2.cs:1:22:1:29 | | Multiple2.cs:1:22:1:29 | Multiple | Multiple1.cs:1:22:1:29 | Multiple1.cs:1:22:1:29 | | Multiple2.cs:1:22:1:29 | Multiple | Multiple2.cs:1:22:1:29 | Multiple2.cs:1:22:1:29 | +| Sub.cs:1:14:1:16 | Sub | Sub.cs:1:14:1:16 | Sub.cs:1:14:1:16 | +calltype_location +| A.cs:12:14:12:15 | call to constructor A | A.cs:3:23:3:26 | A | A.cs:3:23:3:26 | A.cs:3:23:3:26 | +| A.cs:32:20:32:24 | object creation of type A2 | A.cs:12:14:12:15 | A2 | A.cs:12:14:12:15 | A.cs:12:14:12:15 | +| B.cs:3:14:3:14 | call to constructor A | A.cs:3:23:3:26 | A | A.cs:3:23:3:26 | A.cs:3:23:3:26 | +| C.cs:7:15:7:21 | object creation of type B | B.cs:3:14:3:14 | B | B.cs:3:14:3:14 | B.cs:3:14:3:14 | +| C.cs:9:17:9:24 | object creation of type A2 | A.cs:12:14:12:15 | A2 | A.cs:12:14:12:15 | A.cs:12:14:12:15 | +| Sub.cs:1:14:1:16 | call to constructor Base | Base.cs:1:23:1:29 | Base | Base.cs:1:23:1:29 | Base.cs:1:23:1:29 | +| Sub.cs:6:17:6:31 | object creation of type InnerBase | Base.cs:5:18:5:26 | InnerBase | Base.cs:5:18:5:26 | Base.cs:5:18:5:26 | diff --git a/csharp/ql/test/library-tests/locations/locations.ql b/csharp/ql/test/library-tests/locations/locations.ql index bfadcff2ac89..04ea140340bd 100644 --- a/csharp/ql/test/library-tests/locations/locations.ql +++ b/csharp/ql/test/library-tests/locations/locations.ql @@ -16,3 +16,8 @@ query predicate accessor_location(Type t, Accessor a, SourceLocation l) { query predicate type_location(Type t, SourceLocation l) { l = t.getLocation() and not l instanceof EmptyLocation } + +query predicate calltype_location(Call call, Type t, SourceLocation l) { + t = call.getType() and + l = t.getALocation() +} From 6149608c034405cc8a8d76b93c71bd1e9739aa0a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 6 Oct 2025 16:41:12 +0200 Subject: [PATCH 08/10] C#: Always use the unbound type declaration location for type location. --- csharp/ql/lib/semmle/code/csharp/Type.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp/ql/lib/semmle/code/csharp/Type.qll b/csharp/ql/lib/semmle/code/csharp/Type.qll index e417f393b94a..c8b9799bbf64 100644 --- a/csharp/ql/lib/semmle/code/csharp/Type.qll +++ b/csharp/ql/lib/semmle/code/csharp/Type.qll @@ -30,7 +30,7 @@ class Type extends Member, TypeContainer, @type { /** Holds if this type is implicitly convertible to `that` type. */ predicate isImplicitlyConvertibleTo(Type that) { implicitConversion(this, that) } - override Location getALocation() { type_location(this, result) } + override Location getALocation() { type_location(this.getUnboundDeclaration(), result) } override Type getChild(int n) { none() } From f9d62a0efc6fe41bd1cc1aba9d5cfb69dfe7c622 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 7 Oct 2025 09:43:39 +0200 Subject: [PATCH 09/10] C#: Narrow the use of unbound declaration locations to nested types. --- csharp/ql/lib/semmle/code/csharp/Type.qll | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/csharp/ql/lib/semmle/code/csharp/Type.qll b/csharp/ql/lib/semmle/code/csharp/Type.qll index c8b9799bbf64..1efb1aa93bff 100644 --- a/csharp/ql/lib/semmle/code/csharp/Type.qll +++ b/csharp/ql/lib/semmle/code/csharp/Type.qll @@ -30,7 +30,7 @@ class Type extends Member, TypeContainer, @type { /** Holds if this type is implicitly convertible to `that` type. */ predicate isImplicitlyConvertibleTo(Type that) { implicitConversion(this, that) } - override Location getALocation() { type_location(this.getUnboundDeclaration(), result) } + override Location getALocation() { type_location(this, result) } override Type getChild(int n) { none() } @@ -394,6 +394,8 @@ class NestedType extends ValueOrRefType { NestedType() { nested_types(this, _, _) } override ValueOrRefType getDeclaringType() { nested_types(this, result, _) } + + override Location getALocation() { type_location(this.getUnboundDeclaration(), result) } } /** From 584d8c537760ceb0befd5e603918ab0ae758db4c Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Tue, 7 Oct 2025 14:24:21 +0200 Subject: [PATCH 10/10] C#: Add change-note. --- csharp/ql/lib/change-notes/2025-10-07-entity-locations.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/lib/change-notes/2025-10-07-entity-locations.md diff --git a/csharp/ql/lib/change-notes/2025-10-07-entity-locations.md b/csharp/ql/lib/change-notes/2025-10-07-entity-locations.md new file mode 100644 index 000000000000..44f36fe44c6a --- /dev/null +++ b/csharp/ql/lib/change-notes/2025-10-07-entity-locations.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The extraction of location information for named types (classes, structs, etc.) has been optimized. Previously, location information was extracted multiple times for each type when it was declared across multiple files. Now, the extraction context is respected during the extraction phase, ensuring locations are only extracted within the appropriate context. This change should be transparent to end-users but may improve extraction performance in some cases.