summaryrefslogtreecommitdiff
path: root/Zend/zend_inheritance.c
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2019-05-27 11:39:56 +0200
committerNikita Popov <nikita.ppv@gmail.com>2019-06-11 13:09:33 +0200
commit8f8fcbbd397370b407dc2552c4bd6ee4ccb0e93b (patch)
tree88aa957f3c81df9e9eaefa5b9e2c3b58cde020ec /Zend/zend_inheritance.c
parent89b2d88659b8a561769f51dfab1fa325e7fc0603 (diff)
downloadphp-git-8f8fcbbd397370b407dc2552c4bd6ee4ccb0e93b.tar.gz
Support full variance if autoloading is used
Keep track of delayed variance obligations and check them after linking a class is otherwise finished. Obligations may either be unresolved method compatibility (because the necessecary classes aren't available yet) or open parent/interface dependencies. The latter occur because we allow the use of not fully linked classes as parents/interfaces now. An important aspect of the implementation is we do not require classes involved in variance checks to be fully linked in order for the class to be fully linked. Because the involved types do have to exist in the class table (as partially linked classes) and we do check these for correct variance, we have the guarantee that either those classes will successfully link lateron or generate an error, but there is no way to actually use them until that point and as such no possibility of violating the variance contract. This is important because it ensures that a class declaration always either errors or will produce an immediately usable class afterwards -- there are no cases where the finalization of the class declaration has to be delayed until a later time, as earlier variants of this patch did. Because variance checks deal with classes in various stages of linking, we need to use a special instanceof implementation that supports this, and also introduce finer-grained flags that tell us which parts have been linked already and which haven't. Class autoloading for variance checks is delayed into a separate stage after the class is otherwise linked and before delayed variance obligations are processed. This separation is needed to handle cases like A extends B extends C, where B is the autoload root, but C is required to check variance. This could end up loading C while the class structure of B is in an inconsistent state.
Diffstat (limited to 'Zend/zend_inheritance.c')
-rw-r--r--Zend/zend_inheritance.c350
1 files changed, 298 insertions, 52 deletions
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index 738e528308..ba1612c189 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -26,6 +26,11 @@
#include "zend_smart_str.h"
#include "zend_operators.h"
+static void add_dependency_obligation(zend_class_entry *ce, zend_class_entry *dependency_ce);
+static void add_compatibility_obligation(
+ zend_class_entry *ce, const zend_function *child_fn, const zend_function *parent_fn,
+ zend_bool always_error);
+
static void overridden_ptr_dtor(zval *zv) /* {{{ */
{
efree_size(Z_PTR_P(zv), sizeof(zend_function));
@@ -174,7 +179,7 @@ static zend_string *resolve_class_name(const zend_function *fe, zend_string *nam
zend_class_entry *ce = fe->common.scope;
ZEND_ASSERT(ce);
if (zend_string_equals_literal_ci(name, "parent") && ce->parent) {
- if (ce->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_LINKING_IN_PROGRESS)) {
+ if (ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) {
return ce->parent->name;
} else {
return ce->parent_name;
@@ -199,32 +204,75 @@ static zend_bool class_visible(zend_class_entry *ce) {
static zend_class_entry *lookup_class(const zend_function *fe, zend_string *name) {
zend_class_entry *ce;
if (!CG(in_compilation)) {
- ce = zend_lookup_class(name);
+ uint32_t flags = ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD;
+ ce = zend_lookup_class_ex(name, NULL, flags);
if (ce) {
return ce;
}
+
+ /* We'll autoload this class and process delayed variance obligations later. */
+ if (!CG(delayed_autoloads)) {
+ ALLOC_HASHTABLE(CG(delayed_autoloads));
+ zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0);
+ }
+ zend_hash_add_empty_element(CG(delayed_autoloads), name);
} else {
ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
if (ce && class_visible(ce)) {
return ce;
}
- }
- /* The current class may not be registered yet, so check for it explicitly. */
- if (zend_string_equals_ci(fe->common.scope->name, name)) {
- return fe->common.scope;
+ /* The current class may not be registered yet, so check for it explicitly. */
+ if (zend_string_equals_ci(fe->common.scope->name, name)) {
+ return fe->common.scope;
+ }
}
return NULL;
}
-/* Instanceof that's safe to use on unlinked classes. For the unlinked case, we only handle
- * class identity here. */
+/* Instanceof that's safe to use on unlinked classes. */
static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) {
- if ((ce1->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_LINKING_IN_PROGRESS))) {
+ zend_class_entry *ce;
+
+ if (ce1 == ce2) {
+ return 1;
+ }
+
+ if (ce1->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_RESOLVED_INTERFACES)) {
return instanceof_function(ce1, ce2);
}
- return ce1 == ce2;
+
+ ce = ce1;
+ while (ce->parent) {
+ if (ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) {
+ ce = ce->parent;
+ } else {
+ ce = zend_lookup_class_ex(ce->parent_name, NULL,
+ ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD);
+ if (!ce) {
+ break;
+ }
+ }
+ if (ce == ce2) {
+ return 1;
+ }
+ }
+
+ if (ce1->num_interfaces) {
+ uint32_t i;
+ ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES));
+ for (i = 0; i < ce1->num_interfaces; i++) {
+ ce = zend_lookup_class_ex(
+ ce1->interface_names[i].name, ce1->interface_names[i].lc_name,
+ ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD);
+ if (ce && unlinked_instanceof(ce, ce2)) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
}
/* Unresolved means that class declarations that are currently not available are needed to
@@ -261,13 +309,14 @@ static inheritance_status zend_perform_covariant_type_check(
return INHERITANCE_SUCCESS;
}
+ /* Make sure to always load both classes, to avoid only registering one of them as
+ * a delayed autoload. */
fe_ce = lookup_class(fe, fe_class_name);
+ proto_ce = lookup_class(proto, proto_class_name);
if (!fe_ce) {
*unresolved_class = fe_class_name;
return INHERITANCE_UNRESOLVED;
}
-
- proto_ce = lookup_class(proto, proto_class_name);
if (!proto_ce) {
*unresolved_class = proto_class_name;
return INHERITANCE_UNRESOLVED;
@@ -440,6 +489,17 @@ static inheritance_status zend_do_perform_implementation_check(
}
/* }}} */
+static inheritance_status perform_delayable_implementation_check(
+ zend_string **unresolved_class, zend_class_entry *ce,
+ const zend_function *fe, const zend_function *proto, zend_bool always_error) {
+ inheritance_status status = zend_do_perform_implementation_check(
+ unresolved_class, fe, proto);
+ if (status == INHERITANCE_UNRESOLVED) {
+ add_compatibility_obligation(ce, fe, proto, always_error);
+ }
+ return status;
+}
+
static ZEND_COLD void zend_append_type_hint(smart_str *str, const zend_function *fptr, zend_arg_info *arg_info, int return_hint) /* {{{ */
{
@@ -601,12 +661,13 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function
}
/* }}} */
-static zend_always_inline uint32_t func_lineno(zend_function *fn) {
+static zend_always_inline uint32_t func_lineno(const zend_function *fn) {
return fn->common.type == ZEND_USER_FUNCTION ? fn->op_array.line_start : 0;
}
static void ZEND_COLD emit_incompatible_method_error(
- int error_level, const char *error_verb, zend_function *child, zend_function *parent,
+ int error_level, const char *error_verb,
+ const zend_function *child, const zend_function *parent,
inheritance_status status, zend_string *unresolved_class) {
zend_string *parent_prototype = zend_get_function_declaration(parent);
zend_string *child_prototype = zend_get_function_declaration(child);
@@ -623,6 +684,28 @@ static void ZEND_COLD emit_incompatible_method_error(
zend_string_efree(parent_prototype);
}
+static void ZEND_COLD emit_incompatible_method_error_or_warning(
+ const zend_function *child, const zend_function *parent,
+ inheritance_status status, zend_string *unresolved_class, zend_bool always_error) {
+ int error_level;
+ const char *error_verb;
+ if (always_error ||
+ (child->common.prototype &&
+ (child->common.prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) ||
+ ((parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) &&
+ (!(child->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) ||
+ zend_perform_covariant_type_check(&unresolved_class, child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) != INHERITANCE_SUCCESS))
+ ) {
+ error_level = E_COMPILE_ERROR;
+ error_verb = "must";
+ } else {
+ error_level = E_WARNING;
+ error_verb = "should";
+ }
+ emit_incompatible_method_error(
+ error_level, error_verb, child, parent, status, unresolved_class);
+}
+
static void do_inheritance_check_on_method(zend_function *child, zend_function *parent, zend_class_entry *ce, zval *child_zv) /* {{{ */
{
uint32_t child_flags;
@@ -707,26 +790,11 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function *
ZEND_FN_SCOPE_NAME(child), ZSTR_VAL(child->common.function_name), zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
}
- status = zend_do_perform_implementation_check(&unresolved_class, child, parent);
- if (UNEXPECTED(status != INHERITANCE_SUCCESS)) {
- int error_level;
- const char *error_verb;
- if (child->common.prototype && (
- child->common.prototype->common.fn_flags & ZEND_ACC_ABSTRACT
- )) {
- error_level = E_COMPILE_ERROR;
- error_verb = "must";
- } else if ((parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) &&
- (!(child->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) ||
- zend_perform_covariant_type_check(&unresolved_class, child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) != INHERITANCE_SUCCESS)) {
- error_level = E_COMPILE_ERROR;
- error_verb = "must";
- } else {
- error_level = E_WARNING;
- error_verb = "should";
- }
- emit_incompatible_method_error(
- error_level, error_verb, child, parent, status, unresolved_class);
+ status = perform_delayable_implementation_check(
+ &unresolved_class, ce, child, parent, /*always_error*/0);
+ if (status == INHERITANCE_ERROR) {
+ emit_incompatible_method_error_or_warning(
+ child, parent, status, unresolved_class, /*always_error*/0);
}
}
} while (0);
@@ -904,6 +972,7 @@ static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_en
ce->interfaces[ce->num_interfaces++] = entry;
}
}
+ ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES;
/* and now call the implementing handlers */
while (ce_num < ce->num_interfaces) {
@@ -1005,6 +1074,7 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent
zend_string_release_ex(ce->parent_name, 0);
}
ce->parent = parent_ce;
+ ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT;
/* Inherit interfaces */
if (parent_ce->num_interfaces) {
@@ -1314,10 +1384,11 @@ static void zend_do_implement_interfaces(zend_class_entry *ce) /* {{{ */
}
for (i = 0; i < ce->num_interfaces; i++) {
- iface = zend_fetch_class_by_name(ce->interface_names[i].name,
- ce->interface_names[i].lc_name, ZEND_FETCH_CLASS_INTERFACE);
- if (UNEXPECTED(iface == NULL)) {
- return;
+ iface = zend_fetch_class_by_name(
+ ce->interface_names[i].name, ce->interface_names[i].lc_name,
+ ZEND_FETCH_CLASS_INTERFACE|ZEND_FETCH_CLASS_ALLOW_UNLINKED);
+ if (!(iface->ce_flags & ZEND_ACC_LINKED)) {
+ add_dependency_obligation(ce, iface);
}
if (UNEXPECTED(!(iface->ce_flags & ZEND_ACC_INTERFACE))) {
efree(interfaces);
@@ -1354,6 +1425,7 @@ static void zend_do_implement_interfaces(zend_class_entry *ce) /* {{{ */
ce->num_interfaces = num_interfaces;
ce->interfaces = interfaces;
+ ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES;
i = ce->parent ? ce->parent->num_interfaces : 0;
for (; i < ce->num_interfaces; i++) {
@@ -1454,18 +1526,18 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s
if ((existing_fn = zend_hash_find_ptr(*overridden, key)) != NULL) {
if (existing_fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
/* Make sure the trait method is compatible with previosly declared abstract method */
- status = zend_do_perform_implementation_check(
- &unresolved_class, fn, existing_fn);
- if (status != INHERITANCE_SUCCESS) {
+ status = perform_delayable_implementation_check(
+ &unresolved_class, ce, fn, existing_fn, /*always_error*/ 1);
+ if (status == INHERITANCE_ERROR) {
emit_incompatible_method_error(
E_COMPILE_ERROR, "must", fn, existing_fn, status, unresolved_class);
}
}
if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
/* Make sure the abstract declaration is compatible with previous declaration */
- status = zend_do_perform_implementation_check(
- &unresolved_class, existing_fn, fn);
- if (status != INHERITANCE_SUCCESS) {
+ status = perform_delayable_implementation_check(
+ &unresolved_class, ce, existing_fn, fn, /*always_error*/ 1);
+ if (status == INHERITANCE_ERROR) {
emit_incompatible_method_error(
E_COMPILE_ERROR, "must", existing_fn, fn, status, unresolved_class);
}
@@ -1481,15 +1553,17 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s
} else if (existing_fn->common.fn_flags & ZEND_ACC_ABSTRACT &&
(existing_fn->common.scope->ce_flags & ZEND_ACC_INTERFACE) == 0) {
/* Make sure the trait method is compatible with previosly declared abstract method */
- status = zend_do_perform_implementation_check(&unresolved_class, fn, existing_fn);
- if (status != INHERITANCE_SUCCESS) {
+ status = perform_delayable_implementation_check(
+ &unresolved_class, ce, fn, existing_fn, /*always_error*/ 1);
+ if (status == INHERITANCE_ERROR) {
emit_incompatible_method_error(
E_COMPILE_ERROR, "must", fn, existing_fn, status, unresolved_class);
}
} else if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
/* Make sure the abstract declaration is compatible with previous declaration */
- status = zend_do_perform_implementation_check(&unresolved_class, existing_fn, fn);
- if (status != INHERITANCE_SUCCESS) {
+ status = perform_delayable_implementation_check(
+ &unresolved_class, ce, existing_fn, fn, /*always_error*/ 1);
+ if (status == INHERITANCE_ERROR) {
emit_incompatible_method_error(
E_COMPILE_ERROR, "must", existing_fn, fn, status, unresolved_class);
}
@@ -2130,11 +2204,172 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */
}
/* }}} */
+typedef struct {
+ enum { OBLIGATION_DEPENDENCY, OBLIGATION_COMPATIBILITY } type;
+ union {
+ zend_class_entry *dependency_ce;
+ struct {
+ const zend_function *parent_fn;
+ const zend_function *child_fn;
+ zend_bool always_error;
+ };
+ };
+} variance_obligation;
+
+static void variance_obligation_dtor(zval *zv) {
+ efree(Z_PTR_P(zv));
+}
+
+static void variance_obligation_ht_dtor(zval *zv) {
+ zend_hash_destroy(Z_PTR_P(zv));
+ FREE_HASHTABLE(Z_PTR_P(zv));
+}
+
+static HashTable *get_or_init_obligations_for_class(zend_class_entry *ce) {
+ HashTable *ht;
+ zend_ulong key;
+ if (!CG(delayed_variance_obligations)) {
+ ALLOC_HASHTABLE(CG(delayed_variance_obligations));
+ zend_hash_init(CG(delayed_variance_obligations), 0, NULL, variance_obligation_ht_dtor, 0);
+ }
+
+ key = (zend_ulong) (uintptr_t) ce;
+ ht = zend_hash_index_find_ptr(CG(delayed_variance_obligations), key);
+ if (ht) {
+ return ht;
+ }
+
+ ALLOC_HASHTABLE(ht);
+ zend_hash_init(ht, 0, NULL, variance_obligation_dtor, 0);
+ zend_hash_index_add_new_ptr(CG(delayed_variance_obligations), key, ht);
+ ce->ce_flags |= ZEND_ACC_UNRESOLVED_VARIANCE;
+ return ht;
+}
+
+static void add_dependency_obligation(zend_class_entry *ce, zend_class_entry *dependency_ce) {
+ HashTable *obligations = get_or_init_obligations_for_class(ce);
+ variance_obligation *obligation = emalloc(sizeof(variance_obligation));
+ obligation->type = OBLIGATION_DEPENDENCY;
+ obligation->dependency_ce = dependency_ce;
+ zend_hash_next_index_insert_ptr(obligations, obligation);
+}
+
+static void add_compatibility_obligation(
+ zend_class_entry *ce, const zend_function *child_fn, const zend_function *parent_fn,
+ zend_bool always_error) {
+ HashTable *obligations = get_or_init_obligations_for_class(ce);
+ variance_obligation *obligation = emalloc(sizeof(variance_obligation));
+ obligation->type = OBLIGATION_COMPATIBILITY;
+ obligation->child_fn = child_fn;
+ obligation->parent_fn = parent_fn;
+ obligation->always_error = always_error;
+ zend_hash_next_index_insert_ptr(obligations, obligation);
+}
+
+static void resolve_delayed_variance_obligations(zend_class_entry *ce);
+
+static int check_variance_obligation(zval *zv) {
+ variance_obligation *obligation = Z_PTR_P(zv);
+ if (obligation->type == OBLIGATION_DEPENDENCY) {
+ zend_class_entry *dependency_ce = obligation->dependency_ce;
+ if (dependency_ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE) {
+ resolve_delayed_variance_obligations(dependency_ce);
+ }
+ if (!(dependency_ce->ce_flags & ZEND_ACC_LINKED)) {
+ return ZEND_HASH_APPLY_KEEP;
+ }
+ } else {
+ zend_string *unresolved_class;
+ inheritance_status status = zend_do_perform_implementation_check(
+ &unresolved_class, obligation->child_fn, obligation->parent_fn);
+ if (status == INHERITANCE_UNRESOLVED) {
+ return ZEND_HASH_APPLY_KEEP;
+ }
+ if (status == INHERITANCE_ERROR) {
+ emit_incompatible_method_error_or_warning(
+ obligation->child_fn, obligation->parent_fn, status, unresolved_class,
+ obligation->always_error);
+ }
+ /* Either the compatibility check was successful or only threw a warning. */
+ }
+ return ZEND_HASH_APPLY_REMOVE;
+}
+
+static void load_delayed_classes() {
+ HashTable *delayed_autoloads = CG(delayed_autoloads);
+ zend_string *name;
+
+ if (!delayed_autoloads) {
+ return;
+ }
+
+ /* Take ownership of this HT, to avoid concurrent modification during autoloading. */
+ CG(delayed_autoloads) = NULL;
+
+ ZEND_HASH_FOREACH_STR_KEY(delayed_autoloads, name) {
+ zend_lookup_class(name);
+ } ZEND_HASH_FOREACH_END();
+
+ zend_hash_destroy(delayed_autoloads);
+ FREE_HASHTABLE(delayed_autoloads);
+}
+
+static void resolve_delayed_variance_obligations(zend_class_entry *ce) {
+ HashTable *all_obligations = CG(delayed_variance_obligations), *obligations;
+ zend_ulong num_key = (zend_ulong) (uintptr_t) ce;
+
+ ZEND_ASSERT(all_obligations != NULL);
+ obligations = zend_hash_index_find_ptr(all_obligations, num_key);
+ ZEND_ASSERT(obligations != NULL);
+
+ zend_hash_apply(obligations, check_variance_obligation);
+ if (zend_hash_num_elements(obligations) == 0) {
+ ce->ce_flags &= ~ZEND_ACC_UNRESOLVED_VARIANCE;
+ ce->ce_flags |= ZEND_ACC_LINKED;
+ zend_hash_index_del(all_obligations, num_key);
+ }
+}
+
+static void report_variance_errors(zend_class_entry *ce) {
+ HashTable *all_obligations = CG(delayed_variance_obligations), *obligations;
+ variance_obligation *obligation;
+ zend_ulong num_key = (zend_ulong) (uintptr_t) ce;
+
+ ZEND_ASSERT(all_obligations != NULL);
+ obligations = zend_hash_index_find_ptr(all_obligations, num_key);
+ ZEND_ASSERT(obligations != NULL);
+
+ ZEND_HASH_FOREACH_PTR(obligations, obligation) {
+ inheritance_status status;
+ zend_string *unresolved_class;
+
+ /* There should not be any unresolved parents at this point. */
+ ZEND_ASSERT(obligation->type == OBLIGATION_COMPATIBILITY);
+
+ /* Just used to fetch the unresolved_class in this case. */
+ status = zend_do_perform_implementation_check(
+ &unresolved_class, obligation->child_fn, obligation->parent_fn);
+ ZEND_ASSERT(status == INHERITANCE_UNRESOLVED);
+ emit_incompatible_method_error_or_warning(
+ obligation->child_fn, obligation->parent_fn,
+ status, unresolved_class, obligation->always_error);
+ } ZEND_HASH_FOREACH_END();
+
+ /* Only warnings were thrown above -- that means that there are incompatibilities, but only
+ * ones that we permit. Mark all classes with open obligations as fully linked. */
+ ce->ce_flags &= ~ZEND_ACC_UNRESOLVED_VARIANCE;
+ ce->ce_flags |= ZEND_ACC_LINKED;
+ zend_hash_index_del(all_obligations, num_key);
+}
+
ZEND_API void zend_do_link_class(zend_class_entry *ce) /* {{{ */
{
- ce->ce_flags |= ZEND_ACC_LINKING_IN_PROGRESS;
if (ce->parent_name) {
- zend_class_entry *parent = zend_fetch_class_by_name(ce->parent_name, NULL, 0);
+ zend_class_entry *parent = zend_fetch_class_by_name(
+ ce->parent_name, NULL, ZEND_FETCH_CLASS_ALLOW_UNLINKED);
+ if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
+ add_dependency_obligation(ce, parent);
+ }
zend_do_inheritance(ce, parent);
}
if (ce->ce_flags & ZEND_ACC_IMPLEMENT_TRAITS) {
@@ -2148,8 +2383,19 @@ ZEND_API void zend_do_link_class(zend_class_entry *ce) /* {{{ */
}
zend_build_properties_info_table(ce);
- ce->ce_flags &= ~ZEND_ACC_LINKING_IN_PROGRESS;
- ce->ce_flags |= ZEND_ACC_LINKED;
+
+ if (!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)) {
+ ce->ce_flags |= ZEND_ACC_LINKED;
+ return;
+ }
+
+ load_delayed_classes();
+ if (ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE) {
+ resolve_delayed_variance_obligations(ce);
+ if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
+ report_variance_errors(ce);
+ }
+ }
}
/* Check whether early binding is prevented due to unresolved types in inheritance checks. */