Commit b5526aa2fe0 for php

commit b5526aa2fe0db6fbff55270e38aff245bff4a9ca
Author: ndossche <7771979+ndossche@users.noreply.github.com>
Date:   Thu Sep 24 20:27:27 2026 +0200

    Fix OSS-Fuzz #565486253: coerced arg with '...' on non-variadic function

    The forwarding call is built 2x, and if the first time coerces an
    argument, you have a failed assertion the second time. So the type check
    (which does the coercion) has to happen on a value copy.

    Closes GH-23895.

diff --git a/NEWS b/NEWS
index a23e22ab27f..5e233c1b818 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,8 @@ PHP                                                                        NEWS
     proxy objects instead of forwarding to the real instance). (lisachenko)
   . Fixed bug GH-23882 (array_map() optimization is incorrect for
     strict_types=1). (timwolla)
+  . Fixed OSS-Fuzz #565486253 (coerced arg with '...' on non-variadic
+    function). (ndossche)

 - Opcache:
   . Fix zend_analyze_calls() call_stack buffer overrun. (Mrmaxmeier)
diff --git a/Zend/tests/partial_application/const_arg_opt_004.phpt b/Zend/tests/partial_application/const_arg_opt_004.phpt
new file mode 100644
index 00000000000..f80c14dc6ab
--- /dev/null
+++ b/Zend/tests/partial_application/const_arg_opt_004.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Constant argument optimization - coerced arg with '...' on non-variadic function
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.file_update_protection=0
+--FILE--
+<?php
+function g(string $s, $x = 1) {
+    var_dump($s, func_get_args());
+}
+
+$f = g(15, ...);
+$f();
+$f(2, 3);
+
+?>
+--EXPECT--
+string(2) "15"
+array(2) {
+  [0]=>
+  string(2) "15"
+  [1]=>
+  int(1)
+}
+string(2) "15"
+array(3) {
+  [0]=>
+  string(2) "15"
+  [1]=>
+  int(2)
+  [2]=>
+  int(3)
+}
diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c
index 497e9b80d57..83bd67bccb8 100644
--- a/Zend/zend_partial.c
+++ b/Zend/zend_partial.c
@@ -566,7 +566,10 @@ static zend_ast *zp_compile_forwarding_call(
 			ZEND_ASSERT(!Z_REFCOUNTED(argv[offset]));

 			/* This argument never changes, so we can burn it into the op_array
-			 * and check its type ahead of time. */
+			 * and check its type ahead of time.
+			 * Work with a value copy because a scalar type check may coerce this value. */
+			zval value;
+			ZVAL_COPY_VALUE(&value, &argv[offset]);

 			zend_arg_info *arg_info;
 			if (offset < function->common.num_args) {
@@ -577,18 +580,18 @@ static zend_ast *zp_compile_forwarding_call(
 				arg_info = NULL;
 			}
 			if (arg_info && ZEND_TYPE_IS_SET(arg_info->type)
-					&& UNEXPECTED(!zend_check_type_ex(&arg_info->type, &argv[offset],
+					&& UNEXPECTED(!zend_check_type_ex(&arg_info->type, &value,
 						/* current_frame */ true, /* is_internal */ false))) {
 				zend_string *need_msg = zend_type_to_string_resolved(arg_info->type,
 						function->common.scope);
 				zend_argument_type_error_ex(function, offset + 1,
 						"must be of type %s, %s given",
-						ZSTR_VAL(need_msg), zend_zval_value_name(&argv[offset]));
+						ZSTR_VAL(need_msg), zend_zval_value_name(&value));
 				zend_string_release(need_msg);
 				goto error;
 			}

-			args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&argv[offset]));
+			args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&value));
 		} else {
 			args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR,
 						zend_ast_create_zval_from_str(zend_string_copy(var_names->params[offset]))));