Browse Source

Give the builder output a step of its own

The state the ExpressionBuilder and StatementBuilder leave behind was only
reachable as "state before the first AST transform" - an entry that names a
transform rather than the state, and that sits below every member's group in a
tree with one group per member. It is now a top-level step at the seam, where
the whole type is converted and nothing has transformed it yet.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/4029/head
Siegfried Pammer 3 weeks ago
parent
commit
b8d68d9143
  1. 35
      ICSharpCode.Decompiler.Tests/DebugStepRecordingTests.cs
  2. 5
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

35
ICSharpCode.Decompiler.Tests/DebugStepRecordingTests.cs

@ -43,6 +43,8 @@ namespace ICSharpCode.Decompiler.Tests @@ -43,6 +43,8 @@ namespace ICSharpCode.Decompiler.Tests
const string RecordedStep = "recorded IL step";
const string DetachedStep = "step on a detached function";
const string SeamStep = "C# AST built from ILAst";
static readonly FullTypeName SampleType =
new FullTypeName("ICSharpCode.Decompiler.CSharp.ProjectDecompiler.WholeProjectDecompiler");
@ -212,6 +214,39 @@ namespace ICSharpCode.Decompiler.Tests @@ -212,6 +214,39 @@ namespace ICSharpCode.Decompiler.Tests
}
}
/// <summary>
/// The plain ExpressionBuilder/StatementBuilder output is a state worth looking at, so it has
/// a step of its own at the top level rather than being reachable only as "before the first
/// AST transform". Halting there has to print C#: the whole type is converted by then, so
/// nothing is left in ILAst.
/// </summary>
[Test]
public void TheStateAtTheSeamIsUntransformedCSharp()
{
if (!Stepper.SteppingAvailable)
Assert.Ignore("Transform stepping is compiled out without the STEP symbol, so there is no seam step.");
var decompiler = CreateRecordingDecompiler();
string full = decompiler.DecompileTypeAsString(SampleType);
var seam = decompiler.Stepper.Steps.SingleOrDefault(n => n.Description.Contains(SeamStep));
Assert.That(seam, Is.Not.Null, $"'{SeamStep}' must be recorded once, at the top level");
var replay = CreateRecordingDecompiler();
replay.Stepper.StepLimit = seam!.BeginStep;
string atSeam = replay.DecompileTypeAsString(SampleType);
using (Assert.EnterMultipleScope())
{
Assert.That(replay.StepLimitHaltedFunction, Is.Null,
"the seam is past every member's IL phase, so there is no halted function to dump");
Assert.That(atSeam, Does.Contain("class WholeProjectDecompiler"),
"halting at the seam still prints C#");
Assert.That(atSeam, Is.Not.EqualTo(full),
"the AST transforms have not run yet, so this cannot equal the finished output");
}
}
/// <summary>
/// A member group ends where the next member's group begins, so the state after its last step is
/// the state that member finished in - not the untouched IL of the member the halt unwinds from.

5
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -898,6 +898,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -898,6 +898,11 @@ namespace ICSharpCode.Decompiler.CSharp
bool traceTransforms = DecompilerEventSource.Log.IsTransformTracingEnabled();
try
{
// The whole type has been converted and nothing has transformed it yet, so this is the
// one index whose state is the plain ExpressionBuilder/StatementBuilder output. Without
// it that state is only reachable as "before the first AST transform", which names a
// transform rather than the thing being shown and sits after every member's group.
context.Step("C# AST built from ILAst", rootNode);
foreach (var transform in astTransforms)
{
CancellationToken.ThrowIfCancellationRequested();

Loading…
Cancel
Save