Repository navigation
Fix GH-24792: Assignment-result writes compile but fail with invalid opcode - #25046
ChrisAbner wants to merge 1 commit into
Conversation
…id opcode Since the VAR|TMP overhaul, zend_compile_var_inner() compiles assignment expressions for any fetch type, so using an assignment as the container of a write (e.g. ($a = [1])[0] = 2, unset(($a = [1])[0])) emitted a write fetch on a TMP operand, which has no handler. Only compile assignments in write context when they are passed by reference, which is the case the overhaul needed (f([&$x] = $arr)). Otherwise fall through to the "Cannot use temporary expression in write context" compile error, as in PHP 8.5.
|
Quoted below: written with Claude, an LLM, as CONTRIBUTING.md asks us to mark. The builds and test runs behind it are on my machine; the commands to repeat them are under the fold.
|
Fixes GH-24792.
Since the VAR|TMP overhaul (6173a9a),
zend_compile_var_inner()compilesZEND_AST_ASSIGNandZEND_AST_ASSIGN_REFfor every fetch type. When an assignment is the container of a write, such as($a = [1])[0] = 2,unset(($a = [1])[0])or$r = &($a = [1])[0], the compiler emits a write fetch on the TMP result of the assignment. That combination has no handler, so the script compiles and then dies withInvalid opcode. PHP 8.5 rejected all of these at compile time with "Cannot use temporary expression in write context". This patch restores that error.The overhaul added these cases so that a destructuring assignment with references can be passed by reference (
f([&$x] = $arr)). It originally had an assertion limiting them to that case, which a01a8e7 removed. The patch keeps compiling assignments whenby_refis set or when the fetch is not a write (W/RW/UNSET), and otherwise falls through to the existingdefaultbranch and its error. This puts the check in the one place every write container goes through. Checking in each caller (dim, prop, unset, inc/dec, reference fetch) would have needed one check per caller. As a side effect,($a =& $b)[0] = 2and([&$x] = $arr)[0] = 5are compile errors again. PHP 8.5 rejected them too; 8.6.0RC4 started executing them. If you would rather keep those working in 8.6, I can narrow the check.Verification. Debug NTS build of
PHP-8.6(e2fa0c9,--disable-all --enable-cli), compared with the officialphp:8.5-cli(8.5.12) andphp:8.6.0RC4-cliimages:($a = [1])[0] = 2;Invalid opcode 23/2/1($a = new stdClass)->p = 1;Invalid opcode 24/2/1($a = [1])[0] += 2;Invalid opcode 27/2/1($a = [1])[0]++;Invalid opcode 87/2/1unset(($a = [1])[0]);Invalid opcode 75/2/1$r = &($a = [1])[0];Invalid opcode 84/2/1f(($a = [1])[0]);withf(&$x)Invalid opcode 84/2/1($a =& $b)[0] = 2;([&$x] = $arr)[0] = 5;f([&$x] = $arr);/f($a =& $b);withf(&$x)isset(($a = [1])[0]),($a = [1])[5] ?? 'd',($v = ['k' => 'v'])['k']Out of 30 variants in total, all 30 match 8.5.12 with the patch, with and without OPcache + tracing JIT. Without the patch, 15 fail with
Invalid opcodein 8.6.0RC4 and on master.Zend/tests/gh24792*.phpt(9) on 8.5.12 / 8.6.0RC4 / patchedZend/tests+tests, patchedZend/tests+tests+ext/opcache/tests, patched,opcache.enable_cli=1,opcache.jit=tracing,opcache.protect_memory=1Not covered:
foreach (($a =& $b) as &$v) { $v = 3; }writes through to$bin 8.5 but not in 8.6.0RC4, with or without this patch. It is a separate behaviour change from the same overhaul and is left alone here. I only tested on Linux x86_64 NTS debug builds, not ZTS or ASAN builds of the patch. The bug itself also reproduces on the official ZTS RC4 image.