[php-src] PR #23103: opcache: set ZEND_ACC_PRELOADED for compile-only preloaded enums
| From: | Nibbler999 | Date: | Fri, 07 Aug 2026 14:48:21 +0000 |
| Subject: | [php-src] PR #23103: opcache: set ZEND_ACC_PRELOADED for compile-only preloaded enums | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-37618@lists.php.net to get a copy of this message | ||
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.