diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs
index 4ad05eea3833..affb8b224a3d 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Assembly.cs
@@ -72,15 +72,14 @@ public static Assembly CreateOutputAssembly(Context cx)
public override void WriteId(EscapingTextWriter trapFile)
{
- if (isOutputAssembly && Context.ExtractionContext.IsStandalone)
+ if (Context.ExtractionContext.IsStandalone)
{
- trapFile.Write("buildlessOutputAssembly");
- }
- else
- {
- trapFile.Write(assembly.ToString());
+ WriteStarId(trapFile);
+ return;
}
+ trapFile.Write(assembly.ToString());
+
if (assemblyPath is not null)
{
trapFile.Write("#file:///");
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/EmptyLocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/EmptyLocation.cs
new file mode 100644
index 000000000000..38b198102c8b
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/EmptyLocation.cs
@@ -0,0 +1,43 @@
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ ///
+ /// Used to create a single canonical empty location entity.
+ ///
+ public class EmptyLocation : GeneratedLocation
+ {
+ private EmptyLocation(Context cx) : base(cx) { }
+
+ public override void Populate(TextWriter trapFile)
+ {
+ trapFile.locations_default(this, GenFile, -1, -1, -1, -1);
+ }
+
+ public override void WriteId(EscapingTextWriter trapFile)
+ {
+ if (Context.ExtractionContext.IsStandalone)
+ {
+ WriteStarId(trapFile);
+ return;
+ }
+
+ trapFile.Write("loc,");
+ trapFile.WriteSubId(GenFile);
+ trapFile.Write(",-1,-1,-1,-1");
+ }
+
+ public static new EmptyLocation Create(Context cx) => EmptyLocationFactory.Instance.CreateEntity(cx, typeof(EmptyLocation), null);
+
+ private class EmptyLocationFactory : CachedEntityFactory
+ {
+ public static EmptyLocationFactory Instance { get; } = new EmptyLocationFactory();
+
+ ///
+ /// The QL library assumes the presence of a single empty location element.
+ ///
+ public override EmptyLocation Create(Context cx, string? init) => new EmptyLocation(cx);
+ }
+ }
+
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs
index d12f1ca51e00..8badb31f21c2 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/GeneratedLocation.cs
@@ -4,23 +4,29 @@ namespace Semmle.Extraction.CSharp.Entities
{
public class GeneratedLocation : SourceLocation
{
- private readonly File generatedFile;
+ protected File GenFile { get; init; }
- private GeneratedLocation(Context cx)
+ protected GeneratedLocation(Context cx)
: base(cx, null)
{
- generatedFile = GeneratedFile.Create(cx);
+ GenFile = GeneratedFile.Create(cx);
}
public override void Populate(TextWriter trapFile)
{
- trapFile.locations_default(this, generatedFile, 0, 0, 0, 0);
+ trapFile.locations_default(this, GenFile, 0, 0, 0, 0);
}
public override void WriteId(EscapingTextWriter trapFile)
{
+ if (Context.ExtractionContext.IsStandalone)
+ {
+ WriteStarId(trapFile);
+ return;
+ }
+
trapFile.Write("loc,");
- trapFile.WriteSubId(generatedFile);
+ trapFile.WriteSubId(GenFile);
trapFile.Write(",0,0,0,0");
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/Location.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/Location.cs
index 9f9e15e33f33..0ba8aeaa4187 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/Location.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/Location.cs
@@ -5,7 +5,24 @@ public abstract class Location : CachedEntity
{
#nullable restore warnings
protected Location(Context cx, Microsoft.CodeAnalysis.Location? init)
- : base(cx, init) { }
+ : base(cx, init)
+ { }
+
+ protected static void WriteStarId(EscapingTextWriter writer)
+ {
+ writer.Write('*');
+ }
+
+ public sealed override void WriteQuotedId(EscapingTextWriter writer)
+ {
+ if (Context.ExtractionContext.IsStandalone)
+ {
+ WriteStarId(writer);
+ return;
+ }
+
+ base.WriteQuotedId(writer);
+ }
public override Microsoft.CodeAnalysis.Location? ReportingLocation => Symbol;
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/NonGeneratedSourceLocation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/NonGeneratedSourceLocation.cs
index 69e9ea4e9dc7..22a8277b9495 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/NonGeneratedSourceLocation.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Locations/NonGeneratedSourceLocation.cs
@@ -42,6 +42,12 @@ public File FileEntity
public override void WriteId(EscapingTextWriter trapFile)
{
+ if (Context.ExtractionContext.IsStandalone)
+ {
+ WriteStarId(trapFile);
+ return;
+ }
+
trapFile.Write("loc,");
trapFile.WriteSubId(FileEntity);
trapFile.Write(',');
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor/Analyser.cs
index 3ea99a0d772c..0ee52501ba57 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);
+ // Add a single empty location
+ 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/ql/lib/semmle/code/csharp/Location.qll b/csharp/ql/lib/semmle/code/csharp/Location.qll
index 9b2cea470ed7..fc502545ba34 100644
--- a/csharp/ql/lib/semmle/code/csharp/Location.qll
+++ b/csharp/ql/lib/semmle/code/csharp/Location.qll
@@ -74,8 +74,20 @@ class Location extends @location {
}
/** An empty location. */
-class EmptyLocation extends Location {
- EmptyLocation() { this.hasLocationInfo("", 0, 0, 0, 0) }
+class EmptyLocation extends SourceLocation {
+ EmptyLocation() { locations_default(this, _, -1, -1, -1, -1) }
+
+ override predicate hasLocationInfo(
+ string filepath, int startline, int startcolumn, int endline, int endcolumn
+ ) {
+ filepath = "" and
+ startline = 0 and
+ startcolumn = 0 and
+ endline = 0 and
+ endcolumn = 0
+ }
+
+ override string toString() { result = "empty location" }
}
/**