[php-src] Issue #24234: Opcache: optimizer overwrites the operator of ASSIGN_STATIC_PROP_OP, so `$c::$$n += 3` segfaults or computes `|=`
| From: | waroad | Date: | Sat, 10 Oct 2026 07:07:50 +0000 |
| Subject: | [php-src] Issue #24234: Opcache: optimizer overwrites the operator of ASSIGN_STATIC_PROP_OP, so `$c::$$n += 3` segfaults or computes `|=` | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-252962@lists.php.net to get a copy of this message | ||
Issue: https://github.com/php/php-src/issues/24234
Author: waroad
> With opcache enabled, a compound assignment to a static property with a variable class name
> (
$c::$$n += 2) runs with a corrupted operator: UBSan reports an
> out-of-bounds index in zend_binary_op(), and a release build
> segfaults or quietly applies a different operator.
```php
<?php
class K { public static $s = 5; }
function f() { $c = "K"; $n = "s"; $c::$$n += 2; return K::$s; }
var_dump(f());
```
```
php -n -d opcache.enable_cli=1 -d opcache.file_update_protection=0 reduced.php
```
```
Zend/zend_execute.c:1668:24: runtime error: index 24 out of bounds for type '<unknown>
*[12]'
#0 in zend_binary_op Zend/zend_execute.c:1668
#1 in ZEND_ASSIGN_STATIC_PROP_OP_SPEC_HANDLER Zend/zend_vm_execute.h:822
#2 in execute_ex Zend/zend_vm_execute.h:110590
#3 in zend_execute Zend/zend_vm_execute.h:116021
#4 in zend_execute_script Zend/zend.c:1980
```
> I expected int(7) (5 + 2), the same as with opcache off.
> opcache.file_update_protection=0 is only there because the file is
> new; with an older file, -d opcache.enable_cli=1 alone is enough.
> Found at commit b527e1b34ee97f4ea402e36a641509af7436759d, and the UBSan report still appears at
> c79284711bd8. A release build segfaults on this script (exit 139, no output). With one more cache
> slot in the function, the corrupted operator happens to be a valid one
> (ZEND_BW_OR). Then both builds print
> int(7) with no diagnostic, while opcache off and
> opcache.optimization_level=0 print
> int(8):
```php
<?php
class K { public static $s = 5; }
function g() {}
function f() { g(); $c = "K"; $n = "s"; $c::$$n += 3; }
f();
var_dump(K::$s);
```
> The opcode dump (opcache.opt_debug_level=0x30000) shows
> ASSIGN_STATIC_PROP_OP (ADD) before the optimizer and
> ASSIGN_STATIC_PROP_OP (ASSIGN_STATIC_PROP) after it. My guess is
> that zend_optimizer_update_op2_const() and
> zend_optimizer_update_op1_const() in Zend/Optimizer/zend_optimizer.c
> treat ASSIGN_STATIC_PROP_OP like the other static-prop opcodes and write the cache slot into
> opline->extended_value. For this opcode that field holds the
> operator, and the slot belongs in (opline+1)->extended_value. The
> op1 path can be hit with K::${N} += 1 (where ê¥à#BþÕ
> ‰™&óR(‡N is a user constant) at
> opcache.optimization_level=0x4001.
Found with an LLM-based testing tool. As CONTRIBUTING.md asks, the paragraphs in > quotes were
written with Claude; I ran every command myself.