[php-src] PHP-8.4: Fix throwing behaviour with recursive arrays in VM
| From: | ndossche | Date: | Sat, 10 Oct 2026 09:20:30 +0000 |
| Subject: | [php-src] PHP-8.4: Fix throwing behaviour with recursive arrays in VM | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142147@lists.php.net to get a copy of this message | ||
Author: ndossche (ndossche)
Date: 2026-10-10T11:15:34+02:00
Commit: https://github.com/php/php-src/commit/d5a94412e3635fd6537a922603b096b0d0c121eb
Raw diff: https://github.com/php/php-src/commit/d5a94412e3635fd6537a922603b096b0d0c121eb.diff
Fix throwing behaviour with recursive arrays in VM
Behaviour change in 82479e89d0 overlooked this VM type spec
optimization. The JIT had a follow-up already in a0a8624346.
Closes GH-24232.
Changed paths:
A ext/opcache/tests/is_identical_nothrow_recursive_array.phpt
M NEWS
M Zend/zend_vm_def.h
M Zend/zend_vm_execute.h
Diff:
diff --git a/NEWS b/NEWS
index d4ed9198fe39..942a8e9b0651 100644
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,7 @@ PHP NEWS
into the wrong one. (Marc Bennewitz)
. Fixed bug GH-24218 (Invalid opcode for count() of a literal array
without SCCP). (lazerg)
+ . Fix throwing behaviour with recursive arrays in VM. (ndossche)
- DOM:
. Fixed bug GH-23352 (UAF reading an attribute value node retained across
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index b0bda46f8c62..a5605f9a43c0 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -10022,11 +10022,11 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_IS_NOT_EQUAL|ZEND_IS_NOT_IDENTICAL,
(op1_info
ZEND_VM_SMART_BRANCH(result, 0);
}
-ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op1_type == IS_CV && (op->op2_type
& (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info
& (MAY_BE_UNDEF|MAY_BE_REF)), ZEND_IS_IDENTICAL_NOTHROW, CV, CONST|CV, SPEC(COMMUTATIVE))
+ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op1_type == IS_CV && (op->op2_type
& (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info
& (MAY_BE_UNDEF|MAY_BE_REF)) && !((op1_info & op2_info) &
MAY_BE_ARRAY_OF_ARRAY), ZEND_IS_IDENTICAL_NOTHROW, CV, CONST|CV, SPEC(COMMUTATIVE))
{
/* This is declared below the specializations for MAY_BE_LONG/MAY_BE_DOUBLE so those will be used
instead if possible. */
/* This optimizes $x === SOME_CONST_EXPR and $x === $y for non-refs and non-undef, which
can't throw. */
- /* (Infinite recursion when comparing arrays is an uncatchable fatal error) */
+ /* Comparing two arrays that may contain arrays is excluded, as recursion throws an Error. */
USE_OPLINE
zval *op1, *op2;
bool result;
@@ -10038,7 +10038,7 @@ ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op1_type == IS_CV
&& (op->op2_t
ZEND_VM_SMART_BRANCH(result, 0);
}
-ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_NOT_IDENTICAL, op->op1_type == IS_CV &&
(op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF))
&& !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF)), ZEND_IS_NOT_IDENTICAL_NOTHROW, CV, CONST|CV,
SPEC(COMMUTATIVE))
+ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_NOT_IDENTICAL, op->op1_type == IS_CV &&
(op->op2_type & (IS_CONST|IS_CV)) && !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF))
&& !(op2_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !((op1_info & op2_info) &
MAY_BE_ARRAY_OF_ARRAY), ZEND_IS_NOT_IDENTICAL_NOTHROW, CV, CONST|CV, SPEC(COMMUTATIVE))
{
USE_OPLINE
zval *op1, *op2;
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 44def91de748..f17f7bc3b313 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -45931,7 +45931,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL
ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_C
{
/* This is declared below the specializations for MAY_BE_LONG/MAY_BE_DOUBLE so those will be used
instead if possible. */
/* This optimizes $x === SOME_CONST_EXPR and $x === $y for non-refs and non-undef, which
can't throw. */
- /* (Infinite recursion when comparing arrays is an uncatchable fatal error) */
+ /* Comparing two arrays that may contain arrays is excluded, as recursion throws an Error. */
USE_OPLINE
zval *op1, *op2;
bool result;
@@ -55094,7 +55094,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL
ZEND_IS_IDENTICAL_NOTHROW_SPEC_CV_C
{
/* This is declared below the specializations for MAY_BE_LONG/MAY_BE_DOUBLE so those will be used
instead if possible. */
/* This optimizes $x === SOME_CONST_EXPR and $x === $y for non-refs and non-undef, which
can't throw. */
- /* (Infinite recursion when comparing arrays is an uncatchable fatal error) */
+ /* Comparing two arrays that may contain arrays is excluded, as recursion throws an Error. */
USE_OPLINE
zval *op1, *op2;
bool result;
@@ -68363,7 +68363,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op,
uint32_t
break;
}
spec = 2885 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE;
- } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV))
&& !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info &
(MAY_BE_UNDEF|MAY_BE_REF))) {
+ } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV))
&& !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info &
(MAY_BE_UNDEF|MAY_BE_REF)) && !((op1_info & op2_info) & MAY_BE_ARRAY_OF_ARRAY)) {
spec = 3110 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE;
}
break;
@@ -68381,7 +68381,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op,
uint32_t
break;
}
spec = 3035 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE;
- } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV))
&& !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info &
(MAY_BE_UNDEF|MAY_BE_REF))) {
+ } else if (op->op1_type == IS_CV && (op->op2_type & (IS_CONST|IS_CV))
&& !(op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) && !(op2_info &
(MAY_BE_UNDEF|MAY_BE_REF)) && !((op1_info & op2_info) & MAY_BE_ARRAY_OF_ARRAY)) {
spec = 3115 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE;
}
break;
diff --git a/ext/opcache/tests/is_identical_nothrow_recursive_array.phpt
b/ext/opcache/tests/is_identical_nothrow_recursive_array.phpt
new file mode 100644
index 000000000000..a9dcd5b057a9
--- /dev/null
+++ b/ext/opcache/tests/is_identical_nothrow_recursive_array.phpt
@@ -0,0 +1,39 @@
+--TEST--
+IS_IDENTICAL/IS_NOT_IDENTICAL must not use the NOTHROW handler for arrays that may contain arrays
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+--EXTENSIONS--
+opcache
+--FILE--
+<?php
+function identical(array $a, array $b) {
+ $r = $a === $b;
+ echo "not reached\n";
+ return $r;
+}
+
+function not_identical(array $a, array $b) {
+ $r = $a !== $b;
+ echo "not reached\n";
+ return $r;
+}
+
+$x = [&$x];
+
+try {
+ identical($x, [[]]);
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), " on line ", $e->getLine(),
"\n";
+}
+
+try {
+ not_identical($x, [[]]);
+} catch (Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), " on line ", $e->getLine(),
"\n";
+}
+?>
+--EXPECT--
+Error: Nesting level too deep - recursive dependency? on line 3
+Error: Nesting level too deep - recursive dependency? on line 9