From b905d8ee029eeb1c413072eb2f4024cdb66d4680 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 15 May 2026 00:45:04 +0200 Subject: [PATCH] DEBUG-only Disassemble All command Same shape as DecompileAllCommand but routes through ILLanguage. Writes to c:\temp\disassembled\.il in parallel, reports per-assembly time and exception type, with CanExecute gating on the output dir existing. Assisted-by: Claude:claude-opus-4-7:Claude Code --- ILSpy/Commands/DecompileAllCommand.cs | 72 +++++++++++++++++++++++++++ ILSpy/Commands/FileCommands.cs | 8 +-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index 3f5dcddea..c20f01aaf 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -108,6 +108,78 @@ namespace ILSpy.Commands } } + /// + /// DEBUG-only File-menu stress test: disassemble every loaded assembly to + /// c:\temp\disassembled\<Name>.il in parallel. Same shape as + /// but routes through . + /// CanExecute gates on the output directory existing. + /// + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.55)] + [Shared] + sealed class DisassembleAllCommand : SimpleCommand + { + const string OutputDir = @"c:\temp\disassembled"; + + readonly AssemblyTreeModel assemblyTreeModel; + readonly DockWorkspace dockWorkspace; + + [ImportingConstructor] + public DisassembleAllCommand(AssemblyTreeModel assemblyTreeModel, DockWorkspace dockWorkspace) + { + this.assemblyTreeModel = assemblyTreeModel; + this.dockWorkspace = dockWorkspace; + } + + public override bool CanExecute(object? parameter) => Directory.Exists(OutputDir); + + public override void Execute(object? parameter) => _ = ExecuteAsync(); + + async Task ExecuteAsync() + { + try + { + var report = await dockWorkspace.RunWithCancellation(token => Task.Run(() => { + var output = new AvaloniaEditTextOutput { Title = "Disassemble All" }; + var bag = new ConcurrentBag(); + Parallel.ForEach( + Partitioner.Create(assemblyTreeModel.AssemblyList!.GetAssemblies(), loadBalance: true), + new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = token }, + asm => { + if (asm.HasLoadError) + return; + var watch = Stopwatch.StartNew(); + Exception? ex = null; + var safeName = (asm.Text?.ToString() ?? asm.ShortName).Replace("(", "").Replace(")", "").Replace(' ', '_'); + var path = Path.Combine(OutputDir, safeName + ".il"); + try + { + using var writer = new StreamWriter(path); + var options = new DecompilationOptions { + CancellationToken = token, + FullDecompilation = true, + }; + new ILLanguage().DecompileAssembly(asm, new PlainTextOutput(writer), options); + } + catch (Exception caught) + { + ex = caught; + } + watch.Stop(); + bag.Add(asm.ShortName + " - " + watch.Elapsed + (ex != null ? " - " + ex.GetType().Name : "")); + }); + foreach (var line in bag.OrderBy(s => s, StringComparer.OrdinalIgnoreCase)) + { + output.Write(line); + output.WriteLine(); + } + return output; + }, token), "Disassembling all assemblies…"); + dockWorkspace.ShowText(report); + } + catch (OperationCanceledException) { } + } + } + /// /// DEBUG-only stress test: re-decompile the current selection 100 times in series /// and report average wall-clock time. Used for tracking decompilation perf diff --git a/ILSpy/Commands/FileCommands.cs b/ILSpy/Commands/FileCommands.cs index 445d865ac..f37f97860 100644 --- a/ILSpy/Commands/FileCommands.cs +++ b/ILSpy/Commands/FileCommands.cs @@ -110,12 +110,8 @@ namespace ILSpy.Commands // DEBUG-only DecompileAllCommand + Decompile100TimesCommand live in DecompileAllCommand.cs; // the stubs that lived here were replaced with the real implementations in ``. - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)] - [Shared] - sealed class DisassembleAllCommand : SimpleCommand - { - public override void Execute(object? parameter) => NotImplementedDialog.Show(Resources.DEBUGDisassemble); - } + // DEBUG-only DisassembleAllCommand also moved to DecompileAllCommand.cs to keep all + // three parallel-decompile/disassemble stress-test commands in one place. [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)] [Shared]