diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 6eab6a2c0..70eea317f 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -263,13 +263,6 @@ namespace ICSharpCode.Decompiler.CSharp /// public ILFunction? StepLimitHaltedFunction { get; private set; } - /// - /// The last member whose IL transforms ran to completion. A step limit landing on a member's - /// opening group step stops the pipeline at the very point that member's predecessor finished, - /// so this - not the untouched member the halt technically occurred in - is the state the caller - /// is asking to see. - /// - ILFunction? lastCompletedFunction; /// /// Returns all built-in transforms of the C# AST pipeline. @@ -859,7 +852,6 @@ namespace ICSharpCode.Decompiler.CSharp // previous run's ILAst, and the "already halted" guard in DecompileBody would skip every // member from here on. StepLimitHaltedFunction = null; - lastCompletedFunction = null; List resolvedNamespaces = new List(); foreach (var ns in namespaces) { @@ -909,7 +901,7 @@ namespace ICSharpCode.Decompiler.CSharp foreach (var transform in astTransforms) { CancellationToken.ThrowIfCancellationRequested(); - context.StepStartGroup(transform.GetType().Name); + context.StepStartGroup(transform.GetType().Name, rootNode); long traceStart = traceTransforms ? Stopwatch.GetTimestamp() : 0; transform.Run(rootNode, context); if (traceTransforms) @@ -2317,10 +2309,19 @@ namespace ICSharpCode.Decompiler.CSharp /// on, or null when the step named no instruction (a group opener) or the instruction hangs off /// no function at all. /// + /// + /// The function a halt belongs to, taken from the stepper rather than reconstructed. The step + /// the limit stopped on is the first choice; on a member's opening group step that step belongs + /// to the next member, so the last step actually recorded - the previous member's - is the + /// state the halt is showing. Following the instruction to its outermost function also covers + /// a step recorded in a helper the pipeline has not attached yet. + /// ILFunction? HaltedStepFunction() { - return (Stepper.LimitReachedStep?.Position as ILInstruction)? - .Ancestors.OfType().LastOrDefault(); + return FunctionOf(Stepper.LimitReachedStep) ?? FunctionOf(Stepper.LastStep); + + static ILFunction? FunctionOf(Stepper.Node? step) + => (step?.Position as ILInstruction)?.Ancestors.OfType().LastOrDefault(); } /// @@ -2345,9 +2346,8 @@ namespace ICSharpCode.Decompiler.CSharp if (StepLimitHaltedFunction != null) return; // Declared out here so the StepLimitReachedException handler below can report the function - // its transforms were halted in, and tell a halt inside this member from one on its boundary. + // its transforms were halted in. ILFunction? function = null; - bool haltBelongsToThisMember = false; // Only the groups this member opens may be closed when an exception unwinds out of it: a // caller that wrapped this call in a group of its own still owns that group afterwards. int groupDepth = Stepper.GroupDepth; @@ -2403,13 +2403,12 @@ namespace ICSharpCode.Decompiler.CSharp }; if (RecordSteps) context.Stepper = Stepper; + // Deliberately unanchored: a member's opening step is where the *previous* member's + // state ends, so giving it this member's function would attribute a halt on the + // boundary to the member the pipeline is only about to start. context.StepStartGroup(method.FullName); - // From here on a halt belongs to this member: it is standing on one of this member's own - // steps, not on the boundary where the previous member finished. - haltBelongsToThisMember = true; function.RunTransforms(SelectILTransforms(localSettings.DecompileMemberBodies), context); - lastCompletedFunction = function; // Generate C# AST only if bodies should be displayed. if (localSettings.DecompileMemberBodies) @@ -2461,8 +2460,7 @@ namespace ICSharpCode.Decompiler.CSharp // nested function on its way into place) belongs to that function's own tree, which the // member's function does not contain - so follow the halted step's instruction to the // function that actually holds it. - StepLimitHaltedFunction = HaltedStepFunction() - ?? (haltBelongsToThisMember ? function : lastCompletedFunction ?? function); + StepLimitHaltedFunction = HaltedStepFunction() ?? function; Stepper.EndOpenGroups(groupDepth); } catch (Exception innerException) when (!(innerException is OperationCanceledException)) diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index a990f05c6..c0718070c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -406,13 +406,16 @@ namespace ICSharpCode.Decompiler.IL foreach (var transform in transforms) { context.CancellationToken.ThrowIfCancellationRequested(); + // 'near: this' is what lets a halt on a group opener be traced back to the function it + // belongs to; without a position the step carries no anchor and the halt cannot be + // attributed to any member. if (transform is BlockILTransform blockTransform) { - context.StepStartGroup(blockTransform.ToString()); + context.StepStartGroup(blockTransform.ToString(), this); } else { - context.StepStartGroup(transform.GetType().Name); + context.StepStartGroup(transform.GetType().Name, this); } long traceStart = traceTransforms ? System.Diagnostics.Stopwatch.GetTimestamp() : 0; transform.Run(this, context);