[php-src] PHP-8.4: Fix too wide type inference for ASSIGN_DIM_OP
| From: | ndossche | Date: | Thu, 08 Oct 2026 16:49:47 +0000 |
| Subject: | [php-src] PHP-8.4: Fix too wide type inference for ASSIGN_DIM_OP | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142090@lists.php.net to get a copy of this message | ||
Author: ndossche (ndossche)
Date: 2026-10-08T18:47:50+02:00
Commit: https://github.com/php/php-src/commit/fb1bdd6ff6968c42cc14bcc24035aefde7367b99
Raw diff: https://github.com/php/php-src/commit/fb1bdd6ff6968c42cc14bcc24035aefde7367b99.diff
Fix too wide type inference for ASSIGN_DIM_OP
The checks for a null result were done on the incorrect variables: t1
and t2 were already overwritten with the key and value type.
This results in a null being inferred in the result in cases where this
isn't possible.
Closes GH-24092.
Changed paths:
A ext/opcache/tests/opt/assign_dim_op_result_type.phpt
M NEWS
M Zend/Optimizer/zend_inference.c
Diff:
diff --git a/NEWS b/NEWS
index 12b88a7e8e6f..fd17e6a83fe6 100644
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,7 @@ PHP NEWS
root trace at the opcache.jit_max_root_traces limit, causing spurious
"Too few arguments" errors and crashes). (RV7PR)
. Fix type inference of ADD_ARRAY_UNPACK with integer keys. (ndossche)
+ . Fix too wide type inference for ASSIGN_DIM_OP. (ndossche)
- SOAP:
. Fixed use of uninitialized func in do_request() on OOM bailout.
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index 66369a154820..0c24691ebba6 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -2809,11 +2809,11 @@ static zend_always_inline zend_result _zend_update_type_info(
* null will be returned. */
tmp |= MAY_BE_NULL;
}
- if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ if (OP2_INFO() & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
/* Arrays and objects cannot be used as keys. */
tmp |= MAY_BE_NULL;
}
- if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) {
+ if (OP1_INFO() & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING |
MAY_BE_ARRAY))) {
/* null and false are implicitly converted to array, anything else
* results in a null return value. */
tmp |= MAY_BE_NULL;
diff --git a/ext/opcache/tests/opt/assign_dim_op_result_type.phpt
b/ext/opcache/tests/opt/assign_dim_op_result_type.phpt
new file mode 100644
index 000000000000..b449cebdbd9b
--- /dev/null
+++ b/ext/opcache/tests/opt/assign_dim_op_result_type.phpt
@@ -0,0 +1,82 @@
+--TEST--
+Type inference of ASSIGN_DIM_OP result uses the container and key types
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.opt_debug_level=0x400000
+opcache.preload=
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+function f(int $p, int $q, int $k) {
+ $a = [$p, $q];
+ $r = ($a[$k] += 1);
+ return $r;
+}
+
+function g(int $p, int $q, int $k) {
+ $a = [[$p], [$q]];
+ $r = ($a[$k] += [1]);
+ return $r;
+}
+?>
+--EXPECTF--
+$_main:
+ ; (lines=1, args=0, vars=0, tmps=%d, ssa_vars=0, no_loops)
+ ; (after dfa pass)
+ ; %s
+ ; return [long] RANGE[1..1]
+BB0:
+ ; start exit lines=[0-0]
+ ; level=0
+0000 RETURN int(1)
+
+f:
+ ; (lines=9, args=3, vars=5, tmps=%d, ssa_vars=14, no_loops)
+ ; (after dfa pass)
+ ; %s
+ ; return [long, double]
+ ; #0.CV0($p) NOVAL [undef]
+ ; #1.CV1($q) NOVAL [undef]
+ ; #2.CV2($k) NOVAL [undef]
+ ; #3.CV3($a) NOVAL [undef]
+ ; #4.CV4($r) NOVAL [undef]
+BB0:
+ ; start exit lines=[0-8]
+ ; level=0
+0000 #5.CV0($p) [long] RANGE[MIN..MAX] = RECV 1
+0001 #6.CV1($q) [long] RANGE[MIN..MAX] = RECV 2
+0002 #7.CV2($k) [long] RANGE[MIN..MAX] = RECV 3
+0003 #8.T5 NOESC [[packed, hash] array [long] of [long]] = INIT_ARRAY 2 (packed) #5.CV0($p) [long]
RANGE[MIN..MAX] NEXT
+0004 ADD_ARRAY_ELEMENT #6.CV1($q) [long] RANGE[MIN..MAX] NEXT #8.T5 NOESC [[packed, hash] array
[long] of [long]] -> #9.T5 NOESC [[packed, hash] array [long] of [long]]
+0005 #10.CV3($a) NOESC [[packed, hash] array [long] of [long]] = QM_ASSIGN #9.T5 NOESC [[packed,
hash] array [long] of [long]]
+0006 #13.CV4($r) [long, double] = ASSIGN_DIM_OP (ADD) #10.CV3($a) NOESC [[packed, hash] array
[long] of [long]] -> #11.CV3($a) NOVAL NOESC [[packed, hash] array [long] of [long, double]]
#7.CV2($k) [long] RANGE[MIN..MAX]
+0007 OP_DATA int(1)
+0008 RETURN #13.CV4($r) [long, double]
+
+g:
+ ; (lines=11, args=3, vars=5, tmps=%d, ssa_vars=16, no_loops)
+ ; (after dfa pass)
+ ; %s
+ ; return [long, double, array of [any, ref]]
+ ; #0.CV0($p) NOVAL [undef]
+ ; #1.CV1($q) NOVAL [undef]
+ ; #2.CV2($k) NOVAL [undef]
+ ; #3.CV3($a) NOVAL [undef]
+ ; #4.CV4($r) NOVAL [undef]
+BB0:
+ ; start exit lines=[0-10]
+ ; level=0
+0000 #5.CV0($p) [long] RANGE[MIN..MAX] = RECV 1
+0001 #6.CV1($q) [long] RANGE[MIN..MAX] = RECV 2
+0002 #7.CV2($k) [long] RANGE[MIN..MAX] = RECV 3
+0003 #8.T5 NOESC [[packed, hash] array [long] of [long]] = INIT_ARRAY 1 (packed) #5.CV0($p) [long]
RANGE[MIN..MAX] NEXT
+0004 #9.T6 NOESC [[packed, hash] array [long] of [array]] = INIT_ARRAY 2 (packed) #8.T5 NOESC
[[packed, hash] array [long] of [long]] NEXT
+0005 #10.T7 NOESC [[packed, hash] array [long] of [long]] = INIT_ARRAY 1 (packed) #6.CV1($q) [long]
RANGE[MIN..MAX] NEXT
+0006 ADD_ARRAY_ELEMENT #10.T7 NOESC [[packed, hash] array [long] of [long]] NEXT #9.T6 NOESC
[[packed, hash] array [long] of [array]] -> #11.T6 NOESC [[packed, hash] array [long] of [array]]
+0007 #12.CV3($a) NOESC [[packed, hash] array [long] of [array]] = QM_ASSIGN #11.T6 NOESC [[packed,
hash] array [long] of [array]]
+0008 #15.CV4($r) [long, double, array of [any, ref]] = ASSIGN_DIM_OP (ADD) #12.CV3($a) NOESC
[[packed, hash] array [long] of [array]] -> #13.CV3($a) NOVAL NOESC [[packed, hash] array [long]
of [long, double, array]] #7.CV2($k) [long] RANGE[MIN..MAX]
+0009 OP_DATA array(...)
+0010 RETURN #15.CV4($r) [long, double, array of [any, ref]]