[php-src] master: Merge branch 'PHP-8.6'
| From: | ndossche | Date: | Thu, 08 Oct 2026 16:49:54 +0000 |
| Subject: | [php-src] master: Merge branch 'PHP-8.6' | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142094@lists.php.net to get a copy of this message | ||
Author: ndossche (ndossche)
Date: 2026-10-08T18:49:39+02:00
Commit: https://github.com/php/php-src/commit/eb4a7ae65f9671a4f3fef3dca9115259deb87b94
Raw diff: https://github.com/php/php-src/commit/eb4a7ae65f9671a4f3fef3dca9115259deb87b94.diff
Merge branch 'PHP-8.6'
* PHP-8.6:
Fix OSS-Fuzz #568005340: FETCH_DIM_FUNC_ARG partial conversion
Fix too wide type inference for ASSIGN_DIM_OP
Fix type inference of ADD_ARRAY_UNPACK with integer keys
Changed paths:
A ext/opcache/tests/jit/add_array_unpack_packed.phpt
A ext/opcache/tests/opt/assign_dim_op_result_type.phpt
A ext/opcache/tests/opt/fetch_dim_func_arg_unused_chain.phpt
M Zend/Optimizer/optimize_func_calls.c
M Zend/Optimizer/zend_inference.c
Diff:
diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c
index 0e6c5797c680..4275bdb4071c 100644
--- a/Zend/Optimizer/optimize_func_calls.c
+++ b/Zend/Optimizer/optimize_func_calls.c
@@ -247,12 +247,13 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx
*ctx)
case ZEND_FETCH_OBJ_FUNC_ARG:
case ZEND_FETCH_DIM_FUNC_ARG:
if (call_stack[call - 1].func_arg_num != (uint32_t)-1
+ && call_stack[call - 1].last_check_func_arg_opline != NULL
&& has_known_send_mode(&call_stack[call - 1], call_stack[call - 1].func_arg_num))
{
if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
/* There's no TMP specialization for FETCH_OBJ_W/FETCH_DIM_W. Avoid
* converting it and error at runtime in the FUNC_ARG variant. */
if ((opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG || opline->opcode ==
ZEND_FETCH_DIM_FUNC_ARG)
- && (opline->op1_type == IS_TMP_VAR || call_stack[call -
1].last_check_func_arg_opline == NULL)) {
+ && opline->op1_type == IS_TMP_VAR) {
/* Don't remove the associated CHECK_FUNC_ARG opcode. */
call_stack[call - 1].last_check_func_arg_opline = NULL;
break;
@@ -268,6 +269,8 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
/* FETCH_DIM_FUNC_ARG supports UNUSED op2, while FETCH_DIM_R does not.
* Performing the replacement would create an invalid opcode. */
call_stack[call - 1].try_inline = false;
+ /* Don't remove the associated CHECK_FUNC_ARG opcode. */
+ call_stack[call - 1].last_check_func_arg_opline = NULL;
break;
}
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index 9b46134a7bae..71c9247edf67 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -2810,11 +2810,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;
@@ -3455,7 +3455,11 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_ADD_ARRAY_UNPACK:
tmp = ssa_var_info[ssa_op->result_use].type;
ZEND_ASSERT(tmp & MAY_BE_ARRAY);
- tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ if (t1 & MAY_BE_ARRAY_KEY_LONG) {
+ /* Integer keys are appended without copying the hash/packed layout of the source array. */
+ tmp |= MAY_BE_HASH_ONLY(tmp) ? MAY_BE_ARRAY_NUMERIC_HASH : MAY_BE_ARRAY_KEY_LONG;
+ }
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_STRING|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
if (t1 & MAY_BE_OBJECT) {
tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
}
diff --git a/ext/opcache/tests/jit/add_array_unpack_packed.phpt
b/ext/opcache/tests/jit/add_array_unpack_packed.phpt
new file mode 100644
index 000000000000..f0b6b9a33cec
--- /dev/null
+++ b/ext/opcache/tests/jit/add_array_unpack_packed.phpt
@@ -0,0 +1,24 @@
+--TEST--
+JIT: unpacking a hash array with integer keys produces a packed array
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit_buffer_size=64M
+opcache.jit=1205
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+function f($c) {
+ $b = $c ? [-5 => 1, -6 => 2, -7 => 3] : [-7 => 2];
+ $a = [...$b];
+ foreach ($a as $k => $v) {
+ echo "$k => $v\n";
+ }
+}
+f(true);
+?>
+--EXPECT--
+0 => 1
+1 => 2
+2 => 3
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]]
diff --git a/ext/opcache/tests/opt/fetch_dim_func_arg_unused_chain.phpt
b/ext/opcache/tests/opt/fetch_dim_func_arg_unused_chain.phpt
new file mode 100644
index 000000000000..4b2bcf01b49e
--- /dev/null
+++ b/ext/opcache/tests/opt/fetch_dim_func_arg_unused_chain.phpt
@@ -0,0 +1,29 @@
+--TEST--
+FETCH_DIM_FUNC_ARG with UNUSED op2 followed by other FUNC_ARG fetches must not be partially
converted
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+function test() {
+ try {
+ new Node($a[]->b);
+ } catch (Error $e) {
+ echo $e->getMessage(), "\n";
+ }
+ try {
+ byVal($a[][0]);
+ } catch (Error $e) {
+ echo $e->getMessage(), "\n";
+ }
+}
+class Node { function __construct() {} }
+function byVal($x) {}
+test();
+?>
+--EXPECT--
+Cannot use [] for reading
+Cannot use [] for reading