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
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 b527e1b, and the UBSan report still appears at c792847. 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
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 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.
Found with an LLM-based testing tool. As CONTRIBUTING.md asks, the paragraphs in > quotes were written with Claude; I ran every command myself.