Skip to content

Pass immutable array arguments non-refcounted through zv::Args - #6558

Closed
zonuexe wants to merge 2 commits into
phpstan:2.3.xfrom
zonuexe:turbo-args-immutable-array
Closed

zonuexe wants to merge 2 commits into
phpstan:2.3.xfrom
zonuexe:turbo-args-immutable-array

Conversation

@zonuexe

@zonuexe zonuexe commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

zv::Args::set(zval *, HashTable *) stored a table argument with a bare ZVAL_ARR. For an immutable table (the shared zend_empty_array of a PHP [] literal or zv::Arr::empty()) this sets the refcounted type info, and when the argument vector reaches a PHP method through pt_type_call(), the addref in zend_call_function() writes into read-only memory: SIGBUS on macOS arm64, SIGSEGV on Linux. This is the trap documented under "Zend-level gotchas" in turbo-ext/CLAUDE.md.

The reachable path is pt_mutating_scope_add_conditional_expressions(): when the scope's class overrides addConditionalExpressions(), the holders go to the PHP method through zv::Args. ForeachHandler's constant-array branch starts its holder tables as zv::Arr::empty() and passes them on still empty when the constant array has no keys (foreach ($a as $k => $v) with $a being array{}).

The fix wraps immutable tables as plain IS_ARRAY, the same idiom zv::Arr::adoptTable() and copyOfTable() already use.

With the HashTable * overload of zv::Args::set() temporarily deleted, the only instantiation that failed to compile was the one in pt_mutating_scope_add_conditional_expressions(), so this is the only affected call site. The other raw ZVAL_ARR uses wrap freshly allocated tables or are read-only views that are never addref'ed.

ForeachHandlerTest drives ForeachHandler::processStmt() with a MutatingScope subclass that records what addConditionalExpressions() receives, once for array{} and once for a one-key array. With the extension loaded, the array{} case exits with 138 before this change; without the extension, both cases pass before and after. The turbo-run legs run the test suite with the extension loaded, so CI covers it there.

Verified locally on macOS arm64 (PHP 8.5): strict build, smoke.php (ALL OK), signature-parity.php, side-by-side.php, walk-trace.php --shards=8 (identical), the full test suite with the extension loaded and Runtime::isShadowing() true (22235 tests OK), and make phpstan. The second commit is make bump-turbo.

zonuexe and others added 2 commits September 23, 2026 18:32
zv::Args stored a HashTable argument with a bare ZVAL_ARR, which types
an immutable table (the shared zend_empty_array of a PHP [] literal or
zv::Arr::empty()) as refcounted. When the argument vector reached a PHP
method, the addref in zend_call_function() wrote into read-only memory
(SIGBUS on macOS arm64, SIGSEGV on Linux).

ForeachHandler hit this for a constant array without keys: its
conditional holder tables stay empty and are handed to a PHP override
of MutatingScope::addConditionalExpressions(). Wrap immutable tables as
plain IS_ARRAY, like zv::Arr::adoptTable() and copyOfTable() do.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@ondrejmirtes

Copy link
Copy Markdown
Member

Thank you! I got inspired by your PR and opened #6561.

@zonuexe
zonuexe deleted the turbo-args-immutable-array branch September 23, 2026 10:21
@ondrejmirtes

Copy link
Copy Markdown
Member

BTW @zonuexe I'd love to hear how 2.3.x-dev performs on your projects when compared to 2.2.14 so far :)

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.

2 participants