mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Adds the four CLI heap views (#Strings, #US, #GUID, #Blob) under each assembly's Metadata folder. Phase 1 caps each heap's text dump at 200 preview rows with a truncation footer; the full set arrives with the DataGrid view in Phase 2. Heap rows are materialised lazily on first demand so opening the Metadata folder doesn't pay for heaps the user never visits. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
7 changed files with 569 additions and 1 deletions
@ -0,0 +1,145 @@
@@ -0,0 +1,145 @@
|
||||
// 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.Linq; |
||||
using System.Threading.Tasks; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ILSpy.AppEnv; |
||||
using ILSpy.Metadata; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.ViewModels; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests.Metadata; |
||||
|
||||
[TestFixture] |
||||
public class HeapTreeTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task MetadataTreeNode_Surfaces_Four_Heap_Children_For_A_PE_Assembly() |
||||
{ |
||||
// CLI metadata divides ancillary data into four heaps: #Strings (member/type names),
|
||||
// #US (literal user strings emitted by ldstr), #GUID (16-byte module/type IDs), and
|
||||
// #Blob (signatures, marshalling info, custom-attr blobs). All four should be
|
||||
// reachable from the Metadata folder regardless of whether the file's debug
|
||||
// metadata is present.
|
||||
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
assemblyNode.EnsureLazyChildren(); |
||||
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single(); |
||||
metadataNode.EnsureLazyChildren(); |
||||
|
||||
metadataNode.Children.OfType<StringHeapTreeNode>().Should().ContainSingle(); |
||||
metadataNode.Children.OfType<UserStringHeapTreeNode>().Should().ContainSingle(); |
||||
metadataNode.Children.OfType<GuidHeapTreeNode>().Should().ContainSingle(); |
||||
metadataNode.Children.OfType<BlobHeapTreeNode>().Should().ContainSingle(); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task StringHeapTreeNode_Decompiles_Header_Plus_Preview_Of_Entries() |
||||
{ |
||||
// CoreLib's #Strings holds tens of thousands of entries; rendering all of them as
|
||||
// text would bloat the tab and bog down the editor. Phase 1 dumps a capped preview
|
||||
// (header + first N rows + truncation footer) so the user can scan the start of the
|
||||
// heap; Phase 2 swaps to a virtualising DataGrid for the full set.
|
||||
|
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
assemblyNode.EnsureLazyChildren(); |
||||
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single(); |
||||
metadataNode.EnsureLazyChildren(); |
||||
var heapNode = metadataNode.Children.OfType<StringHeapTreeNode>().Single(); |
||||
|
||||
vm.AssemblyTreeModel.SelectNode(heapNode); |
||||
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||
|
||||
tab.Text.Should().Contain("String Heap"); |
||||
tab.Text.Should().Contain("Offset"); |
||||
tab.Text.Should().Contain("Length"); |
||||
tab.Text.Should().Contain("Value"); |
||||
tab.Text.Should().Contain("more entries", "CoreLib's #Strings is well over the preview cap"); |
||||
// The preview includes a (count entries) header — should match e.g. "(24573 entries)".
|
||||
tab.Text.Should().MatchRegex(@"\(\d{3,} entries\)"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task GuidHeapTreeNode_Decompiles_Header_Plus_Index_Length_Value_Columns() |
||||
{ |
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
assemblyNode.EnsureLazyChildren(); |
||||
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single(); |
||||
metadataNode.EnsureLazyChildren(); |
||||
var heapNode = metadataNode.Children.OfType<GuidHeapTreeNode>().Single(); |
||||
|
||||
vm.AssemblyTreeModel.SelectNode(heapNode); |
||||
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||
|
||||
tab.Text.Should().Contain("Guid Heap"); |
||||
tab.Text.Should().Contain("Index"); |
||||
tab.Text.Should().Contain("Length"); |
||||
tab.Text.Should().MatchRegex(@"\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b", |
||||
"the GUID heap should contain at least one parsed GUID"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task BlobHeapTreeNode_Decompiles_Header_Plus_Preview_Of_Hex_Encoded_Entries() |
||||
{ |
||||
var window = AppComposition.Current.GetExport<MainWindow>(); |
||||
window.Show(); |
||||
var vm = (MainWindowViewModel)window.DataContext!; |
||||
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); |
||||
|
||||
var coreLibName = typeof(object).Assembly.GetName().Name!; |
||||
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName); |
||||
assemblyNode.EnsureLazyChildren(); |
||||
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single(); |
||||
metadataNode.EnsureLazyChildren(); |
||||
var heapNode = metadataNode.Children.OfType<BlobHeapTreeNode>().Single(); |
||||
|
||||
vm.AssemblyTreeModel.SelectNode(heapNode); |
||||
var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); |
||||
|
||||
tab.Text.Should().Contain("Blob Heap"); |
||||
tab.Text.Should().Contain("Offset"); |
||||
tab.Text.Should().Contain("Length"); |
||||
tab.Text.Should().Contain("Value"); |
||||
} |
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
// 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; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.Languages; |
||||
|
||||
namespace ILSpy.Metadata |
||||
{ |
||||
/// <summary>
|
||||
/// View of #Blob — variable-length byte payloads holding signatures, marshalling info,
|
||||
/// custom-attribute values, and similar binary data. Each entry is rendered as a hex
|
||||
/// dump; full structural decoding (e.g. parsed type signatures) is out of scope for
|
||||
/// the metadata viewer.
|
||||
/// </summary>
|
||||
public sealed class BlobHeapTreeNode : MetadataHeapTreeNode |
||||
{ |
||||
List<BlobHeapEntry>? entries; |
||||
|
||||
public BlobHeapTreeNode(MetadataFile metadataFile) |
||||
: base(HandleKind.Blob, metadataFile) |
||||
{ |
||||
} |
||||
|
||||
public override object Text => $"Blob Heap ({EnsureEntries().Count})"; |
||||
public override string ToString() => "Blob Heap"; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
var all = EnsureEntries(); |
||||
var preview = all.Count > PreviewLimit |
||||
? (IReadOnlyList<BlobHeapEntry>)all.GetRange(0, PreviewLimit) |
||||
: all; |
||||
language.WriteCommentLine(output, $"Blob Heap ({all.Count} entries)"); |
||||
MetadataTextWriter.WriteTable(language, output, preview); |
||||
if (all.Count > preview.Count) |
||||
language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); |
||||
} |
||||
|
||||
List<BlobHeapEntry> EnsureEntries() |
||||
{ |
||||
if (entries is { } cached) |
||||
return cached; |
||||
var list = new List<BlobHeapEntry>(); |
||||
var metadata = metadataFile.Metadata; |
||||
var handle = MetadataTokens.BlobHandle(0); |
||||
do |
||||
{ |
||||
list.Add(new BlobHeapEntry(metadata, handle)); |
||||
handle = metadata.GetNextHandle(handle); |
||||
} while (!handle.IsNil); |
||||
entries = list; |
||||
return list; |
||||
} |
||||
|
||||
public sealed class BlobHeapEntry |
||||
{ |
||||
readonly MetadataReader metadata; |
||||
readonly BlobHandle handle; |
||||
|
||||
[ColumnInfo("X8")] |
||||
public int Offset => metadata.GetHeapOffset(handle); |
||||
public int Length => metadata.GetBlobReader(handle).Length; |
||||
public string Value => metadata.GetBlobReader(handle).ToHexString(); |
||||
|
||||
public BlobHeapEntry(MetadataReader metadata, BlobHandle handle) |
||||
{ |
||||
this.metadata = metadata; |
||||
this.handle = handle; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,88 @@
@@ -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.Collections.Generic; |
||||
using System.Reflection.Metadata; |
||||
using System.Reflection.Metadata.Ecma335; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.Languages; |
||||
|
||||
namespace ILSpy.Metadata |
||||
{ |
||||
/// <summary>
|
||||
/// View of #GUID — a contiguous array of 16-byte GUIDs indexed (1-based) by ModuleDef
|
||||
/// rows and a handful of other tables. Indexing starts at 1 because index 0 is reserved
|
||||
/// for "no GUID".
|
||||
/// </summary>
|
||||
public sealed class GuidHeapTreeNode : MetadataHeapTreeNode |
||||
{ |
||||
List<GuidHeapEntry>? entries; |
||||
|
||||
public GuidHeapTreeNode(MetadataFile metadataFile) |
||||
: base(HandleKind.Guid, metadataFile) |
||||
{ |
||||
} |
||||
|
||||
public override object Text => $"Guid Heap ({EnsureEntries().Count})"; |
||||
public override string ToString() => "Guid Heap"; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
var all = EnsureEntries(); |
||||
var preview = all.Count > PreviewLimit |
||||
? (IReadOnlyList<GuidHeapEntry>)all.GetRange(0, PreviewLimit) |
||||
: all; |
||||
language.WriteCommentLine(output, $"Guid Heap ({all.Count} entries)"); |
||||
MetadataTextWriter.WriteTable(language, output, preview); |
||||
if (all.Count > preview.Count) |
||||
language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); |
||||
} |
||||
|
||||
List<GuidHeapEntry> EnsureEntries() |
||||
{ |
||||
if (entries is { } cached) |
||||
return cached; |
||||
var list = new List<GuidHeapEntry>(); |
||||
var metadata = metadataFile.Metadata; |
||||
int count = metadata.GetHeapSize(HeapIndex.Guid) >> 4; |
||||
for (int i = 1; i <= count; i++) |
||||
list.Add(new GuidHeapEntry(metadata, MetadataTokens.GuidHandle(i))); |
||||
entries = list; |
||||
return list; |
||||
} |
||||
|
||||
public sealed class GuidHeapEntry |
||||
{ |
||||
readonly MetadataReader metadata; |
||||
readonly GuidHandle handle; |
||||
|
||||
public int Index => metadata.GetHeapOffset(handle); |
||||
public int Length => 16; |
||||
public string Value => metadata.GetGuid(handle).ToString(); |
||||
|
||||
public GuidHeapEntry(MetadataReader metadata, GuidHandle handle) |
||||
{ |
||||
this.metadata = metadata; |
||||
this.handle = handle; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
// 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.Reflection.Metadata; |
||||
|
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.TreeNodes; |
||||
|
||||
namespace ILSpy.Metadata |
||||
{ |
||||
/// <summary>
|
||||
/// Common parent for the four heap views (#Strings, #US, #GUID, #Blob). Holds the
|
||||
/// shared icon and the <see cref="HandleKind"/> that distinguishes them — so token
|
||||
/// navigation in Phase 3 can dispatch a heap-typed handle to the right node by walking
|
||||
/// children and matching <see cref="Kind"/>. Concrete subclasses own row materialisation
|
||||
/// and rendering because each heap has a different row shape.
|
||||
/// </summary>
|
||||
public abstract class MetadataHeapTreeNode : ILSpyTreeNode |
||||
{ |
||||
public const int PreviewLimit = 200; |
||||
|
||||
protected readonly MetadataFile metadataFile; |
||||
|
||||
public HandleKind Kind { get; } |
||||
|
||||
protected MetadataHeapTreeNode(HandleKind kind, MetadataFile metadataFile) |
||||
{ |
||||
Kind = kind; |
||||
this.metadataFile = metadataFile ?? throw new ArgumentNullException(nameof(metadataFile)); |
||||
} |
||||
|
||||
public override object Icon => Images.Images.MetadataTable; |
||||
} |
||||
} |
||||
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
// 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; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.Languages; |
||||
|
||||
namespace ILSpy.Metadata |
||||
{ |
||||
/// <summary>
|
||||
/// View of #Strings — the heap that holds every UTF-8 type/member/namespace name
|
||||
/// referenced by the metadata tables. Lazy materialisation: handles are walked on
|
||||
/// demand via <see cref="MetadataReader.GetNextHandle(StringHandle)"/> the first time a
|
||||
/// caller asks for the count or a preview row.
|
||||
/// </summary>
|
||||
public sealed class StringHeapTreeNode : MetadataHeapTreeNode |
||||
{ |
||||
List<StringHeapEntry>? entries; |
||||
|
||||
public StringHeapTreeNode(MetadataFile metadataFile) |
||||
: base(HandleKind.String, metadataFile) |
||||
{ |
||||
} |
||||
|
||||
public override object Text => $"String Heap ({EnsureEntries().Count})"; |
||||
public override string ToString() => "String Heap"; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
var all = EnsureEntries(); |
||||
var preview = all.Count > PreviewLimit |
||||
? (IReadOnlyList<StringHeapEntry>)all.GetRange(0, PreviewLimit) |
||||
: all; |
||||
language.WriteCommentLine(output, $"String Heap ({all.Count} entries)"); |
||||
MetadataTextWriter.WriteTable(language, output, preview); |
||||
if (all.Count > preview.Count) |
||||
language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); |
||||
} |
||||
|
||||
List<StringHeapEntry> EnsureEntries() |
||||
{ |
||||
if (entries is { } cached) |
||||
return cached; |
||||
var list = new List<StringHeapEntry>(); |
||||
var metadata = metadataFile.Metadata; |
||||
var handle = MetadataTokens.StringHandle(0); |
||||
do |
||||
{ |
||||
list.Add(new StringHeapEntry(metadata, handle)); |
||||
handle = metadata.GetNextHandle(handle); |
||||
} while (!handle.IsNil); |
||||
entries = list; |
||||
return list; |
||||
} |
||||
|
||||
public sealed class StringHeapEntry |
||||
{ |
||||
readonly MetadataReader metadata; |
||||
readonly StringHandle handle; |
||||
|
||||
[ColumnInfo("X8")] |
||||
public int Offset => metadata.GetHeapOffset(handle); |
||||
public int Length => metadata.GetString(handle).Length; |
||||
public string Value => metadata.GetString(handle); |
||||
|
||||
public StringHeapEntry(MetadataReader metadata, StringHandle handle) |
||||
{ |
||||
this.metadata = metadata; |
||||
this.handle = handle; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
// 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; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.Languages; |
||||
|
||||
namespace ILSpy.Metadata |
||||
{ |
||||
/// <summary>
|
||||
/// View of #US — the UTF-16 heap holding every literal string emitted by an <c>ldstr</c>
|
||||
/// instruction. Distinct from #Strings (which carries identifier names); user strings
|
||||
/// are referenced by metadata tokens, not by member-name resolution.
|
||||
/// </summary>
|
||||
public sealed class UserStringHeapTreeNode : MetadataHeapTreeNode |
||||
{ |
||||
List<UserStringHeapEntry>? entries; |
||||
|
||||
public UserStringHeapTreeNode(MetadataFile metadataFile) |
||||
: base(HandleKind.UserString, metadataFile) |
||||
{ |
||||
} |
||||
|
||||
public override object Text => $"UserString Heap ({EnsureEntries().Count})"; |
||||
public override string ToString() => "UserString Heap"; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
var all = EnsureEntries(); |
||||
var preview = all.Count > PreviewLimit |
||||
? (IReadOnlyList<UserStringHeapEntry>)all.GetRange(0, PreviewLimit) |
||||
: all; |
||||
language.WriteCommentLine(output, $"UserString Heap ({all.Count} entries)"); |
||||
MetadataTextWriter.WriteTable(language, output, preview); |
||||
if (all.Count > preview.Count) |
||||
language.WriteCommentLine(output, $"... ({all.Count - preview.Count} more entries; full view ships with the metadata DataGrid in a future release)"); |
||||
} |
||||
|
||||
List<UserStringHeapEntry> EnsureEntries() |
||||
{ |
||||
if (entries is { } cached) |
||||
return cached; |
||||
var list = new List<UserStringHeapEntry>(); |
||||
var metadata = metadataFile.Metadata; |
||||
var handle = MetadataTokens.UserStringHandle(0); |
||||
do |
||||
{ |
||||
list.Add(new UserStringHeapEntry(metadata, handle)); |
||||
handle = metadata.GetNextHandle(handle); |
||||
} while (!handle.IsNil); |
||||
entries = list; |
||||
return list; |
||||
} |
||||
|
||||
public sealed class UserStringHeapEntry |
||||
{ |
||||
readonly MetadataReader metadata; |
||||
readonly UserStringHandle handle; |
||||
|
||||
[ColumnInfo("X8")] |
||||
public int Offset => metadata.GetHeapOffset(handle); |
||||
public int Length => metadata.GetUserString(handle).Length; |
||||
public string Value => metadata.GetUserString(handle); |
||||
|
||||
public UserStringHeapEntry(MetadataReader metadata, UserStringHandle handle) |
||||
{ |
||||
this.metadata = metadata; |
||||
this.handle = handle; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue