From 795a3be8ff671e87150bb940b2b2ff9869db514a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 1 Jun 2026 23:23:57 +0200 Subject: [PATCH] Fix Load Dependencies dropping the assemblies it resolved The command resolves each selected assembly's references through the per-assembly resolver, which adds the targets to the live list as auto-loaded entries. It then finished with a full list Refresh (F5), whose LoadList rebuilds the list from persisted state -- which never contains on-demand auto-loaded assemblies -- so the just-resolved dependencies were discarded the instant they were added and the command looked like a no-op. Re-decompile the active view instead (the original behaviour) without reloading the list. F5 still drops auto-loaded assemblies by design; the two refresh paths must stay distinct. --- ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs | 51 +++++++++++++++++++ ILSpy/AssemblyTree/AssemblyTreeModel.cs | 34 +++++++++++++ ILSpy/TreeNodes/AssemblyTreeNode.cs | 14 +---- 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs b/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs index aef68d233..6320054ed 100644 --- a/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs +++ b/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs @@ -1659,4 +1659,55 @@ public class AssemblyTreeTests ReferenceEquals(modelSelection[0], targetNode).Should().BeTrue( "the surviving selection must be the row the user clicked"); } + + [AvaloniaTest] + public async Task Load_Dependencies_Resolves_References_And_Keeps_Them_In_The_List() + { + // "Load Dependencies" must resolve every reference of the selected assembly and leave + // the resolved (auto-loaded) assemblies in the list. Regression: the command used to + // finish with a full list Refresh (F5), which rebuilds the list from persisted state and + // silently drops the just-resolved auto-loaded dependencies -- so the command appeared + // to do nothing. + + // Arrange -- boot, open System.Net.Http (it references many assemblies not in the list). + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3); + + var newAsmPath = typeof(System.Net.Http.HttpClient).Assembly.Location; + var registry = AppComposition.Current.GetExport(); + var openCommand = registry.Commands + .Single(c => c.Metadata.Header == nameof(Resources._Open)) + .CreateExport().Value; + openCommand.Execute(newAsmPath); + await Waiters.WaitForAsync(() => + vm.AssemblyTreeModel.AssemblyList!.GetAssemblies().Any(a => + string.Equals(a.FileName, newAsmPath, System.StringComparison.OrdinalIgnoreCase))); + + var httpNode = vm.AssemblyTreeModel.FindNode( + System.IO.Path.GetFileNameWithoutExtension(newAsmPath)); + await httpNode.LoadedAssembly.GetLoadResultAsync(); + + var before = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies() + .Select(a => a.FileName) + .ToHashSet(System.StringComparer.OrdinalIgnoreCase); + + // Act -- run Load Dependencies on the System.Net.Http node. + await vm.AssemblyTreeModel.LoadDependenciesAsync(new SharpTreeNode[] { httpNode }); + for (int i = 0; i < 8; i++) + { + Dispatcher.UIThread.RunJobs(); + await Task.Delay(25); + } + + // Assert -- references were resolved AND survive in the list as auto-loaded entries. + var added = vm.AssemblyTreeModel.AssemblyList!.GetAssemblies() + .Where(a => !before.Contains(a.FileName)) + .ToList(); + added.Should().NotBeEmpty( + "Load Dependencies must resolve referenced assemblies and keep them in the list"); + added.Should().OnlyContain(a => a.IsAutoLoaded, + "freshly resolved dependencies are auto-loaded"); + } } diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index 5008c54e7..1622ade40 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -928,6 +928,40 @@ namespace ILSpy.AssemblyTree } } + /// + /// Re-runs decompilation of the active tab WITHOUT reloading the assembly list. Mirrors + /// WPF's RefreshDecompiledView(). Unlike (F5), this must not rebuild + /// the list from persisted state -- that would discard on-demand auto-loaded assemblies + /// (e.g. the ones just resolved). + /// + public void RefreshDecompiledView() + => TryGetExport()?.ForceRefreshActiveTab(); + + /// + /// Resolves every assembly reference of each supplied assembly node through that + /// assembly's own resolver -- which auto-loads the targets into the live list -- then + /// re-decompiles the active tab so newly available references render. Mirrors WPF's + /// LoadDependencies command. + /// + public async Task LoadDependenciesAsync(IReadOnlyList nodes) + { + var tasks = new List(); + foreach (var node in nodes) + { + if (node is not AssemblyTreeNode { LoadedAssembly: { } la }) + continue; + var resolver = la.GetAssemblyResolver(); + var module = la.GetMetadataFileOrNull(); + if (module is null) + continue; + foreach (var assyRef in module.Metadata.AssemblyReferences) + tasks.Add(resolver.ResolveAsync( + new ICSharpCode.Decompiler.Metadata.AssemblyReference(module, assyRef))); + } + await Task.WhenAll(tasks); + RefreshDecompiledView(); + } + async Task RefreshInternalAsync() { if (AssemblyList == null || listManager == null) diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index f4f9d0a30..4606ec5a1 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -623,19 +623,7 @@ namespace ILSpy.TreeNodes { if (context.SelectedTreeNodes == null) return; - var tasks = new List(); - foreach (var node in context.SelectedTreeNodes) - { - var la = ((AssemblyTreeNode)node).LoadedAssembly; - var resolver = la.GetAssemblyResolver(); - var module = la.GetMetadataFileOrNull(); - if (module is null) - continue; - foreach (var assyRef in module.Metadata.AssemblyReferences) - tasks.Add(resolver.ResolveAsync(new AssemblyReference(module, assyRef))); - } - await Task.WhenAll(tasks); - assemblyTreeModel.Refresh(); + await assemblyTreeModel.LoadDependenciesAsync(context.SelectedTreeNodes); } } }