[php-src] PHP-8.4: Fix throwing behaviour with recursive arrays in VM

From: 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

« previous php.cvs (#142147) next »