Skip to content

Commit d7dc33e

Browse files
committed
Fix missing variance check for abstract set with asymmetric type
Closes GH-15140
1 parent 90371e6 commit d7dc33e

File tree

2 files changed

+65
-13
lines changed

2 files changed

+65
-13
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
GH-15140: Asymmetric set type cannot be satisfied by plain property
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
public string $prop {
8+
set(int|string $value);
9+
}
10+
}
11+
class C implements I {
12+
public string $prop;
13+
}
14+
15+
?>
16+
--EXPECTF--
17+
Fatal error: Set type of C::$prop must be supertype of string|int (as in interface I) in %s on line %d

Zend/zend_inheritance.c

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ static void do_inherit_method(zend_string *key, zend_function *parent, zend_clas
12421242
}
12431243
/* }}} */
12441244

1245-
static inheritance_status property_types_compatible(
1245+
static inheritance_status full_property_types_compatible(
12461246
const zend_property_info *parent_info, const zend_property_info *child_info,
12471247
prop_variance variance) {
12481248
if (ZEND_TYPE_PURE_MASK(parent_info->type) == ZEND_TYPE_PURE_MASK(child_info->type)
@@ -1284,6 +1284,49 @@ static void emit_incompatible_property_error(
12841284
ZSTR_VAL(parent->ce->name));
12851285
}
12861286

1287+
static void emit_set_hook_type_error(
1288+
const zend_property_info *child, const zend_property_info *parent
1289+
) {
1290+
zend_type set_type = parent->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type;
1291+
zend_string *type_str = zend_type_to_string_resolved(set_type, parent->ce);
1292+
zend_error_noreturn(E_COMPILE_ERROR,
1293+
"Set type of %s::$%s must be supertype of %s (as in %s %s)",
1294+
ZSTR_VAL(child->ce->name),
1295+
zend_get_unmangled_property_name(child->name),
1296+
ZSTR_VAL(type_str),
1297+
zend_get_object_type_case(parent->ce, false),
1298+
ZSTR_VAL(parent->ce->name));
1299+
}
1300+
1301+
static inheritance_status property_types_compatible(
1302+
const zend_property_info *parent_info,
1303+
const zend_property_info *child_info,
1304+
prop_variance variance,
1305+
bool throw_on_error,
1306+
bool throw_on_unresolved
1307+
) {
1308+
inheritance_status result = full_property_types_compatible(parent_info, child_info, variance);
1309+
if ((result == INHERITANCE_ERROR && throw_on_error) || (result == INHERITANCE_UNRESOLVED && throw_on_unresolved)) {
1310+
emit_incompatible_property_error(child_info, parent_info, variance);
1311+
}
1312+
if (result != INHERITANCE_SUCCESS) {
1313+
return result;
1314+
}
1315+
if (parent_info->flags & ZEND_ACC_ABSTRACT) {
1316+
ZEND_ASSERT(parent_info->hooks);
1317+
if (parent_info->hooks[ZEND_PROPERTY_HOOK_SET]
1318+
&& (!child_info->hooks || !child_info->hooks[ZEND_PROPERTY_HOOK_SET])) {
1319+
zend_type set_type = parent_info->hooks[ZEND_PROPERTY_HOOK_SET]->common.arg_info[0].type;
1320+
inheritance_status result = zend_perform_covariant_type_check(
1321+
parent_info->ce, set_type, child_info->ce, child_info->type);
1322+
if ((result == INHERITANCE_ERROR && throw_on_error) || (result == INHERITANCE_UNRESOLVED && throw_on_unresolved)) {
1323+
emit_set_hook_type_error(child_info, parent_info);
1324+
}
1325+
}
1326+
}
1327+
return INHERITANCE_SUCCESS;
1328+
}
1329+
12871330
static bool property_has_operation(zend_property_info *prop_info, zend_property_hook_kind kind)
12881331
{
12891332
return (!(prop_info->flags & ZEND_ACC_VIRTUAL)
@@ -1428,10 +1471,7 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke
14281471
prop_variance variance = prop_get_variance(parent_info);
14291472
if (ZEND_TYPE_IS_SET(parent_info->type)) {
14301473
inheritance_status status = property_types_compatible(
1431-
parent_info, child_info, variance);
1432-
if (status == INHERITANCE_ERROR) {
1433-
emit_incompatible_property_error(child_info, parent_info, variance);
1434-
}
1474+
parent_info, child_info, variance, true, false);
14351475
if (status == INHERITANCE_UNRESOLVED) {
14361476
add_property_compatibility_obligation(ce, child_info, parent_info, variance);
14371477
}
@@ -2747,7 +2787,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
27472787
}
27482788

27492789
if ((colliding_prop->flags & flags_mask) == (flags & flags_mask) &&
2750-
property_types_compatible(property_info, colliding_prop, PROP_INVARIANT) == INHERITANCE_SUCCESS
2790+
property_types_compatible(property_info, colliding_prop, PROP_INVARIANT, false, false) == INHERITANCE_SUCCESS
27512791
) {
27522792
/* the flags are identical, thus, the properties may be compatible */
27532793
zval *op1, *op2;
@@ -3098,11 +3138,7 @@ static void check_variance_obligation(variance_obligation *obligation) {
30983138
}
30993139
/* Either the compatibility check was successful or only threw a warning. */
31003140
} else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) {
3101-
inheritance_status status =
3102-
property_types_compatible(obligation->parent_prop, obligation->child_prop, obligation->variance);
3103-
if (status != INHERITANCE_SUCCESS) {
3104-
emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop, obligation->variance);
3105-
}
3141+
property_types_compatible(obligation->parent_prop, obligation->child_prop, obligation->variance, true, true);
31063142
} else if (obligation->type == OBLIGATION_CLASS_CONSTANT_COMPATIBILITY) {
31073143
inheritance_status status =
31083144
class_constant_types_compatible(obligation->parent_const, obligation->child_const);
@@ -3644,8 +3680,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
36443680
if (zv) {
36453681
zend_property_info *child_info = Z_PTR_P(zv);
36463682
if (ZEND_TYPE_IS_SET(child_info->type)) {
3647-
inheritance_status status = property_types_compatible(parent_info, child_info, prop_get_variance(parent_info));
3648-
ZEND_ASSERT(status != INHERITANCE_WARNING);
3683+
inheritance_status status = property_types_compatible(parent_info, child_info, prop_get_variance(parent_info), false, false);
36493684
if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
36503685
return status;
36513686
}

0 commit comments

Comments
 (0)