Skip to content

Fix missing leaveNode() calls after removal in enterNode() - #1172

Open
ydah wants to merge 1 commit into
nikic:masterfrom
ydah:fix-enter-node-unwinding
Open

ydah wants to merge 1 commit into
nikic:masterfrom
ydah:fix-enter-node-unwinding

Conversation

@ydah

@ydah ydah commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Returning REMOVE_NODE, an array, or REPLACE_WITH_NULL from enterNode() skips leaveNode() for visitors that already entered the node. This can leave ParentConnectingVisitor's stack unbalanced:

$nodes = $parser->parse('<?php function a() { foo(); } function b() {}');
$traverser = new NodeTraverser(
    new ParentConnectingVisitor(),
    new class extends NodeVisitorAbstract {
        public function enterNode(Node $node) {
            if ($node instanceof Node\Stmt\Expression) {
                return NodeVisitor::REMOVE_NODE;
            }
        }
    }
);
$nodes = $traverser->traverse($nodes);
$nodes[1]->getAttribute('parent'); // Before: function a; after: null.

The same missing callback occurs with each return value:

Return from enterNode() Example input → result
NodeVisitor::REMOVE_NODE foo(); → removed
[new Node\Stmt\Nop()] foo(); → ; ([] removes it)
NodeVisitor::REPLACE_WITH_NULL on Else_ if ($x) {} else {} → if ($x) {}

Call leaveNode() in reverse order for visitors that entered the node, while skipping children and later visitors. Ignore replacement return values during these calls to preserve the removal, while honoring STOP_TRAVERSAL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant