Commit d5a94412e36 for php

commit d5a94412e3635fd6537a922603b096b0d0c121eb
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date:   Sat Oct 10 00:14:51 2026 +0200

    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.

diff --git a/NEWS b/NEWS
index d4ed9198fe3..942a8e9b065 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 b0bda46f8c6..a5605f9a43c 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 44def91de74..f17f7bc3b31 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
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 00000000000..a9dcd5b057a
--- /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