From 98b519075253c01ccd70fecb33d046bd8998cd96 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 10 Jun 2026 23:18:44 +0200 Subject: [PATCH] Add Offset and friendly Kind columns to the CustomDebugInformation table WPF showed a row Offset column and decoded the Kind GUID into one cell combining heap offset, friendly name, and raw GUID; the Avalonia table only carried the bare heap offsets. Offset follows the GetRowOffset convention every other table uses. Kind is plain text rather than a hex column plus tooltip so the per-column filter matches the friendly names. Assisted-by: Claude:claude-fable-5[1m]:Claude Code --- .../CustomDebugInformationRowDetailsTests.cs | 20 +++++++ .../CustomDebugInformationTableTreeNode.cs | 54 +++++++++++++++++-- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/ILSpy.Tests/Metadata/CustomDebugInformationRowDetailsTests.cs b/ILSpy.Tests/Metadata/CustomDebugInformationRowDetailsTests.cs index 6a2db951b..f45859430 100644 --- a/ILSpy.Tests/Metadata/CustomDebugInformationRowDetailsTests.cs +++ b/ILSpy.Tests/Metadata/CustomDebugInformationRowDetailsTests.cs @@ -138,6 +138,26 @@ public class CustomDebugInformationRowDetailsTests .Select(h => new CustomDebugInformationEntry(metadataFile, h)) .ToList(); + [Test] + public void Offset_And_Friendly_Kind_Columns_Match_The_Tables_Conventions() + { + // The Kind column carries the GUID heap offset, the decoded friendly name, and the + // raw GUID in one cell; Offset positions the row within the metadata stream like + // every other table's Offset column. + var metadataFile = BuildPdbFixture(); + var entries = LoadEntries(metadataFile); + + entries[0].Kind.Should().MatchRegex("^[0-9A-F]{8} - Source Link \\(C# / VB\\) \\[cc110556-a091-4d38-9fec-25ab9a351a6a\\]$"); + entries[1].Kind.Should().EndWith($"- Unknown [{UnknownKindGuid}]"); + entries[3].Kind.Should().Contain("State Machine Hoisted Local Scopes (C# / VB)"); + + int rowSize = metadataFile.Metadata.GetTableRowSize(TableIndex.CustomDebugInformation); + entries[0].Offset.Should().BePositive("the table lives at a real offset inside the PDB metadata"); + entries.Select(e => e.Offset).Should().BeInAscendingOrder() + .And.HaveCount(7); + (entries[1].Offset - entries[0].Offset).Should().Be(rowSize); + } + [Test] public void RowDetails_Parses_The_Structured_Kinds_Into_Typed_Rows() { diff --git a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs index 70bed1137..ea41f93e7 100644 --- a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs @@ -45,9 +45,9 @@ namespace ILSpy.Metadata.DebugTables /// /// View of the CustomDebugInformation table — extensible per-entity payloads used for /// async/iterator state-machine info, embedded source, source-link JSON, and similar - /// debug-time data. Each row carries a Parent token, a Kind GUID, and an opaque Value - /// blob. The friendly-name decoding of well-known Kind GUIDs (StateMachineHoistedLocalScopes, - /// SourceLink, etc.) is deferred until the Phase 4 cell-tooltip work. + /// debug-time data. Each row carries a Parent token, a Kind GUID (decoded to a friendly + /// name in the Kind column), and an opaque Value blob whose parsed contents show as the + /// row's details. /// public sealed class CustomDebugInformationTableTreeNode : MetadataTableTreeNode { @@ -106,14 +106,58 @@ namespace ILSpy.Metadata.DebugTables [ColumnInfo("X8")] public int Token => MetadataTokens.GetToken(handle); + [ColumnInfo("X8")] + public int Offset => GetRowOffset(metadataFile, TableIndex.CustomDebugInformation, RID); + [ColumnInfo("X8", Kind = ColumnKind.Token)] public int Parent => MetadataTokens.GetToken(debugInfo.Parent); string? parentTooltip; public string? ParentTooltip => GenerateTooltip(ref parentTooltip, metadataFile, debugInfo.Parent); - [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] - public int Kind => MetadataTokens.GetHeapOffset(debugInfo.Kind); + static readonly (Guid Guid, string Name)[] knownKindNames = { + (KnownGuids.StateMachineHoistedLocalScopes, "State Machine Hoisted Local Scopes (C# / VB)"), + (KnownGuids.DynamicLocalVariables, "Dynamic Local Variables (C#)"), + (KnownGuids.DefaultNamespaces, "Default Namespaces (VB)"), + (KnownGuids.EditAndContinueLocalSlotMap, "Edit And Continue Local Slot Map (C# / VB)"), + (KnownGuids.EditAndContinueLambdaAndClosureMap, "Edit And Continue Lambda And Closure Map (C# / VB)"), + (KnownGuids.EncStateMachineStateMap, "Edit And Continue State Machine State Map (C# / VB)"), + (KnownGuids.EmbeddedSource, "Embedded Source (C# / VB)"), + (KnownGuids.SourceLink, "Source Link (C# / VB)"), + (KnownGuids.MethodSteppingInformation, "Method Stepping Information (C# / VB)"), + (KnownGuids.CompilationOptions, "Compilation Options (C# / VB)"), + (KnownGuids.CompilationMetadataReferences, "Compilation Metadata References (C# / VB)"), + (KnownGuids.TupleElementNames, "Tuple Element Names (C#)"), + (KnownGuids.TypeDefinitionDocuments, "Type Definition Documents (C# / VB)"), + }; + + string? kindString; + + /// + /// One cell carrying the GUID heap offset, the decoded friendly name of well-known + /// kind GUIDs, and the raw GUID, e.g. + /// 0000000B - Source Link (C# / VB) [cc110556-...]. Plain text (instead of a + /// bare heap offset plus tooltip) so the column filter matches the friendly names. + /// + public string Kind { + get { + if (kindString != null) + return kindString; + if (debugInfo.Kind.IsNil) + return kindString = ""; + var guid = metadataFile.Metadata.GetGuid(debugInfo.Kind); + string name = "Unknown"; + foreach (var (knownGuid, knownName) in knownKindNames) + { + if (guid == knownGuid) + { + name = knownName; + break; + } + } + return kindString = $"{MetadataTokens.GetHeapOffset(debugInfo.Kind):X8} - {name} [{guid}]"; + } + } [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] public int Value => MetadataTokens.GetHeapOffset(debugInfo.Value);