Skip to content

Language: readonly property hooks — set hook cannot assign to readonly backing store outside __construct (zend_compile.c) #4518

Closed

Description

@PurHur

Category

language

Problem

PHP 8.4 property hooks on readonly properties forbid writes to the backing value outside __construct, even from the property's own set hook (only get may read). Zend rejects invalid hook bodies at compile time or runtime per zend_compile.c / hook dispatch.

This compiler preprocesses hooks to __phpc_property_set_* methods (lib/SourcePreprocessor/PropertyHooks.php) and dispatches via lib/VM.php / lib/JIT/PropertyHookDispatch.php (#3145). Gap: a set hook that assigns $this->prop = … on a readonly promoted property may succeed on VM/JIT when Zend fatals or throws.

php-src reference

Repro

repro_readonly_hook_set.php:

<?php
class C {
    public readonly string $name {
        set (string $value) {
            $this->name = strtoupper($value);
        }
    }
    public function __construct(string $v) {
        $this->name = $v;
    }
}

$c = new C('hi');
try {
    $c->name = 'no';
} catch (Error $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

Compare Zend vs this compiler:

php repro_readonly_hook_set.php
./script/docker-exec.sh -- bash -lc 'source script/php-env.sh && php bin/vm.php repro_readonly_hook_set.php'

Scope (this repo)

  • lib/SourcePreprocessor/PropertyHooks.php — compile-time diagnostics where possible
  • lib/VM.php — asymmetricPropertyWriteMessage / readonly guards in hook dispatch
  • lib/JIT/PropertyHookDispatch.php — readonly write mask for hook bodies
  • Compliance: test/compliance/cases/language/readonly_property_hook_set.phpt

Done when

  • Post-construct assignment through set hook matches Zend (Error/exception class + message)
  • Constructor initialization still works
  • JIT/AOT same as VM (no MCJIT-only bypass)

Related

Activity

  1. added
    area:vmVirtual machine
    implementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claim
    on Jun 2, 2026
  2. PurHur commented on Jun 7, 2026

    @PurHur
    OwnerAuthor

    claim: worker-lane-a — starting this run

  3. PurHur commented on Jun 7, 2026

    @PurHur
    OwnerAuthor

    Merged via #7545

    Root cause: php-cfg-property-readonly overlay was not reliably applied before compile, so ClassProperty::readonly stayed false and post-construct writes (including set-hook backing stores) were not guarded.

    Fix:

    • Apply apply_php_cfg_property_readonly_overlay early in script/apply-patches.sh
    • VM: defer outer readonly check when a set hook will dispatch; enforce readonly on raw $this->prop = … inside the hook after __construct
    • Compliance: test/compliance/cases/language/readonly_property_hook_set.phpt

    Verification (docker):

    ./script/apply-patches.sh 2>&1 | grep property-readonly  # Skip ... (already applied)
    php bin/vm.php repro_readonly_hook_set.php               # Error: Cannot modify readonly property C::$name
    php bin/jit.php repro_readonly_hook_set.php              # same
    vendor/bin/phpunit test/compliance/VMTest.php --filter readonly_property_hook_set  # OK
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:compilerCompiler / CFG / JITarea:vmVirtual machineimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-2:languagePhase 2 – language features

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions