From d6bbb6135f8d6b8e8bad15c4abe7133c80bdd250 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 27 Jun 2026 08:19:29 +0200 Subject: [PATCH] Show metadata tables when opening a standalone portable PDB A standalone portable PDB (or raw metadata stream) loads as a metadata-only MetadataFile, but the tree node was left empty: LoadChildren early-returned for any kind other than PortableExecutable/WebCIL, so the node showed an expander over no children. This regressed in the WPF -> Avalonia port, which dropped the old default switch arm; release/10.1 still populated such nodes with the metadata table and heap nodes directly. Restore that behavior. Assisted-by: Claude:claude-opus-4-8:Claude Code --- .../Metadata/StandalonePdbTreeTests.cs | 73 +++++++++++++++++++ ILSpy/TreeNodes/AssemblyTreeNode.cs | 8 ++ 2 files changed, 81 insertions(+) create mode 100644 ILSpy.Tests/Metadata/StandalonePdbTreeTests.cs diff --git a/ILSpy.Tests/Metadata/StandalonePdbTreeTests.cs b/ILSpy.Tests/Metadata/StandalonePdbTreeTests.cs new file mode 100644 index 000000000..270e0467f --- /dev/null +++ b/ILSpy.Tests/Metadata/StandalonePdbTreeTests.cs @@ -0,0 +1,73 @@ +// 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.IO; +using System.Linq; +using System.Threading.Tasks; + +using Avalonia.Headless.NUnit; + +using AwesomeAssertions; + +using ICSharpCode.ILSpy.Metadata; +using ICSharpCode.ILSpy.Metadata.DebugTables; +using ICSharpCode.ILSpy.TreeNodes; + +using NUnit.Framework; + +namespace ICSharpCode.ILSpy.Tests.Metadata; + +[TestFixture] +public class StandalonePdbTreeTests +{ + [AvaloniaTest] + public async Task Opening_A_Standalone_Portable_PDB_Surfaces_Its_Metadata_Tables_And_Heaps() + { + // A standalone .pdb (no companion assembly) loads as a metadata-only MetadataFile of + // kind ProgramDebugDatabase. Its tree node must expand to the PDB's metadata tables and + // heaps directly under the file node; before the fix the node had an expander but zero + // children (the "empty tree" symptom). + + // The test SDK builds this assembly with a sidecar portable PDB, so use that PDB on its + // own as the standalone fixture. + var pdbPath = Path.ChangeExtension(typeof(StandalonePdbTreeTests).Assembly.Location, ".pdb"); + File.Exists(pdbPath).Should().BeTrue( + $"the test assembly must ship a sidecar portable PDB to use as a standalone fixture (looked for '{pdbPath}')"); + + var (_, vm) = await TestHarness.BootAsync(); + + var loaded = await vm.OpenAssemblyAsync(pdbPath); + TestCapture.Step("opened-standalone-pdb"); + + var pdbNode = vm.AssemblyTreeModel.FindNode(loaded.ShortName); + pdbNode.EnsureLazyChildren(); + TestCapture.Step("pdb-node-expanded"); + + pdbNode.Children.Should().NotBeEmpty( + "a standalone portable PDB must populate its tree node instead of rendering empty"); + + var tables = pdbNode.GetChild(); + tables.EnsureLazyChildren(); + tables.Children.OfType().Should().NotBeEmpty( + "the standalone PDB's Tables subtree must expose the Document table — the lead debug-only table that always has rows in a populated PDB"); + + // The heap nodes are also surfaced directly (string/userstring/guid/blob). + pdbNode.Children.OfType().Should().ContainSingle(); + pdbNode.Children.OfType().Should().ContainSingle(); + } +} diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index a7df6c323..47f0e3e1e 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -359,6 +359,14 @@ namespace ICSharpCode.ILSpy.TreeNodes if (module.Kind != MetadataFile.MetadataFileKind.PortableExecutable && module.Kind != MetadataFile.MetadataFileKind.WebCIL) { + // Standalone metadata-only files (a portable .pdb, or a raw metadata stream) + // are entirely metadata, so surface the tables and heaps directly under the + // file node rather than nesting them inside a "Metadata" folder. + Children.Add(new ICSharpCode.ILSpy.Metadata.MetadataTablesTreeNode(module)); + Children.Add(new ICSharpCode.ILSpy.Metadata.StringHeapTreeNode(module)); + Children.Add(new ICSharpCode.ILSpy.Metadata.UserStringHeapTreeNode(module)); + Children.Add(new ICSharpCode.ILSpy.Metadata.GuidHeapTreeNode(module)); + Children.Add(new ICSharpCode.ILSpy.Metadata.BlobHeapTreeNode(module)); return; }