Browse Source

Carry step recording in the decompilation request

Whether a run records was read from the pane's own state at the moment the
decompiler was built, on a background task. A step index only means anything
against a run recorded the same way, so the full run and the step-limited replay
of one of its indices have to agree on it - and a view that can be attached or
detached between the two is the wrong place to ask.

The flag now travels in DecompilationOptions beside StepLimit and IsDebug, read
from the workspace on the UI thread when a run starts. Putting it on the tab
instead would miss the tabs opened after the pane, and the pane can be opened
before any tab exists at all.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/4029/head
Siegfried Pammer 3 weeks ago
parent
commit
f24e7faa96
  1. 8
      ILSpy/DecompilationOptions.cs
  2. 9
      ILSpy/Docking/DockWorkspace.cs
  3. 9
      ILSpy/Languages/CSharpLanguage.DebugSteps.cs
  4. 12
      ILSpy/Languages/CSharpLanguage.cs
  5. 6
      ILSpy/TextView/DecompilerTabPageModel.cs
  6. 9
      ILSpy/ViewModels/DebugStepsPaneModel.cs

8
ILSpy/DecompilationOptions.cs

@ -78,6 +78,14 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
public bool IsDebug { get; set; } public bool IsDebug { get; set; }
/// <summary>
/// When true, the pipeline records its steps so a step view can display them. It travels with
/// the request rather than being read from the view: a step index only means anything against
/// a run recorded the same way, so the full run and the step-limited replay of one of its
/// indices have to agree on it, and the decompile that reads it happens on a background task.
/// </summary>
public bool RecordSteps { get; set; }
/// <summary> /// <summary>
/// Optional sink for whole-project decompilation progress. Wired onto /// Optional sink for whole-project decompilation progress. Wired onto
/// <see cref="ICSharpCode.Decompiler.CSharp.ProjectDecompiler.WholeProjectDecompiler.ProgressIndicator"/> /// <see cref="ICSharpCode.Decompiler.CSharp.ProjectDecompiler.WholeProjectDecompiler.ProgressIndicator"/>

9
ILSpy/Docking/DockWorkspace.cs

@ -84,6 +84,15 @@ namespace ICSharpCode.ILSpy.Docking
/// behaviour (e.g. ShowOptionsCommand).</summary> /// behaviour (e.g. ShowOptionsCommand).</summary>
public IDocumentDock? Documents => factory.Documents; public IDocumentDock? Documents => factory.Documents;
/// <summary>
/// Whether decompiles started in this workspace record their transform steps. Set on the UI
/// thread by whoever displays them and copied into each run's <see cref="DecompilationOptions"/>,
/// so a background decompile never samples live view state - and every tab, including ones
/// opened later, records the same way. A step index only means anything against a run
/// recorded like the one the index was taken from.
/// </summary>
public bool RecordSteps { get; set; }
public IRelayCommand NavigateBackCommand { get; } public IRelayCommand NavigateBackCommand { get; }
public IRelayCommand NavigateForwardCommand { get; } public IRelayCommand NavigateForwardCommand { get; }
public IRelayCommand<NavigationEntry> NavigateToHistoryCommand { get; } public IRelayCommand<NavigationEntry> NavigateToHistoryCommand { get; }

9
ILSpy/Languages/CSharpLanguage.DebugSteps.cs

@ -77,15 +77,6 @@ namespace ICSharpCode.ILSpy.Languages
/// </summary> /// </summary>
static DebugStepsPaneModel? DebugStepsPane => AppComposition.TryGetExport<DebugStepsPaneModel>(); static DebugStepsPaneModel? DebugStepsPane => AppComposition.TryGetExport<DebugStepsPaneModel>();
/// <summary>
/// Points the whole pipeline at the shared stepper, but only while the Debug Steps pane is
/// there to display what it records - see <see cref="DebugStepsPaneModel.IsRecording"/>.
/// </summary>
static partial void ConfigureStepRecording(CSharpDecompiler decompiler)
{
decompiler.RecordSteps = DebugStepsPane?.IsRecording ?? false;
}
/// <summary> /// <summary>
/// Drops the recorded steps of the last run. The stepper pins every ILAst its steps captured, so /// Drops the recorded steps of the last run. The stepper pins every ILAst its steps captured, so
/// this is what actually releases that memory once nothing is displaying it. /// this is what actually releases that memory once nothing is displaying it.

12
ILSpy/Languages/CSharpLanguage.cs

@ -327,10 +327,10 @@ namespace ICSharpCode.ILSpy.Languages
}; };
decompiler.Stepper.StepLimit = options.StepLimit; decompiler.Stepper.StepLimit = options.StepLimit;
decompiler.Stepper.IsDebug = options.IsDebug; decompiler.Stepper.IsDebug = options.IsDebug;
// The Debug Steps pane walks the whole pipeline, so the IL transforms have to record into // The Debug Steps pane walks the whole pipeline, so the IL transforms record into the same
// the same stepper as the C# AST transforms. Implemented only under DEBUG, where the pane // stepper as the C# AST transforms. Which runs record is decided by whoever started the
// exists; a Release decompiler records no steps at all. // decompile, not by the pane's state at the moment this runs on a background task.
ConfigureStepRecording(decompiler); decompiler.RecordSteps = options.RecordSteps;
if (options.EscapeInvalidIdentifiers) if (options.EscapeInvalidIdentifiers)
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()); decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
return decompiler; return decompiler;
@ -776,10 +776,6 @@ namespace ICSharpCode.ILSpy.Languages
} }
} }
// Implemented only under DEBUG (CSharpLanguage.DebugSteps.cs): enables IL-transform step
// recording while the Debug Steps pane is open. A no-op partial in Release.
static partial void ConfigureStepRecording(CSharpDecompiler decompiler);
// Implemented only under DEBUG (CSharpLanguage.DebugSteps.cs): writes the ILAst the pipeline was // Implemented only under DEBUG (CSharpLanguage.DebugSteps.cs): writes the ILAst the pipeline was
// halted in, when a Debug Steps replay stopped it before there was any C# to show. A no-op // halted in, when a Debug Steps replay stopped it before there was any C# to show. A no-op
// partial in Release, where nothing sets a step limit. // partial in Release, where nothing sets a step limit.

6
ILSpy/TextView/DecompilerTabPageModel.cs

@ -395,6 +395,7 @@ namespace ICSharpCode.ILSpy.TextView
int? pendingHighlightStep; int? pendingHighlightStep;
bool pendingIsDebug; bool pendingIsDebug;
/// <summary> /// <summary>
/// Output-length safety limits (characters): a decompile that produces more than the active /// Output-length safety limits (characters): a decompile that produces more than the active
/// limit is stopped and replaced with a "too much code" message rather than hanging/OOMing the /// limit is stopped and replaced with a "too much code" message rather than hanging/OOMing the
@ -559,6 +560,10 @@ namespace ICSharpCode.ILSpy.TextView
var stepLimit = pendingStepLimit; var stepLimit = pendingStepLimit;
var highlightStep = pendingHighlightStep; var highlightStep = pendingHighlightStep;
var isDebug = pendingIsDebug; var isDebug = pendingIsDebug;
// Unlike the per-run overrides above, this is not reset: it describes the tab, and
// every run has to record the same way or a step index picked from one tree would
// select a different step on replay.
var recordSteps = AppEnv.AppComposition.TryGetExport<Docking.DockWorkspace>()?.RecordSteps ?? false;
pendingStepLimit = int.MaxValue; pendingStepLimit = int.MaxValue;
pendingHighlightStep = null; pendingHighlightStep = null;
pendingIsDebug = false; pendingIsDebug = false;
@ -580,6 +585,7 @@ namespace ICSharpCode.ILSpy.TextView
StepLimit = stepLimit, StepLimit = stepLimit,
HighlightStep = highlightStep, HighlightStep = highlightStep,
IsDebug = isDebug, IsDebug = isDebug,
RecordSteps = recordSteps,
}; };
try try
{ {

9
ILSpy/ViewModels/DebugStepsPaneModel.cs

@ -415,6 +415,10 @@ namespace ICSharpCode.ILSpy.ViewModels
if (IsRecording == enabled) if (IsRecording == enabled)
return; return;
IsRecording = enabled; IsRecording = enabled;
// The workspace carries the flag into every run it starts, including the ones the user
// triggers by selecting another node, so closing the pane has to clear it there too.
if (AppComposition.TryGetExport<DockWorkspace>() is { } workspace)
workspace.RecordSteps = enabled;
if (enabled) if (enabled)
{ {
// C# is the only language that records anything, so re-running any other one would // C# is the only language that records anything, so re-running any other one would
@ -434,7 +438,10 @@ namespace ICSharpCode.ILSpy.ViewModels
lastSelectedStep = stepLimit; lastSelectedStep = stepLimit;
// Composition unavailable in design-time previews; the gesture is a no-op there. // Composition unavailable in design-time previews; the gesture is a no-op there.
var dock = AppComposition.TryGetExport<DockWorkspace>(); var dock = AppComposition.TryGetExport<DockWorkspace>();
dock?.ActiveDecompilerTab?.RestartDecompileWithStepLimit(stepLimit, isDebug, highlightStep); if (dock == null)
return;
dock.RecordSteps = IsRecording;
dock.ActiveDecompilerTab?.RestartDecompileWithStepLimit(stepLimit, isDebug, highlightStep);
} }
} }
} }

Loading…
Cancel
Save