From 4e04371e7ea86a36898b70f9c8b357d1515a3705 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Thu, 13 Aug 2026 16:45:05 +0200 Subject: [PATCH] PFA: Fix strict_types checking when a const arg is inlined --- .../const_arg_opt_002.phpt | 42 +++++++++++++++++++ .../const_arg_opt_003.phpt | 32 ++++++++++++++ Zend/zend_partial.c | 24 +++++++++++ 3 files changed, 98 insertions(+) create mode 100644 Zend/tests/partial_application/const_arg_opt_002.phpt create mode 100644 Zend/tests/partial_application/const_arg_opt_003.phpt diff --git a/Zend/tests/partial_application/const_arg_opt_002.phpt b/Zend/tests/partial_application/const_arg_opt_002.phpt new file mode 100644 index 000000000000..5f3351276673 --- /dev/null +++ b/Zend/tests/partial_application/const_arg_opt_002.phpt @@ -0,0 +1,42 @@ +--TEST-- +Constant argument optimization - strict_types bug +--CREDITS-- +Ryan @ Calif.io +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 +--FILE-- +getMessage(), "\n"; +} + +if (isset($partial)) { + try { + $partial(null); + } catch (Throwable $e) { + echo 'CALL: ', get_class($e), ': ', $e->getMessage(), "\n"; + } +} + +?> +--EXPECT-- +CREATE: TypeError: cand_86014_typed(): Argument #1 ($bound) must be of type int, string given diff --git a/Zend/tests/partial_application/const_arg_opt_003.phpt b/Zend/tests/partial_application/const_arg_opt_003.phpt new file mode 100644 index 000000000000..0841bff9e6f0 --- /dev/null +++ b/Zend/tests/partial_application/const_arg_opt_003.phpt @@ -0,0 +1,32 @@ +--TEST-- +Constant argument optimization - strict_types +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.file_update_protection=0 +--FILE-- +getClosureUsedVariables(); +if ($usedVars !== []) { + echo "const arg optimization was not applied\n"; + var_dump($usedVars); + exit; +} + +var_dump($f(0)); + +// Actual test. Not catching this as it disables optimizations +f("3", ?)(0); + +?> +--EXPECTF-- +int(3) + +Fatal error: Uncaught TypeError: f(): Argument #1 ($a) must be of type int, string given in %a diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index ec60383a2bc8..e5e93217a681 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -561,6 +561,30 @@ static zend_ast *zp_compile_forwarding_call( args_ast = zend_ast_list_add(args_ast, default_value_ast); } else if (zp_is_const_arg(const_args, offset)) { ZEND_ASSERT(Z_TYPE(argv[offset]) < IS_OBJECT); + + /* This argument never changes, so we can burn it into the op_array + * and check its type ahead of time. */ + + zend_arg_info *arg_info; + if (offset < function->common.num_args) { + arg_info = &function->common.arg_info[offset]; + } else if (function->common.fn_flags & ZEND_ACC_VARIADIC) { + arg_info = &function->common.arg_info[function->common.num_args]; + } else { + arg_info = NULL; + } + if (arg_info && ZEND_TYPE_IS_SET(arg_info->type) + && UNEXPECTED(!zend_check_type_ex(&arg_info->type, &argv[offset], + /* 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])); + zend_string_release(need_msg); + goto error; + } + args_ast = zend_ast_list_add(args_ast, zend_ast_create_zval(&argv[offset])); } else { args_ast = zend_ast_list_add(args_ast, zend_ast_create(ZEND_AST_VAR,