diff --git a/Extensions/ILSpy.Decompiler/ICSharpCode.Decompiler b/Extensions/ILSpy.Decompiler/ICSharpCode.Decompiler
index 4a5d661149..22b67d3ccc 160000
--- a/Extensions/ILSpy.Decompiler/ICSharpCode.Decompiler
+++ b/Extensions/ILSpy.Decompiler/ICSharpCode.Decompiler
@@ -1 +1 @@
-Subproject commit 4a5d661149aefd35ae55cedc46e469f47fb465f8
+Subproject commit 22b67d3cccfa4efd7118b1923e7aa7203bcdc1a8
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs
index 2908c00e02..811d6691fb 100644
--- a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/ILAstDecompiler.cs
@@ -19,7 +19,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
using System.Text;
using dnlib.DotNet;
using dnSpy.Contracts.Decompiler;
@@ -66,8 +65,13 @@ sealed class ILAstDecompiler : DecompilerBase {
readonly string uniqueNameUI;
Guid uniqueGuid;
+ // Hacky fields and properties for Stepper functionality
+ MethodDef? lastMethod;
+ public Stepper Stepper { get; set; } = new Stepper();
+ public event EventHandler? StepperUpdated;
+ void OnStepperUpdated(EventArgs? e = null) => StepperUpdated?.Invoke(this, e ?? EventArgs.Empty);
+
public override DecompilerSettingsBase Settings { get; }
- const int settingsVersion = 1;
ILAstDecompiler(ILAstDecompilerSettings langSettings, double orderUI, string uniqueNameUI) {
Settings = langSettings;
@@ -96,7 +100,7 @@ public override void Decompile(MethodDef method, IDecompilerOutput output, Decom
var bodyInfo = StartKeywordBlock(output, ".body", method);
var ts = new DecompilerTypeSystem(new PEFile(method.Module), TypeSystemOptions.Default);
- var reader = new ILReader(ts.MainModule) { CalculateILSpans = true };
+ var reader = new ILReader(ts.MainModule) { UseDebugSymbols = true, CalculateILSpans = true };
var il = reader.ReadIL(method, kind: ILFunctionKind.TopLevelFunction, cancellationToken: ctx.CancellationToken);
var settings = new DecompilerSettings(LanguageVersion.Latest);
@@ -104,9 +108,11 @@ public override void Decompile(MethodDef method, IDecompilerOutput output, Decom
var context = run.CreateILTransformContext(il);
context.CalculateILSpans = true;
- //context.Stepper.StepLimit = options.StepLimit;
- context.Stepper.IsDebug = Debugger.IsAttached;
+ if (lastMethod != method && Settings is ILAstDecompilerSettings s)
+ s.StepLimit = int.MaxValue;
+ int stepLimit = (Settings as ILAstDecompilerSettings)?.StepLimit ?? int.MaxValue;
+ context.Stepper.StepLimit = stepLimit;
try
{
@@ -125,18 +131,19 @@ public override void Decompile(MethodDef method, IDecompilerOutput output, Decom
}
finally
{
- // update stepper even if a transform crashed unexpectedly
- // if (options.StepLimit == int.MaxValue)
- // {
- // Stepper = context.Stepper;
- // OnStepperUpdated(new EventArgs());
- // }
+ if (stepLimit == int.MaxValue)
+ {
+ Stepper = context.Stepper;
+ OnStepperUpdated();
+ }
}
output.WriteLine();
- il.WriteTo(output, new ILAstWritingOptions());
+ il.WriteTo(output, new ILAstWritingOptions() {ShowILRanges = true});
EndKeywordBlock(output, bodyInfo, CodeBracesRangeFlags.MethodBraces, true);
+
+ lastMethod = method;
}
struct BraceInfo {
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/StepperConstants.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/StepperConstants.cs
new file mode 100644
index 0000000000..8dd5cb7e1f
--- /dev/null
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/ILAst/StepperConstants.cs
@@ -0,0 +1,29 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System;
+
+#if DEBUG
+namespace dnSpy.Decompiler.ILSpy.Core.ILAst {
+ public static class StepperConstants {
+ public static readonly Guid ToolWindowGuid = new Guid("38FBE664-81E4-4456-961D-37CFB7A9FB8E");
+ public static readonly Guid TreeViewGuid = new Guid("D3707DFF-3728-4B4D-BB81-A9D85793FD65");
+ }
+}
+#endif
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs
index 1469df3712..a9c929ae77 100644
--- a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy.Core/Settings/ILAstDecompilerSettings.cs
@@ -20,27 +20,47 @@ You should have received a copy of the GNU General Public License
#if DEBUG
using System;
using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Threading;
using dnSpy.Contracts.Decompiler;
namespace dnSpy.Decompiler.ILSpy.Core.Settings {
sealed class ILAstDecompilerSettings : DecompilerSettingsBase {
- public override int Version => 0;
- public override event EventHandler? VersionChanged { add { } remove { } }
+ public override int Version => settingsVersion;
+ int settingsVersion;
- public ILAstDecompilerSettings() {
+ public override event EventHandler? VersionChanged;
+
+ public override IEnumerable Options {
+ get { yield break; }
}
- ILAstDecompilerSettings(ILAstDecompilerSettings other) {
+ public int StepLimit {
+ get => stepLimit;
+ set {
+ if (stepLimit == value)
+ return;
+ stepLimit = value;
+ OnPropertyChanged();
+ }
}
+ int stepLimit = int.MaxValue;
- public override DecompilerSettingsBase Clone() => new ILAstDecompilerSettings(this);
+ public ILAstDecompilerSettings() { }
- public override IEnumerable Options {
- get { yield break; }
+ ILAstDecompilerSettings(ILAstDecompilerSettings other) => stepLimit = other.stepLimit;
+
+ void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
+ Interlocked.Increment(ref settingsVersion);
+ VersionChanged?.Invoke(this, EventArgs.Empty);
}
- public override bool Equals(object? obj) => obj is ILAstDecompilerSettings;
- public override int GetHashCode() => 0;
+ public override DecompilerSettingsBase Clone() => new ILAstDecompilerSettings(this);
+
+ public override bool Equals(object? obj) => obj is ILAstDecompilerSettings settings && settings.stepLimit == stepLimit;
+
+ // ReSharper disable once NonReadonlyMemberInGetHashCode
+ public override int GetHashCode() => stepLimit;
}
}
#endif
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/ILAstStepperToolWindowContent.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/ILAstStepperToolWindowContent.cs
new file mode 100644
index 0000000000..c566a3f0f8
--- /dev/null
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/ILAstStepperToolWindowContent.cs
@@ -0,0 +1,152 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using dnSpy.Contracts.Controls;
+using dnSpy.Contracts.Decompiler;
+using dnSpy.Contracts.Documents;
+using dnSpy.Contracts.Documents.Tabs;
+using dnSpy.Contracts.ToolWindows;
+using dnSpy.Contracts.ToolWindows.App;
+using dnSpy.Contracts.TreeView;
+using dnSpy.Contracts.TreeView.Text;
+using dnSpy.Decompiler.ILSpy.Core.ILAst;
+using dnSpy.Decompiler.ILSpy.Core.Settings;
+using ICSharpCode.Decompiler.IL.Transforms;
+
+#if DEBUG
+namespace dnSpy.Decompiler.ILSpy.ILAst {
+ [Export(typeof(IToolWindowContentProvider))]
+ sealed class AnalyzerToolWindowContentProvider : IToolWindowContentProvider {
+ readonly IDecompilerService decompilerService;
+ readonly IDocumentTabService documentTabService;
+ readonly ITreeViewService treeViewService;
+ readonly ITreeViewNodeTextElementProvider treeViewNodeTextElementProvider;
+
+ public ILAstStepperToolWindowContent DocumentTreeViewWindowContent => analyzerToolWindowContent ??= new ILAstStepperToolWindowContent(decompilerService, documentTabService, treeViewService, treeViewNodeTextElementProvider);
+ ILAstStepperToolWindowContent? analyzerToolWindowContent;
+
+ [ImportingConstructor]
+ AnalyzerToolWindowContentProvider(IDecompilerService decompilerService, IDocumentTabService documentTabService, ITreeViewService treeViewService, ITreeViewNodeTextElementProvider treeViewNodeTextElementProvider) {
+ this.decompilerService = decompilerService;
+ this.documentTabService = documentTabService;
+ this.treeViewService = treeViewService;
+ this.treeViewNodeTextElementProvider = treeViewNodeTextElementProvider;
+ }
+
+ public IEnumerable ContentInfos {
+ get { yield return new ToolWindowContentInfo(StepperConstants.ToolWindowGuid); }
+ }
+
+ public ToolWindowContent? GetOrCreate(Guid guid) => guid == StepperConstants.ToolWindowGuid ? DocumentTreeViewWindowContent : null;
+ }
+
+ sealed class ILAstStepperToolWindowContent : ToolWindowContent, IFocusable, IStepperTreeNodeDataContext {
+ readonly IDecompilerService decompilerService;
+ readonly IDocumentTabService documentTabService;
+ readonly ITreeViewNodeTextElementProvider treeViewNodeTextElementProvider;
+ readonly ITreeView treeView;
+ ILAstDecompiler? decompiler;
+
+ public override object UIObject => treeView.UIObject;
+ public override IInputElement? FocusedElement => null;
+ public override FrameworkElement ZoomElement => treeView.UIObject;
+ public override Guid Guid => StepperConstants.ToolWindowGuid;
+ public override string Title => "ILAst Stepper";
+ public bool CanFocus => true;
+ public ITreeView TreeView => treeView;
+ public ITreeViewNodeTextElementProvider TreeViewNodeTextElementProvider => treeViewNodeTextElementProvider;
+ public Stepper.Node? CurrentState { get; private set; }
+
+ public ILAstStepperToolWindowContent(IDecompilerService decompilerService, IDocumentTabService documentTabService, ITreeViewService treeViewService, ITreeViewNodeTextElementProvider treeViewNodeTextElementProvider) {
+ this.decompilerService = decompilerService;
+ this.documentTabService = documentTabService;
+ this.treeViewNodeTextElementProvider = treeViewNodeTextElementProvider;
+ decompilerService.DecompilerChanged += DecompilerService_DecompilerChanged;
+
+ var options = new TreeViewOptions {
+ CanDragAndDrop = false,
+ SelectionMode = SelectionMode.Single,
+ };
+ treeView = treeViewService.Create(StepperConstants.TreeViewGuid, options);
+ treeView.UIObject.Padding = new Thickness(0, 2, 0, 2);
+ treeView.UIObject.BorderThickness = new Thickness(1);
+
+ OnDecompilerSelected(decompilerService.Decompiler);
+ }
+
+ void DecompilerService_DecompilerChanged(object? sender, EventArgs e) =>
+ OnDecompilerSelected(decompilerService.Decompiler);
+
+ void OnDecompilerSelected(IDecompiler newDecompiler) {
+ if (decompiler is not null)
+ decompiler.StepperUpdated -= ILAstStepperUpdated;
+ decompiler = newDecompiler as ILAstDecompiler;
+ if (decompiler is not null)
+ decompiler.StepperUpdated += ILAstStepperUpdated;
+ else {
+ CurrentState = null;
+ treeView.UIObject.Dispatcher.Invoke(() => treeView.Root.Children.Clear());
+ }
+ }
+
+ void ILAstStepperUpdated(object? sender, EventArgs e) {
+ if (decompiler is null)
+ return;
+
+ CurrentState = null;
+
+ treeView.UIObject.Dispatcher.Invoke(() => {
+ treeView.Root.Children.Clear();
+ for (var i = 0; i < decompiler.Stepper.Steps.Count; i++)
+ treeView.Root.AddChild(treeView.Create(new StepperNodeTreeNodeData(this, decompiler.Stepper.Steps[i])));
+ });
+ }
+
+ public void ShowStateAfter(Stepper.Node node) {
+ CurrentState = node;
+ SetNewStepLimit(node.EndStep);
+ RefreshTabs();
+ treeView.RefreshAllNodes();
+ }
+
+ void SetNewStepLimit(int stepLimit) {
+ if (decompiler is not null && decompiler.Settings is ILAstDecompilerSettings settings)
+ settings.StepLimit = stepLimit;
+ }
+
+ void RefreshTabs() {
+ var toRefresh = new HashSet();
+ foreach (var tab in documentTabService.VisibleFirstTabs) {
+ var decomp = (tab.Content as IDecompilerTabContent)?.Decompiler;
+ if (decomp is not null && decomp == decompiler)
+ toRefresh.Add(tab);
+ }
+ documentTabService.Refresh(toRefresh.ToArray());
+ }
+
+ public void Focus() => treeView.Focus();
+ }
+}
+#endif
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/IStepperTreeNodeDataContext.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/IStepperTreeNodeDataContext.cs
new file mode 100644
index 0000000000..d5621ba542
--- /dev/null
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/IStepperTreeNodeDataContext.cs
@@ -0,0 +1,33 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using dnSpy.Contracts.TreeView;
+using dnSpy.Contracts.TreeView.Text;
+using ICSharpCode.Decompiler.IL.Transforms;
+
+#if DEBUG
+namespace dnSpy.Decompiler.ILSpy.ILAst {
+ interface IStepperTreeNodeDataContext {
+ ITreeView TreeView { get; }
+ ITreeViewNodeTextElementProvider TreeViewNodeTextElementProvider { get; }
+ Stepper.Node? CurrentState { get; }
+ void ShowStateAfter(Stepper.Node node);
+ }
+}
+#endif
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/ShowStepperToolWindowLoader.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/ShowStepperToolWindowLoader.cs
new file mode 100644
index 0000000000..eb999f6bc9
--- /dev/null
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/ShowStepperToolWindowLoader.cs
@@ -0,0 +1,34 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System.ComponentModel.Composition;
+using dnSpy.Contracts.Extension;
+using dnSpy.Contracts.ToolWindows.App;
+using dnSpy.Decompiler.ILSpy.Core.ILAst;
+
+#if DEBUG
+namespace dnSpy.Decompiler.ILSpy.ILAst {
+ [ExportAutoLoaded]
+ sealed class ShowStepperToolWindowLoader : IAutoLoaded {
+ [ImportingConstructor]
+ public ShowStepperToolWindowLoader(IDsToolWindowService toolWindowService) =>
+ toolWindowService.Show(StepperConstants.ToolWindowGuid);
+ }
+}
+#endif
diff --git a/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/StepperNodeTreeNodeData.cs b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/StepperNodeTreeNodeData.cs
new file mode 100644
index 0000000000..c286f919c9
--- /dev/null
+++ b/Extensions/ILSpy.Decompiler/dnSpy.Decompiler.ILSpy/ILAst/StepperNodeTreeNodeData.cs
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) 2023 ElektroKill
+
+ This file is part of dnSpy
+
+ dnSpy is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ dnSpy is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with dnSpy. If not, see .
+*/
+
+using System;
+using System.Collections.Generic;
+using dnSpy.Contracts.Images;
+using dnSpy.Contracts.Text;
+using dnSpy.Contracts.Text.Classification;
+using dnSpy.Contracts.TreeView;
+using dnSpy.Contracts.TreeView.Text;
+using ICSharpCode.Decompiler.IL.Transforms;
+
+#if DEBUG
+namespace dnSpy.Decompiler.ILSpy.ILAst {
+ sealed class StepperNodeTreeNodeData : TreeNodeData {
+ readonly IStepperTreeNodeDataContext context;
+ readonly Stepper.Node node;
+
+ public override Guid Guid => Guid.Empty;
+
+ static class Cache {
+ static readonly TextClassifierTextColorWriter writer = new TextClassifierTextColorWriter();
+ public static TextClassifierTextColorWriter GetWriter() => writer;
+ public static void FreeWriter(TextClassifierTextColorWriter writer) => writer.Clear();
+ }
+
+ public override object Text {
+ get {
+ var writer = Cache.GetWriter();
+ try {
+ writer.Write(node == context.CurrentState ? BoxedTextColor.Blue : BoxedTextColor.Text, node.Description);
+ var classifierContext = new TreeViewNodeClassifierContext(writer.Text, context.TreeView, this, isToolTip: false, colorize: true, colors: writer.Colors);
+ return context.TreeViewNodeTextElementProvider.CreateTextElement(classifierContext, TreeViewContentTypes.TreeViewNode, TextElementFlags.FilterOutNewLines);
+ }
+ finally {
+ Cache.FreeWriter(writer);
+ }
+ }
+ }
+
+ public override object? ToolTip => null;
+
+ public override ImageReference Icon => ImageReference.None;
+
+ public StepperNodeTreeNodeData(IStepperTreeNodeDataContext context, Stepper.Node node) {
+ this.context = context;
+ this.node = node;
+ }
+
+ public override IEnumerable CreateChildren() {
+ for (int i = 0; i < node.Children.Count; i++)
+ yield return new StepperNodeTreeNodeData(context, node.Children[i]);
+ }
+
+ public override bool Activate() {
+ context.ShowStateAfter(node);
+ return true;
+ }
+
+ public override void OnRefreshUI() { }
+ }
+}
+#endif