Browse Source

Share debug-step candidate recording between IL and C#

The IL (Stepper) and C# (TransformContext) paths each recorded the changed
node, its seam neighbours, and its ancestor chain with duplicated logic that
could drift. Move the ordering/dedup/seam strategy onto
Stepper.Node.RecordModifiedNode; each language keeps only its own node
navigation.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3847/head
Siegfried Pammer 6 days ago committed by Siegfried Pammer
parent
commit
ffd1eac3d0
  1. 39
      ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs
  2. 3
      ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs
  3. 57
      ICSharpCode.Decompiler/IL/Transforms/Stepper.cs

39
ICSharpCode.Decompiler/CSharp/Transforms/TransformContext.cs

@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
@ -131,34 +132,26 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -131,34 +132,26 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
if (modifiedNode == null)
return;
AddCandidate(step, modifiedNode, insertFirst);
// The marker rides CopyAnnotationsFrom so a replaced node's step still resolves to the
// emitted text; it is recorded as a second identity for the changed node. The seam
// neighbours and ancestor chain are captured from the original node on the initial pass
// (insertFirst == false); a produced-node update only prepends the new node.
var marker = new DebugStepMarker();
modifiedNode.AddAnnotation(marker);
AddCandidate(step, marker, insertFirst: false);
// insertFirst marks the produced-node update from EndStep; the seam neighbours and
// ancestor chain are recorded once, from the original node captured before the mutation.
if (!insertFirst)
step.RecordModifiedNode(
modifiedNode,
insertFirst ? null : modifiedNode.NextSibling,
insertFirst ? null : modifiedNode.PrevSibling,
insertFirst ? null : Ancestors(modifiedNode),
extraIdentity: marker,
insertFirst: insertFirst);
static IEnumerable<object> Ancestors(AstNode node)
{
if (modifiedNode.NextSibling is { } nextSibling)
step.SeamAnchors.Add((nextSibling, false));
if (modifiedNode.PrevSibling is { } prevSibling)
step.SeamAnchors.Add((prevSibling, true));
for (var parent = modifiedNode.Parent; parent != null; parent = parent.Parent)
{
step.AncestorCandidates.Add(parent);
}
for (var parent = node.Parent; parent != null; parent = parent.Parent)
yield return parent;
}
}
static void AddCandidate(Stepper.Node step, object candidate, bool insertFirst)
{
if (step.ModifiedNodeCandidates.Contains(candidate))
return;
if (insertFirst)
step.ModifiedNodeCandidates.Insert(0, candidate);
else
step.ModifiedNodeCandidates.Add(candidate);
}
}
/// <summary>

3
ICSharpCode.Decompiler/IL/Transforms/IILTransform.cs

@ -135,8 +135,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -135,8 +135,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (Stepper.LastStep is { } step && modifiedNode != null)
{
step.ModifiedNode = modifiedNode;
if (!step.ModifiedNodeCandidates.Contains(modifiedNode))
step.ModifiedNodeCandidates.Insert(0, modifiedNode);
step.RecordModifiedNode(modifiedNode, insertFirst: true);
}
}
}

57
ICSharpCode.Decompiler/IL/Transforms/Stepper.cs

@ -96,6 +96,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -96,6 +96,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
Description = description;
}
/// <summary>
/// Records the highlight candidates for this step from an already-navigated node: the
/// node's identity (optionally a second identity such as a debug-step marker), its
/// immediate siblings as seam anchors, and its ancestor chain. Callers pass the
/// neighbours because <see cref="ILInstruction"/> and the C# AST expose navigation
/// differently; the ordering/dedup/seam strategy lives here so the two languages'
/// recording can't drift. <paramref name="insertFirst"/> marks a produced-node update
/// (from EndStep): the node is preferred over existing candidates and the seam and
/// ancestor anchors are left untouched, since they were captured from the original node.
/// </summary>
public void RecordModifiedNode(
object node,
object? nextSibling = null,
object? previousSibling = null,
IEnumerable<object>? ancestors = null,
object? extraIdentity = null,
bool insertFirst = false)
{
AddCandidate(node, insertFirst);
if (extraIdentity != null)
AddCandidate(extraIdentity, insertFirst: false);
if (insertFirst)
return;
if (nextSibling != null)
SeamAnchors.Add((nextSibling, false));
if (previousSibling != null)
SeamAnchors.Add((previousSibling, true));
if (ancestors != null)
{
foreach (var ancestor in ancestors)
AncestorCandidates.Add(ancestor);
}
}
void AddCandidate(object candidate, bool insertFirst)
{
if (ModifiedNodeCandidates.Contains(candidate))
return;
if (insertFirst)
ModifiedNodeCandidates.Insert(0, candidate);
else
ModifiedNodeCandidates.Add(candidate);
}
}
readonly Stack<Node> groups;
@ -137,18 +181,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -137,18 +181,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// surviving ancestor (ultimately the ILFunction) still resolves.
if (near != null)
{
stepNode.ModifiedNodeCandidates.Add(near);
object? next = null, prev = null;
if (near.Parent is { } parent)
{
int index = near.ChildIndex;
if (index + 1 < parent.Children.Count)
stepNode.SeamAnchors.Add((parent.Children[index + 1], false));
next = parent.Children[index + 1];
if (index - 1 >= 0)
stepNode.SeamAnchors.Add((parent.Children[index - 1], true));
prev = parent.Children[index - 1];
}
for (var node = near.Parent; node != null; node = node.Parent)
stepNode.RecordModifiedNode(near, next, prev, Ancestors(near));
static IEnumerable<object> Ancestors(ILInstruction inst)
{
stepNode.AncestorCandidates.Add(node);
for (var node = inst.Parent; node != null; node = node.Parent)
yield return node;
}
}
if (step == StepLimit)

Loading…
Cancel
Save