[GIT-PULLS] [php-src] PR #23103: opcache: set ZEND_ACC_PRELOADED for compile-only preloaded enums
[email protected] (Nibbler999)
| Newsgroups | php.git-pulls |
|---|---|
| Message-ID | <[email protected]> |
Pull Request: https://github.com/php/php-src/pull/23103
Author: Nibbler999
> **Disclosure: this description, the patch and its test were drafted with an LLM.**
### Summary
GH-17835 fixed [GH-17715](https://github.com/php/php-src/issues/17715) for enums that preload **declares**. It does not cover enums that preload only **compiles**, and those still crash on 8.4 and 8.5.
```php
// preload.php
opcache_compile_file(__DIR__ . '/e.php');
// e.php
enum E { case Foo; }
// request
E::cases(); // SIGSEGV with any Observer API extension loaded
```
### Cause
`zend_enum_register_func()` sets `ZEND_ACC_PRELOADED` inside the `if (EG(active))` branch. `zend_persist_class_method()` keys on that flag to choose `ZEND_MAP_PTR_NEW_STATIC()` over `ZEND_MAP_PTR_NEW()`, i.e. a run_time_cache slot that survives into every request — which is what a class living in SHM permanently needs.
An enum that preload reaches via `opcache_compile_file()` is not linked while the preload script executes. It is linked from `preload_link()`, which runs after the preload request has ended, so `EG(active)` is already false, the `else` branch is taken and the flag is never set.
gdb on the crash, on an unpatched 8.5.9:
```
Program received signal SIGSEGV, Segmentation fault.
#0 zend_observer_fcall_begin_specialized (...) at Zend/zend_observer.h:92
#1 ZEND_DO_FCALL_SPEC_OBSERVER_HANDLER () at Zend/zend_vm_execute.h:2281
#2 execute_ex (...) at Zend/zend_vm_execute.h:116541
function : cases type: 1 (internal) T: 1
fn_flags : 0x2002011 = PUBLIC|STATIC|HAS_RETURN_TYPE|ARENA_ALLOCATED
ZEND_ACC_PRELOADED (1<<10) absent
run_time_cache : 0
```
`ZEND_OBSERVER_DATA()` computes `RUN_TIME_CACHE(...) + handle` = `NULL + 0` and dereferences it — the same null deref, and the same `Zend/zend_observer.h:92`, that GH-17715's UBSAN output reports.
### Fix
Move the tag out of the `EG(active)` branch so both linking paths set it. The compile-only case then follows the identical, already-proven route as the declared case; no new mechanism is introduced.
### Test
`ext/opcache/tests/preload_enum_observed_compile_only.phpt` mirrors the existing `preload_enum_observed.phpt` and reuses its `preload_enum.inc` fixture, changing only how preload reaches it.