[GIT-PULLS] [php-src] PR #22867: Fix GH-22857: virtual property hook mis-primes SIMPLE_GET under FUNC_ARG
[email protected] (zhaohao19941221) Thu, 23 Jul 2026 06:14:46 +0000
| Newsgroups | php.git-pulls |
|---|---|
| Message-ID | <JF1O8s4w1YXhYVuy8pfNWnn8ywllSa65QZU0gHZIRYU@main.internal.php.net> |
Pull Request: https://github.com/php/php-src/pull/22867
Author: zhaohao19941221
## Summary
Fixes GH-22857: heap corruption / spurious `ValueError`s / `TypeError`s when a virtual property hook is read via `FETCH_OBJ_FUNC_ARG` under function-mode JIT (`opcache.jit=1205`).
## Root cause
`zend_std_read_property()` primed the `SIMPLE_GET` property-hook cache-slot bit unconditionally after every successful hook invocation, ignoring which opcode had triggered the read.
`ZEND_FETCH_OBJ_FUNC_ARG` dispatches into the `ZEND_FETCH_OBJ_R` handler for by-value arguments via `ZEND_VM_TAIL_CALL`, keeping `EX(opline)` on the FUNC_ARG opcode. When the cache-slot bit was cached under such a mismatched opline, the next read went through the `SIMPLE_GET` fast path in `zend_vm_def.h`, which pushes a hook call frame and returns `opline | ZEND_VM_ENTER_BIT` to signal the caller to re-enter the VM.
Function-mode JIT handles this only for `FETCH_OBJ_R` (via the specialised hook-enter guard in `zend_jit.c`) — for `FETCH_OBJ_FUNC_ARG` the generic `zend_jit_handler` path is used, which has no such guard. The JIT-compiled `SEND_FUNC_ARG` therefore reads from the argument slot before the hook has actually run.
Depending on the adjacent property slot's contents, symptoms are:
- `ValueError: file_get_contents(): Argument #1 ($filename) must not contain any null bytes`
- `TypeError: <fn>(): Argument #N ($arg) must be of type ?string, <adjacent-class> given`
- `zend_mm_heap corrupted` (SIGABRT)
All three have the same root cause.
## Fix
Only prime `SIMPLE_GET` when the currently-executing opline is a plain `ZEND_FETCH_OBJ_R`. This mirrors the guard already used for `SIMPLE_READ` a few lines above in the same function.
## Reproducer (before the fix)
```console
$ php -d opcache.enable_cli=1 -d opcache.jit_buffer_size=64M \
-d opcache.jit=1205 Zend/tests/property_hooks/virtual_hook_as_func_arg.phpt
```
was 20/20 crashing; with `-d opcache.jit=disable` or `-d opcache.jit=tracing` was 20/20 clean. After the fix, both modes are clean.
The reproducer needs all of:
1. `opcache.jit=1205` (function JIT).
2. Class inside a `namespace`.
3. Call is **unqualified** (no leading `\`, no `use function`) so that `INIT_NS_FCALL_BY_NAME` + `FETCH_OBJ_FUNC_ARG` is emitted.
4. Argument is a **virtual** property hook (get-only, no backing storage).
5. Hook passed **directly** — assigning to a local first uses `FETCH_OBJ_R` and hides the bug.
6. Call wrapped in `@` (`BEGIN_SILENCE` / `END_SILENCE`).
7. Class layout with adjacent asymmetric-visibility fields and two promoted asymmetric-visibility constructor parameters.
8. Hook `get =>` calls a static method reading the promoted asymmetric properties.
Delta-debugging established that removing any one of these hides the bug.
## Test
`Zend/tests/property_hooks/virtual_hook_as_func_arg.phpt` — 200 iterations exercising the exact eight-condition shape from the issue. Fails before the fix (20/20 under `jit=1205`), passes after.
## Backport
Bug is present since 8.4 (property hooks introduced in 8.4). Targeting `PHP-8.4` so it lands in 8.4/8.5/master via the normal merge chain.