Skip to content

Commit cf85eb2

Browse files
committed
Integrate property types with variance system
Property types are invariant, but may still have to load classes in order to check for class aliases. This class loading should follow the same rules as all other variance checks, rather than just loading unconditionally. This change integrates property type invariance checks into the variance system as a new obligation type, and prevent early binding if the type check cannot be performed.
1 parent 8b160f5 commit cf85eb2

File tree

1 file changed

+124
-68
lines changed

1 file changed

+124
-68
lines changed

Zend/zend_inheritance.c

Lines changed: 124 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ static void add_dependency_obligation(zend_class_entry *ce, zend_class_entry *de
3131
static void add_compatibility_obligation(
3232
zend_class_entry *ce, const zend_function *child_fn, const zend_function *parent_fn,
3333
zend_bool always_error);
34+
static void add_property_compatibility_obligation(
35+
zend_class_entry *ce, const zend_property_info *child_prop,
36+
const zend_property_info *parent_prop);
3437

3538
static void overridden_ptr_dtor(zval *zv) /* {{{ */
3639
{
@@ -192,17 +195,16 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */
192195
}
193196
/* }}} */
194197

195-
static zend_string *resolve_class_name(const zend_function *fe, zend_string *name) {
196-
zend_class_entry *ce = fe->common.scope;
197-
ZEND_ASSERT(ce);
198-
if (zend_string_equals_literal_ci(name, "parent") && ce->parent) {
199-
if (ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) {
200-
return ce->parent->name;
198+
static zend_string *resolve_class_name(zend_class_entry *scope, zend_string *name) {
199+
ZEND_ASSERT(scope);
200+
if (zend_string_equals_literal_ci(name, "parent") && scope->parent) {
201+
if (scope->ce_flags & ZEND_ACC_RESOLVED_PARENT) {
202+
return scope->parent->name;
201203
} else {
202-
return ce->parent_name;
204+
return scope->parent_name;
203205
}
204206
} else if (zend_string_equals_literal_ci(name, "self")) {
205-
return ce->name;
207+
return scope->name;
206208
} else {
207209
return name;
208210
}
@@ -218,7 +220,7 @@ static zend_bool class_visible(zend_class_entry *ce) {
218220
}
219221
}
220222

221-
static zend_class_entry *lookup_class(const zend_function *fe, zend_string *name) {
223+
static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name) {
222224
zend_class_entry *ce;
223225
if (!CG(in_compilation)) {
224226
uint32_t flags = ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD;
@@ -240,8 +242,8 @@ static zend_class_entry *lookup_class(const zend_function *fe, zend_string *name
240242
}
241243

242244
/* The current class may not be registered yet, so check for it explicitly. */
243-
if (zend_string_equals_ci(fe->common.scope->name, name)) {
244-
return fe->common.scope;
245+
if (zend_string_equals_ci(scope->name, name)) {
246+
return scope;
245247
}
246248
}
247249

@@ -320,16 +322,16 @@ static inheritance_status zend_perform_covariant_type_check(
320322
return INHERITANCE_ERROR;
321323
}
322324

323-
fe_class_name = resolve_class_name(fe, ZEND_TYPE_NAME(fe_type));
324-
proto_class_name = resolve_class_name(proto, ZEND_TYPE_NAME(proto_type));
325+
fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type));
326+
proto_class_name = resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type));
325327
if (zend_string_equals_ci(fe_class_name, proto_class_name)) {
326328
return INHERITANCE_SUCCESS;
327329
}
328330

329331
/* Make sure to always load both classes, to avoid only registering one of them as
330332
* a delayed autoload. */
331-
fe_ce = lookup_class(fe, fe_class_name);
332-
proto_ce = lookup_class(proto, proto_class_name);
333+
fe_ce = lookup_class(fe->common.scope, fe_class_name);
334+
proto_ce = lookup_class(proto->common.scope, proto_class_name);
333335
if (!fe_ce) {
334336
*unresolved_class = fe_class_name;
335337
return INHERITANCE_UNRESOLVED;
@@ -342,8 +344,9 @@ static inheritance_status zend_perform_covariant_type_check(
342344
return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
343345
} else if (ZEND_TYPE_CODE(proto_type) == IS_ITERABLE) {
344346
if (ZEND_TYPE_IS_CLASS(fe_type)) {
345-
zend_string *fe_class_name = resolve_class_name(fe, ZEND_TYPE_NAME(fe_type));
346-
zend_class_entry *fe_ce = lookup_class(fe, fe_class_name);
347+
zend_string *fe_class_name =
348+
resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type));
349+
zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name);
347350
if (!fe_ce) {
348351
*unresolved_class = fe_class_name;
349352
return INHERITANCE_UNRESOLVED;
@@ -359,8 +362,9 @@ static inheritance_status zend_perform_covariant_type_check(
359362
/* Currently, any class name would be allowed here. We still perform a class lookup
360363
* for forward-compatibility reasons, as we may have named types in the future that
361364
* are not classes (such as enums or typedefs). */
362-
zend_string *fe_class_name = resolve_class_name(fe, ZEND_TYPE_NAME(fe_type));
363-
zend_class_entry *fe_ce = lookup_class(fe, fe_class_name);
365+
zend_string *fe_class_name =
366+
resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type));
367+
zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name);
364368
if (!fe_ce) {
365369
*unresolved_class = fe_class_name;
366370
return INHERITANCE_UNRESOLVED;
@@ -882,53 +886,53 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function
882886
}
883887
/* }}} */
884888

885-
zend_string* zend_resolve_property_type(zend_string *type, zend_class_entry *scope) /* {{{ */
886-
{
887-
if (zend_string_equals_literal_ci(type, "parent")) {
888-
if (scope && scope->parent) {
889-
return scope->parent->name;
890-
}
891-
}
892-
893-
if (zend_string_equals_literal_ci(type, "self")) {
894-
if (scope) {
895-
return scope->name;
896-
}
897-
}
898-
899-
return type;
900-
} /* }}} */
901-
902-
zend_bool property_types_compatible(zend_property_info *parent_info, zend_property_info *child_info) {
889+
inheritance_status property_types_compatible(
890+
const zend_property_info *parent_info, const zend_property_info *child_info) {
903891
zend_string *parent_name, *child_name;
904892
zend_class_entry *parent_type_ce, *child_type_ce;
905893
if (parent_info->type == child_info->type) {
906-
return 1;
894+
return INHERITANCE_SUCCESS;
907895
}
908896

909897
if (!ZEND_TYPE_IS_CLASS(parent_info->type) || !ZEND_TYPE_IS_CLASS(child_info->type) ||
910898
ZEND_TYPE_ALLOW_NULL(parent_info->type) != ZEND_TYPE_ALLOW_NULL(child_info->type)) {
911-
return 0;
899+
return INHERITANCE_ERROR;
912900
}
913901

914902
parent_name = ZEND_TYPE_IS_CE(parent_info->type)
915903
? ZEND_TYPE_CE(parent_info->type)->name
916-
: zend_resolve_property_type(ZEND_TYPE_NAME(parent_info->type), parent_info->ce);
904+
: resolve_class_name(parent_info->ce, ZEND_TYPE_NAME(parent_info->type));
917905
child_name = ZEND_TYPE_IS_CE(child_info->type)
918906
? ZEND_TYPE_CE(child_info->type)->name
919-
: zend_resolve_property_type(ZEND_TYPE_NAME(child_info->type), child_info->ce);
907+
: resolve_class_name(child_info->ce, ZEND_TYPE_NAME(child_info->type));
920908
if (zend_string_equals_ci(parent_name, child_name)) {
921-
return 1;
909+
return INHERITANCE_SUCCESS;
922910
}
923911

924912
/* Check for class aliases */
925913
parent_type_ce = ZEND_TYPE_IS_CE(parent_info->type)
926914
? ZEND_TYPE_CE(parent_info->type)
927-
: zend_lookup_class(parent_name);
915+
: lookup_class(parent_info->ce, parent_name);
928916
child_type_ce = ZEND_TYPE_IS_CE(child_info->type)
929917
? ZEND_TYPE_CE(child_info->type)
930-
: zend_lookup_class(child_name);
931-
return parent_type_ce && child_type_ce && parent_type_ce == child_type_ce;
918+
: lookup_class(child_info->ce, child_name);
919+
if (!parent_type_ce || !child_type_ce) {
920+
return INHERITANCE_UNRESOLVED;
921+
}
922+
return parent_type_ce == child_type_ce ? INHERITANCE_SUCCESS : INHERITANCE_ERROR;
923+
}
924+
925+
static void emit_incompatible_property_error(
926+
const zend_property_info *child, const zend_property_info *parent) {
927+
zend_error_noreturn(E_COMPILE_ERROR,
928+
"Type of %s::$%s must be %s%s (as in class %s)",
929+
ZSTR_VAL(child->ce->name),
930+
ZSTR_VAL(child->name),
931+
ZEND_TYPE_ALLOW_NULL(parent->type) ? "?" : "",
932+
ZEND_TYPE_IS_CLASS(parent->type)
933+
? ZSTR_VAL(ZEND_TYPE_IS_CE(parent->type) ? ZEND_TYPE_CE(parent->type)->name : resolve_class_name(parent->ce, ZEND_TYPE_NAME(parent->type)))
934+
: zend_get_type_by_const(ZEND_TYPE_CODE(parent->type)),
935+
ZSTR_VAL(parent->ce->name));
932936
}
933937

934938
static void do_inherit_property(zend_property_info *parent_info, zend_string *key, zend_class_entry *ce) /* {{{ */
@@ -962,16 +966,12 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke
962966
}
963967

964968
if (UNEXPECTED(ZEND_TYPE_IS_SET(parent_info->type))) {
965-
if (!property_types_compatible(parent_info, child_info)) {
966-
zend_error_noreturn(E_COMPILE_ERROR,
967-
"Type of %s::$%s must be %s%s (as in class %s)",
968-
ZSTR_VAL(ce->name),
969-
ZSTR_VAL(key),
970-
ZEND_TYPE_ALLOW_NULL(parent_info->type) ? "?" : "",
971-
ZEND_TYPE_IS_CLASS(parent_info->type)
972-
? ZSTR_VAL(ZEND_TYPE_IS_CE(parent_info->type) ? ZEND_TYPE_CE(parent_info->type)->name : zend_resolve_property_type(ZEND_TYPE_NAME(parent_info->type), parent_info->ce))
973-
: zend_get_type_by_const(ZEND_TYPE_CODE(parent_info->type)),
974-
ZSTR_VAL(ce->parent->name));
969+
inheritance_status status = property_types_compatible(parent_info, child_info);
970+
if (status == INHERITANCE_ERROR) {
971+
emit_incompatible_property_error(child_info, parent_info);
972+
}
973+
if (status == INHERITANCE_UNRESOLVED) {
974+
add_property_compatibility_obligation(ce, child_info, parent_info);
975975
}
976976
} else if (UNEXPECTED(ZEND_TYPE_IS_SET(child_info->type) && !ZEND_TYPE_IS_SET(parent_info->type))) {
977977
zend_error_noreturn(E_COMPILE_ERROR,
@@ -1958,7 +1958,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
19581958

19591959
if ((coliding_prop->flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC))
19601960
== (flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) &&
1961-
property_types_compatible(property_info, coliding_prop)
1961+
property_types_compatible(property_info, coliding_prop) == INHERITANCE_SUCCESS
19621962
) {
19631963
/* the flags are identical, thus, the properties may be compatible */
19641964
zval *op1, *op2;
@@ -2223,14 +2223,22 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */
22232223
/* }}} */
22242224

22252225
typedef struct {
2226-
enum { OBLIGATION_DEPENDENCY, OBLIGATION_COMPATIBILITY } type;
2226+
enum {
2227+
OBLIGATION_DEPENDENCY,
2228+
OBLIGATION_COMPATIBILITY,
2229+
OBLIGATION_PROPERTY_COMPATIBILITY
2230+
} type;
22272231
union {
22282232
zend_class_entry *dependency_ce;
22292233
struct {
22302234
const zend_function *parent_fn;
22312235
const zend_function *child_fn;
22322236
zend_bool always_error;
22332237
};
2238+
struct {
2239+
const zend_property_info *parent_prop;
2240+
const zend_property_info *child_prop;
2241+
};
22342242
};
22352243
} variance_obligation;
22362244

@@ -2284,6 +2292,17 @@ static void add_compatibility_obligation(
22842292
zend_hash_next_index_insert_ptr(obligations, obligation);
22852293
}
22862294

2295+
static void add_property_compatibility_obligation(
2296+
zend_class_entry *ce, const zend_property_info *child_prop,
2297+
const zend_property_info *parent_prop) {
2298+
HashTable *obligations = get_or_init_obligations_for_class(ce);
2299+
variance_obligation *obligation = emalloc(sizeof(variance_obligation));
2300+
obligation->type = OBLIGATION_PROPERTY_COMPATIBILITY;
2301+
obligation->child_prop = child_prop;
2302+
obligation->parent_prop = parent_prop;
2303+
zend_hash_next_index_insert_ptr(obligations, obligation);
2304+
}
2305+
22872306
static void resolve_delayed_variance_obligations(zend_class_entry *ce);
22882307

22892308
static int check_variance_obligation(zval *zv) {
@@ -2296,7 +2315,7 @@ static int check_variance_obligation(zval *zv) {
22962315
if (!(dependency_ce->ce_flags & ZEND_ACC_LINKED)) {
22972316
return ZEND_HASH_APPLY_KEEP;
22982317
}
2299-
} else {
2318+
} else if (obligation->type == OBLIGATION_COMPATIBILITY) {
23002319
zend_string *unresolved_class;
23012320
inheritance_status status = zend_do_perform_implementation_check(
23022321
&unresolved_class, obligation->child_fn, obligation->parent_fn);
@@ -2311,6 +2330,17 @@ static int check_variance_obligation(zval *zv) {
23112330
obligation->always_error);
23122331
}
23132332
/* Either the compatibility check was successful or only threw a warning. */
2333+
} else {
2334+
ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY);
2335+
inheritance_status status =
2336+
property_types_compatible(obligation->parent_prop, obligation->child_prop);
2337+
if (status != INHERITANCE_SUCCESS) {
2338+
if (status == INHERITANCE_UNRESOLVED) {
2339+
return ZEND_HASH_APPLY_KEEP;
2340+
}
2341+
ZEND_ASSERT(status == INHERITANCE_ERROR);
2342+
emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop);
2343+
}
23142344
}
23152345
return ZEND_HASH_APPLY_REMOVE;
23162346
}
@@ -2363,16 +2393,19 @@ static void report_variance_errors(zend_class_entry *ce) {
23632393
inheritance_status status;
23642394
zend_string *unresolved_class;
23652395

2366-
/* There should not be any unresolved parents at this point. */
2367-
ZEND_ASSERT(obligation->type == OBLIGATION_COMPATIBILITY);
2368-
2369-
/* Just used to fetch the unresolved_class in this case. */
2370-
status = zend_do_perform_implementation_check(
2371-
&unresolved_class, obligation->child_fn, obligation->parent_fn);
2372-
ZEND_ASSERT(status == INHERITANCE_UNRESOLVED);
2373-
emit_incompatible_method_error_or_warning(
2374-
obligation->child_fn, obligation->parent_fn,
2375-
status, unresolved_class, obligation->always_error);
2396+
if (obligation->type == OBLIGATION_COMPATIBILITY) {
2397+
/* Just used to fetch the unresolved_class in this case. */
2398+
status = zend_do_perform_implementation_check(
2399+
&unresolved_class, obligation->child_fn, obligation->parent_fn);
2400+
ZEND_ASSERT(status == INHERITANCE_UNRESOLVED);
2401+
emit_incompatible_method_error_or_warning(
2402+
obligation->child_fn, obligation->parent_fn,
2403+
status, unresolved_class, obligation->always_error);
2404+
} else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) {
2405+
emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop);
2406+
} else {
2407+
zend_error_noreturn(E_CORE_ERROR, "Bug #78647");
2408+
}
23762409
} ZEND_HASH_FOREACH_END();
23772410

23782411
/* Only warnings were thrown above -- that means that there are incompatibilities, but only
@@ -2483,6 +2516,7 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
24832516
inheritance_status ret = INHERITANCE_SUCCESS;
24842517
zend_string *key;
24852518
zend_function *parent_func;
2519+
zend_property_info *parent_info;
24862520

24872521
ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) {
24882522
zval *zv = zend_hash_find_ex(&ce->function_table, key, 1);
@@ -2501,6 +2535,28 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
25012535
}
25022536
} ZEND_HASH_FOREACH_END();
25032537

2538+
ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->properties_info, key, parent_info) {
2539+
zval *zv;
2540+
if ((parent_info->flags & ZEND_ACC_PRIVATE) || !ZEND_TYPE_IS_SET(parent_info->type)) {
2541+
continue;
2542+
}
2543+
2544+
zv = zend_hash_find_ex(&ce->properties_info, key, 1);
2545+
if (zv) {
2546+
zend_property_info *child_info = Z_PTR_P(zv);
2547+
if (ZEND_TYPE_IS_SET(child_info->type)) {
2548+
inheritance_status status = property_types_compatible(parent_info, child_info);
2549+
if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
2550+
if (EXPECTED(status == INHERITANCE_UNRESOLVED)) {
2551+
return INHERITANCE_UNRESOLVED;
2552+
}
2553+
ZEND_ASSERT(status == INHERITANCE_ERROR);
2554+
ret = INHERITANCE_ERROR;
2555+
}
2556+
}
2557+
}
2558+
} ZEND_HASH_FOREACH_END();
2559+
25042560
return ret;
25052561
}
25062562
/* }}} */

0 commit comments

Comments
 (0)