Skip to content

Commit

Permalink
Updated decompiler submodule and added ILAst stepper
Browse files Browse the repository at this point in the history
  • Loading branch information
ElektroKill committed Nov 5, 2023
1 parent 2331f12 commit 34e0666
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -96,17 +100,19 @@ 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);
var run = new CSharpDecompiler(ts, settings);
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
{
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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
Original file line number Diff line number Diff line change
Expand Up @@ -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<IDecompilerOption> 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<IDecompilerOption> 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
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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<ToolWindowContentInfo> 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<IDocumentTab>();
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
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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
Loading

0 comments on commit 34e0666

Please sign in to comment.