From c4396f9f8126741b51dad82836aa162c4666835c Mon Sep 17 00:00:00 2001 From: Mrmaxmeier Date: Mon, 24 Aug 2026 16:26:10 +0200 Subject: [PATCH] Fix IS_IDENTICAL_EMPTY_ARRAY specialization for an IS_CONST op1 The ZEND_IS_IDENTICAL_EMPTY_ARRAY / ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY specializations are declared as TMPVARCV, CONST, but their selection condition only looked at op2. The other specializations of these opcodes bail out for a CONST/CONST operand pair, this one did not, so an IS_CONST op1 selected a handler that cannot read it and _get_zval_ptr_tmpvarcv() asserted: if (A::class->p === []) { } Type inference proves the property fetch to be null and substitutes the constant into op1, but the comparison itself is not folded, which leaves ZEND_IS_IDENTICAL with two IS_CONST operands. Assisted-By: Claude Opus 5 --- NEWS | 2 ++ Zend/zend_vm_def.h | 4 ++-- Zend/zend_vm_execute.h | 4 ++-- ...on_jit_is_identical_empty_array_const.phpt | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 ext/opcache/tests/fuzzer_function_jit_is_identical_empty_array_const.phpt diff --git a/NEWS b/NEWS index ec85f966ed38..4ceea762f9a1 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ PHP NEWS next() call on the inner generator). (iliaal) . Fixed bug GH-23301 (Nested "yield from" yields a value twice when the middle generator delegates again). (Lazizbek Ergashev) + . Fixed the IS_IDENTICAL_EMPTY_ARRAY specialization being selected for an + IS_CONST op1. (Mrmaxmeier) - DOM: . Fixed NamedNodeMap::getNamedItemNS() with an empty URI not matching diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 90a666ab71e7..a7eaf686dd4a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -10105,7 +10105,7 @@ 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->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE)) +ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op1_type != IS_CONST && op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE)) { USE_OPLINE zval *op1; @@ -10118,7 +10118,7 @@ ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_IDENTICAL, op->op2_type == IS_CONST && (Z_TYPE ZEND_VM_SMART_BRANCH(result, 0); } -ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_NOT_IDENTICAL, op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE)) +ZEND_VM_TYPE_SPEC_HANDLER(ZEND_IS_NOT_IDENTICAL, op->op1_type != IS_CONST && op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0), ZEND_IS_NOT_IDENTICAL_EMPTY_ARRAY, TMPVARCV, CONST, SPEC(SMART_BRANCH,COMMUTATIVE)) { USE_OPLINE zval *op1; diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9a98804f25c2..6195cbf94fc8 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -129501,7 +129501,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; } spec = 2886 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; - } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { + } else if (op->op1_type != IS_CONST && op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { spec = 3111 | 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))) { spec = 3117 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; @@ -129521,7 +129521,7 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; } spec = 3036 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_SMART_BRANCH | SPEC_RULE_COMMUTATIVE; - } else if (op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { + } else if (op->op1_type != IS_CONST && op->op2_type == IS_CONST && (Z_TYPE_P(RT_CONSTANT(op, op->op2)) == IS_ARRAY && zend_hash_num_elements(Z_ARR_P(RT_CONSTANT(op, op->op2))) == 0)) { spec = 3114 | 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))) { spec = 3122 | SPEC_RULE_OP2 | SPEC_RULE_COMMUTATIVE; diff --git a/ext/opcache/tests/fuzzer_function_jit_is_identical_empty_array_const.phpt b/ext/opcache/tests/fuzzer_function_jit_is_identical_empty_array_const.phpt new file mode 100644 index 000000000000..50b297da652c --- /dev/null +++ b/ext/opcache/tests/fuzzer_function_jit_is_identical_empty_array_const.phpt @@ -0,0 +1,24 @@ +--TEST-- +IS_IDENTICAL_EMPTY_ARRAY specialization must not be used for an IS_CONST op1 +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=disable +--FILE-- +p === []) { + echo "identical\n"; +} +if (A::class->p !== []) { + echo "not identical\n"; +} +echo "OK\n"; +?> +--EXPECTF-- +Warning: Attempt to read property "p" on string in %s on line %d + +Warning: Attempt to read property "p" on string in %s on line %d +not identical +OK