From ef06d5aaf4e3f3b810d62093f5f300554f361696 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 7 May 2026 14:52:41 +0200 Subject: [PATCH] Typed leaves for the last 12 cor tables Closes out Phase 1e: every CLI metadata table now has its own typed leaf. Bytes-level table walking goes through MetadataReader.AsBlobReader (public API) instead of the WPF Helpers' AsReadOnlySpan path; coded-token columns are decoded by the new MetadataReaderHelpers shim, which reflects against the same private TypeDefOrRefTag / HasFieldMarshalTag / MemberForwardedTag.ConvertToHandle methods + ComputeCodedTokenSize that the WPF host uses (a known forward-compatibility risk per the plan's risk register). Assisted-by: Claude:claude-opus-4-7:Claude Code --- .../CorTables/ClassLayoutTableTreeNode.cs | 83 ++++++++++++++ .../CorTables/EventMapTableTreeNode.cs | 80 +++++++++++++ .../CorTables/FieldLayoutTableTreeNode.cs | 81 +++++++++++++ .../CorTables/FieldMarshalTableTreeNode.cs | 83 ++++++++++++++ .../CorTables/FieldRVATableTreeNode.cs | 80 +++++++++++++ .../CorTables/ImplMapTableTreeNode.cs | 95 ++++++++++++++++ .../CorTables/InterfaceImplTableTreeNode.cs | 82 ++++++++++++++ .../CorTables/MethodImplTableTreeNode.cs | 75 +++++++++++++ .../CorTables/MethodSemanticsTableTreeNode.cs | 77 +++++++++++++ .../CorTables/NestedClassTableTreeNode.cs | 80 +++++++++++++ .../CorTables/PropertyMapTableTreeNode.cs | 80 +++++++++++++ ILSpy/Metadata/CorTables/PtrTableTreeNode.cs | 88 +++++++++++++++ ILSpy/Metadata/Helpers.cs | 106 ++++++++++++++++++ ILSpy/Metadata/MetadataTablesTreeNode.cs | 13 +++ 14 files changed, 1103 insertions(+) create mode 100644 ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs create mode 100644 ILSpy/Metadata/CorTables/PtrTableTreeNode.cs diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs new file mode 100644 index 000000000..1bd746efd --- /dev/null +++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the ClassLayout table — explicit field-layout overrides on TypeDefs marked + /// Sequential or Explicit (e.g. [StructLayout(LayoutKind.Explicit)]). System.Reflection.Metadata + /// doesn't surface these rows through a typed enumeration so we walk the table bytes + /// directly via a BlobReader. + /// + public sealed class ClassLayoutTableTreeNode : MetadataTableTreeNode + { + public ClassLayoutTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.ClassLayout, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.ClassLayout); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.ClassLayout); + int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + ushort packingSize = reader.ReadUInt16(); + uint classSize = reader.ReadUInt32(); + int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new ClassLayoutEntry(rid, packingSize, classSize, MetadataTokens.TypeDefinitionHandle(parentRow))); + } + return list; + } + + public sealed class ClassLayoutEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x0F000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Parent => MetadataTokens.GetToken(parent); + + public ushort PackingSize { get; } + + public uint ClassSize { get; } + + readonly TypeDefinitionHandle parent; + + public ClassLayoutEntry(int rid, ushort packingSize, uint classSize, TypeDefinitionHandle parent) + { + RID = rid; + PackingSize = packingSize; + ClassSize = classSize; + this.parent = parent; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs new file mode 100644 index 000000000..9acedccd6 --- /dev/null +++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the EventMap table — pairs each TypeDef that declares events with the first + /// row of its event list (later rows belong to the same type until the next EventMap row). + /// + public sealed class EventMapTableTreeNode : MetadataTableTreeNode + { + public EventMapTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.EventMap, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.EventMap); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.EventMap); + int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; + int eventDefSize = metadata.GetTableRowCount(TableIndex.Event) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + int eventListRow = eventDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new EventMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.EventDefinitionHandle(eventListRow))); + } + return list; + } + + public sealed class EventMapEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x12000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Parent => MetadataTokens.GetToken(parent); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int EventList => MetadataTokens.GetToken(eventList); + + readonly TypeDefinitionHandle parent; + readonly EventDefinitionHandle eventList; + + public EventMapEntry(int rid, TypeDefinitionHandle parent, EventDefinitionHandle eventList) + { + RID = rid; + this.parent = parent; + this.eventList = eventList; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs new file mode 100644 index 000000000..28976fb1f --- /dev/null +++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs @@ -0,0 +1,81 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the FieldLayout table — explicit per-field byte offsets for fields whose + /// owning TypeDef has set. + /// + public sealed class FieldLayoutTableTreeNode : MetadataTableTreeNode + { + public FieldLayoutTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.FieldLayout, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.FieldLayout); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.FieldLayout); + int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + int offset = reader.ReadInt32(); + int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new FieldLayoutEntry(rid, offset, MetadataTokens.FieldDefinitionHandle(fieldRow))); + } + return list; + } + + public sealed class FieldLayoutEntry + { + // Use a verbatim fieldHandle local because C# 14 made `field` contextual inside property + // accessors — referencing it without `@` would name the auto-property's hidden + // backing field instead of this member. + readonly FieldDefinitionHandle fieldHandle; + + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x10000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Field => MetadataTokens.GetToken(fieldHandle); + + [ColumnInfo("X8")] + public int FieldOffset { get; } + + public FieldLayoutEntry(int rid, int fieldOffset, FieldDefinitionHandle field) + { + RID = rid; + FieldOffset = fieldOffset; + fieldHandle = field; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs new file mode 100644 index 000000000..9bd226b48 --- /dev/null +++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs @@ -0,0 +1,83 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the FieldMarshal table — native-marshalling descriptors attached to fields + /// or parameters. Parent is a HasFieldMarshal coded token (Field / Param); NativeType + /// is the heap-offset of a marshal-spec blob. + /// + public sealed class FieldMarshalTableTreeNode : MetadataTableTreeNode + { + public FieldMarshalTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.FieldMarshal, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.FieldMarshal); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.FieldMarshal); + int hasFieldMarshalRefSize = metadata.ComputeCodedTokenSize(32768, TableMask.Field | TableMask.Param); + int blobHeapSize = metadata.GetHeapSize(HeapIndex.Blob) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + uint parentTag = (uint)(hasFieldMarshalRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32()); + int blobOffset = blobHeapSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new FieldMarshalEntry(rid, + MetadataReaderHelpers.FromHasFieldMarshalTag(parentTag), + MetadataTokens.BlobHandle(blobOffset))); + } + return list; + } + + public sealed class FieldMarshalEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x0D000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Parent => MetadataTokens.GetToken(parent); + + [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] + public int NativeType => MetadataTokens.GetHeapOffset(nativeType); + + readonly EntityHandle parent; + readonly BlobHandle nativeType; + + public FieldMarshalEntry(int rid, EntityHandle parent, BlobHandle nativeType) + { + RID = rid; + this.parent = parent; + this.nativeType = nativeType; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs new file mode 100644 index 000000000..e550a13fc --- /dev/null +++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the FieldRVA table — for static fields backed by a fixed PE-image RVA, the + /// table maps each FieldDef to its data location. Common with fixed array initializers + /// (the compiler emits a synthesised type whose static fields point at the .rdata bytes). + /// + public sealed class FieldRVATableTreeNode : MetadataTableTreeNode + { + public FieldRVATableTreeNode(MetadataFile metadataFile) + : base(TableIndex.FieldRva, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.FieldRva); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.FieldRva); + int fieldDefSize = metadata.GetTableRowCount(TableIndex.Field) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + int rva = reader.ReadInt32(); + int fieldRow = fieldDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new FieldRVAEntry(rid, rva, MetadataTokens.FieldDefinitionHandle(fieldRow))); + } + return list; + } + + public sealed class FieldRVAEntry + { + // Verbatim fieldHandle — see FieldLayoutTableTreeNode for the C# 14 keyword note. + readonly FieldDefinitionHandle fieldHandle; + + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x1D000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Field => MetadataTokens.GetToken(fieldHandle); + + [ColumnInfo("X8")] + public int RVA { get; } + + public FieldRVAEntry(int rid, int rva, FieldDefinitionHandle field) + { + RID = rid; + RVA = rva; + fieldHandle = field; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs new file mode 100644 index 000000000..6e1b6e94a --- /dev/null +++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs @@ -0,0 +1,95 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the ImplMap table — P/Invoke linkage. Each row maps a managed MethodDef (or + /// Field, in legacy CIL) to its native entry point: the extern name plus the + /// ModuleRef holding the DLL identifier. + /// + public sealed class ImplMapTableTreeNode : MetadataTableTreeNode + { + public ImplMapTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.ImplMap, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.ImplMap); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.ImplMap); + int moduleRefSize = metadata.GetTableRowCount(TableIndex.ModuleRef) < ushort.MaxValue ? 2 : 4; + int memberForwardedTagSize = metadata.ComputeCodedTokenSize(32768, TableMask.MethodDef | TableMask.Field); + int stringHandleSize = metadata.GetHeapSize(HeapIndex.String) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + MethodImportAttributes mappingFlags = (MethodImportAttributes)reader.ReadUInt16(); + uint memberForwardedTag = (uint)(memberForwardedTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32()); + int importNameOffset = stringHandleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + int importScopeRow = moduleRefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new ImplMapEntry(rid, mappingFlags, + MetadataReaderHelpers.FromMemberForwardedTag(memberForwardedTag), + metadata.GetString(MetadataTokens.StringHandle(importNameOffset)), + MetadataTokens.ModuleReferenceHandle(importScopeRow))); + } + return list; + } + + public sealed class ImplMapEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x1C000000 | RID; + + [ColumnInfo("X8")] + public MethodImportAttributes MappingFlags { get; } + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int MemberForwarded => MetadataTokens.GetToken(memberForwarded); + + public string ImportName { get; } + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int ImportScope => MetadataTokens.GetToken(importScope); + + readonly EntityHandle memberForwarded; + readonly ModuleReferenceHandle importScope; + + public ImplMapEntry(int rid, MethodImportAttributes mappingFlags, EntityHandle memberForwarded, string importName, ModuleReferenceHandle importScope) + { + RID = rid; + MappingFlags = mappingFlags; + this.memberForwarded = memberForwarded; + ImportName = importName; + this.importScope = importScope; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs new file mode 100644 index 000000000..3116335e7 --- /dev/null +++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs @@ -0,0 +1,82 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the InterfaceImpl table — every type/interface implementation pair. Class is + /// a TypeDef row; Interface is a TypeDefOrRef coded token (TypeDef / TypeRef / TypeSpec). + /// + public sealed class InterfaceImplTableTreeNode : MetadataTableTreeNode + { + public InterfaceImplTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.InterfaceImpl, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.InterfaceImpl); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.InterfaceImpl); + int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; + int interfaceTagSize = metadata.ComputeCodedTokenSize(32768, TableMask.TypeDef | TableMask.TypeRef | TableMask.TypeSpec); + for (int rid = 1; rid <= length; rid++) + { + int classRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + uint interfaceTag = (uint)(interfaceTagSize == 2 ? reader.ReadUInt16() : reader.ReadInt32()); + list.Add(new InterfaceImplEntry(rid, + MetadataTokens.TypeDefinitionHandle(classRow), + MetadataReaderHelpers.FromTypeDefOrRefTag(interfaceTag))); + } + return list; + } + + public sealed class InterfaceImplEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x09000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Class => MetadataTokens.GetToken(@class); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Interface => MetadataTokens.GetToken(@interface); + + readonly TypeDefinitionHandle @class; + readonly EntityHandle @interface; + + public InterfaceImplEntry(int rid, TypeDefinitionHandle @class, EntityHandle @interface) + { + RID = rid; + this.@class = @class; + this.@interface = @interface; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs new file mode 100644 index 000000000..f3f07bf17 --- /dev/null +++ b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the MethodImpl table — explicit interface implementations and method-body + /// overrides where the declared signature differs from the impl signature. Each row + /// pairs a Type, a MethodDeclaration (the override target), and a MethodBody (the impl). + /// + public sealed class MethodImplTableTreeNode : MetadataTableTreeNode + { + public MethodImplTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.MethodImpl, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + for (int row = 1; row <= metadataFile.Metadata.GetTableRowCount(TableIndex.MethodImpl); row++) + list.Add(new MethodImplEntry(metadataFile, MetadataTokens.MethodImplementationHandle(row))); + return list; + } + + public sealed class MethodImplEntry + { + readonly MetadataFile metadataFile; + readonly MethodImplementationHandle handle; + readonly MethodImplementation methodImpl; + + public int RID => MetadataTokens.GetToken(handle) & 0xFFFFFF; + + [ColumnInfo("X8")] + public int Token => MetadataTokens.GetToken(handle); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Type => MetadataTokens.GetToken(methodImpl.Type); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int MethodDeclaration => MetadataTokens.GetToken(methodImpl.MethodDeclaration); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int MethodBody => MetadataTokens.GetToken(methodImpl.MethodBody); + + public MethodImplEntry(MetadataFile metadataFile, MethodImplementationHandle handle) + { + this.metadataFile = metadataFile; + this.handle = handle; + methodImpl = metadataFile.Metadata.GetMethodImplementation(handle); + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs new file mode 100644 index 000000000..5efc61ec3 --- /dev/null +++ b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs @@ -0,0 +1,77 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the MethodSemantics table — links accessor methods (get/set/add/remove/raise) + /// to the property or event they implement. + /// returns a typed enumeration so byte-level reading isn't required. + /// + public sealed class MethodSemanticsTableTreeNode : MetadataTableTreeNode + { + public MethodSemanticsTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.MethodSemantics, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + foreach (var row in metadataFile.Metadata.GetMethodSemantics()) + list.Add(new MethodSemanticsEntry(row.Handle, row.Semantics, row.Method, row.Association)); + return list; + } + + public sealed class MethodSemanticsEntry + { + public int RID => MetadataTokens.GetToken(handle) & 0xFFFFFF; + + [ColumnInfo("X8")] + public int Token => MetadataTokens.GetToken(handle); + + [ColumnInfo("X8")] + public MethodSemanticsAttributes Semantics { get; } + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Method => MetadataTokens.GetToken(method); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Association => MetadataTokens.GetToken(association); + + readonly Handle handle; + readonly MethodDefinitionHandle method; + readonly EntityHandle association; + + public MethodSemanticsEntry(Handle handle, MethodSemanticsAttributes semantics, MethodDefinitionHandle method, EntityHandle association) + { + this.handle = handle; + Semantics = semantics; + this.method = method; + this.association = association; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs new file mode 100644 index 000000000..a8b4de244 --- /dev/null +++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the NestedClass table — pairs each nested type with its enclosing type. Walked + /// directly from the table bytes because System.Reflection.Metadata only exposes + /// nested-type discovery as a per-typedef enumeration. + /// + public sealed class NestedClassTableTreeNode : MetadataTableTreeNode + { + public NestedClassTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.NestedClass, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.NestedClass); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.NestedClass); + int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + int nestedRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + int enclosingRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new NestedClassEntry(rid, MetadataTokens.TypeDefinitionHandle(nestedRow), MetadataTokens.TypeDefinitionHandle(enclosingRow))); + } + return list; + } + + public sealed class NestedClassEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x29000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int NestedClass => MetadataTokens.GetToken(nested); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int EnclosingClass => MetadataTokens.GetToken(enclosing); + + readonly TypeDefinitionHandle nested; + readonly TypeDefinitionHandle enclosing; + + public NestedClassEntry(int rid, TypeDefinitionHandle nested, TypeDefinitionHandle enclosing) + { + RID = rid; + this.nested = nested; + this.enclosing = enclosing; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs new file mode 100644 index 000000000..c1d86ce66 --- /dev/null +++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs @@ -0,0 +1,80 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the PropertyMap table — pairs each TypeDef that declares properties with the + /// first row of its property list. Same shape as EventMap but for properties. + /// + public sealed class PropertyMapTableTreeNode : MetadataTableTreeNode + { + public PropertyMapTableTreeNode(MetadataFile metadataFile) + : base(TableIndex.PropertyMap, metadataFile) + { + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(TableIndex.PropertyMap); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(TableIndex.PropertyMap); + int typeDefSize = metadata.GetTableRowCount(TableIndex.TypeDef) < ushort.MaxValue ? 2 : 4; + int propertyDefSize = metadata.GetTableRowCount(TableIndex.Property) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + int parentRow = typeDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + int propertyListRow = propertyDefSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new PropertyMapEntry(rid, MetadataTokens.TypeDefinitionHandle(parentRow), MetadataTokens.PropertyDefinitionHandle(propertyListRow))); + } + return list; + } + + public sealed class PropertyMapEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => 0x15000000 | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Parent => MetadataTokens.GetToken(parent); + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int PropertyList => MetadataTokens.GetToken(propertyList); + + readonly TypeDefinitionHandle parent; + readonly PropertyDefinitionHandle propertyList; + + public PropertyMapEntry(int rid, TypeDefinitionHandle parent, PropertyDefinitionHandle propertyList) + { + RID = rid; + this.parent = parent; + this.propertyList = propertyList; + } + } + } +} diff --git a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs new file mode 100644 index 000000000..71fc2c481 --- /dev/null +++ b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs @@ -0,0 +1,88 @@ +// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; + +using ICSharpCode.Decompiler.Metadata; + +namespace ILSpy.Metadata.CorTables +{ + /// + /// View of the five Ptr tables (FieldPtr / MethodPtr / ParamPtr / EventPtr / PropertyPtr). + /// Present only in metadata produced with #~ + indirection (rare; mostly EnC or some + /// custom emitters), and each row carries a single handle into the table the Ptr points + /// at. One node type drives all five since the row shape is identical. + /// + public sealed class PtrTableTreeNode : MetadataTableTreeNode + { + readonly TableIndex referencedTableKind; + + public PtrTableTreeNode(TableIndex kind, MetadataFile metadataFile) + : base(kind, metadataFile) + { + referencedTableKind = kind switch { + TableIndex.EventPtr => TableIndex.Event, + TableIndex.FieldPtr => TableIndex.Field, + TableIndex.MethodPtr => TableIndex.MethodDef, + TableIndex.ParamPtr => TableIndex.Param, + TableIndex.PropertyPtr => TableIndex.Property, + _ => throw new ArgumentOutOfRangeException(nameof(kind), $"PtrTable does not support {kind}"), + }; + } + + protected override IReadOnlyList LoadTable() + { + var list = new List(); + var metadata = metadataFile.Metadata; + var length = metadata.GetTableRowCount(Kind); + var reader = metadata.AsBlobReader(); + reader.Offset = metadata.GetTableMetadataOffset(Kind); + int handleSize = metadata.GetTableRowCount(referencedTableKind) < ushort.MaxValue ? 2 : 4; + for (int rid = 1; rid <= length; rid++) + { + int handleRow = handleSize == 2 ? reader.ReadUInt16() : reader.ReadInt32(); + list.Add(new PtrEntry(rid, Kind, MetadataTokens.EntityHandle(((int)referencedTableKind << 24) | handleRow))); + } + return list; + } + + public sealed class PtrEntry + { + public int RID { get; } + + [ColumnInfo("X8")] + public int Token => ((int)kind << 24) | RID; + + [ColumnInfo("X8", Kind = ColumnKind.Token)] + public int Handle => MetadataTokens.GetToken(handle); + + readonly TableIndex kind; + readonly EntityHandle handle; + + public PtrEntry(int rid, TableIndex kind, EntityHandle handle) + { + RID = rid; + this.kind = kind; + this.handle = handle; + } + } + } +} diff --git a/ILSpy/Metadata/Helpers.cs b/ILSpy/Metadata/Helpers.cs index ad161638e..1fb09a307 100644 --- a/ILSpy/Metadata/Helpers.cs +++ b/ILSpy/Metadata/Helpers.cs @@ -18,6 +18,8 @@ using System; using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Metadata; namespace ILSpy.Metadata { @@ -90,4 +92,108 @@ namespace ILSpy.Metadata Meaning = meaning; } } + + /// + /// Bitmask covering each CLI metadata table. Used as the second argument to + /// when computing how many bytes + /// a coded-token column occupies in a given metadata blob — same encoding as the WPF + /// host's TableMask. + /// + [Flags] + public enum TableMask : ulong + { + Module = 0x1, + TypeRef = 0x2, + TypeDef = 0x4, + FieldPtr = 0x8, + Field = 0x10, + MethodPtr = 0x20, + MethodDef = 0x40, + ParamPtr = 0x80, + Param = 0x100, + InterfaceImpl = 0x200, + MemberRef = 0x400, + Constant = 0x800, + CustomAttribute = 0x1000, + FieldMarshal = 0x2000, + DeclSecurity = 0x4000, + ClassLayout = 0x8000, + FieldLayout = 0x10000, + StandAloneSig = 0x20000, + EventMap = 0x40000, + EventPtr = 0x80000, + Event = 0x100000, + PropertyMap = 0x200000, + PropertyPtr = 0x400000, + Property = 0x800000, + MethodSemantics = 0x1000000, + MethodImpl = 0x2000000, + ModuleRef = 0x4000000, + TypeSpec = 0x8000000, + ImplMap = 0x10000000, + FieldRva = 0x20000000, + EnCLog = 0x40000000, + EnCMap = 0x80000000, + Assembly = 0x100000000, + AssemblyRef = 0x800000000, + File = 0x4000000000, + ExportedType = 0x8000000000, + ManifestResource = 0x10000000000, + NestedClass = 0x20000000000, + GenericParam = 0x40000000000, + MethodSpec = 0x80000000000, + GenericParamConstraint = 0x100000000000, + Document = 0x1000000000000, + MethodDebugInformation = 0x2000000000000, + LocalScope = 0x4000000000000, + LocalVariable = 0x8000000000000, + LocalConstant = 0x10000000000000, + ImportScope = 0x20000000000000, + StateMachineMethod = 0x40000000000000, + CustomDebugInformation = 0x80000000000000, + } + + /// + /// Reflection-backed shims onto System.Reflection.Metadata internals — needed to + /// decode coded-token columns and compute their on-disk widths. The corresponding public + /// APIs do not exist; the WPF host carries the same reflection infrastructure. Pre-existing + /// risk: if a future runtime renames TypeDefOrRefTag.ConvertToHandle or the + /// TableRowCounts field on MetadataReader, these methods will throw and + /// the affected metadata-table viewers will break. + /// + public static class MetadataReaderHelpers + { + static readonly FieldInfo? rowCountsField; + static readonly MethodInfo? computeCodedTokenSizeMethod; + static readonly MethodInfo? typeDefOrRefConvert; + static readonly MethodInfo? hasFieldMarshalConvert; + static readonly MethodInfo? memberForwardedConvert; + + static MetadataReaderHelpers() + { + rowCountsField = typeof(MetadataReader) + .GetField("TableRowCounts", BindingFlags.NonPublic | BindingFlags.Instance); + computeCodedTokenSizeMethod = typeof(MetadataReader) + .GetMethod("ComputeCodedTokenSize", BindingFlags.Instance | BindingFlags.NonPublic); + var asm = typeof(TypeDefinitionHandle).Assembly; + typeDefOrRefConvert = asm.GetType("System.Reflection.Metadata.Ecma335.TypeDefOrRefTag") + ?.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic); + hasFieldMarshalConvert = asm.GetType("System.Reflection.Metadata.Ecma335.HasFieldMarshalTag") + ?.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic); + memberForwardedConvert = asm.GetType("System.Reflection.Metadata.Ecma335.MemberForwardedTag") + ?.GetMethod("ConvertToHandle", BindingFlags.Static | BindingFlags.NonPublic); + } + + public static EntityHandle FromTypeDefOrRefTag(uint tag) + => (EntityHandle)typeDefOrRefConvert!.Invoke(null, [tag])!; + + public static EntityHandle FromHasFieldMarshalTag(uint tag) + => (EntityHandle)hasFieldMarshalConvert!.Invoke(null, [tag])!; + + public static EntityHandle FromMemberForwardedTag(uint tag) + => (EntityHandle)memberForwardedConvert!.Invoke(null, [tag])!; + + public static int ComputeCodedTokenSize(this MetadataReader metadata, int largeRowSize, TableMask mask) + => (int)computeCodedTokenSizeMethod!.Invoke(metadata, [largeRowSize, rowCountsField!.GetValue(metadata)!, (ulong)mask])!; + } } diff --git a/ILSpy/Metadata/MetadataTablesTreeNode.cs b/ILSpy/Metadata/MetadataTablesTreeNode.cs index bfbd1f474..d371fb6a5 100644 --- a/ILSpy/Metadata/MetadataTablesTreeNode.cs +++ b/ILSpy/Metadata/MetadataTablesTreeNode.cs @@ -106,6 +106,19 @@ namespace ILSpy.Metadata TableIndex.ImportScope => new ImportScopeTableTreeNode(metadataFile), TableIndex.StateMachineMethod => new StateMachineMethodTableTreeNode(metadataFile), TableIndex.CustomDebugInformation => new CustomDebugInformationTableTreeNode(metadataFile), + TableIndex.MethodImpl => new MethodImplTableTreeNode(metadataFile), + TableIndex.MethodSemantics => new MethodSemanticsTableTreeNode(metadataFile), + TableIndex.ClassLayout => new ClassLayoutTableTreeNode(metadataFile), + TableIndex.FieldLayout => new FieldLayoutTableTreeNode(metadataFile), + TableIndex.FieldRva => new FieldRVATableTreeNode(metadataFile), + TableIndex.NestedClass => new NestedClassTableTreeNode(metadataFile), + TableIndex.EventMap => new EventMapTableTreeNode(metadataFile), + TableIndex.PropertyMap => new PropertyMapTableTreeNode(metadataFile), + TableIndex.InterfaceImpl => new InterfaceImplTableTreeNode(metadataFile), + TableIndex.FieldMarshal => new FieldMarshalTableTreeNode(metadataFile), + TableIndex.ImplMap => new ImplMapTableTreeNode(metadataFile), + TableIndex.FieldPtr or TableIndex.MethodPtr or TableIndex.ParamPtr + or TableIndex.EventPtr or TableIndex.PropertyPtr => new PtrTableTreeNode(table, metadataFile), _ => new UnsupportedMetadataTableTreeNode(table, metadataFile), }; }