Skip to content

Fix GH-24250: Set visibility not enforced after write forwarded to __set() - #24251

Open
DirkHoffman wants to merge 1 commit into
php:PHP-8.6from
DirkHoffman:fix-avis-set-cache-slot
Open

DirkHoffman wants to merge 1 commit into
php:PHP-8.6from
DirkHoffman:fix-avis-set-cache-slot

Conversation

@DirkHoffman

Copy link
Copy Markdown

Fixes GH-24250.

LLM-generated description: Since 94136cf (GH-22709), the ZEND_ASSIGN_OBJ fast path skips the set visibility check when the cache slot is primed, relying on zend_std_write_property() to reset the slot when the check fails. When an unset property is written through __set(), the check is skipped altogether and the slot primed by zend_get_property_offset() stays valid, so the next write at the same call site to an initialized property bypasses protected(set)/private(set) (also from child classes, and for private(set) readonly in __clone()). This resets the cache slot in that case as well, so __set() forwarding itself is unchanged.

The new test fails on 8.6.0RC3 / current PHP-8.6 and passes on 8.4 and 8.5 (same expected output).

Found and verified with the help of AI tools.

… __set()

Since 94136cf, the ZEND_ASSIGN_OBJ fast path skips the set visibility
check when the cache slot is primed, relying on zend_std_write_property()
to reset the slot when the check fails. When an unset property is written
through __set(), the check is skipped altogether and the slot primed by
zend_get_property_offset() stays valid, so the next write at the same call
site to an initialized property bypasses protected(set)/private(set).
Reset the cache slot in that case as well.
@anun333

anun333 commented Oct 26, 2026

Copy link
Copy Markdown

Quoted below: written with Claude, an LLM, as CONTRIBUTING.md asks us to mark. The builds and test runs behind it are on my machine; the commands to repeat them are under the fold.

Tested this alongside #24566, which fixes the same GH-22709 regression (94136cf9bfb, on PHP-8.6 and master only), each applied onto today's PHP-8.6 (a588565de24), debug builds:

Test PHP-8.6 this PR #24566
gh24250.phpt (this PR) FAIL PASS PASS
cache_slot_magic_set.phpt (#24566) FAIL PASS PASS
cache_slot_clone_reinit.phpt (#24566) FAIL FAIL PASS

The third is the other path #24566 covers: a "Cannot modify readonly property" error also returns with the cache slot primed, so the same $p->x = $v opline later reinitializes a public readonly property from global scope inside __clone(). With this PR it prints int(3) where "Cannot modify protected(set) readonly property P::$x from global scope" is expected. Zend/tests/asymmetric_visibility, readonly_props and property_hooks pass on all three builds.

How this was tested

Linux x86_64, gcc 13.3. Each tree is origin/PHP-8.6 with the PR's commit cherry-picked on top:

./buildconf --force
./configure --enable-debug --disable-cgi --disable-phpdbg --disable-fileinfo --enable-pcntl --enable-zend-test --enable-fpm
make -j4

The three tests copied out of the two PRs and run on each build with TEST_PHP_EXECUTABLE=sapi/cli/php sapi/cli/php run-tests.php -n -q <tests>; the folders with run-tests.php -n -q Zend/tests/asymmetric_visibility Zend/tests/readonly_props Zend/tests/property_hooks.

@ArtUkrainskiy

Copy link
Copy Markdown
Contributor

Reproduced on current master without the patch. Once a call site is routed through __set, a later protected(set) write from global scope goes through silently instead of throwing. The slot reset mirrors the existing error path and stays narrow enough to leave normal writes alone. Test covers the child-class and readonly-clone cases too. LGTM.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants