mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Assisted-by: Claude:claude-opus-4-7:Claude Code Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
3 changed files with 303 additions and 15 deletions
@ -0,0 +1,133 @@
@@ -0,0 +1,133 @@
|
||||
// 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; |
||||
|
||||
/// <summary>
|
||||
/// Pins the typed-children breakdown of <see cref="DebugDirectoryTreeNode"/>: CodeView
|
||||
/// + PdbChecksum entries get specialised tree nodes that surface their fields, other
|
||||
/// entry types fall back to a generic node with a hex-dump <c>Decompile</c>. CoreLib's
|
||||
/// PE always ships at least a CodeView entry plus a Reproducible entry, which lets us
|
||||
/// validate both the typed and the fallback paths against a real assembly.
|
||||
/// </summary>
|
||||
[TestFixture] |
||||
public class DebugDirectoryChildNodesTests |
||||
{ |
||||
[AvaloniaTest] |
||||
public async Task DebugDirectoryTreeNode_Surfaces_CodeViewTreeNode_For_The_CodeView_Entry() |
||||
{ |
||||
var debugDir = await GetDebugDirectoryForCoreLib(); |
||||
|
||||
// CoreLib ships at least one CodeView entry (private + public PDB descriptions
|
||||
// can produce multiple); the contract is "every CodeView entry surfaces as a
|
||||
// typed child", not a specific count.
|
||||
debugDir.Children.OfType<CodeViewTreeNode>().Should().NotBeEmpty( |
||||
"every System.Private.CoreLib build ships at least one CodeView debug-directory entry"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task DebugDirectoryTreeNode_Surfaces_PdbChecksumTreeNode_For_PdbChecksum_Entries() |
||||
{ |
||||
var debugDir = await GetDebugDirectoryForCoreLib(); |
||||
|
||||
debugDir.Children.OfType<PdbChecksumTreeNode>().Should().NotBeEmpty( |
||||
"modern CoreLib builds ship one or more PdbChecksum debug-directory entries"); |
||||
var sum = debugDir.Children.OfType<PdbChecksumTreeNode>().First(); |
||||
sum.AlgorithmName.Should().NotBeNullOrEmpty(); |
||||
sum.Checksum.Should().NotBeNullOrEmpty(); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task DebugDirectoryTreeNode_Falls_Back_To_Generic_Entry_For_Unspecialised_Types() |
||||
{ |
||||
// Reproducible entries are zero-payload and have no specialised viewer; they should
|
||||
// still surface as DebugDirectoryEntryTreeNode children so the user can see them in
|
||||
// the tree alongside the typed entries.
|
||||
var debugDir = await GetDebugDirectoryForCoreLib(); |
||||
|
||||
var generic = debugDir.Children.OfType<DebugDirectoryEntryTreeNode>() |
||||
.Where(n => n.GetType() == typeof(DebugDirectoryEntryTreeNode)) |
||||
.ToList(); |
||||
generic.Should().NotBeEmpty("expected at least one fallback entry (e.g. Reproducible)"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public async Task Every_Entry_In_The_PE_Debug_Directory_Becomes_A_Child_Node() |
||||
{ |
||||
// Sanity check: no entry is silently dropped. The child count must match the raw
|
||||
// directory's entry count exactly (modulo embedded-PDB BadImageFormat skips, which
|
||||
// don't happen on healthy CoreLib).
|
||||
var (debugDir, peEntryCount) = await GetDebugDirectoryAndRawCountForCoreLib(); |
||||
|
||||
debugDir.Children.Count.Should().Be(peEntryCount); |
||||
} |
||||
|
||||
// --- helpers ---------------------------------------------------------------------------
|
||||
|
||||
static async Task<DebugDirectoryTreeNode> GetDebugDirectoryForCoreLib() |
||||
{ |
||||
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 debugDir = metadataNode.Children.OfType<DebugDirectoryTreeNode>().Single(); |
||||
debugDir.EnsureLazyChildren(); |
||||
return debugDir; |
||||
} |
||||
|
||||
static async Task<(DebugDirectoryTreeNode node, int rawCount)> GetDebugDirectoryAndRawCountForCoreLib() |
||||
{ |
||||
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); |
||||
var module = (ICSharpCode.Decompiler.Metadata.PEFile)assemblyNode.LoadedAssembly.GetMetadataFileOrNull()!; |
||||
int rawCount = module.Reader.ReadDebugDirectory().Length; |
||||
|
||||
assemblyNode.EnsureLazyChildren(); |
||||
var metadataNode = assemblyNode.Children.OfType<MetadataTreeNode>().Single(); |
||||
metadataNode.EnsureLazyChildren(); |
||||
var debugDir = metadataNode.Children.OfType<DebugDirectoryTreeNode>().Single(); |
||||
debugDir.EnsureLazyChildren(); |
||||
return (debugDir, rawCount); |
||||
} |
||||
} |
||||
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
// 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.PortableExecutable; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.Languages; |
||||
using ILSpy.TreeNodes; |
||||
|
||||
namespace ILSpy.Metadata |
||||
{ |
||||
/// <summary>
|
||||
/// Per-entry tree node under <see cref="DebugDirectoryTreeNode"/>. Specialised typed
|
||||
/// subclasses (<see cref="CodeViewTreeNode"/>, <see cref="PdbChecksumTreeNode"/>)
|
||||
/// surface their fields as a tab page; this fallback shows the raw entry bytes for
|
||||
/// types we don't decode further (Reproducible, EmbeddedPortablePdb, vendor entries).
|
||||
/// </summary>
|
||||
public class DebugDirectoryEntryTreeNode : ILSpyTreeNode |
||||
{ |
||||
readonly PEFile module; |
||||
readonly DebugDirectoryEntry entry; |
||||
|
||||
public DebugDirectoryEntryTreeNode(PEFile module, DebugDirectoryEntry entry) |
||||
{ |
||||
this.module = module ?? throw new ArgumentNullException(nameof(module)); |
||||
this.entry = entry; |
||||
} |
||||
|
||||
public override object Text => entry.Type.ToString(); |
||||
public override object Icon => Images.Images.MetadataTable; |
||||
public override string ToString() => entry.Type.ToString(); |
||||
|
||||
protected DebugDirectoryEntry Entry => entry; |
||||
protected PEFile Module => module; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
language.WriteCommentLine(output, entry.Type.ToString()); |
||||
if (entry.DataSize > 0) |
||||
{ |
||||
language.WriteCommentLine(output, $"Raw Data ({entry.DataSize}):"); |
||||
int dataOffset = module.Reader.IsLoadedImage |
||||
? entry.DataRelativeVirtualAddress |
||||
: entry.DataPointer; |
||||
var data = module.Reader.GetEntireImage().GetContent(dataOffset, entry.DataSize); |
||||
language.WriteCommentLine(output, data.ToHexString(data.Length)); |
||||
} |
||||
else |
||||
{ |
||||
language.WriteCommentLine(output, "(no data)"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// CodeView debug-directory entry — names the associated PDB. Surfaces the GUID,
|
||||
/// age, and original symbol-file path.
|
||||
/// </summary>
|
||||
public sealed class CodeViewTreeNode : DebugDirectoryEntryTreeNode |
||||
{ |
||||
readonly CodeViewDebugDirectoryData data; |
||||
|
||||
public CodeViewTreeNode(PEFile module, DebugDirectoryEntry entry, CodeViewDebugDirectoryData data) |
||||
: base(module, entry) |
||||
{ |
||||
this.data = data; |
||||
} |
||||
|
||||
public override object Text => nameof(DebugDirectoryEntryType.CodeView); |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
language.WriteCommentLine(output, Text.ToString()!); |
||||
language.WriteCommentLine(output, $"GUID: {data.Guid}"); |
||||
language.WriteCommentLine(output, $"Age: {data.Age}"); |
||||
language.WriteCommentLine(output, $"Path: {data.Path}"); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// PdbChecksum debug-directory entry — a cryptographic hash of the symbol file's
|
||||
/// contents, used to validate that a PDB matches its PE/COFF parent. Multiple entries
|
||||
/// can coexist when the build emits both public and private PDBs.
|
||||
/// </summary>
|
||||
public sealed class PdbChecksumTreeNode : DebugDirectoryEntryTreeNode |
||||
{ |
||||
readonly PdbChecksumDebugDirectoryData data; |
||||
|
||||
public PdbChecksumTreeNode(PEFile module, DebugDirectoryEntry entry, PdbChecksumDebugDirectoryData data) |
||||
: base(module, entry) |
||||
{ |
||||
this.data = data; |
||||
} |
||||
|
||||
public override object Text => nameof(DebugDirectoryEntryType.PdbChecksum); |
||||
|
||||
public string AlgorithmName => data.AlgorithmName; |
||||
public string Checksum => data.Checksum.ToHexString(data.Checksum.Length); |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
language.WriteCommentLine(output, Text.ToString()!); |
||||
language.WriteCommentLine(output, $"AlgorithmName: {AlgorithmName}"); |
||||
language.WriteCommentLine(output, $"Checksum: {Checksum}"); |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue