Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ protected enum ObjectNodeOrder
ArrayOfEmbeddedPointersNode,
ExternalTypeMapObjectNode,
ProxyTypeMapObjectNode,
StackTraceLineNumbersNode,
StackTraceDocumentsNode,
ExternalReferencesTableNode,
StackTraceEmbeddedMetadataNode,
StackTraceMethodMappingNode,
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/tools/Common/Internal/Runtime/MetadataBlob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ internal enum ReflectionMapBlob
BlobIdResourceData = 25,
BlobIdStackTraceEmbeddedMetadata = 26,
BlobIdStackTraceMethodRvaToTokenMapping = 27,
BlobIdStackTraceLineNumbers = 28,
BlobIdStackTraceDocuments = 29,

//Native layout blobs:
NativeLayoutInfo = 30,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ protected override void ComputeMetadata(NodeFactory factory,
out Dictionary<MethodDesc, int> methodMetadataMappings,
out List<MetadataMapping<FieldDesc>> fieldMappings,
out Dictionary<FieldDesc, int> fieldMetadataMappings,
out List<StackTraceMapping> stackTraceMapping)
out List<StackTraceMapping> stackTraceMapping,
out List<ReflectionStackTraceMapping> reflectionStackTraceMapping)
{
ComputeMetadata(new Policy(_blockingPolicy, this), factory,
out metadataBlob,
Expand All @@ -156,7 +157,8 @@ protected override void ComputeMetadata(NodeFactory factory,
out methodMetadataMappings,
out fieldMappings,
out fieldMetadataMappings,
out stackTraceMapping);
out stackTraceMapping,
out reflectionStackTraceMapping);
}

protected sealed override MetadataCategory GetMetadataCategory(MethodDesc method)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Internal.Text;

namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Contains information about source files in this compilation.
/// </summary>
public sealed class StackTraceDocumentsNode : ObjectNode, ISymbolDefinitionNode, INodeWithSize
{
private Dictionary<string, int> _documentToIndex = new Dictionary<string, int>(StringComparer.Ordinal);
private List<string> _documents = new List<string>();
private int? _size;

public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix).Append("__stacktrace_documents"u8);
}

int INodeWithSize.Size => _size.Value;
public int Offset => 0;
public override bool IsShareable => false;

public override ObjectNodeSection GetSection(NodeFactory factory)
{
if (factory.Target.IsWindows || factory.Target.SupportsRelativePointers)
return ObjectNodeSection.ReadOnlyDataSection;
else
return ObjectNodeSection.DataSection;
}

public int GetDocumentId(string documentName)
{
if (!_documentToIndex.TryGetValue(documentName, out int index))
{
index = _documents.Count;
_documents.Add(documentName);
_documentToIndex.Add(documentName, index);
}

return index;
}

public override bool StaticDependenciesAreComputed => true;
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
// This node does not trigger generation of other nodes.
if (relocsOnly)
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });

// Zero out the hashtable so that we crash if someone tries to use this after emission
_documentToIndex = null;

var ms = new MemoryStream();
var bw = new BinaryWriter(ms);

// We write out:
// (Int32) Number of documents
// (Int32) Offset of document1 from beginning of blob
// (Int32) Offset of document2 from beginning of blob
// ...
// (Int32) Offset of documentN from beginning of blob
// Null-terminated UTF-8 bytes of document1
// Null-terminated UTF-8 bytes of document2
// ...
// Null-terminated UTF-8 bytes of documentN

bw.Write(_documents.Count);

int position = sizeof(int) /* count of documents */ + _documents.Count * sizeof(int);
for (int i = 0; i < _documents.Count; i++)
{
bw.Write(position);
position += Encoding.UTF8.GetByteCount(_documents[i]) + 1;
}

for (int i = 0; i < _documents.Count; i++)
{
bw.Write(Encoding.UTF8.GetBytes(_documents[i]));
bw.Write((byte)0);
}

_size = checked((int)ms.Length);

return new ObjectData(ms.ToArray(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
}

protected internal override int Phase => (int)ObjectNodePhase.Ordered;
public override int ClassCode => (int)ObjectNodeOrder.StackTraceDocumentsNode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;

using Internal.Text;
using Internal.TypeSystem;
using Internal.NativeFormat;
using Internal;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Contains information about mapping native code offsets to line numbers.
/// </summary>
public sealed class StackTraceLineNumbersNode : ObjectNode, ISymbolDefinitionNode, INodeWithSize
{
private int? _size;
private readonly ExternalReferencesTableNode _externalReferences;
private readonly StackTraceDocumentsNode _documents;

public StackTraceLineNumbersNode(ExternalReferencesTableNode externalReferences, StackTraceDocumentsNode documents)
{
_externalReferences = externalReferences;
_documents = documents;
}

public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
{
sb.Append(nameMangler.CompilationUnitPrefix).Append("__stacktrace_line_numbers"u8);
}

int INodeWithSize.Size => _size.Value;
public int Offset => 0;
public override bool IsShareable => false;
public override ObjectNodeSection GetSection(NodeFactory factory) => _externalReferences.GetSection(factory);
public override bool StaticDependenciesAreComputed => true;
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{
// This node does not trigger generation of other nodes.
if (relocsOnly)
return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });

NativeWriter nativeWriter = new NativeWriter();
VertexHashtable hashtable = new VertexHashtable();
Section nativeSection = nativeWriter.NewSection();
nativeSection.Place(hashtable);

foreach (StackTraceMapping mapping in factory.MetadataManager.GetStackTraceMapping(factory))
{
var entrypointSymbol = factory.MethodEntrypoint(mapping.Method);
if (entrypointSymbol is not INodeWithDebugInfo debugInfo)
continue;

BlobVertex blob = CreateLineNumbersBlob(_documents, debugInfo);
if (blob == null)
continue;

Vertex methodPointer = nativeWriter.GetUnsignedConstant(_externalReferences.GetIndex(entrypointSymbol));
var hashtableEntry = nativeWriter.GetTuple(methodPointer, blob);

uint hashcode = VersionResilientHashCode.CombineThreeValuesIntoHash((uint)mapping.OwningTypeHandle, (uint)mapping.MethodNameHandle, (uint)mapping.MethodSignatureHandle);
hashtable.Append(hashcode, nativeSection.Place(hashtableEntry));
}
Comment on lines +54 to +69
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing flag check: The loop should verify that (mapping.Flags & StackTraceRecordFlags.HasLineNumbers) != 0 before attempting to generate line numbers. Currently, it will attempt to generate line numbers for all methods in the stack trace mapping, regardless of whether the HasLineNumbers flag is set. This could result in generating line numbers for methods where line number emission was not intended according to the policy.

Copilot uses AI. Check for mistakes.

foreach (ReflectionStackTraceMapping mapping in factory.MetadataManager.GetReflectionStackTraceMappings(factory))
{
var entrypointSymbol = factory.MethodEntrypoint(mapping.Method);
if (entrypointSymbol is not INodeWithDebugInfo debugInfo)
continue;

BlobVertex blob = CreateLineNumbersBlob(_documents, debugInfo);
if (blob == null)
continue;

Vertex methodPointer = nativeWriter.GetUnsignedConstant(_externalReferences.GetIndex(entrypointSymbol));
var hashtableEntry = nativeWriter.GetTuple(methodPointer, blob);

uint hashcode = VersionResilientHashCode.CombineTwoValuesIntoHash((uint)mapping.OwningTypeHandle, (uint)mapping.MethodHandle);
hashtable.Append(hashcode, nativeSection.Place(hashtableEntry));
}

static BlobVertex CreateLineNumbersBlob(StackTraceDocumentsNode documents, INodeWithDebugInfo debugInfoNode)
{
var ms = new MemoryStream();
var bw = new BinaryWriter(ms);

int currentNativeOffset = 0;
int currentLineNumber = 0;
string currentDocument = null;
foreach (NativeSequencePoint sequencePoint in debugInfoNode.GetNativeSequencePoints())
{
if (currentLineNumber == sequencePoint.LineNumber && currentDocument == sequencePoint.FileName)
continue;

// Make sure a zero native offset delta is not possible because we use it below
// to indicate an update to the current document.
if (currentDocument != null && currentNativeOffset == sequencePoint.NativeOffset)
continue;

if (currentDocument != sequencePoint.FileName)
{
// We start with currentDocument == null, so the reader knows the first byte of the output
// is a document number. Otherwise we use NativeOffsetDelta == 0 as a marker that the next
// byte is a document number and not a native offset delta.
if (currentDocument != null)
bw.Write7BitEncodedInt((byte)0);
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast to (byte)0 is incorrect. Write7BitEncodedInt expects an int parameter, not a byte. This should be bw.Write7BitEncodedInt(0); instead.

Suggested change
bw.Write7BitEncodedInt((byte)0);
bw.Write7BitEncodedInt(0);

Copilot uses AI. Check for mistakes.
bw.Write7BitEncodedInt(documents.GetDocumentId(sequencePoint.FileName));
}

int nativeOffsetDelta = sequencePoint.NativeOffset - currentNativeOffset;
bw.Write7BitEncodedInt(nativeOffsetDelta);

int lineNumberDelta = sequencePoint.LineNumber - currentLineNumber;
bw.Write7BitEncodedInt(lineNumberDelta);

currentLineNumber = sequencePoint.LineNumber;
currentNativeOffset = sequencePoint.NativeOffset;
currentDocument = sequencePoint.FileName;
}

return ms.Length == 0 ? null : new BlobVertex(ms.ToArray());
}

byte[] streamBytes = nativeWriter.Save();

_size = streamBytes.Length;

return new ObjectData(streamBytes, Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
}

protected internal override int Phase => (int)ObjectNodePhase.Ordered;
public override int ClassCode => (int)ObjectNodeOrder.StackTraceLineNumbersNode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
}
}

if (entry.IsHidden)
if ((entry.Flags & StackTraceRecordFlags.IsHidden) != 0)
{
command |= StackTraceDataCommand.IsStackTraceHidden;
}
Expand Down
Loading
Loading