From 4598cf571655028b45029eed2cf0bc7e16c81f9b Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 3 Oct 2026 13:18:21 +0200 Subject: [PATCH] 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. --- Zend/Optimizer/zend_inference.c | 4 +- .../tests/opt/assign_dim_op_result_type.phpt | 82 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/opt/assign_dim_op_result_type.phpt diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index f0d8d873977e..1e33f19f7409 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-- + +--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]]