diff options
Diffstat (limited to 'Zend/zend_inheritance.c')
-rw-r--r-- | Zend/zend_inheritance.c | 350 |
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. */ |