[php-src] PR #25046: Fix GH-24792: Assignment-result writes compile but fail with invalid opcode

From: Date: Fri, 30 Oct 2026 03:14:14 +0000
Subject: [php-src] PR #25046: Fix GH-24792: Assignment-result writes compile but fail with invalid opcode
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-40077@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/25046 Author: ChrisAbner Fixes GH-24792. Since the VAR|TMP overhaul (6173a9a109), zend_compile_var_inner() compiles ZEND_AST_ASSIGN and ZEND_AST_ASSIGN_REF for 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 with Invalid 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 a01a8e72ac removed. The patch keeps compiling assignments when by_ref is set or when the fetch is not a write (W/RW/UNSET), and otherwise falls through to the existing default branch 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] = 2 and ([&$x] = $arr)[0] = 5 are 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 (e2fa0c9f7f, --disable-all --enable-cli), compared with the official php:8.5-cli (8.5.12) and php:8.6.0RC4-cli images: | Code | 8.5.12 | 8.6.0RC4 | PHP-8.6 + patch | |---|---|---|---| | ($a = [1])[0] = 2; | compile error | Invalid opcode 23/2/1 | compile error | | ($a = new stdClass)->p = 1; | compile error | Invalid opcode 24/2/1 | compile error | | ($a = [1])[0] += 2; | compile error | Invalid opcode 27/2/1 | compile error | | ($a = [1])[0]++; | compile error | Invalid opcode 87/2/1 | compile error | | unset(($a = [1])[0]); | compile error | Invalid opcode 75/2/1 | compile error | | $r = &($a = [1])[0]; | compile error | Invalid opcode 84/2/1 | compile error | | f(($a = [1])[0]); with f(&$x) | compile error | Invalid opcode 84/2/1 | compile error | | ($a =& $b)[0] = 2; | compile error | runs | compile error | | ([&$x] = $arr)[0] = 5; | compile error | runs | compile error | | f([&$x] = $arr); / f($a =& $b); with f(&$x) | works | works | works | | isset(($a = [1])[0]), ($a = [1])[5] ?? 'd', ($v = ['k' => 'v'])['k'] | works | works | works | 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 opcode in 8.6.0RC4 and on master. | Tests | Result | |---|---| | New Zend/tests/gh24792*.phpt (9) on 8.5.12 / 8.6.0RC4 / patched | 9 pass / 8 fail / 9 pass | | Zend/tests + tests, patched | 6301 passed, 0 failed | | Zend/tests + tests + ext/opcache/tests, patched, opcache.enable_cli=1, opcache.jit=tracing, opcache.protect_memory=1 | 7275 passed, 0 failed | Not covered: foreach (($a =& $b) as &$v) { $v = 3; } writes through to $b in 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.

« previous php.git-pulls (#40077) next »