Skip to content

Commit 5cc59f5

Browse files
committed
Type check arguments and return values
1 parent 136c0b7 commit 5cc59f5

4 files changed

Lines changed: 72 additions & 11 deletions

File tree

Zend/tests/type_declarations/abstract_generics/generic_parameter_declarations/parameter_basic.phpt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ $cs = new CS();
2727
$ci = new CI();
2828

2929
bar($cs);
30-
bar($ci);
30+
try {
31+
bar($ci);
32+
} catch (Throwable $e) {
33+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
34+
}
3135

3236
?>
33-
--EXPECT--
37+
--EXPECTF--
3438
object(CS)#1 (0) {
3539
}
36-
object(CI)#2 (0) {
37-
}
40+
TypeError: bar(): Argument #1 ($v) must be of type I<string>, CI given, called in %s on line %d

Zend/tests/type_declarations/abstract_generics/generic_parameter_declarations/return_basic.phpt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ $cs = new CS();
2727
$ci = new CI();
2828

2929
var_dump(bar($cs));
30-
var_dump(bar($ci));
30+
try {
31+
var_dump(bar($ci));
32+
} catch (Throwable $e) {
33+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
34+
}
3135

3236
?>
3337
--EXPECT--
3438
object(CS)#1 (0) {
3539
}
36-
object(CI)#2 (0) {
37-
}
40+
TypeError: bar(): Return value must be of type I<string>, CI returned

Zend/zend_execute.c

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,23 +1149,77 @@ static bool zend_check_intersection_type_from_list(
11491149
return true;
11501150
}
11511151

1152+
static bool zend_type_is_equal(zend_type given_type, zend_type constraint_type) {
1153+
if (ZEND_TYPE_PURE_MASK(given_type) != ZEND_TYPE_PURE_MASK(constraint_type)) {
1154+
return false;
1155+
}
1156+
if (ZEND_TYPE_HAS_NAME(given_type)) {
1157+
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(constraint_type));
1158+
return zend_string_equals(ZEND_TYPE_NAME(given_type), ZEND_TYPE_NAME(constraint_type));
1159+
} else if (ZEND_TYPE_HAS_LIST(given_type)) {
1160+
ZEND_ASSERT(ZEND_TYPE_HAS_LIST(constraint_type));
1161+
const zend_type_list *given_list_type = ZEND_TYPE_LIST(given_type);
1162+
const zend_type_list *constraint_list_type = ZEND_TYPE_LIST(constraint_type);
1163+
for (uint32_t list_type_index = 0; list_type_index < given_list_type->num_types; list_type_index++) {
1164+
zend_type inner_given_type = given_list_type->types[list_type_index];
1165+
zend_type inner_constraint_type = constraint_list_type->types[list_type_index];
1166+
if (!zend_type_is_equal(inner_given_type, inner_constraint_type)) {
1167+
return false;
1168+
}
1169+
}
1170+
return true;
1171+
}
1172+
return true;
1173+
}
1174+
11521175
static zend_always_inline bool zend_check_type_slow(
11531176
const zend_type *type, zval *arg, const zend_reference *ref,
11541177
bool is_return_type, bool is_internal)
11551178
{
11561179
if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
1157-
zend_class_entry *ce;
1180+
const zend_class_entry *ce;
11581181
if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
1159-
if (ZEND_TYPE_IS_INTERSECTION(*type)) {
1182+
if (ZEND_TYPE_IS_NAME_WITH_GENERIC_TYPES(*type)) {
1183+
zend_ulong inner_type = 0;
1184+
bool is_ce = true;
1185+
const HashTable *bound_generic_types = NULL;
1186+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), const zend_type *list_type) {
1187+
if (is_ce) {
1188+
is_ce = false;
1189+
ce = zend_fetch_ce_from_type(list_type);
1190+
if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) {
1191+
return false;
1192+
}
1193+
bound_generic_types = zend_hash_find_ptr_lc(Z_OBJCE_P(arg)->bound_types, ce->name);
1194+
if (!bound_generic_types
1195+
|| zend_hash_num_elements(bound_generic_types) != ce->num_generic_parameters
1196+
/* -1 because the class name must be excluded */
1197+
|| zend_hash_num_elements(bound_generic_types) != ZEND_TYPE_LIST(*type)->num_types-1
1198+
) {
1199+
return false;
1200+
}
1201+
continue;
1202+
}
1203+
ZEND_ASSERT(ce);
1204+
ZEND_ASSERT(bound_generic_types);
1205+
const zend_type *bound_type = zend_hash_index_find_ptr(bound_generic_types, inner_type++);
1206+
ZEND_ASSERT(bound_type);
1207+
if (!zend_type_is_equal(*list_type, *bound_type)) {
1208+
return false;
1209+
}
1210+
} ZEND_TYPE_LIST_FOREACH_END();
1211+
/* TODO: Should this actually continue outside the loop */
1212+
return true;
1213+
} else if (ZEND_TYPE_IS_INTERSECTION(*type)) {
11601214
return zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg));
11611215
} else {
1162-
const zend_type *list_type;
1163-
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
1216+
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), const zend_type *list_type) {
11641217
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
11651218
if (zend_check_intersection_type_from_list(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg))) {
11661219
return true;
11671220
}
11681221
} else {
1222+
/* TODO: Handle generic, or is this actually prevented ? */
11691223
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
11701224
ce = zend_fetch_ce_from_type(list_type);
11711225
/* Instance of a single type part of a union is sufficient to pass the type check */

Zend/zend_inheritance.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,7 @@ ZEND_ATTRIBUTE_NONNULL static void bind_generic_types_for_inherited_interfaces(z
23962396
}
23972397
const zend_type t1 = *iface_bound_type_ptr;
23982398
const zend_type t2 = *ce_bound_type_ptr;
2399+
// TODO Use zend_types_equals()
23992400
if (
24002401
ZEND_TYPE_FULL_MASK(t1) != ZEND_TYPE_FULL_MASK(t2)
24012402
|| (ZEND_TYPE_HAS_NAME(t1) && !zend_string_equals(ZEND_TYPE_NAME(t1), ZEND_TYPE_NAME(t2)))

0 commit comments

Comments
 (0)