mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
237 lines
8.5 KiB
237 lines
8.5 KiB
// 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. |
|
|
|
#if DEBUG |
|
using System; |
|
using System.Collections.Concurrent; |
|
using System.Composition; |
|
using System.Diagnostics; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Threading.Tasks; |
|
|
|
using ICSharpCode.Decompiler; |
|
using ICSharpCode.ILSpy.Properties; |
|
using ICSharpCode.ILSpyX; |
|
|
|
using ILSpy.AssemblyTree; |
|
using ILSpy.Docking; |
|
using ILSpy.Languages; |
|
using ILSpy.TextView; |
|
using ILSpy.TreeNodes; |
|
|
|
namespace ILSpy.Commands |
|
{ |
|
/// <summary> |
|
/// DEBUG-only File-menu stress test: decompile every loaded assembly to |
|
/// <c>c:\temp\decompiled\<ShortName>.cs</c> in parallel and report per-assembly |
|
/// timing in a results tab. Hard-coded output path matches WPF — the command's |
|
/// CanExecute gates on that directory existing so it's effectively opt-in. |
|
/// </summary> |
|
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile), MenuCategory = "Debug", MenuOrder = 30)] |
|
[Shared] |
|
sealed class DecompileAllCommand : SimpleCommand |
|
{ |
|
const string OutputDir = @"c:\temp\decompiled"; |
|
|
|
readonly AssemblyTreeModel assemblyTreeModel; |
|
readonly DockWorkspace dockWorkspace; |
|
|
|
[ImportingConstructor] |
|
public DecompileAllCommand(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 = "Decompile All" }; |
|
var bag = new ConcurrentBag<string>(); |
|
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 path = Path.Combine(OutputDir, asm.ShortName + ".cs"); |
|
try |
|
{ |
|
using var writer = new StreamWriter(path); |
|
var options = new DecompilationOptions { |
|
CancellationToken = token, |
|
FullDecompilation = true, |
|
}; |
|
new CSharpLanguage().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), "Decompiling all assemblies…"); |
|
dockWorkspace.ShowText(report); |
|
} |
|
catch (OperationCanceledException) { } |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// DEBUG-only File-menu stress test: disassemble every loaded assembly to |
|
/// <c>c:\temp\disassembled\<Name>.il</c> in parallel. Same shape as |
|
/// <see cref="DecompileAllCommand"/> but routes through <see cref="ILLanguage"/>. |
|
/// CanExecute gates on the output directory existing. |
|
/// </summary> |
|
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = "Debug", MenuOrder = 31)] |
|
[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<string>(); |
|
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) { } |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// DEBUG-only stress test: re-decompile the current selection 100 times in series |
|
/// and report average wall-clock time. Used for tracking decompilation perf |
|
/// regressions across changes. |
|
/// </summary> |
|
[ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile100x), MenuCategory = "Debug", MenuOrder = 32)] |
|
[Shared] |
|
sealed class Decompile100TimesCommand : SimpleCommand |
|
{ |
|
const int NumRuns = 100; |
|
|
|
readonly AssemblyTreeModel assemblyTreeModel; |
|
readonly LanguageService languageService; |
|
readonly DockWorkspace dockWorkspace; |
|
|
|
[ImportingConstructor] |
|
public Decompile100TimesCommand(AssemblyTreeModel assemblyTreeModel, LanguageService languageService, DockWorkspace dockWorkspace) |
|
{ |
|
this.assemblyTreeModel = assemblyTreeModel; |
|
this.languageService = languageService; |
|
this.dockWorkspace = dockWorkspace; |
|
} |
|
|
|
public override void Execute(object? parameter) => _ = ExecuteAsync(); |
|
|
|
async Task ExecuteAsync() |
|
{ |
|
var language = languageService.CurrentLanguage; |
|
var nodes = assemblyTreeModel.SelectedItems.OfType<ILSpyTreeNode>().ToArray(); |
|
if (nodes.Length == 0) |
|
return; |
|
try |
|
{ |
|
var report = await dockWorkspace.RunWithCancellation(token => Task.Run(() => { |
|
var watch = Stopwatch.StartNew(); |
|
var options = new DecompilationOptions { CancellationToken = token }; |
|
for (int i = 0; i < NumRuns; i++) |
|
{ |
|
foreach (var node in nodes) |
|
node.Decompile(language, new PlainTextOutput(), options); |
|
} |
|
watch.Stop(); |
|
var output = new AvaloniaEditTextOutput { Title = "Decompile 100×" }; |
|
var msPerRun = watch.Elapsed.TotalMilliseconds / NumRuns; |
|
output.Write($"Average time: {msPerRun:f1}ms"); |
|
output.WriteLine(); |
|
return output; |
|
}, token), "Decompiling 100×…"); |
|
dockWorkspace.ShowText(report); |
|
} |
|
catch (OperationCanceledException) { } |
|
} |
|
} |
|
} |
|
#endif
|
|
|