Repository navigation
Commit e8e7544
Keep a node's subnode array alive while the native traverser walks it
traverseNode() separated the subnode array so it could be mutated in place
and then handed traverseArray() the property's own table - owned by nothing
but that property. traverseArray() walks the buckets directly, and between
two of them it calls the visitor hooks, which run arbitrary PHP. That code
can reach the very property being walked: PHPStan resolves a PHPDoc from
inside ExportedNodeVisitor::enterNode(), which re-enters the parser for the
file currently being traversed. Assigning to the property, or dropping the
node's last reference, then frees the table under the loop, and a write into
it reallocates the buckets the iterator holds - the traversal reads freed
memory and reports "Invalid node structure: Contains nested arrays", or
segfaults.
The PHP twin never sees this: `$subNode = $node->$name` gives traverseArray()
a local of its own, so the table cannot go away and any writer separates
first. The native side now holds the same reference for the duration of the
call. It costs one addref and one release per subnode array, no duplication:
self-analysis under the extension measured 17.97s against 18.73s for the
build without it, so the difference is noise.
Reproducer, both files fetched in one process with the extension loaded:
ExportedNodeFetcher::fetchNodes() on tests/PHPStan/Analyser/data/bug-7094.php
and then on tests/PHPStan/Analyser/nsrt/bug-3142.php. Over all 5129 files
under tests/*/data and tests/PHPStan/Analyser/nsrt, 8 threw before this
change and none do after; the PHP twin never threw on any of them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvMWsNHbCdSgx97Y56XYcU1 parent 3eb12b6 commit e8e7544
1 file changed
Lines changed: 16 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
360 | 360 | | |
361 | 361 | | |
362 | 362 | | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
363 | 379 | | |
364 | 380 | | |
365 | 381 | | |
| |||
0 commit comments