Skip to content

Commit b1a471a

Browse files
authored
Check partial-application bound arguments in the creating scope (php#22789)
Bound-argument type checks in zp_bind() resolved strict_types from ZEND_ARG_USES_STRICT_TYPES(), which reads the caller of the frame creating the partial rather than the file where the partial is written, so the same f("3", ?) threw at one call depth and silently coerced at another, and the TypeError named the enclosing function instead of the target. Resolve the check against the creating frame's strict_types and attribute the error to the target function.
1 parent 55c4eb2 commit b1a471a

5 files changed

Lines changed: 47 additions & 15 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Partial application checks bound arguments with the creating scope's strict_types
3+
--FILE--
4+
<?php
5+
declare(strict_types=1);
6+
7+
function f(int $a, $b) { return $a; }
8+
9+
try {
10+
f("3", ?);
11+
} catch (\TypeError $e) {
12+
echo $e::class, ": ", $e->getMessage(), "\n";
13+
}
14+
15+
function make() {
16+
try {
17+
f("3", ?);
18+
} catch (\TypeError $e) {
19+
echo $e::class, ": ", $e->getMessage(), "\n";
20+
}
21+
}
22+
make();
23+
?>
24+
--EXPECT--
25+
TypeError: f(): Argument #1 ($a) must be of type int, string given
26+
TypeError: f(): Argument #1 ($a) must be of type int, string given

Zend/zend_execute.c

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ static bool zend_check_intersection_type_from_list(
11531153

11541154
static zend_always_inline bool zend_check_type_slow(
11551155
const zend_type *type, zval *arg, const zend_reference *ref,
1156-
bool is_return_type, bool is_internal)
1156+
bool current_frame, bool is_internal)
11571157
{
11581158
if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
11591159
zend_class_entry *ce;
@@ -1199,23 +1199,23 @@ static zend_always_inline bool zend_check_type_slow(
11991199
/* We cannot have conversions for typed refs. */
12001200
return 0;
12011201
}
1202-
if (is_internal && is_return_type) {
1202+
if (is_internal && current_frame) {
12031203
/* For internal returns, the type has to match exactly, because we're not
12041204
* going to check it for non-debug builds, and there will be no chance to
12051205
* apply coercions. */
12061206
return 0;
12071207
}
12081208

12091209
return zend_verify_scalar_type_hint(type_mask, arg,
1210-
is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(),
1210+
current_frame ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(),
12111211
is_internal);
12121212

12131213
/* Special handling for IS_VOID is not necessary (for return types),
12141214
* because this case is already checked at compile-time. */
12151215
}
12161216

12171217
static zend_always_inline bool zend_check_type(
1218-
const zend_type *type, zval *arg, bool is_return_type, bool is_internal)
1218+
const zend_type *type, zval *arg, bool current_frame, bool is_internal)
12191219
{
12201220
const zend_reference *ref = NULL;
12211221
ZEND_ASSERT(ZEND_TYPE_IS_SET(*type));
@@ -1229,21 +1229,21 @@ static zend_always_inline bool zend_check_type(
12291229
return 1;
12301230
}
12311231

1232-
return zend_check_type_slow(type, arg, ref, is_return_type, is_internal);
1232+
return zend_check_type_slow(type, arg, ref, current_frame, is_internal);
12331233
}
12341234

12351235
/* We can not expose zend_check_type() directly because it's inline and uses static functions */
12361236
ZEND_API bool zend_check_type_ex(
1237-
const zend_type *type, zval *arg, bool is_return_type, bool is_internal)
1237+
const zend_type *type, zval *arg, bool current_frame, bool is_internal)
12381238
{
1239-
return zend_check_type(type, arg, is_return_type, is_internal);
1239+
return zend_check_type(type, arg, current_frame, is_internal);
12401240
}
12411241

12421242
ZEND_API bool zend_check_user_type_slow(
1243-
const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type)
1243+
const zend_type *type, zval *arg, const zend_reference *ref, bool current_frame)
12441244
{
12451245
return zend_check_type_slow(
1246-
type, arg, ref, is_return_type, /* is_internal */ false);
1246+
type, arg, ref, current_frame, /* is_internal */ false);
12471247
}
12481248

12491249
static zend_always_inline bool zend_verify_recv_arg_type(const zend_function *zf, uint32_t arg_num, zval *arg)

Zend/zend_execute.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ ZEND_API zend_never_inline ZEND_COLD void zend_verify_never_error(
108108
const zend_function *zf);
109109
ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref);
110110
ZEND_API bool zend_check_user_type_slow(
111-
const zend_type *type, zval *arg, const zend_reference *ref, bool is_return_type);
111+
const zend_type *type, zval *arg, const zend_reference *ref, bool current_frame);
112112
ZEND_API bool zend_check_type_ex(
113-
const zend_type *type, zval *arg, bool is_return_type, bool is_internal);
113+
const zend_type *type, zval *arg, bool current_frame, bool is_internal);
114114

115115
#if ZEND_DEBUG
116116
ZEND_API bool zend_internal_call_should_throw(const zend_function *fbc, zend_execute_data *call);

Zend/zend_partial.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,8 +1105,14 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *
11051105
arg_info = NULL;
11061106
}
11071107
if (arg_info && ZEND_TYPE_IS_SET(arg_info->type)
1108-
&& UNEXPECTED(!zend_check_type_ex(&arg_info->type, var, 0, 0))) {
1109-
zend_verify_arg_error(function, arg_info, offset+1, var);
1108+
&& UNEXPECTED(!zend_check_type_ex(&arg_info->type, var,
1109+
/* current_frame */ true, /* is_internal */ false))) {
1110+
zend_string *need_msg = zend_type_to_string_resolved(arg_info->type,
1111+
function->common.scope);
1112+
zend_argument_type_error_ex(function, offset + 1,
1113+
"must be of type %s, %s given",
1114+
ZSTR_VAL(need_msg), zend_zval_value_name(var));
1115+
zend_string_release(need_msg);
11101116
zval_ptr_dtor(result);
11111117
ZVAL_NULL(result);
11121118
zp_free_unbound_args(offset, argc, argv);

ext/opcache/jit/zend_jit_helpers.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,7 +1974,7 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg
19741974
zend_execute_data *execute_data = EG(current_execute_data);
19751975
const zend_op *opline = EX(opline);
19761976
bool ret = zend_check_user_type_slow(
1977-
&arg_info->type, arg, /* ref */ NULL, /* is_return_type */ false);
1977+
&arg_info->type, arg, /* ref */ NULL, /* current_frame */ false);
19781978
if (UNEXPECTED(!ret)) {
19791979
zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg);
19801980
return false;
@@ -1991,7 +1991,7 @@ static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_a
19911991
}
19921992
}
19931993
if (UNEXPECTED(!zend_check_user_type_slow(
1994-
&arg_info->type, arg, /* ref */ NULL, /* is_return_type */ true))) {
1994+
&arg_info->type, arg, /* ref */ NULL, /* current_frame */ true))) {
19951995
zend_verify_return_error((zend_function*)op_array, arg);
19961996
}
19971997
}

0 commit comments

Comments
 (0)