Commit ddbb581f1b2 for php
commit ddbb581f1b29d3bb14d4a437f1e75515b4de50c5
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date: Thu Oct 1 22:44:57 2026 +0200
Fix OSS-Fuzz #568005340: FETCH_DIM_FUNC_ARG partial conversion
The new call is compiled to FETCH_DIM_FUNC_ARG going to
FETCH_OBJ_FUNC_ARG. It tries to convert the FETCH_*_FUNC_ARG to FETCH_*_R, but
it skips FETCH_DIM_FUNC_ARG with the UNUSED op2, and erroneously
converts the next FETCH_OBJ_FUNC_ARG to FETCH_OBJ_R, which is an
unexpected state for the optimizer.
Interestingly, the reference path already had the appropriate check, so
we move the check upwards and do the same thing as the reference path.
Closes GH-24059.
diff --git a/NEWS b/NEWS
index fd17e6a83fe..f075f2ffbae 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,7 @@ PHP NEWS
"Too few arguments" errors and crashes). (RV7PR)
. Fix type inference of ADD_ARRAY_UNPACK with integer keys. (ndossche)
. Fix too wide type inference for ASSIGN_DIM_OP. (ndossche)
+ . Fix OSS-Fuzz #568005340 (FETCH_DIM_FUNC_ARG partial conversion). (ndossche)
- SOAP:
. Fixed use of uninitialized func in do_request() on OOM bailout.
diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c
index 5449535c560..03eb83cc46c 100644
--- a/Zend/Optimizer/optimize_func_calls.c
+++ b/Zend/Optimizer/optimize_func_calls.c
@@ -240,12 +240,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;
@@ -261,6 +262,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 = 0;
+ /* Don't remove the associated CHECK_FUNC_ARG opcode. */
+ call_stack[call - 1].last_check_func_arg_opline = NULL;
break;
}
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 00000000000..4b2bcf01b49
--- /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