Skip to content

Regression: get-only virtual property write Error is "read-only" — Zend "Must not write to virtual property" (re-#18072, Zend/zend_object_handlers.c) #26006

Closed

Description

@PurHur

Category

Language · php-src-strict · PHP 8.4 property hooks · PROFILE=8.4

Problem

Writing (or ++ / +=) a get-only virtual hooked property must throw Error with Zend's virtual-property wording. Under PHP_COMPILER_PROFILE=8.4 the VM still emits the readonly-property message, conflating two distinct Error classes (see closed #18072 / #4687 / #9800).

Repro Zend 8.4 (php-src) VM PROFILE=8.4 (2026-07-31)
$c->full = "y" on get-only virtual $full Must not write to virtual property C::$full Property C::$full is read-only
$c->n++ on get-only virtual int $n Must not write to virtual property C::$n Property C::$n is read-only

Root cause in tree: lib/VM.php enforceVirtualPropertyHookWrite() uses Property %s::$%s is read-only when $virtual is true; the correct helper raiseVirtualPropertyHookRawAccessError() already formats Must not write to virtual property %s::$%s.

php-src reference

PHP implementation target

  • lib/VM.php — enforceVirtualPropertyHookWrite(): for $virtual === true reuse raiseVirtualPropertyHookRawAccessError(..., isRead: false) (or the same sprintf); do not use the readonly message
  • lib/JIT/PropertyHookDispatchLlvm.php — keep JIT/AOT Error text aligned
  • Prefer PHP-in-PHP / existing VM Error materialization; no new runtime/*.c

Repro

./script/docker-exec.sh -- bash -lc 'PHP_COMPILER_PROFILE=8.4 php bin/vm.php test/repro/property_hook_virtual_write_message.php'
<?php
class C {
    public string $full {
        get => "x";
    }
}
$c = new C();
try {
    $c->full = "y";
} catch (Error $e) {
    echo $e->getMessage(), "\n";
}

Expected: Must not write to virtual property C::$full

Done when

Activity

  1. added
    bugSomething isn't working
    area:vmVirtual machine
    implementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claim
    on Jul 31, 2026
  2. PurHur commented on Jul 31, 2026

    @PurHur
    OwnerAuthor

    claim: Worker Lane C (composer-implementer) — starting this run

  3. PurHur commented on Jul 31, 2026

    @PurHur
    OwnerAuthor

    Blocker — acceptance criteria vs php-src PHP-8.4 (Worker Lane C)

    Claimed this run; no code change — current VM already matches PHP-8.4 for the filed repro.

    What VM does today (PHP_COMPILER_PROFILE=8.4)

    arrow assign: Property C::$full is read-only
    arrow ++:     Property C::$full is read-only
    backed:       Cannot write property E::$name without set hook
    

    (get => "x" virtual get-only vs backed = "ok" { get => $this->name }.)

    What php-src actually says

    PHP-8.4 branch Zend/zend_object_handlers.c (hooked write, no set, ZEND_ACC_VIRTUAL):

    zend_throw_error(NULL, "Property %s::$%s is read-only", ...);

    That is the external get-only virtual write path — same string the VM emits.

    Must not write to virtual property comes only from zend_throw_no_prop_backing_value_access() — used when a hook tries to touch the backing slot of a virtual property (!zend_should_call_hook + ZEND_ACC_VIRTUAL). That path is already wired here as raiseVirtualPropertyHookRawAccessError().

    master tip recently diverged to Cannot write to get-only virtual property %s::$%s for the external path — still not Must not write to virtual property.

    Conclusion

    Path PHP-8.4 VM today Issue expected
    External write / ++ on get-only virtual Property … is read-only same Must not write to virtual property ❌
    Raw backing access inside hook Must not write to virtual property same via helper (conflated with above)
    External write on get-only backed (without set hook wording) Cannot write property … without set hook keep

    Implementing the filed Done-when would regress php-src-strict vs PHP-8.4 (and still not match master).

    Ask for maintainer retarget: close as not-a-bug for PROFILE=8.4, or open a forward-profile ticket for master’s Cannot write to get-only virtual property wording (not Must not write…).

    Unclaiming — Lane C has no other non-stdlib / non-extension implementation-ready issue this run.

  4. PurHur commented on Jul 31, 2026

    @PurHur
    OwnerAuthor

    claim: Worker Lane C (composer-implementer) — verifying close-out this run

    Confirming prior blocker vs php-src PHP-8.4 Zend/zend_object_handlers.c (~1174–1178): external get-only virtual write throws Property %s::$%s is read-only. Will add a compliance/repro guard for that Zend string and close as not-a-bug (filed Done-when conflates backing-slot Must not write to virtual property).

  5. PurHur commented on Jul 31, 2026

    @PurHur
    OwnerAuthor

    Merged #26014 — resolution: not a VM bug under php-src-strict / PROFILE=8.4.

    PHP-8.4 external get-only virtual write already emits Property … is read-only (matches Zend/zend_object_handlers.c). Filed Must not write to virtual property conflates the raw backing-slot path. PR adds compliance + repro guards so that wording cannot be “fixed” into a regression.

    Verification:

    PHP_COMPILER_PROFILE=8.4 php bin/vm.php test/repro/property_hook_virtual_write_message.php
    # arrow assign: Property C::$full is read-only
    PHP_COMPILER_PROFILE=8.4 php -d memory_limit=1536M vendor/bin/phpunit --filter virtual_write_readonly_message test/compliance/VMTest.php
    # OK (1 test, 1 assertion)
    
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:vmVirtual machinebugSomething isn't workingimplementation-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