diff options
Diffstat (limited to 'Zend/zend_inheritance.c')
| -rw-r--r-- | Zend/zend_inheritance.c | 1719 |
1 files changed, 1263 insertions, 456 deletions
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d271b74154..190ec57813 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2,7 +2,7 @@ +----------------------------------------------------------------------+ | Zend Engine | +----------------------------------------------------------------------+ - | Copyright (c) 1998-2018 Zend Technologies Ltd. (http://www.zend.com) | + | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) | +----------------------------------------------------------------------+ | This source file is subject to version 2.00 of the Zend license, | | that is bundled with this package in the file LICENSE, and is | @@ -22,301 +22,439 @@ #include "zend_compile.h" #include "zend_execute.h" #include "zend_inheritance.h" +#include "zend_interfaces.h" #include "zend_smart_str.h" #include "zend_operators.h" +#include "zend_exceptions.h" -static void overriden_ptr_dtor(zval *zv) /* {{{ */ +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 add_property_compatibility_obligation( + zend_class_entry *ce, const zend_property_info *child_prop, + const zend_property_info *parent_prop); + +static void overridden_ptr_dtor(zval *zv) /* {{{ */ { efree_size(Z_PTR_P(zv), sizeof(zend_function)); } /* }}} */ -static zend_property_info *zend_duplicate_property_info(zend_property_info *property_info) /* {{{ */ +static zend_property_info *zend_duplicate_property_info_internal(zend_property_info *property_info) /* {{{ */ { - zend_property_info* new_property_info; - - new_property_info = zend_arena_alloc(&CG(arena), sizeof(zend_property_info)); + zend_property_info* new_property_info = pemalloc(sizeof(zend_property_info), 1); memcpy(new_property_info, property_info, sizeof(zend_property_info)); zend_string_addref(new_property_info->name); - if (new_property_info->doc_comment) { - zend_string_addref(new_property_info->doc_comment); + if (ZEND_TYPE_IS_NAME(new_property_info->type)) { + zend_string_addref(ZEND_TYPE_NAME(new_property_info->type)); } + return new_property_info; } /* }}} */ -static zend_property_info *zend_duplicate_property_info_internal(zend_property_info *property_info) /* {{{ */ +static zend_function *zend_duplicate_internal_function(zend_function *func, zend_class_entry *ce) /* {{{ */ { - zend_property_info* new_property_info = pemalloc(sizeof(zend_property_info), 1); - memcpy(new_property_info, property_info, sizeof(zend_property_info)); - zend_string_addref(new_property_info->name); - return new_property_info; + zend_function *new_function; + + if (UNEXPECTED(ce->type & ZEND_INTERNAL_CLASS)) { + new_function = pemalloc(sizeof(zend_internal_function), 1); + memcpy(new_function, func, sizeof(zend_internal_function)); + } else { + new_function = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); + memcpy(new_function, func, sizeof(zend_internal_function)); + new_function->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED; + } + if (EXPECTED(new_function->common.function_name)) { + zend_string_addref(new_function->common.function_name); + } + return new_function; } /* }}} */ -static zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce) /* {{{ */ +static zend_function *zend_duplicate_user_function(zend_function *func) /* {{{ */ { zend_function *new_function; + new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); + memcpy(new_function, func, sizeof(zend_op_array)); + if (ZEND_MAP_PTR_GET(func->op_array.static_variables_ptr)) { + /* See: Zend/tests/method_static_var.phpt */ + new_function->op_array.static_variables = ZEND_MAP_PTR_GET(func->op_array.static_variables_ptr); + } + if (!(GC_FLAGS(new_function->op_array.static_variables) & IS_ARRAY_IMMUTABLE)) { + GC_ADDREF(new_function->op_array.static_variables); + } + + if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) { + ZEND_ASSERT(new_function->op_array.fn_flags & ZEND_ACC_PRELOADED); + ZEND_MAP_PTR_NEW(new_function->op_array.static_variables_ptr); + } else { + ZEND_MAP_PTR_INIT(new_function->op_array.static_variables_ptr, &new_function->op_array.static_variables); + } + + return new_function; +} +/* }}} */ + +static zend_always_inline zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce, zend_bool is_interface) /* {{{ */ +{ if (UNEXPECTED(func->type == ZEND_INTERNAL_FUNCTION)) { - if (UNEXPECTED(ce->type & ZEND_INTERNAL_CLASS)) { - new_function = pemalloc(sizeof(zend_internal_function), 1); - memcpy(new_function, func, sizeof(zend_internal_function)); - } else { - new_function = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); - memcpy(new_function, func, sizeof(zend_internal_function)); - new_function->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED; - } - if (EXPECTED(new_function->common.function_name)) { - zend_string_addref(new_function->common.function_name); - } + return zend_duplicate_internal_function(func, ce); } else { if (func->op_array.refcount) { (*func->op_array.refcount)++; } - if (EXPECTED(!func->op_array.static_variables)) { + if (is_interface + || EXPECTED(!func->op_array.static_variables)) { /* reuse the same op_array structure */ return func; } - if (!(GC_FLAGS(func->op_array.static_variables) & IS_ARRAY_IMMUTABLE)) { - GC_ADDREF(func->op_array.static_variables); - } - new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); - memcpy(new_function, func, sizeof(zend_op_array)); + return zend_duplicate_user_function(func); } - return new_function; } /* }}} */ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */ { - ZEND_ASSERT(ce->parent != NULL); + zend_class_entry *parent = ce->parent; + + ZEND_ASSERT(parent != NULL); /* You cannot change create_object */ - ce->create_object = ce->parent->create_object; + ce->create_object = parent->create_object; /* Inherit special functions if needed */ if (EXPECTED(!ce->get_iterator)) { - ce->get_iterator = ce->parent->get_iterator; + ce->get_iterator = parent->get_iterator; } - if (EXPECTED(!ce->iterator_funcs_ptr) && UNEXPECTED(ce->parent->iterator_funcs_ptr)) { - if (ce->type == ZEND_INTERNAL_CLASS) { - ce->iterator_funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs)); - if (ce->parent->iterator_funcs_ptr->zf_new_iterator) { - ce->iterator_funcs_ptr->zf_new_iterator = zend_hash_str_find_ptr(&ce->function_table, "getiterator", sizeof("getiterator") - 1); - } - if (ce->parent->iterator_funcs_ptr->zf_current) { - ce->iterator_funcs_ptr->zf_rewind = zend_hash_str_find_ptr(&ce->function_table, "rewind", sizeof("rewind") - 1); - ce->iterator_funcs_ptr->zf_valid = zend_hash_str_find_ptr(&ce->function_table, "valid", sizeof("valid") - 1); - ce->iterator_funcs_ptr->zf_key = zend_hash_str_find_ptr(&ce->function_table, "key", sizeof("key") - 1); - ce->iterator_funcs_ptr->zf_current = zend_hash_str_find_ptr(&ce->function_table, "current", sizeof("current") - 1); - ce->iterator_funcs_ptr->zf_next = zend_hash_str_find_ptr(&ce->function_table, "next", sizeof("next") - 1); - } - } else { - ce->iterator_funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs)); - memset(ce->iterator_funcs_ptr, 0, sizeof(zend_class_iterator_funcs)); - } + if (parent->iterator_funcs_ptr) { + /* Must be initialized through iface->interface_gets_implemented() */ + ZEND_ASSERT(ce->iterator_funcs_ptr); } if (EXPECTED(!ce->__get)) { - ce->__get = ce->parent->__get; + ce->__get = parent->__get; } if (EXPECTED(!ce->__set)) { - ce->__set = ce->parent->__set; + ce->__set = parent->__set; } if (EXPECTED(!ce->__unset)) { - ce->__unset = ce->parent->__unset; + ce->__unset = parent->__unset; } if (EXPECTED(!ce->__isset)) { - ce->__isset = ce->parent->__isset; + ce->__isset = parent->__isset; } if (EXPECTED(!ce->__call)) { - ce->__call = ce->parent->__call; + ce->__call = parent->__call; } if (EXPECTED(!ce->__callstatic)) { - ce->__callstatic = ce->parent->__callstatic; + ce->__callstatic = parent->__callstatic; } if (EXPECTED(!ce->__tostring)) { - ce->__tostring = ce->parent->__tostring; + ce->__tostring = parent->__tostring; } if (EXPECTED(!ce->clone)) { - ce->clone = ce->parent->clone; + ce->clone = parent->clone; + } + if (EXPECTED(!ce->serialize_func)) { + ce->serialize_func = parent->serialize_func; } if (EXPECTED(!ce->serialize)) { - ce->serialize = ce->parent->serialize; + ce->serialize = parent->serialize; + } + if (EXPECTED(!ce->unserialize_func)) { + ce->unserialize_func = parent->unserialize_func; } if (EXPECTED(!ce->unserialize)) { - ce->unserialize = ce->parent->unserialize; + ce->unserialize = parent->unserialize; } if (!ce->destructor) { - ce->destructor = ce->parent->destructor; + ce->destructor = parent->destructor; } if (EXPECTED(!ce->__debugInfo)) { - ce->__debugInfo = ce->parent->__debugInfo; + ce->__debugInfo = parent->__debugInfo; } if (ce->constructor) { - if (ce->parent->constructor && UNEXPECTED(ce->parent->constructor->common.fn_flags & ZEND_ACC_FINAL)) { + if (parent->constructor && UNEXPECTED(parent->constructor->common.fn_flags & ZEND_ACC_FINAL)) { zend_error_noreturn(E_ERROR, "Cannot override final %s::%s() with %s::%s()", - ZSTR_VAL(ce->parent->name), ZSTR_VAL(ce->parent->constructor->common.function_name), + ZSTR_VAL(parent->name), ZSTR_VAL(parent->constructor->common.function_name), ZSTR_VAL(ce->name), ZSTR_VAL(ce->constructor->common.function_name)); } return; } - ce->constructor = ce->parent->constructor; + ce->constructor = parent->constructor; } /* }}} */ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ { - if (fn_flags & ZEND_ACC_PRIVATE) { - return "private"; - } - if (fn_flags & ZEND_ACC_PROTECTED) { - return "protected"; - } if (fn_flags & ZEND_ACC_PUBLIC) { return "public"; + } else if (fn_flags & ZEND_ACC_PRIVATE) { + return "private"; + } else { + ZEND_ASSERT(fn_flags & ZEND_ACC_PROTECTED); + return "protected"; } - return ""; } /* }}} */ -static zend_always_inline zend_bool zend_iterable_compatibility_check(zend_arg_info *arg_info) /* {{{ */ -{ - if (ZEND_TYPE_CODE(arg_info->type) == IS_ARRAY) { - return 1; +static zend_string *resolve_class_name(zend_class_entry *scope, zend_string *name) { + ZEND_ASSERT(scope); + if (zend_string_equals_literal_ci(name, "parent") && scope->parent) { + if (scope->ce_flags & ZEND_ACC_RESOLVED_PARENT) { + return scope->parent->name; + } else { + return scope->parent_name; + } + } else if (zend_string_equals_literal_ci(name, "self")) { + return scope->name; + } else { + return name; } +} - if (ZEND_TYPE_IS_CLASS(arg_info->type) && zend_string_equals_literal_ci(ZEND_TYPE_NAME(arg_info->type), "Traversable")) { - return 1; +static zend_bool class_visible(zend_class_entry *ce) { + if (ce->type == ZEND_INTERNAL_CLASS) { + return !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES); + } else { + ZEND_ASSERT(ce->type == ZEND_USER_CLASS); + return !(CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) + || ce->info.user.filename == CG(compiled_filename); } +} - return 0; +static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name) { + zend_class_entry *ce; + if (!CG(in_compilation)) { + 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(scope->name, name)) { + return scope; + } + } + + return NULL; } -/* }}} */ -static int zend_do_perform_type_hint_check(const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ -{ - ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_arg_info->type) && ZEND_TYPE_IS_SET(proto_arg_info->type)); +/* Instanceof that's safe to use on unlinked classes. */ +static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) { + if (ce1 == ce2) { + return 1; + } - if (ZEND_TYPE_IS_CLASS(fe_arg_info->type) && ZEND_TYPE_IS_CLASS(proto_arg_info->type)) { - zend_string *fe_class_name, *proto_class_name; - const char *class_name; - size_t class_name_len; + if (ce1->ce_flags & ZEND_ACC_LINKED) { + return instanceof_function(ce1, ce2); + } - fe_class_name = ZEND_TYPE_NAME(fe_arg_info->type); - class_name = ZSTR_VAL(fe_class_name); - class_name_len = ZSTR_LEN(fe_class_name); - if (class_name_len == sizeof("parent")-1 && !strcasecmp(class_name, "parent") && fe->common.scope && fe->common.scope->parent) { - fe_class_name = zend_string_copy(fe->common.scope->parent->name); - } else if (class_name_len == sizeof("self")-1 && !strcasecmp(class_name, "self") && fe->common.scope) { - fe_class_name = zend_string_copy(fe->common.scope->name); + if (ce1->parent) { + zend_class_entry *parent_ce; + if (ce1->ce_flags & ZEND_ACC_RESOLVED_PARENT) { + parent_ce = ce1->parent; } else { - zend_string_addref(fe_class_name); + parent_ce = zend_lookup_class_ex(ce1->parent_name, NULL, + ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD); } - proto_class_name = ZEND_TYPE_NAME(proto_arg_info->type); - class_name = ZSTR_VAL(proto_class_name); - class_name_len = ZSTR_LEN(proto_class_name); - if (class_name_len == sizeof("parent")-1 && !strcasecmp(class_name, "parent") && proto->common.scope && proto->common.scope->parent) { - proto_class_name = zend_string_copy(proto->common.scope->parent->name); - } else if (class_name_len == sizeof("self")-1 && !strcasecmp(class_name, "self") && proto->common.scope) { - proto_class_name = zend_string_copy(proto->common.scope->name); - } else { - zend_string_addref(proto_class_name); + /* It's not sufficient to only check the parent chain itself, as need to do a full + * recursive instanceof in case the parent interfaces haven't been copied yet. */ + if (parent_ce && unlinked_instanceof(parent_ce, ce2)) { + return 1; } + } - if (fe_class_name != proto_class_name && strcasecmp(ZSTR_VAL(fe_class_name), ZSTR_VAL(proto_class_name)) != 0) { - if (fe->common.type != ZEND_USER_FUNCTION) { - zend_string_release(proto_class_name); - zend_string_release(fe_class_name); - return 0; - } else { - zend_class_entry *fe_ce, *proto_ce; - - fe_ce = zend_lookup_class(fe_class_name); - proto_ce = zend_lookup_class(proto_class_name); - - /* Check for class alias */ - if (!fe_ce || !proto_ce || - fe_ce->type == ZEND_INTERNAL_CLASS || - proto_ce->type == ZEND_INTERNAL_CLASS || - fe_ce != proto_ce) { - zend_string_release(proto_class_name); - zend_string_release(fe_class_name); - return 0; + if (ce1->num_interfaces) { + uint32_t i; + if (ce1->ce_flags & ZEND_ACC_RESOLVED_INTERFACES) { + /* Unlike the normal instanceof_function(), we have to perform a recursive + * check here, as the parent interfaces might not have been fully copied yet. */ + for (i = 0; i < ce1->num_interfaces; i++) { + if (unlinked_instanceof(ce1->interfaces[i], ce2)) { + return 1; + } + } + } else { + for (i = 0; i < ce1->num_interfaces; i++) { + zend_class_entry *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; } } } - zend_string_release(proto_class_name); - zend_string_release(fe_class_name); - } else if (ZEND_TYPE_CODE(fe_arg_info->type) != ZEND_TYPE_CODE(proto_arg_info->type)) { - /* Incompatible built-in types */ - return 0; } - return 1; + return 0; +} + +/* Unresolved means that class declarations that are currently not available are needed to + * determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated + * as an ERROR. */ +typedef enum { + INHERITANCE_UNRESOLVED = -1, + INHERITANCE_ERROR = 0, + INHERITANCE_SUCCESS = 1, +} inheritance_status; + +static inheritance_status zend_perform_covariant_type_check( + zend_string **unresolved_class, + const zend_function *fe, zend_arg_info *fe_arg_info, + const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ +{ + zend_type fe_type = fe_arg_info->type, proto_type = proto_arg_info->type; + ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); + + if (ZEND_TYPE_ALLOW_NULL(fe_type) && !ZEND_TYPE_ALLOW_NULL(proto_type)) { + return INHERITANCE_ERROR; + } + + if (ZEND_TYPE_IS_CLASS(proto_type)) { + zend_string *fe_class_name, *proto_class_name; + zend_class_entry *fe_ce, *proto_ce; + if (!ZEND_TYPE_IS_CLASS(fe_type)) { + return INHERITANCE_ERROR; + } + + fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); + proto_class_name = resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + 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->common.scope, fe_class_name); + proto_ce = lookup_class(proto->common.scope, proto_class_name); + if (!fe_ce) { + *unresolved_class = fe_class_name; + return INHERITANCE_UNRESOLVED; + } + if (!proto_ce) { + *unresolved_class = proto_class_name; + return INHERITANCE_UNRESOLVED; + } + + return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } else if (ZEND_TYPE_CODE(proto_type) == IS_ITERABLE) { + if (ZEND_TYPE_IS_CLASS(fe_type)) { + zend_string *fe_class_name = + resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); + zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name); + if (!fe_ce) { + *unresolved_class = fe_class_name; + return INHERITANCE_UNRESOLVED; + } + return unlinked_instanceof(fe_ce, zend_ce_traversable) + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } + + return ZEND_TYPE_CODE(fe_type) == IS_ITERABLE || ZEND_TYPE_CODE(fe_type) == IS_ARRAY + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } else if (ZEND_TYPE_CODE(proto_type) == IS_OBJECT) { + if (ZEND_TYPE_IS_CLASS(fe_type)) { + /* Currently, any class name would be allowed here. We still perform a class lookup + * for forward-compatibility reasons, as we may have named types in the future that + * are not classes (such as enums or typedefs). */ + zend_string *fe_class_name = + resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); + zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name); + if (!fe_ce) { + *unresolved_class = fe_class_name; + return INHERITANCE_UNRESOLVED; + } + return INHERITANCE_SUCCESS; + } + + return ZEND_TYPE_CODE(fe_type) == IS_OBJECT ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } else { + return ZEND_TYPE_CODE(fe_type) == ZEND_TYPE_CODE(proto_type) + ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } } /* }}} */ -static int zend_do_perform_arg_type_hint_check(const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ +static inheritance_status zend_do_perform_arg_type_hint_check( + zend_string **unresolved_class, + const zend_function *fe, zend_arg_info *fe_arg_info, + const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ { if (!ZEND_TYPE_IS_SET(fe_arg_info->type)) { /* Child with no type is always compatible */ - return 1; + return INHERITANCE_SUCCESS; } if (!ZEND_TYPE_IS_SET(proto_arg_info->type)) { /* Child defines a type, but parent doesn't, violates LSP */ - return 0; + return INHERITANCE_ERROR; } - return zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info); + /* Contravariant type check is performed as a covariant type check with swapped + * argument order. */ + return zend_perform_covariant_type_check( + unresolved_class, proto, proto_arg_info, fe, fe_arg_info); } /* }}} */ -static zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto) /* {{{ */ +static inheritance_status zend_do_perform_implementation_check( + zend_string **unresolved_class, const zend_function *fe, const zend_function *proto) /* {{{ */ { uint32_t i, num_args; + inheritance_status status, local_status; /* If it's a user function then arg_info == NULL means we don't have any parameters but * we still need to do the arg number checks. We are only willing to ignore this for internal * functions because extensions don't always define arg_info. */ - if (!proto || (!proto->common.arg_info && proto->common.type != ZEND_USER_FUNCTION)) { - return 1; + if (!proto->common.arg_info && proto->common.type != ZEND_USER_FUNCTION) { + return INHERITANCE_SUCCESS; } /* Checks for constructors only if they are declared in an interface, * or explicitly marked as abstract */ - if ((fe->common.fn_flags & ZEND_ACC_CTOR) + ZEND_ASSERT(!((fe->common.fn_flags & ZEND_ACC_CTOR) && ((proto->common.scope->ce_flags & ZEND_ACC_INTERFACE) == 0 - && (proto->common.fn_flags & ZEND_ACC_ABSTRACT) == 0)) { - return 1; - } + && (proto->common.fn_flags & ZEND_ACC_ABSTRACT) == 0))); /* If the prototype method is private do not enforce a signature */ - if (proto->common.fn_flags & ZEND_ACC_PRIVATE) { - return 1; - } + ZEND_ASSERT(!(proto->common.fn_flags & ZEND_ACC_PRIVATE)); /* check number of arguments */ if (proto->common.required_num_args < fe->common.required_num_args || proto->common.num_args > fe->common.num_args) { - return 0; + return INHERITANCE_ERROR; } /* by-ref constraints on return values are covariant */ if ((proto->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) && !(fe->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)) { - return 0; + return INHERITANCE_ERROR; } if ((proto->common.fn_flags & ZEND_ACC_VARIADIC) && !(fe->common.fn_flags & ZEND_ACC_VARIADIC)) { - return 0; + return INHERITANCE_ERROR; } /* For variadic functions any additional (optional) arguments that were added must be @@ -334,6 +472,7 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c } } + status = INHERITANCE_SUCCESS; for (i = 0; i < num_args; i++) { zend_arg_info *fe_arg_info = &fe->common.arg_info[i]; @@ -344,28 +483,20 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - if (!zend_do_perform_arg_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) { - switch (ZEND_TYPE_CODE(fe_arg_info->type)) { - case IS_ITERABLE: - if (!zend_iterable_compatibility_check(proto_arg_info)) { - return 0; - } - break; + local_status = zend_do_perform_arg_type_hint_check( + unresolved_class, fe, fe_arg_info, proto, proto_arg_info); - default: - return 0; + if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { + if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { + return INHERITANCE_ERROR; } - } - - // This introduces BC break described at https://bugs.php.net/bug.php?id=72119 - if (ZEND_TYPE_IS_SET(proto_arg_info->type) && ZEND_TYPE_ALLOW_NULL(proto_arg_info->type) && !ZEND_TYPE_ALLOW_NULL(fe_arg_info->type)) { - /* incompatible nullability */ - return 0; + ZEND_ASSERT(local_status == INHERITANCE_UNRESOLVED); + status = INHERITANCE_UNRESOLVED; } /* by-ref constraints on arguments are invariant */ if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) { - return 0; + return INHERITANCE_ERROR; } } @@ -374,27 +505,22 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c if (proto->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { /* Removing a return type is not valid. */ if (!(fe->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { - return 0; + return INHERITANCE_ERROR; } - if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) { - switch (ZEND_TYPE_CODE(proto->common.arg_info[-1].type)) { - case IS_ITERABLE: - if (!zend_iterable_compatibility_check(fe->common.arg_info - 1)) { - return 0; - } - break; + local_status = zend_perform_covariant_type_check( + unresolved_class, fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); - default: - return 0; + if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { + if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { + return INHERITANCE_ERROR; } - } - - if (ZEND_TYPE_ALLOW_NULL(fe->common.arg_info[-1].type) && !ZEND_TYPE_ALLOW_NULL(proto->common.arg_info[-1].type)) { - return 0; + ZEND_ASSERT(local_status == INHERITANCE_UNRESOLVED); + status = INHERITANCE_UNRESOLVED; } } - return 1; + + return status; } /* }}} */ @@ -559,112 +685,269 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function } /* }}} */ -static void do_inheritance_check_on_method(zend_function *child, zend_function *parent) /* {{{ */ +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, + 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); + if (status == INHERITANCE_UNRESOLVED) { + zend_error_at(error_level, NULL, func_lineno(child), + "Could not check compatibility between %s and %s, because class %s is not available", + ZSTR_VAL(child_prototype), ZSTR_VAL(parent_prototype), ZSTR_VAL(unresolved_class)); + } else { + zend_error_at(error_level, NULL, func_lineno(child), + "Declaration of %s %s be compatible with %s", + ZSTR_VAL(child_prototype), error_verb, ZSTR_VAL(parent_prototype)); + } + zend_string_efree(child_prototype); + 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 perform_delayable_implementation_check( + zend_class_entry *ce, const zend_function *fe, + const zend_function *proto, zend_bool always_error) +{ + zend_string *unresolved_class; + inheritance_status status = zend_do_perform_implementation_check( + &unresolved_class, fe, proto); + + if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { + if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { + add_compatibility_obligation(ce, fe, proto, always_error); + } else { + ZEND_ASSERT(status == INHERITANCE_ERROR); + if (always_error) { + emit_incompatible_method_error( + E_COMPILE_ERROR, "must", fe, proto, status, unresolved_class); + } else { + emit_incompatible_method_error_or_warning( + fe, proto, status, unresolved_class, always_error); + } + } + } +} + +static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(zend_function *child, zend_function *parent, zend_class_entry *ce, zval *child_zv, zend_bool check_only, zend_bool checked) /* {{{ */ { uint32_t child_flags; uint32_t parent_flags = parent->common.fn_flags; + zend_function *proto; - if (UNEXPECTED(parent_flags & ZEND_ACC_FINAL)) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot override final method %s::%s()", ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name)); + if (!checked && UNEXPECTED(parent_flags & ZEND_ACC_FINAL)) { + if (check_only) { + return INHERITANCE_ERROR; + } + zend_error_at_noreturn(E_COMPILE_ERROR, NULL, func_lineno(child), + "Cannot override final method %s::%s()", + ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name)); } child_flags = child->common.fn_flags; /* You cannot change from static to non static and vice versa. */ - if (UNEXPECTED((child_flags & ZEND_ACC_STATIC) != (parent_flags & ZEND_ACC_STATIC))) { + if (!checked && UNEXPECTED((child_flags & ZEND_ACC_STATIC) != (parent_flags & ZEND_ACC_STATIC))) { + if (check_only) { + return INHERITANCE_ERROR; + } if (child_flags & ZEND_ACC_STATIC) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot make non static method %s::%s() static in class %s", ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); + zend_error_at_noreturn(E_COMPILE_ERROR, NULL, func_lineno(child), + "Cannot make non static method %s::%s() static in class %s", + ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); } else { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot make static method %s::%s() non static in class %s", ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); + zend_error_at_noreturn(E_COMPILE_ERROR, NULL, func_lineno(child), + "Cannot make static method %s::%s() non static in class %s", + ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); } } /* Disallow making an inherited method abstract. */ - if (UNEXPECTED((child_flags & ZEND_ACC_ABSTRACT) > (parent_flags & ZEND_ACC_ABSTRACT))) { - zend_error_noreturn(E_COMPILE_ERROR, "Cannot make non abstract method %s::%s() abstract in class %s", ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); - } - - /* Prevent derived classes from restricting access that was available in parent classes (except deriving from non-abstract ctors) */ - if (UNEXPECTED((!(child_flags & ZEND_ACC_CTOR) || (parent_flags & (ZEND_ACC_ABSTRACT | ZEND_ACC_IMPLEMENTED_ABSTRACT))) && - (child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK))) { - zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::%s() must be %s (as in class %s)%s", 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"); + if (!checked && UNEXPECTED((child_flags & ZEND_ACC_ABSTRACT) > (parent_flags & ZEND_ACC_ABSTRACT))) { + if (check_only) { + return INHERITANCE_ERROR; + } + zend_error_at_noreturn(E_COMPILE_ERROR, NULL, func_lineno(child), + "Cannot make non abstract method %s::%s() abstract in class %s", + ZEND_FN_SCOPE_NAME(parent), ZSTR_VAL(child->common.function_name), ZEND_FN_SCOPE_NAME(child)); } - if ((child_flags & ZEND_ACC_PRIVATE) < (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))) { + if (!check_only && (parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED))) { child->common.fn_flags |= ZEND_ACC_CHANGED; } if (parent_flags & ZEND_ACC_PRIVATE) { - child->common.prototype = NULL; - } else if (parent_flags & ZEND_ACC_ABSTRACT) { - child->common.fn_flags |= ZEND_ACC_IMPLEMENTED_ABSTRACT; - child->common.prototype = parent; - } else if (!(parent->common.fn_flags & ZEND_ACC_CTOR)) { - child->common.prototype = parent->common.prototype ? parent->common.prototype : parent; - } else if (parent->common.prototype && (parent->common.prototype->common.scope->ce_flags & ZEND_ACC_INTERFACE)) { - /* ctors only have a prototype if it comes from an interface */ - child->common.prototype = parent->common.prototype ? parent->common.prototype : parent; + return INHERITANCE_SUCCESS; + } + + proto = parent->common.prototype ? + parent->common.prototype : parent; + + if (parent_flags & ZEND_ACC_CTOR) { + /* ctors only have a prototype if is abstract (or comes from an interface) */ /* and if that is the case, we want to check inheritance against it */ - parent = child->common.prototype; - } - - if (UNEXPECTED(!zend_do_perform_implementation_check(child, parent))) { - int error_level; - const char *error_verb; - zend_string *method_prototype = zend_get_function_declaration(parent); - zend_string *child_prototype = zend_get_function_declaration(child); - - 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_do_perform_type_hint_check(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1) || - (ZEND_TYPE_ALLOW_NULL(child->common.arg_info[-1].type) && !ZEND_TYPE_ALLOW_NULL(parent->common.arg_info[-1].type)))) { - error_level = E_COMPILE_ERROR; - error_verb = "must"; - } else { - error_level = E_WARNING; - error_verb = "should"; + if (!(proto->common.fn_flags & ZEND_ACC_ABSTRACT)) { + return INHERITANCE_SUCCESS; + } + parent = proto; + } + + if (!check_only && child->common.prototype != proto) { + do { + if (child->common.scope != ce + && child->type == ZEND_USER_FUNCTION + && !child->op_array.static_variables) { + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + /* Few parent interfaces contain the same method */ + break; + } else if (child_zv) { + /* op_array wasn't duplicated yet */ + zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); + memcpy(new_function, child, sizeof(zend_op_array)); + Z_PTR_P(child_zv) = child = new_function; + } + } + child->common.prototype = proto; + } while (0); + } + + /* Prevent derived classes from restricting access that was available in parent classes (except deriving from non-abstract ctors) */ + if (!checked && (child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK)) { + if (check_only) { + return INHERITANCE_ERROR; } - zend_error(error_level, "Declaration of %s %s be compatible with %s", ZSTR_VAL(child_prototype), error_verb, ZSTR_VAL(method_prototype)); - zend_string_efree(child_prototype); - zend_string_efree(method_prototype); + zend_error_at_noreturn(E_COMPILE_ERROR, NULL, func_lineno(child), + "Access level to %s::%s() must be %s (as in class %s)%s", + 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"); } + + if (!checked) { + if (check_only) { + zend_string *unresolved_class; + + return zend_do_perform_implementation_check( + &unresolved_class, child, parent); + } + perform_delayable_implementation_check( + ce, child, parent, /*always_error*/0); + } + return INHERITANCE_SUCCESS; +} +/* }}} */ + +static zend_never_inline void do_inheritance_check_on_method(zend_function *child, zend_function *parent, zend_class_entry *ce, zval *child_zv) /* {{{ */ +{ + do_inheritance_check_on_method_ex(child, parent, ce, child_zv, 0, 0); } /* }}} */ -static zend_function *do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce) /* {{{ */ +static zend_always_inline void do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce, zend_bool is_interface, zend_bool checked) /* {{{ */ { zval *child = zend_hash_find_ex(&ce->function_table, key, 1); if (child) { zend_function *func = (zend_function*)Z_PTR_P(child); - zend_function *orig_prototype = func->common.prototype; - do_inheritance_check_on_method(func, parent); - if (func->common.prototype != orig_prototype && - func->type == ZEND_USER_FUNCTION && - func->common.scope != ce && - !func->op_array.static_variables) { - /* Lazy duplication */ - zend_function *new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); - memcpy(new_function, func, sizeof(zend_op_array)); - Z_PTR_P(child) = new_function; - func->common.prototype = orig_prototype; + if (is_interface && UNEXPECTED(func == parent)) { + /* The same method in interface may be inherited few times */ + return; + } + + if (checked) { + do_inheritance_check_on_method_ex(func, parent, ce, child, 0, checked); + } else { + do_inheritance_check_on_method(func, parent, ce, child); + } + } else { + + if (is_interface || (parent->common.fn_flags & (ZEND_ACC_ABSTRACT))) { + ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } + + parent = zend_duplicate_function(parent, ce, is_interface); + + if (!is_interface) { + _zend_hash_append_ptr(&ce->function_table, key, parent); + } else { + zend_hash_add_new_ptr(&ce->function_table, key, parent); } - return NULL; + } +} +/* }}} */ + +inheritance_status property_types_compatible( + const zend_property_info *parent_info, const zend_property_info *child_info) { + zend_string *parent_name, *child_name; + zend_class_entry *parent_type_ce, *child_type_ce; + if (parent_info->type == child_info->type) { + return INHERITANCE_SUCCESS; + } + + if (!ZEND_TYPE_IS_CLASS(parent_info->type) || !ZEND_TYPE_IS_CLASS(child_info->type) || + ZEND_TYPE_ALLOW_NULL(parent_info->type) != ZEND_TYPE_ALLOW_NULL(child_info->type)) { + return INHERITANCE_ERROR; + } + + parent_name = ZEND_TYPE_IS_CE(parent_info->type) + ? ZEND_TYPE_CE(parent_info->type)->name + : resolve_class_name(parent_info->ce, ZEND_TYPE_NAME(parent_info->type)); + child_name = ZEND_TYPE_IS_CE(child_info->type) + ? ZEND_TYPE_CE(child_info->type)->name + : resolve_class_name(child_info->ce, ZEND_TYPE_NAME(child_info->type)); + if (zend_string_equals_ci(parent_name, child_name)) { + return INHERITANCE_SUCCESS; } - if (parent->common.fn_flags & (ZEND_ACC_ABSTRACT)) { - ce->ce_flags |= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + /* Check for class aliases */ + parent_type_ce = ZEND_TYPE_IS_CE(parent_info->type) + ? ZEND_TYPE_CE(parent_info->type) + : lookup_class(parent_info->ce, parent_name); + child_type_ce = ZEND_TYPE_IS_CE(child_info->type) + ? ZEND_TYPE_CE(child_info->type) + : lookup_class(child_info->ce, child_name); + if (!parent_type_ce || !child_type_ce) { + return INHERITANCE_UNRESOLVED; } + return parent_type_ce == child_type_ce ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; +} - return zend_duplicate_function(parent, ce); +static void emit_incompatible_property_error( + const zend_property_info *child, const zend_property_info *parent) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type of %s::$%s must be %s%s (as in class %s)", + ZSTR_VAL(child->ce->name), + zend_get_unmangled_property_name(child->name), + ZEND_TYPE_ALLOW_NULL(parent->type) ? "?" : "", + ZEND_TYPE_IS_CLASS(parent->type) + ? ZSTR_VAL(ZEND_TYPE_IS_CE(parent->type) ? ZEND_TYPE_CE(parent->type)->name : resolve_class_name(parent->ce, ZEND_TYPE_NAME(parent->type))) + : zend_get_type_by_const(ZEND_TYPE_CODE(parent->type)), + ZSTR_VAL(parent->ce->name)); } -/* }}} */ static void do_inherit_property(zend_property_info *parent_info, zend_string *key, zend_class_entry *ce) /* {{{ */ { @@ -673,19 +956,16 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke if (UNEXPECTED(child)) { child_info = Z_PTR_P(child); - if (UNEXPECTED(parent_info->flags & (ZEND_ACC_PRIVATE|ZEND_ACC_SHADOW))) { + if (parent_info->flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED)) { child_info->flags |= ZEND_ACC_CHANGED; - } else { + } + if (!(parent_info->flags & ZEND_ACC_PRIVATE)) { if (UNEXPECTED((parent_info->flags & ZEND_ACC_STATIC) != (child_info->flags & ZEND_ACC_STATIC))) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s%s::$%s as %s%s::$%s", (parent_info->flags & ZEND_ACC_STATIC) ? "static " : "non static ", ZSTR_VAL(ce->parent->name), ZSTR_VAL(key), (child_info->flags & ZEND_ACC_STATIC) ? "static " : "non static ", ZSTR_VAL(ce->name), ZSTR_VAL(key)); } - if (parent_info->flags & ZEND_ACC_CHANGED) { - child_info->flags |= ZEND_ACC_CHANGED; - } - if (UNEXPECTED((child_info->flags & ZEND_ACC_PPP_MASK) > (parent_info->flags & ZEND_ACC_PPP_MASK))) { zend_error_noreturn(E_COMPILE_ERROR, "Access level to %s::$%s must be %s (as in class %s)%s", ZSTR_VAL(ce->name), ZSTR_VAL(key), zend_visibility_string(parent_info->flags), ZSTR_VAL(ce->parent->name), (parent_info->flags&ZEND_ACC_PUBLIC) ? "" : " or weaker"); } else if ((child_info->flags & ZEND_ACC_STATIC) == 0) { @@ -698,22 +978,28 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke ZVAL_UNDEF(&ce->default_properties_table[child_num]); child_info->offset = parent_info->offset; } + + if (UNEXPECTED(ZEND_TYPE_IS_SET(parent_info->type))) { + inheritance_status status = property_types_compatible(parent_info, child_info); + if (status == INHERITANCE_ERROR) { + emit_incompatible_property_error(child_info, parent_info); + } + if (status == INHERITANCE_UNRESOLVED) { + add_property_compatibility_obligation(ce, child_info, parent_info); + } + } else if (UNEXPECTED(ZEND_TYPE_IS_SET(child_info->type) && !ZEND_TYPE_IS_SET(parent_info->type))) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type of %s::$%s must not be defined (as in class %s)", + ZSTR_VAL(ce->name), + ZSTR_VAL(key), + ZSTR_VAL(ce->parent->name)); + } } } else { - if (UNEXPECTED(parent_info->flags & ZEND_ACC_PRIVATE)) { - if (UNEXPECTED(ce->type & ZEND_INTERNAL_CLASS)) { - child_info = zend_duplicate_property_info_internal(parent_info); - } else { - child_info = zend_duplicate_property_info(parent_info); - } - child_info->flags &= ~ZEND_ACC_PRIVATE; /* it's not private anymore */ - child_info->flags |= ZEND_ACC_SHADOW; /* but it's a shadow of private */ + if (UNEXPECTED(ce->type & ZEND_INTERNAL_CLASS)) { + child_info = zend_duplicate_property_info_internal(parent_info); } else { - if (UNEXPECTED(ce->type & ZEND_INTERNAL_CLASS)) { - child_info = zend_duplicate_property_info_internal(parent_info); - } else { - child_info = parent_info; - } + child_info = parent_info; } _zend_hash_append_ptr(&ce->properties_info, key, child_info); } @@ -725,21 +1011,17 @@ static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry if (!(ce->ce_flags & ZEND_ACC_INTERFACE) && iface->interface_gets_implemented && iface->interface_gets_implemented(iface, ce) == FAILURE) { zend_error_noreturn(E_CORE_ERROR, "Class %s could not implement interface %s", ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); } - if (UNEXPECTED(ce == iface)) { - zend_error_noreturn(E_ERROR, "Interface %s cannot implement itself", ZSTR_VAL(ce->name)); - } + /* This should be prevented by the class lookup logic. */ + ZEND_ASSERT(ce != iface); } /* }}} */ -ZEND_API void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ +static void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_entry *iface) /* {{{ */ { /* expects interface to be contained in ce's interface list already */ uint32_t i, ce_num, if_num = iface->num_interfaces; zend_class_entry *entry; - if (if_num==0) { - return; - } ce_num = ce->num_interfaces; if (ce->type == ZEND_INTERNAL_CLASS) { @@ -760,6 +1042,7 @@ ZEND_API void zend_do_inherit_interfaces(zend_class_entry *ce, const zend_class_ 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) { @@ -793,7 +1076,46 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa } /* }}} */ -ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce) /* {{{ */ +void zend_build_properties_info_table(zend_class_entry *ce) +{ + zend_property_info **table, *prop; + size_t size; + if (ce->default_properties_count == 0) { + return; + } + + ZEND_ASSERT(ce->properties_info_table == NULL); + size = sizeof(zend_property_info *) * ce->default_properties_count; + if (ce->type == ZEND_USER_CLASS) { + ce->properties_info_table = table = zend_arena_alloc(&CG(arena), size); + } else { + ce->properties_info_table = table = pemalloc(size, 1); + } + + /* Dead slots may be left behind during inheritance. Make sure these are NULLed out. */ + memset(table, 0, size); + + if (ce->parent && ce->parent->default_properties_count != 0) { + zend_property_info **parent_table = ce->parent->properties_info_table; + memcpy( + table, parent_table, + sizeof(zend_property_info *) * ce->parent->default_properties_count + ); + + /* Child did not add any new properties, we are done */ + if (ce->default_properties_count == ce->parent->default_properties_count) { + return; + } + } + + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { + if (prop->ce == ce && (prop->flags & ZEND_ACC_STATIC) == 0) { + table[OBJ_PROP_TO_NUM(prop->offset)] = prop; + } + } ZEND_HASH_FOREACH_END(); +} + +ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, zend_bool checked) /* {{{ */ { zend_property_info *property_info; zend_function *func; @@ -818,10 +1140,24 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent } } + if (ce->parent_name) { + zend_string_release_ex(ce->parent_name, 0); + } ce->parent = parent_ce; + ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT; /* Inherit interfaces */ - zend_do_inherit_interfaces(ce, parent_ce); + if (parent_ce->num_interfaces) { + if (!(ce->ce_flags & ZEND_ACC_IMPLEMENT_INTERFACES)) { + zend_do_inherit_interfaces(ce, parent_ce); + } else { + uint32_t i; + + for (i = 0; i < parent_ce->num_interfaces; i++) { + do_implement_interface(ce, parent_ce->interfaces[i]); + } + } + } /* Inherit properties */ if (parent_ce->default_properties_count) { @@ -836,7 +1172,7 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent do { dst--; src--; - ZVAL_COPY_VALUE(dst, src); + ZVAL_COPY_VALUE_PROP(dst, src); } while (dst != end); pefree(src, ce->type == ZEND_INTERNAL_CLASS); end = ce->default_properties_table; @@ -851,7 +1187,7 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent do { dst--; src--; - ZVAL_COPY_OR_DUP(dst, src); + ZVAL_COPY_OR_DUP_PROP(dst, src); if (Z_OPT_TYPE_P(dst) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; } @@ -861,7 +1197,7 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent do { dst--; src--; - ZVAL_COPY(dst, src); + ZVAL_COPY_PROP(dst, src); if (Z_OPT_TYPE_P(dst) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; } @@ -911,7 +1247,11 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent } } while (dst != end); } else if (ce->type == ZEND_USER_CLASS) { - src = parent_ce->default_static_members_table + parent_ce->default_static_members_count; + if (CE_STATIC_MEMBERS(parent_ce) == NULL) { + ZEND_ASSERT(parent_ce->ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED)); + zend_class_init_statics(parent_ce); + } + src = CE_STATIC_MEMBERS(parent_ce) + parent_ce->default_static_members_count; do { dst--; src--; @@ -937,8 +1277,14 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent } while (dst != end); } ce->default_static_members_count += parent_ce->default_static_members_count; - if (ce->type == ZEND_USER_CLASS) { - ce->static_members_table = ce->default_static_members_table; + if (!ZEND_MAP_PTR(ce->static_members_table)) { + ZEND_ASSERT(ce->type == ZEND_INTERNAL_CLASS); + if (!EG(current_execute_data)) { + ZEND_MAP_PTR_NEW(ce->static_members_table); + } else { + /* internal class loaded by dl() */ + ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table); + } } } @@ -979,24 +1325,25 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent zend_hash_num_elements(&ce->function_table) + zend_hash_num_elements(&parent_ce->function_table), 0); - ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) { - zend_function *new_func = do_inherit_method(key, func, ce); - - if (new_func) { - _zend_hash_append_ptr(&ce->function_table, key, new_func); - } - } ZEND_HASH_FOREACH_END(); + if (checked) { + ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) { + do_inherit_method(key, func, ce, 0, 1); + } ZEND_HASH_FOREACH_END(); + } else { + ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, func) { + do_inherit_method(key, func, ce, 0, 0); + } ZEND_HASH_FOREACH_END(); + } } do_inherit_parent_constructor(ce); - if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS && ce->type == ZEND_INTERNAL_CLASS) { - ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; - } else if (!(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) { - /* The verification will be done in runtime by ZEND_VERIFY_ABSTRACT_CLASS */ - zend_verify_abstract_class(ce); + if (ce->type == ZEND_INTERNAL_CLASS) { + if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) { + ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; + } } - ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_USE_GUARDS); + ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS); } /* }}} */ @@ -1033,15 +1380,37 @@ static void do_inherit_iface_constant(zend_string *name, zend_class_constant *c, } /* }}} */ +static void do_interface_implementation(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ +{ + zend_function *func; + zend_string *key; + zend_class_constant *c; + + ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { + do_inherit_iface_constant(key, c, ce, iface); + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->function_table, key, func) { + do_inherit_method(key, func, ce, 1, 0); + } ZEND_HASH_FOREACH_END(); + + do_implement_interface(ce, iface); + if (iface->num_interfaces) { + zend_do_inherit_interfaces(ce, iface); + } +} +/* }}} */ + ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface) /* {{{ */ { uint32_t i, ignore = 0; uint32_t current_iface_num = ce->num_interfaces; uint32_t parent_iface_num = ce->parent ? ce->parent->num_interfaces : 0; - zend_function *func; zend_string *key; zend_class_constant *c; + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_LINKED); + for (i = 0; i < ce->num_interfaces; i++) { if (ce->interfaces[i] == NULL) { memmove(ce->interfaces + i, ce->interfaces + i + 1, sizeof(zend_class_entry*) * (--ce->num_interfaces - i)); @@ -1069,67 +1438,76 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry } ce->interfaces[ce->num_interfaces++] = iface; - ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->constants_table, key, c) { - do_inherit_iface_constant(key, c, ce, iface); - } ZEND_HASH_FOREACH_END(); - - ZEND_HASH_FOREACH_STR_KEY_PTR(&iface->function_table, key, func) { - zend_function *new_func = do_inherit_method(key, func, ce); - - if (new_func) { - zend_hash_add_new_ptr(&ce->function_table, key, new_func); - } - } ZEND_HASH_FOREACH_END(); - - do_implement_interface(ce, iface); - zend_do_inherit_interfaces(ce, iface); + do_interface_implementation(ce, iface); } } /* }}} */ -ZEND_API void zend_do_implement_trait(zend_class_entry *ce, zend_class_entry *trait) /* {{{ */ +static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry **interfaces) /* {{{ */ { - uint32_t i, ignore = 0; - uint32_t current_trait_num = ce->num_traits; - uint32_t parent_trait_num = ce->parent ? ce->parent->num_traits : 0; + zend_class_entry *iface; + uint32_t num_parent_interfaces = ce->parent ? ce->parent->num_interfaces : 0; + uint32_t num_interfaces = num_parent_interfaces; + zend_string *key; + zend_class_constant *c; + uint32_t i, j; - for (i = 0; i < ce->num_traits; i++) { - if (ce->traits[i] == NULL) { - memmove(ce->traits + i, ce->traits + i + 1, sizeof(zend_class_entry*) * (--ce->num_traits - i)); - i--; - } else if (ce->traits[i] == trait) { - if (i < parent_trait_num) { - ignore = 1; - } + for (i = 0; i < ce->num_interfaces; i++) { + iface = interfaces[num_parent_interfaces + i]; + if (!(iface->ce_flags & ZEND_ACC_LINKED)) { + add_dependency_obligation(ce, iface); } - } - if (!ignore) { - if (ce->num_traits >= current_trait_num) { - if (ce->type == ZEND_INTERNAL_CLASS) { - ce->traits = (zend_class_entry **) realloc(ce->traits, sizeof(zend_class_entry *) * (++current_trait_num)); - } else { - ce->traits = (zend_class_entry **) erealloc(ce->traits, sizeof(zend_class_entry *) * (++current_trait_num)); + if (UNEXPECTED(!(iface->ce_flags & ZEND_ACC_INTERFACE))) { + efree(interfaces); + zend_error_noreturn(E_ERROR, "%s cannot implement %s - it is not an interface", ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); + return; + } + for (j = 0; j < num_interfaces; j++) { + if (interfaces[j] == iface) { + if (j >= num_parent_interfaces) { + efree(interfaces); + zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot implement previously implemented interface %s", ZSTR_VAL(ce->name), ZSTR_VAL(iface->name)); + return; + } + /* skip duplications */ + ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) { + do_inherit_constant_check(&iface->constants_table, c, key, iface); + } ZEND_HASH_FOREACH_END(); + + iface = NULL; + break; } } - ce->traits[ce->num_traits++] = trait; + if (iface) { + interfaces[num_interfaces] = iface; + num_interfaces++; + } } -} -/* }}} */ -static zend_bool zend_traits_method_compatibility_check(zend_function *fn, zend_function *other_fn) /* {{{ */ -{ - uint32_t fn_flags = fn->common.scope->ce_flags; - uint32_t other_flags = other_fn->common.scope->ce_flags; + for (i = 0; i < ce->num_interfaces; i++) { + zend_string_release_ex(ce->interface_names[i].name, 0); + zend_string_release_ex(ce->interface_names[i].lc_name, 0); + } + efree(ce->interface_names); - return zend_do_perform_implementation_check(fn, other_fn) - && ((fn_flags & (ZEND_ACC_FINAL|ZEND_ACC_STATIC)) == - (other_flags & (ZEND_ACC_FINAL|ZEND_ACC_STATIC))); /* equal final and static qualifier */ + ce->num_interfaces = num_interfaces; + ce->interfaces = interfaces; + ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES; + + i = num_parent_interfaces; + for (; i < ce->num_interfaces; i++) { + do_interface_implementation(ce, ce->interfaces[i]); + } } /* }}} */ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zend_function* fe) /* {{{ */ { - if (ZSTR_LEN(ce->name) != ZSTR_LEN(mname) && (ZSTR_VAL(mname)[0] != '_' || ZSTR_VAL(mname)[1] != '_')) { + if (zend_string_equals_literal(mname, "serialize")) { + ce->serialize_func = fe; + } else if (zend_string_equals_literal(mname, "unserialize")) { + ce->unserialize_func = fe; + } else if (ZSTR_LEN(ce->name) != ZSTR_LEN(mname) && (ZSTR_VAL(mname)[0] != '_' || ZSTR_VAL(mname)[1] != '_')) { /* pass */ } else if (zend_string_equals_literal(mname, ZEND_CLONE_FUNC_NAME)) { ce->clone = fe; @@ -1137,9 +1515,9 @@ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zen if (ce->constructor && (!ce->parent || ce->constructor != ce->parent->constructor)) { zend_error_noreturn(E_COMPILE_ERROR, "%s has colliding constructor definitions coming from traits", ZSTR_VAL(ce->name)); } - ce->constructor = fe; fe->common.fn_flags |= ZEND_ACC_CTOR; + ce->constructor = fe; } else if (zend_string_equals_literal(mname, ZEND_DESTRUCTOR_FUNC_NAME)) { - ce->destructor = fe; fe->common.fn_flags |= ZEND_ACC_DTOR; + ce->destructor = fe; } else if (zend_string_equals_literal(mname, ZEND_GET_FUNC_NAME)) { ce->__get = fe; ce->ce_flags |= ZEND_ACC_USE_GUARDS; @@ -1164,7 +1542,7 @@ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zen zend_string *lowercase_name = zend_string_tolower(ce->name); lowercase_name = zend_new_interned_string(lowercase_name); if (!memcmp(ZSTR_VAL(mname), ZSTR_VAL(lowercase_name), ZSTR_LEN(mname))) { - if (ce->constructor && (!ce->parent || ce->constructor != ce->parent->constructor)) { + if (ce->constructor && (!ce->parent || ce->constructor != ce->parent->constructor)) { zend_error_noreturn(E_COMPILE_ERROR, "%s has colliding constructor definitions coming from traits", ZSTR_VAL(ce->name)); } ce->constructor = fe; @@ -1175,7 +1553,7 @@ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zen } /* }}} */ -static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_string *key, zend_function *fn, HashTable **overriden) /* {{{ */ +static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_string *key, zend_function *fn, HashTable **overridden) /* {{{ */ { zend_function *existing_fn = NULL; zend_function *new_fn; @@ -1191,50 +1569,35 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s if (existing_fn->common.scope == ce) { /* members from the current class override trait methods */ - /* use temporary *overriden HashTable to detect hidden conflict */ - if (*overriden) { - if ((existing_fn = zend_hash_find_ptr(*overriden, key)) != NULL) { + /* use temporary *overridden HashTable to detect hidden conflict */ + if (*overridden) { + 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 */ - if (UNEXPECTED(!zend_traits_method_compatibility_check(fn, existing_fn))) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(fn)), - ZSTR_VAL(zend_get_function_declaration(existing_fn))); - } + perform_delayable_implementation_check( + ce, fn, existing_fn, /*always_error*/ 1); } if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) { /* Make sure the abstract declaration is compatible with previous declaration */ - if (UNEXPECTED(!zend_traits_method_compatibility_check(existing_fn, fn))) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(existing_fn)), - ZSTR_VAL(zend_get_function_declaration(fn))); - } + perform_delayable_implementation_check( + ce, existing_fn, fn, /*always_error*/ 1); return; } } } else { - ALLOC_HASHTABLE(*overriden); - zend_hash_init_ex(*overriden, 8, NULL, overriden_ptr_dtor, 0, 0); + ALLOC_HASHTABLE(*overridden); + zend_hash_init_ex(*overridden, 8, NULL, overridden_ptr_dtor, 0, 0); } - zend_hash_update_mem(*overriden, key, fn, sizeof(zend_function)); + zend_hash_update_mem(*overridden, key, fn, sizeof(zend_function)); return; - } 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 */ - if (UNEXPECTED(!zend_traits_method_compatibility_check(fn, existing_fn))) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(fn)), - ZSTR_VAL(zend_get_function_declaration(existing_fn))); - } - } else if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) { + } else if ((fn->common.fn_flags & ZEND_ACC_ABSTRACT) + && !(existing_fn->common.fn_flags & ZEND_ACC_ABSTRACT)) { /* Make sure the abstract declaration is compatible with previous declaration */ - if (UNEXPECTED(!zend_traits_method_compatibility_check(existing_fn, fn))) { - zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s must be compatible with %s", - ZSTR_VAL(zend_get_function_declaration(existing_fn)), - ZSTR_VAL(zend_get_function_declaration(fn))); - } + perform_delayable_implementation_check( + ce, existing_fn, fn, /*always_error*/ 1); return; - } else if (UNEXPECTED(existing_fn->common.scope->ce_flags & ZEND_ACC_TRAIT)) { + } else if (UNEXPECTED((existing_fn->common.scope->ce_flags & ZEND_ACC_TRAIT) + && !(existing_fn->common.fn_flags & ZEND_ACC_ABSTRACT))) { /* two traits can't define the same non-abstract method */ #if 1 zend_error_noreturn(E_COMPILE_ERROR, "Trait method %s has not been applied, because there are collisions with other trait methods on %s", @@ -1248,12 +1611,11 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s } else { /* inherited members are overridden by members inserted by traits */ /* check whether the trait method fulfills the inheritance requirements */ - do_inheritance_check_on_method(fn, existing_fn); + do_inheritance_check_on_method(fn, existing_fn, ce, NULL); fn->common.prototype = NULL; } } - function_add_ref(fn); if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) { new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); memcpy(new_fn, fn, sizeof(zend_internal_function)); @@ -1261,7 +1623,10 @@ static void zend_add_trait_method(zend_class_entry *ce, const char *name, zend_s } else { new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); memcpy(new_fn, fn, sizeof(zend_op_array)); + new_fn->op_array.fn_flags |= ZEND_ACC_TRAIT_CLONE; + new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE; } + function_add_ref(new_fn); fn = zend_hash_update_ptr(&ce->function_table, key, new_fn); zend_add_magic_methods(ce, key, fn); } @@ -1283,7 +1648,7 @@ static void zend_fixup_trait_method(zend_function *fn, zend_class_entry *ce) /* } /* }}} */ -static int zend_traits_copy_functions(zend_string *fnname, zend_function *fn, zend_class_entry *ce, HashTable **overriden, HashTable *exclude_table, zend_class_entry **aliases) /* {{{ */ +static void zend_traits_copy_functions(zend_string *fnname, zend_function *fn, zend_class_entry *ce, HashTable **overridden, HashTable *exclude_table, zend_class_entry **aliases) /* {{{ */ { zend_trait_alias *alias, **alias_ptr; zend_string *lcname; @@ -1309,7 +1674,7 @@ static int zend_traits_copy_functions(zend_string *fnname, zend_function *fn, ze } lcname = zend_string_tolower(alias->alias); - zend_add_trait_method(ce, ZSTR_VAL(alias->alias), lcname, &fn_copy, overriden); + zend_add_trait_method(ce, ZSTR_VAL(alias->alias), lcname, &fn_copy, overridden); zend_string_release_ex(lcname, 0); /* Record the trait from which this alias was resolved. */ @@ -1361,14 +1726,12 @@ static int zend_traits_copy_functions(zend_string *fnname, zend_function *fn, ze } } - zend_add_trait_method(ce, ZSTR_VAL(fn->common.function_name), fnname, &fn_copy, overriden); + zend_add_trait_method(ce, ZSTR_VAL(fn->common.function_name), fnname, &fn_copy, overridden); } - - return ZEND_HASH_APPLY_KEEP; } /* }}} */ -static uint32_t zend_check_trait_usage(zend_class_entry *ce, zend_class_entry *trait) /* {{{ */ +static uint32_t zend_check_trait_usage(zend_class_entry *ce, zend_class_entry *trait, zend_class_entry **traits) /* {{{ */ { uint32_t i; @@ -1378,7 +1741,7 @@ static uint32_t zend_check_trait_usage(zend_class_entry *ce, zend_class_entry *t } for (i = 0; i < ce->num_traits; i++) { - if (ce->traits[i] == trait) { + if (traits[i] == trait) { return i; } } @@ -1387,14 +1750,13 @@ static uint32_t zend_check_trait_usage(zend_class_entry *ce, zend_class_entry *t } /* }}} */ -static void zend_traits_init_trait_structures(zend_class_entry *ce, HashTable ***exclude_tables_ptr, zend_class_entry ***aliases_ptr) /* {{{ */ +static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_entry **traits, HashTable ***exclude_tables_ptr, zend_class_entry ***aliases_ptr) /* {{{ */ { size_t i, j = 0; zend_trait_precedence **precedences; zend_trait_precedence *cur_precedence; zend_trait_method_reference *cur_method_ref; zend_string *lcname; - zend_bool method_exists; HashTable **exclude_tables = NULL; zend_class_entry **aliases = NULL; zend_class_entry *trait; @@ -1413,12 +1775,11 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, HashTable ** if (!trait) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name)); } - zend_check_trait_usage(ce, trait); + zend_check_trait_usage(ce, trait, traits); /** Ensure that the preferred method is actually available. */ lcname = zend_string_tolower(cur_method_ref->method_name); - method_exists = zend_hash_exists(&trait->function_table, lcname); - if (!method_exists) { + if (!zend_hash_exists(&trait->function_table, lcname)) { zend_error_noreturn(E_COMPILE_ERROR, "A precedence rule was defined for %s::%s but this method does not exist", ZSTR_VAL(trait->name), @@ -1440,7 +1801,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, HashTable ** if (!exclude_ce) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(class_name)); } - trait_num = zend_check_trait_usage(ce, exclude_ce); + trait_num = zend_check_trait_usage(ce, exclude_ce, traits); if (!exclude_tables[trait_num]) { ALLOC_HASHTABLE(exclude_tables[trait_num]); zend_hash_init(exclude_tables[trait_num], 0, NULL, NULL, 0); @@ -1481,17 +1842,15 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, HashTable ** if (!trait) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name)); } - zend_check_trait_usage(ce, trait); + zend_check_trait_usage(ce, trait, traits); aliases[i] = trait; /** And, ensure that the referenced method is resolvable, too. */ lcname = zend_string_tolower(cur_method_ref->method_name); - method_exists = zend_hash_exists(&trait->function_table, lcname); - zend_string_release_ex(lcname, 0); - - if (!method_exists) { + if (!zend_hash_exists(&trait->function_table, lcname)) { zend_error_noreturn(E_COMPILE_ERROR, "An alias was defined for %s::%s but this method does not exist", ZSTR_VAL(trait->name), ZSTR_VAL(cur_method_ref->method_name)); } + zend_string_release_ex(lcname, 0); } i++; } @@ -1502,31 +1861,35 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, HashTable ** } /* }}} */ -static void zend_do_traits_method_binding(zend_class_entry *ce, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */ +static void zend_do_traits_method_binding(zend_class_entry *ce, zend_class_entry **traits, HashTable **exclude_tables, zend_class_entry **aliases) /* {{{ */ { uint32_t i; - HashTable *overriden = NULL; + HashTable *overridden = NULL; zend_string *key; zend_function *fn; if (exclude_tables) { for (i = 0; i < ce->num_traits; i++) { - /* copies functions, applies defined aliasing, and excludes unused trait methods */ - ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->traits[i]->function_table, key, fn) { - zend_traits_copy_functions(key, fn, ce, &overriden, exclude_tables[i], aliases); - } ZEND_HASH_FOREACH_END(); - - if (exclude_tables[i]) { - zend_hash_destroy(exclude_tables[i]); - FREE_HASHTABLE(exclude_tables[i]); - exclude_tables[i] = NULL; + if (traits[i]) { + /* copies functions, applies defined aliasing, and excludes unused trait methods */ + ZEND_HASH_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) { + zend_traits_copy_functions(key, fn, ce, &overridden, exclude_tables[i], aliases); + } ZEND_HASH_FOREACH_END(); + + if (exclude_tables[i]) { + zend_hash_destroy(exclude_tables[i]); + FREE_HASHTABLE(exclude_tables[i]); + exclude_tables[i] = NULL; + } } } } else { for (i = 0; i < ce->num_traits; i++) { - ZEND_HASH_FOREACH_STR_KEY_PTR(&ce->traits[i]->function_table, key, fn) { - zend_traits_copy_functions(key, fn, ce, &overriden, NULL, aliases); - } ZEND_HASH_FOREACH_END(); + if (traits[i]) { + ZEND_HASH_FOREACH_STR_KEY_PTR(&traits[i]->function_table, key, fn) { + zend_traits_copy_functions(key, fn, ce, &overridden, NULL, aliases); + } ZEND_HASH_FOREACH_END(); + } } } @@ -1534,21 +1897,22 @@ static void zend_do_traits_method_binding(zend_class_entry *ce, HashTable **excl zend_fixup_trait_method(fn, ce); } ZEND_HASH_FOREACH_END(); - if (overriden) { - zend_hash_destroy(overriden); - FREE_HASHTABLE(overriden); + if (overridden) { + zend_hash_destroy(overridden); + FREE_HASHTABLE(overridden); } } /* }}} */ -static zend_class_entry* find_first_definition(zend_class_entry *ce, size_t current_trait, zend_string *prop_name, zend_class_entry *coliding_ce) /* {{{ */ +static zend_class_entry* find_first_definition(zend_class_entry *ce, zend_class_entry **traits, size_t current_trait, zend_string *prop_name, zend_class_entry *coliding_ce) /* {{{ */ { size_t i; if (coliding_ce == ce) { for (i = 0; i < current_trait; i++) { - if (zend_hash_exists(&ce->traits[i]->properties_info, prop_name)) { - return ce->traits[i]; + if (traits[i] + && zend_hash_exists(&traits[i]->properties_info, prop_name)) { + return traits[i]; } } } @@ -1557,7 +1921,7 @@ static zend_class_entry* find_first_definition(zend_class_entry *ce, size_t curr } /* }}} */ -static void zend_do_traits_property_binding(zend_class_entry *ce) /* {{{ */ +static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */ { size_t i; zend_property_info *property_info; @@ -1575,7 +1939,10 @@ static void zend_do_traits_property_binding(zend_class_entry *ce) /* {{{ */ * - if compatible, then strict notice */ for (i = 0; i < ce->num_traits; i++) { - ZEND_HASH_FOREACH_PTR(&ce->traits[i]->properties_info, property_info) { + if (!traits[i]) { + continue; + } + ZEND_HASH_FOREACH_PTR(&traits[i]->properties_info, property_info) { /* first get the unmangeld name if necessary, * then check whether the property is already there */ @@ -1594,34 +1961,28 @@ static void zend_do_traits_property_binding(zend_class_entry *ce) /* {{{ */ /* next: check for conflicts with current class */ if ((coliding_prop = zend_hash_find_ptr(&ce->properties_info, prop_name)) != NULL) { - if (coliding_prop->flags & ZEND_ACC_SHADOW) { - /* Only free if shadow is coming from direct parent, - * otherwise these weren't copied in the first place. */ - if (coliding_prop->ce == ce->parent) { - zend_string_release_ex(coliding_prop->name, 0); - if (coliding_prop->doc_comment) { - zend_string_release_ex(coliding_prop->doc_comment, 0); - } - } + if ((coliding_prop->flags & ZEND_ACC_PRIVATE) && coliding_prop->ce != ce) { zend_hash_del(&ce->properties_info, prop_name); flags |= ZEND_ACC_CHANGED; } else { not_compatible = 1; if ((coliding_prop->flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) - == (flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC))) { + == (flags & (ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC)) && + property_types_compatible(property_info, coliding_prop) == INHERITANCE_SUCCESS + ) { /* the flags are identical, thus, the properties may be compatible */ zval *op1, *op2; zval op1_tmp, op2_tmp; if (flags & ZEND_ACC_STATIC) { op1 = &ce->default_static_members_table[coliding_prop->offset]; - op2 = &ce->traits[i]->default_static_members_table[property_info->offset]; + op2 = &traits[i]->default_static_members_table[property_info->offset]; ZVAL_DEINDIRECT(op1); ZVAL_DEINDIRECT(op2); } else { op1 = &ce->default_properties_table[OBJ_PROP_TO_NUM(coliding_prop->offset)]; - op2 = &ce->traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; + op2 = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; } /* if any of the values is a constant, we try to resolve it */ @@ -1649,7 +2010,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce) /* {{{ */ if (not_compatible) { zend_error_noreturn(E_COMPILE_ERROR, "%s and %s define the same property ($%s) in the composition of %s. However, the definition differs and is considered incompatible. Class was composed", - ZSTR_VAL(find_first_definition(ce, i, prop_name, coliding_prop->ce)->name), + ZSTR_VAL(find_first_definition(ce, traits, i, prop_name, coliding_prop->ce)->name), ZSTR_VAL(property_info->ce->name), ZSTR_VAL(prop_name), ZSTR_VAL(ce->name)); @@ -1662,17 +2023,18 @@ static void zend_do_traits_property_binding(zend_class_entry *ce) /* {{{ */ /* property not found, so lets add it */ if (flags & ZEND_ACC_STATIC) { - prop_value = &ce->traits[i]->default_static_members_table[property_info->offset]; + prop_value = &traits[i]->default_static_members_table[property_info->offset]; ZEND_ASSERT(Z_TYPE_P(prop_value) != IS_INDIRECT); } else { - prop_value = &ce->traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; + prop_value = &traits[i]->default_properties_table[OBJ_PROP_TO_NUM(property_info->offset)]; } Z_TRY_ADDREF_P(prop_value); doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; - zend_declare_property_ex(ce, prop_name, - prop_value, flags, - doc_comment); + if (ZEND_TYPE_IS_NAME(property_info->type)) { + zend_string_addref(ZEND_TYPE_NAME(property_info->type)); + } + zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, property_info->type); zend_string_release_ex(prop_name, 0); } ZEND_HASH_FOREACH_END(); } @@ -1699,7 +2061,7 @@ static void zend_do_check_for_inconsistent_traits_aliasing(zend_class_entry *ce, ZSTR_VAL(cur_alias->trait_method.method_name)); } else { /** Here are two possible cases: - 1) this is an attempt to modifiy the visibility + 1) this is an attempt to modify the visibility of a method introduce as part of another alias. Since that seems to violate the DRY principle, we check against it and abort. @@ -1729,20 +2091,42 @@ static void zend_do_check_for_inconsistent_traits_aliasing(zend_class_entry *ce, } /* }}} */ -ZEND_API void zend_do_bind_traits(zend_class_entry *ce) /* {{{ */ +static void zend_do_bind_traits(zend_class_entry *ce) /* {{{ */ { HashTable **exclude_tables; zend_class_entry **aliases; + zend_class_entry **traits, *trait; + uint32_t i, j; - if (ce->num_traits == 0) { - return; + ZEND_ASSERT(ce->num_traits > 0); + + traits = emalloc(sizeof(zend_class_entry*) * ce->num_traits); + + for (i = 0; i < ce->num_traits; i++) { + trait = zend_fetch_class_by_name(ce->trait_names[i].name, + ce->trait_names[i].lc_name, ZEND_FETCH_CLASS_TRAIT); + if (UNEXPECTED(trait == NULL)) { + return; + } + if (UNEXPECTED(!(trait->ce_flags & ZEND_ACC_TRAIT))) { + zend_error_noreturn(E_ERROR, "%s cannot use %s - it is not a trait", ZSTR_VAL(ce->name), ZSTR_VAL(trait->name)); + return; + } + for (j = 0; j < i; j++) { + if (traits[j] == trait) { + /* skip duplications */ + trait = NULL; + break; + } + } + traits[i] = trait; } /* complete initialization of trait strutures in ce */ - zend_traits_init_trait_structures(ce, &exclude_tables, &aliases); + zend_traits_init_trait_structures(ce, traits, &exclude_tables, &aliases); /* first care about all methods to be flattened into the class */ - zend_do_traits_method_binding(ce, exclude_tables, aliases); + zend_do_traits_method_binding(ce, traits, exclude_tables, aliases); /* Aliases which have not been applied indicate typos/bugs. */ zend_do_check_for_inconsistent_traits_aliasing(ce, aliases); @@ -1756,18 +2140,12 @@ ZEND_API void zend_do_bind_traits(zend_class_entry *ce) /* {{{ */ } /* then flatten the properties into it, to, mostly to notfiy developer about problems */ - zend_do_traits_property_binding(ce); + zend_do_traits_property_binding(ce, traits); - /* verify that all abstract methods from traits have been implemented */ - zend_verify_abstract_class(ce); + efree(traits); /* Emit E_DEPRECATED for PHP 4 constructors */ zend_check_deprecated_constructor(ce); - - /* now everything should be fine and an added ZEND_ACC_IMPLICIT_ABSTRACT_CLASS should be removed */ - if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) { - ce->ce_flags -= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; - } } /* }}} */ @@ -1794,12 +2172,441 @@ void zend_check_deprecated_constructor(const zend_class_entry *ce) /* {{{ */ } /* }}} */ -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * indent-tabs-mode: t - * End: - * vim600: sw=4 ts=4 fdm=marker - * vim<600: sw=4 ts=4 - */ +#define MAX_ABSTRACT_INFO_CNT 3 +#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s" +#define DISPLAY_ABSTRACT_FN(idx) \ + ai.afn[idx] ? ZEND_FN_SCOPE_NAME(ai.afn[idx]) : "", \ + ai.afn[idx] ? "::" : "", \ + ai.afn[idx] ? ZSTR_VAL(ai.afn[idx]->common.function_name) : "", \ + ai.afn[idx] && ai.afn[idx + 1] ? ", " : (ai.afn[idx] && ai.cnt > MAX_ABSTRACT_INFO_CNT ? ", ..." : "") + +typedef struct _zend_abstract_info { + zend_function *afn[MAX_ABSTRACT_INFO_CNT + 1]; + int cnt; + int ctor; +} zend_abstract_info; + +static void zend_verify_abstract_class_function(zend_function *fn, zend_abstract_info *ai) /* {{{ */ +{ + if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) { + if (ai->cnt < MAX_ABSTRACT_INFO_CNT) { + ai->afn[ai->cnt] = fn; + } + if (fn->common.fn_flags & ZEND_ACC_CTOR) { + if (!ai->ctor) { + ai->cnt++; + ai->ctor = 1; + } else { + ai->afn[ai->cnt] = NULL; + } + } else { + ai->cnt++; + } + } +} +/* }}} */ + +void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ +{ + zend_function *func; + zend_abstract_info ai; + + ZEND_ASSERT((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS); + memset(&ai, 0, sizeof(ai)); + + ZEND_HASH_FOREACH_PTR(&ce->function_table, func) { + zend_verify_abstract_class_function(func, &ai); + } ZEND_HASH_FOREACH_END(); + + if (ai.cnt) { + zend_error_noreturn(E_ERROR, "Class %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")", + ZSTR_VAL(ce->name), ai.cnt, + ai.cnt > 1 ? "s" : "", + DISPLAY_ABSTRACT_FN(0), + DISPLAY_ABSTRACT_FN(1), + DISPLAY_ABSTRACT_FN(2) + ); + } else { + /* now everything should be fine and an added ZEND_ACC_IMPLICIT_ABSTRACT_CLASS should be removed */ + ce->ce_flags &= ~ZEND_ACC_IMPLICIT_ABSTRACT_CLASS; + } +} +/* }}} */ + +typedef struct { + enum { + OBLIGATION_DEPENDENCY, + OBLIGATION_COMPATIBILITY, + OBLIGATION_PROPERTY_COMPATIBILITY + } type; + union { + zend_class_entry *dependency_ce; + struct { + /* Traits may use temporary on-stack functions during inheritance checks, + * so use copies of functions here as well. */ + zend_function parent_fn; + zend_function child_fn; + zend_bool always_error; + }; + struct { + const zend_property_info *parent_prop; + const zend_property_info *child_prop; + }; + }; +} 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; + /* Copy functions, because they may be stack-allocated in the case of traits. */ + if (child_fn->common.type == ZEND_INTERNAL_FUNCTION) { + memcpy(&obligation->child_fn, child_fn, sizeof(zend_internal_function)); + } else { + memcpy(&obligation->child_fn, child_fn, sizeof(zend_op_array)); + } + if (parent_fn->common.type == ZEND_INTERNAL_FUNCTION) { + memcpy(&obligation->parent_fn, parent_fn, sizeof(zend_internal_function)); + } else { + memcpy(&obligation->parent_fn, parent_fn, sizeof(zend_op_array)); + } + obligation->always_error = always_error; + zend_hash_next_index_insert_ptr(obligations, obligation); +} + +static void add_property_compatibility_obligation( + zend_class_entry *ce, const zend_property_info *child_prop, + const zend_property_info *parent_prop) { + HashTable *obligations = get_or_init_obligations_for_class(ce); + variance_obligation *obligation = emalloc(sizeof(variance_obligation)); + obligation->type = OBLIGATION_PROPERTY_COMPATIBILITY; + obligation->child_prop = child_prop; + obligation->parent_prop = parent_prop; + 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 if (obligation->type == OBLIGATION_COMPATIBILITY) { + zend_string *unresolved_class; + inheritance_status status = zend_do_perform_implementation_check( + &unresolved_class, &obligation->child_fn, &obligation->parent_fn); + + if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { + if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { + return ZEND_HASH_APPLY_KEEP; + } + ZEND_ASSERT(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. */ + } else { + ZEND_ASSERT(obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY); + inheritance_status status = + property_types_compatible(obligation->parent_prop, obligation->child_prop); + if (status != INHERITANCE_SUCCESS) { + if (status == INHERITANCE_UNRESOLVED) { + return ZEND_HASH_APPLY_KEEP; + } + ZEND_ASSERT(status == INHERITANCE_ERROR); + emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); + } + } + 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; + + if (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); + } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { + emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); + } else { + zend_error_noreturn(E_CORE_ERROR, "Bug #78647"); + } + } 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); +} + +static void check_unrecoverable_load_failure(zend_class_entry *ce) { + /* If this class has been used while unlinked through a variance obligation, it is not legal + * to remove the class from the class table and throw an exception, because there is already + * a dependence on the inheritance hierarchy of this specific class. Instead we fall back to + * a fatal error, as would happen if we did not allow exceptions in the first place. */ + if (ce->ce_flags & ZEND_ACC_HAS_UNLINKED_USES) { + zend_string *exception_str; + zval exception_zv; + ZEND_ASSERT(EG(exception) && "Exception must have been thrown"); + ZVAL_OBJ(&exception_zv, EG(exception)); + Z_ADDREF(exception_zv); + zend_clear_exception(); + exception_str = zval_get_string(&exception_zv); + zend_error_noreturn(E_ERROR, + "During inheritance of %s with variance dependencies: Uncaught %s", ZSTR_VAL(ce->name), ZSTR_VAL(exception_str)); + } +} + +ZEND_API int zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_name) /* {{{ */ +{ + /* Load parent/interface dependencies first, so we can still gracefully abort linking + * with an exception and remove the class from the class table. This is only possible + * if no variance obligations on the current class have been added during autoloading. */ + zend_class_entry *parent = NULL; + zend_class_entry **interfaces = NULL; + + if (ce->parent_name) { + parent = zend_fetch_class_by_name( + ce->parent_name, lc_parent_name, + ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); + if (!parent) { + check_unrecoverable_load_failure(ce); + return FAILURE; + } + } + + if (ce->num_interfaces) { + /* Also copy the parent interfaces here, so we don't need to reallocate later. */ + uint32_t i, num_parent_interfaces = parent ? parent->num_interfaces : 0; + interfaces = emalloc( + sizeof(zend_class_entry *) * (ce->num_interfaces + num_parent_interfaces)); + if (num_parent_interfaces) { + memcpy(interfaces, parent->interfaces, + sizeof(zend_class_entry *) * num_parent_interfaces); + } + for (i = 0; i < ce->num_interfaces; i++) { + zend_class_entry *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_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); + if (!iface) { + check_unrecoverable_load_failure(ce); + efree(interfaces); + return FAILURE; + } + interfaces[num_parent_interfaces + i] = iface; + } + } + + if (parent) { + 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) { + zend_do_bind_traits(ce); + } + if (ce->ce_flags & ZEND_ACC_IMPLEMENT_INTERFACES) { + zend_do_implement_interfaces(ce, interfaces); + } + if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) { + zend_verify_abstract_class(ce); + } + + zend_build_properties_info_table(ce); + + if (!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)) { + ce->ce_flags |= ZEND_ACC_LINKED; + return SUCCESS; + } + + ce->ce_flags |= ZEND_ACC_NEARLY_LINKED; + 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); + } + } + + return SUCCESS; +} +/* }}} */ + +/* Check whether early binding is prevented due to unresolved types in inheritance checks. */ +static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce) /* {{{ */ +{ + inheritance_status ret = INHERITANCE_SUCCESS; + zend_string *key; + zend_function *parent_func; + zend_property_info *parent_info; + + ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->function_table, key, parent_func) { + zval *zv = zend_hash_find_ex(&ce->function_table, key, 1); + if (zv) { + zend_function *child_func = Z_FUNC_P(zv); + inheritance_status status = + do_inheritance_check_on_method_ex(child_func, parent_func, ce, NULL, 1, 0); + + if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { + if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { + return INHERITANCE_UNRESOLVED; + } + ZEND_ASSERT(status == INHERITANCE_ERROR); + ret = INHERITANCE_ERROR; + } + } + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_FOREACH_STR_KEY_PTR(&parent_ce->properties_info, key, parent_info) { + zval *zv; + if ((parent_info->flags & ZEND_ACC_PRIVATE) || !ZEND_TYPE_IS_SET(parent_info->type)) { + continue; + } + + zv = zend_hash_find_ex(&ce->properties_info, key, 1); + if (zv) { + zend_property_info *child_info = Z_PTR_P(zv); + if (ZEND_TYPE_IS_SET(child_info->type)) { + inheritance_status status = property_types_compatible(parent_info, child_info); + if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { + if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { + return INHERITANCE_UNRESOLVED; + } + ZEND_ASSERT(status == INHERITANCE_ERROR); + ret = INHERITANCE_ERROR; + } + } + } + } ZEND_HASH_FOREACH_END(); + + return ret; +} +/* }}} */ + +zend_bool zend_try_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce, zend_string *lcname, zval *delayed_early_binding) /* {{{ */ +{ + inheritance_status status = zend_can_early_bind(ce, parent_ce); + + if (EXPECTED(status != INHERITANCE_UNRESOLVED)) { + if (delayed_early_binding) { + if (UNEXPECTED(zend_hash_set_bucket_key(EG(class_table), (Bucket*)delayed_early_binding, lcname) == NULL)) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name)); + return 0; + } + } else { + if (UNEXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) == NULL)) { + return 0; + } + } + zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS); + zend_build_properties_info_table(ce); + if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) { + zend_verify_abstract_class(ce); + } + ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)); + ce->ce_flags |= ZEND_ACC_LINKED; + return 1; + } + return 0; +} +/* }}} */ |
