diff options
Diffstat (limited to 'Zend/zend_inheritance.c')
-rw-r--r-- | Zend/zend_inheritance.c | 627 |
1 files changed, 512 insertions, 115 deletions
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 4b161692af..a78a0bd4f7 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -26,6 +26,10 @@ #include "zend_smart_str.h" #include "zend_operators.h" #include "zend_exceptions.h" +#include "zend_enum.h" + +ZEND_API zend_class_entry* (*zend_inheritance_cache_get)(zend_class_entry *ce, zend_class_entry *parent, zend_class_entry **traits_and_interfaces) = NULL; +ZEND_API zend_class_entry* (*zend_inheritance_cache_add)(zend_class_entry *ce, zend_class_entry *proto, zend_class_entry *parent, zend_class_entry **traits_and_interfaces, HashTable *dependencies) = NULL; static void add_dependency_obligation(zend_class_entry *ce, zend_class_entry *dependency_ce); static void add_compatibility_obligation( @@ -35,7 +39,7 @@ static void add_property_compatibility_obligation( zend_class_entry *ce, const zend_property_info *child_prop, const zend_property_info *parent_prop); -static void zend_type_copy_ctor(zend_type *type, zend_bool persistent) { +static void zend_type_copy_ctor(zend_type *type, bool persistent) { if (ZEND_TYPE_HAS_LIST(*type)) { zend_type_list *old_list = ZEND_TYPE_LIST(*type); size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types); @@ -86,32 +90,14 @@ static zend_function *zend_duplicate_internal_function(zend_function *func, zend 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 (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); - } - - HashTable *static_properties_ptr = ZEND_MAP_PTR_GET(func->op_array.static_variables_ptr); - if (static_properties_ptr) { - /* See: Zend/tests/method_static_var.phpt */ - ZEND_MAP_PTR_SET(new_function->op_array.static_variables_ptr, static_properties_ptr); - GC_TRY_ADDREF(static_properties_ptr); - } else { - GC_TRY_ADDREF(new_function->op_array.static_variables); - } - - return new_function; + zend_op_array *new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); + memcpy(new_op_array, func, sizeof(zend_op_array)); + zend_init_static_variables_map_ptr(new_op_array); + return (zend_function *) new_op_array; } /* }}} */ -static zend_always_inline zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce, zend_bool is_interface) /* {{{ */ +static zend_always_inline zend_function *zend_duplicate_function(zend_function *func, zend_class_entry *ce, bool is_interface) /* {{{ */ { if (UNEXPECTED(func->type == ZEND_INTERNAL_FUNCTION)) { return zend_duplicate_internal_function(func, ce); @@ -229,7 +215,7 @@ static zend_string *resolve_class_name(zend_class_entry *scope, zend_string *nam } } -static zend_bool class_visible(zend_class_entry *ce) { +static bool class_visible(zend_class_entry *ce) { if (ce->type == ZEND_INTERNAL_CLASS) { return !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES); } else { @@ -240,7 +226,7 @@ static zend_bool class_visible(zend_class_entry *ce) { } static zend_class_entry *lookup_class( - zend_class_entry *scope, zend_string *name, zend_bool register_unresolved) { + zend_class_entry *scope, zend_string *name, bool register_unresolved) { uint32_t flags = ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD; zend_class_entry *ce = zend_lookup_class_ex(name, NULL, flags); if (!CG(in_compilation)) { @@ -271,7 +257,7 @@ static zend_class_entry *lookup_class( } /* Instanceof that's safe to use on unlinked classes. */ -static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) { +static bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce2) { if (ce1 == ce2) { return 1; } @@ -322,7 +308,7 @@ static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce return 0; } -static zend_bool zend_type_contains_traversable(zend_type type) { +static bool zend_type_contains_traversable(zend_type type) { zend_type *single_type; if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) { return 1; @@ -337,7 +323,7 @@ static zend_bool zend_type_contains_traversable(zend_type type) { return 0; } -static zend_bool zend_type_permits_self( +static bool zend_type_permits_self( zend_type type, zend_class_entry *scope, zend_class_entry *self) { if (ZEND_TYPE_FULL_MASK(type) & MAY_BE_OBJECT) { return 1; @@ -368,11 +354,52 @@ typedef enum { INHERITANCE_SUCCESS = 1, } inheritance_status; + +static void track_class_dependency(zend_class_entry *ce, zend_string *class_name) +{ + HashTable *ht; + + if (!CG(current_linking_class) || ce == CG(current_linking_class)) { + return; + } else if (!class_name) { + class_name = ce->name; + } else if (zend_string_equals_literal_ci(class_name, "self") + || zend_string_equals_literal_ci(class_name, "parent")) { + return; + } + + ht = (HashTable*)CG(current_linking_class)->inheritance_cache; + +#ifndef ZEND_WIN32 + if (ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { +#else + if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { +#endif + // TODO: dependency on not-immutable class ??? + if (ht) { + zend_hash_destroy(ht); + FREE_HASHTABLE(ht); + CG(current_linking_class)->inheritance_cache = NULL; + } + CG(current_linking_class)->ce_flags &= ~ZEND_ACC_CACHEABLE; + CG(current_linking_class) = NULL; + return; + } + + /* Record dependency */ + if (!ht) { + ALLOC_HASHTABLE(ht); + zend_hash_init(ht, 0, NULL, NULL, 0); + CG(current_linking_class)->inheritance_cache = (zend_inheritance_cache_entry*)ht; + } + zend_hash_add_ptr(ht, class_name, ce); +} + static inheritance_status zend_perform_covariant_class_type_check( zend_class_entry *fe_scope, zend_string *fe_class_name, zend_class_entry *fe_ce, zend_class_entry *proto_scope, zend_type proto_type, - zend_bool register_unresolved) { - zend_bool have_unresolved = 0; + bool register_unresolved) { + bool have_unresolved = 0; if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) { /* 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 @@ -381,6 +408,7 @@ static inheritance_status zend_perform_covariant_class_type_check( if (!fe_ce) { have_unresolved = 1; } else { + track_class_dependency(fe_ce, fe_class_name); return INHERITANCE_SUCCESS; } } @@ -389,6 +417,7 @@ static inheritance_status zend_perform_covariant_class_type_check( if (!fe_ce) { have_unresolved = 1; } else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { + track_class_dependency(fe_ce, fe_class_name); return INHERITANCE_SUCCESS; } } @@ -396,8 +425,9 @@ static inheritance_status zend_perform_covariant_class_type_check( zend_type *single_type; ZEND_TYPE_FOREACH(proto_type, single_type) { zend_class_entry *proto_ce; + zend_string *proto_class_name = NULL; if (ZEND_TYPE_HAS_NAME(*single_type)) { - zend_string *proto_class_name = + proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); if (zend_string_equals_ci(fe_class_name, proto_class_name)) { return INHERITANCE_SUCCESS; @@ -416,6 +446,8 @@ static inheritance_status zend_perform_covariant_class_type_check( if (!fe_ce || !proto_ce) { have_unresolved = 1; } else if (unlinked_instanceof(fe_ce, proto_ce)) { + track_class_dependency(fe_ce, fe_class_name); + track_class_dependency(proto_ce, proto_class_name); return INHERITANCE_SUCCESS; } } ZEND_TYPE_FOREACH_END(); @@ -458,7 +490,7 @@ static inheritance_status zend_perform_covariant_type_check( } zend_type *single_type; - zend_bool all_success = 1; + bool all_success = 1; /* First try to check whether we can succeed without resolving anything */ ZEND_TYPE_FOREACH(fe_type, single_type) { @@ -540,7 +572,7 @@ static inheritance_status zend_do_perform_implementation_check( { uint32_t i, num_args, proto_num_args, fe_num_args; inheritance_status status, local_status; - zend_bool proto_is_variadic, fe_is_variadic; + bool proto_is_variadic, fe_is_variadic; /* Checks for constructors only if they are declared in an interface, * or explicitly marked as abstract @@ -663,8 +695,12 @@ static ZEND_COLD zend_string *zend_get_function_declaration( } if (fptr->common.scope) { - /* cut off on NULL byte ... class@anonymous */ - smart_str_appendl(&str, ZSTR_VAL(fptr->common.scope->name), strlen(ZSTR_VAL(fptr->common.scope->name))); + if (fptr->common.scope->ce_flags & ZEND_ACC_ANON_CLASS) { + /* cut off on NULL byte ... class@anonymous */ + smart_str_appends(&str, ZSTR_VAL(fptr->common.scope->name)); + } else { + smart_str_appendl(&str, ZSTR_VAL(fptr->common.scope->name), ZSTR_LEN(fptr->common.scope->name)); + } smart_str_appends(&str, "::"); } @@ -833,7 +869,7 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex( zend_function *child, zend_class_entry *child_scope, zend_function *parent, zend_class_entry *parent_scope, zend_class_entry *ce, zval *child_zv, - zend_bool check_visibility, zend_bool check_only, zend_bool checked) /* {{{ */ + bool check_visibility, bool check_only, bool checked) /* {{{ */ { uint32_t child_flags; uint32_t parent_flags = parent->common.fn_flags; @@ -943,12 +979,12 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex( static zend_never_inline void do_inheritance_check_on_method( zend_function *child, zend_class_entry *child_scope, zend_function *parent, zend_class_entry *parent_scope, - zend_class_entry *ce, zval *child_zv, zend_bool check_visibility) + zend_class_entry *ce, zval *child_zv, bool check_visibility) { do_inheritance_check_on_method_ex(child, child_scope, parent, parent_scope, ce, child_zv, check_visibility, 0, 0); } -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) /* {{{ */ +static zend_always_inline void do_inherit_method(zend_string *key, zend_function *parent, zend_class_entry *ce, bool is_interface, bool checked) /* {{{ */ { zval *child = zend_hash_find_ex(&ce->function_table, key, 1); @@ -1139,6 +1175,12 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa } else if (!(Z_ACCESS_FLAGS(parent_const->value) & ZEND_ACC_PRIVATE)) { if (Z_TYPE(parent_const->value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + if (ce->parent->ce_flags & ZEND_ACC_IMMUTABLE) { + c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant)); + memcpy(c, parent_const, sizeof(zend_class_constant)); + parent_const = c; + } } if (ce->type & ZEND_INTERNAL_CLASS) { c = pemalloc(sizeof(zend_class_constant), 1); @@ -1189,7 +1231,7 @@ void zend_build_properties_info_table(zend_class_entry *ce) } ZEND_HASH_FOREACH_END(); } -ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, zend_bool checked) /* {{{ */ +ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, bool checked) /* {{{ */ { zend_property_info *property_info; zend_function *func; @@ -1251,6 +1293,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par ZVAL_COPY_OR_DUP_PROP(dst, src); if (Z_OPT_TYPE_P(dst) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; } continue; } while (dst != end); @@ -1261,6 +1304,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par ZVAL_COPY_PROP(dst, src); if (Z_OPT_TYPE_P(dst) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + ce->ce_flags |= ZEND_ACC_HAS_AST_PROPERTIES; } continue; } while (dst != end); @@ -1323,6 +1367,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } if (Z_TYPE_P(Z_INDIRECT_P(dst)) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + ce->ce_flags |= ZEND_ACC_HAS_AST_STATICS; } } while (dst != end); } else { @@ -1412,7 +1457,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par } /* }}} */ -static zend_bool do_inherit_constant_check(HashTable *child_constants_table, zend_class_constant *parent_constant, zend_string *name, const zend_class_entry *iface) /* {{{ */ +static bool do_inherit_constant_check(HashTable *child_constants_table, zend_class_constant *parent_constant, zend_string *name, const zend_class_entry *iface) /* {{{ */ { zval *zv = zend_hash_find_ex(child_constants_table, name, 1); zend_class_constant *old_constant; @@ -1434,6 +1479,12 @@ static void do_inherit_iface_constant(zend_string *name, zend_class_constant *c, zend_class_constant *ct; if (Z_TYPE(c->value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; + ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + if (iface->ce_flags & ZEND_ACC_IMMUTABLE) { + ct = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant)); + memcpy(ct, c, sizeof(zend_class_constant)); + c = ct; + } } if (ce->type & ZEND_INTERNAL_CLASS) { ct = pemalloc(sizeof(zend_class_constant), 1); @@ -1549,11 +1600,13 @@ static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry } } - 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); + if (!(ce->ce_flags & ZEND_ACC_CACHED)) { + 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); } - efree(ce->interface_names); ce->num_interfaces = num_interfaces; ce->interfaces = interfaces; @@ -1745,6 +1798,7 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e zend_trait_precedence **precedences; zend_trait_precedence *cur_precedence; zend_trait_method_reference *cur_method_ref; + zend_string *lc_trait_name; zend_string *lcname; HashTable **exclude_tables = NULL; zend_class_entry **aliases = NULL; @@ -1759,9 +1813,10 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e while ((cur_precedence = precedences[i])) { /** Resolve classes for all precedence operations. */ cur_method_ref = &cur_precedence->trait_method; - trait = zend_fetch_class(cur_method_ref->class_name, - ZEND_FETCH_CLASS_TRAIT|ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!trait) { + lc_trait_name = zend_string_tolower(cur_method_ref->class_name); + trait = zend_hash_find_ptr(EG(class_table), lc_trait_name); + zend_string_release_ex(lc_trait_name, 0); + if (!trait || !(trait->ce_flags & ZEND_ACC_LINKED)) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name)); } zend_check_trait_usage(ce, trait, traits); @@ -1784,10 +1839,13 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e for (j = 0; j < cur_precedence->num_excludes; j++) { zend_string* class_name = cur_precedence->exclude_class_names[j]; - zend_class_entry *exclude_ce = zend_fetch_class(class_name, ZEND_FETCH_CLASS_TRAIT |ZEND_FETCH_CLASS_NO_AUTOLOAD); + zend_class_entry *exclude_ce; uint32_t trait_num; - if (!exclude_ce) { + lc_trait_name = zend_string_tolower(class_name); + exclude_ce = zend_hash_find_ptr(EG(class_table), lc_trait_name); + zend_string_release_ex(lc_trait_name, 0); + if (!exclude_ce || !(exclude_ce->ce_flags & ZEND_ACC_LINKED)) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(class_name)); } trait_num = zend_check_trait_usage(ce, exclude_ce, traits); @@ -1829,8 +1887,10 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e lcname = zend_string_tolower(cur_method_ref->method_name); if (cur_method_ref->class_name) { /* For all aliases with an explicit class name, resolve the class now. */ - trait = zend_fetch_class(cur_method_ref->class_name, ZEND_FETCH_CLASS_TRAIT|ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!trait) { + lc_trait_name = zend_string_tolower(cur_method_ref->class_name); + trait = zend_hash_find_ptr(EG(class_table), lc_trait_name); + zend_string_release_ex(lc_trait_name, 0); + if (!trait || !(trait->ce_flags & ZEND_ACC_LINKED)) { zend_error_noreturn(E_COMPILE_ERROR, "Could not find trait %s", ZSTR_VAL(cur_method_ref->class_name)); } zend_check_trait_usage(ce, trait, traits); @@ -1876,9 +1936,6 @@ static void zend_traits_init_trait_structures(zend_class_entry *ce, zend_class_e } aliases[i] = trait; - - /* TODO: try to avoid this assignment (it's necessary only for reflection) */ - cur_method_ref->class_name = zend_string_copy(trait->name); } zend_string_release_ex(lcname, 0); i++; @@ -1952,7 +2009,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent zend_property_info *new_prop; zend_string* prop_name; const char* class_name_unused; - zend_bool not_compatible; + bool not_compatible; zval* prop_value; uint32_t flags; zend_string *doc_comment; @@ -2074,37 +2131,13 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent } /* }}} */ -static void zend_do_bind_traits(zend_class_entry *ce) /* {{{ */ +static void zend_do_bind_traits(zend_class_entry *ce, zend_class_entry **traits) /* {{{ */ { HashTable **exclude_tables; zend_class_entry **aliases; - zend_class_entry **traits, *trait; - uint32_t i, j; 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, traits, &exclude_tables, &aliases); @@ -2121,8 +2154,6 @@ static 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, traits); - - efree(traits); } /* }}} */ @@ -2162,7 +2193,7 @@ void zend_verify_abstract_class(zend_class_entry *ce) /* {{{ */ { zend_function *func; zend_abstract_info ai; - zend_bool is_explicit_abstract = (ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) != 0; + bool is_explicit_abstract = (ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) != 0; memset(&ai, 0, sizeof(ai)); ZEND_HASH_FOREACH_PTR(&ce->function_table, func) { @@ -2294,7 +2325,12 @@ static int check_variance_obligation(zval *zv) { if (obligation->type == OBLIGATION_DEPENDENCY) { zend_class_entry *dependency_ce = obligation->dependency_ce; if (dependency_ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE) { + zend_class_entry *orig_linking_class = CG(current_linking_class); + + CG(current_linking_class) = + (dependency_ce->ce_flags & ZEND_ACC_CACHEABLE) ? dependency_ce : NULL; resolve_delayed_variance_obligations(dependency_ce); + CG(current_linking_class) = orig_linking_class; } if (!(dependency_ce->ce_flags & ZEND_ACC_LINKED)) { return ZEND_HASH_APPLY_KEEP; @@ -2402,7 +2438,10 @@ static void check_unrecoverable_load_failure(zend_class_entry *ce) { * 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) { + if ((ce->ce_flags & ZEND_ACC_HAS_UNLINKED_USES) + || ((ce->ce_flags & ZEND_ACC_IMMUTABLE) + && CG(unlinked_uses) + && zend_hash_index_del(CG(unlinked_uses), (zend_long)(zend_uintptr_t)ce) == SUCCESS)) { zend_string *exception_str; zval exception_zv; ZEND_ASSERT(EG(exception) && "Exception must have been thrown"); @@ -2414,13 +2453,179 @@ static void check_unrecoverable_load_failure(zend_class_entry *ce) { } } -ZEND_API zend_result zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_name) /* {{{ */ +#define zend_update_inherited_handler(handler) do { \ + if (ce->handler == (zend_function*)op_array) { \ + ce->handler = (zend_function*)new_op_array; \ + } \ + } while (0) + +static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) +{ + zend_class_entry *ce; + Bucket *p, *end; + + ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry)); + memcpy(ce, pce, sizeof(zend_class_entry)); + ce->ce_flags &= ~ZEND_ACC_IMMUTABLE; + ce->refcount = 1; + ce->inheritance_cache = NULL; + ZEND_MAP_PTR_INIT(ce->mutable_data, NULL); + + /* properties */ + if (ce->default_properties_table) { + zval *dst = emalloc(sizeof(zval) * ce->default_properties_count); + zval *src = ce->default_properties_table; + zval *end = src + ce->default_properties_count; + + ce->default_properties_table = dst; + for (; src != end; src++, dst++) { + ZVAL_COPY_VALUE_PROP(dst, src); + } + } + + /* methods */ + ce->function_table.pDestructor = ZEND_FUNCTION_DTOR; + if (!(HT_FLAGS(&ce->function_table) & HASH_FLAG_UNINITIALIZED)) { + p = emalloc(HT_SIZE(&ce->function_table)); + memcpy(p, HT_GET_DATA_ADDR(&ce->function_table), HT_USED_SIZE(&ce->function_table)); + HT_SET_DATA_ADDR(&ce->function_table, p); + p = ce->function_table.arData; + end = p + ce->function_table.nNumUsed; + for (; p != end; p++) { + zend_op_array *op_array, *new_op_array; + void ***run_time_cache_ptr; + size_t alloc_size; + + op_array = Z_PTR(p->val); + ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); + ZEND_ASSERT(op_array->scope == pce); + ZEND_ASSERT(op_array->prototype == NULL); + alloc_size = sizeof(zend_op_array) + sizeof(void *); + if (op_array->static_variables) { + alloc_size += sizeof(HashTable *); + } + new_op_array = zend_arena_alloc(&CG(arena), alloc_size); + Z_PTR(p->val) = new_op_array; + memcpy(new_op_array, op_array, sizeof(zend_op_array)); + run_time_cache_ptr = (void***)(new_op_array + 1); + *run_time_cache_ptr = NULL; + new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; + new_op_array->scope = ce; + ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, run_time_cache_ptr); + if (op_array->static_variables) { + HashTable **static_variables_ptr = (HashTable **) (run_time_cache_ptr + 1); + *static_variables_ptr = NULL; + ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, static_variables_ptr); + } + + zend_update_inherited_handler(constructor); + zend_update_inherited_handler(destructor); + zend_update_inherited_handler(clone); + zend_update_inherited_handler(__get); + zend_update_inherited_handler(__set); + zend_update_inherited_handler(__call); + zend_update_inherited_handler(__isset); + zend_update_inherited_handler(__unset); + zend_update_inherited_handler(__tostring); + zend_update_inherited_handler(__callstatic); + zend_update_inherited_handler(__debugInfo); + zend_update_inherited_handler(__serialize); + zend_update_inherited_handler(__unserialize); + } + } + + /* static members */ + if (ce->default_static_members_table) { + zval *dst = emalloc(sizeof(zval) * ce->default_static_members_count); + zval *src = ce->default_static_members_table; + zval *end = src + ce->default_static_members_count; + + ce->default_static_members_table = dst; + for (; src != end; src++, dst++) { + ZVAL_COPY_VALUE(dst, src); + } + } + ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table); + + /* properties_info */ + if (!(HT_FLAGS(&ce->properties_info) & HASH_FLAG_UNINITIALIZED)) { + p = emalloc(HT_SIZE(&ce->properties_info)); + memcpy(p, HT_GET_DATA_ADDR(&ce->properties_info), HT_USED_SIZE(&ce->properties_info)); + HT_SET_DATA_ADDR(&ce->properties_info, p); + p = ce->properties_info.arData; + end = p + ce->properties_info.nNumUsed; + for (; p != end; p++) { + zend_property_info *prop_info, *new_prop_info; + + prop_info = Z_PTR(p->val); + ZEND_ASSERT(prop_info->ce == pce); + new_prop_info= zend_arena_alloc(&CG(arena), sizeof(zend_property_info)); + Z_PTR(p->val) = new_prop_info; + memcpy(new_prop_info, prop_info, sizeof(zend_property_info)); + new_prop_info->ce = ce; + if (ZEND_TYPE_HAS_LIST(new_prop_info->type)) { + zend_type_list *new_list; + zend_type_list *list = ZEND_TYPE_LIST(new_prop_info->type); + + new_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->num_types)); + memcpy(new_list, list, ZEND_TYPE_LIST_SIZE(list->num_types)); + ZEND_TYPE_SET_PTR(new_prop_info->type, list); + ZEND_TYPE_FULL_MASK(new_prop_info->type) |= _ZEND_TYPE_ARENA_BIT; + } + } + } + + /* constants table */ + if (!(HT_FLAGS(&ce->constants_table) & HASH_FLAG_UNINITIALIZED)) { + p = emalloc(HT_SIZE(&ce->constants_table)); + memcpy(p, HT_GET_DATA_ADDR(&ce->constants_table), HT_USED_SIZE(&ce->constants_table)); + HT_SET_DATA_ADDR(&ce->constants_table, p); + p = ce->constants_table.arData; + end = p + ce->constants_table.nNumUsed; + for (; p != end; p++) { + zend_class_constant *c, *new_c; + + c = Z_PTR(p->val); + ZEND_ASSERT(c->ce == pce); + new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant)); + Z_PTR(p->val) = new_c; + memcpy(new_c, c, sizeof(zend_class_constant)); + new_c->ce = ce; + } + } + + return ce; +} + +#ifndef ZEND_WIN32 +# define UPDATE_IS_CACHEABLE(ce) do { \ + if ((ce)->type == ZEND_USER_CLASS) { \ + is_cacheable &= (ce)->ce_flags; \ + } \ + } while (0) +#else +// TODO: ASLR may cause different addresses in different workers ??? +# define UPDATE_IS_CACHEABLE(ce) do { \ + is_cacheable &= (ce)->ce_flags; \ + } while (0) +#endif + +ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_name, zend_string *key) /* {{{ */ { /* 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; + zend_class_entry **traits_and_interfaces = NULL; + zend_class_entry *proto = NULL; + zend_class_entry *orig_linking_class; + uint32_t is_cacheable = ce->ce_flags & ZEND_ACC_IMMUTABLE; + uint32_t i, j; + zval *zv; + ALLOCA_FLAG(use_heap) + + SET_ALLOCA_FLAG(use_heap); + ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_LINKED)); if (ce->parent_name) { parent = zend_fetch_class_by_name( @@ -2428,19 +2633,41 @@ ZEND_API zend_result zend_do_link_class(zend_class_entry *ce, zend_string *lc_pa ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); if (!parent) { check_unrecoverable_load_failure(ce); - return FAILURE; + return NULL; } + UPDATE_IS_CACHEABLE(parent); } - 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); + if (ce->num_traits || ce->num_interfaces) { + traits_and_interfaces = do_alloca(sizeof(zend_class_entry*) * (ce->num_traits + ce->num_interfaces), use_heap); + + for (i = 0; i < ce->num_traits; i++) { + zend_class_entry *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)) { + free_alloca(traits_and_interfaces, use_heap); + return NULL; + } + 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)); + free_alloca(traits_and_interfaces, use_heap); + return NULL; + } + for (j = 0; j < i; j++) { + if (traits_and_interfaces[j] == trait) { + /* skip duplications */ + trait = NULL; + break; + } + } + traits_and_interfaces[i] = trait; + if (trait) { + UPDATE_IS_CACHEABLE(trait); + } } + } + + if (ce->num_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, @@ -2448,11 +2675,67 @@ ZEND_API zend_result zend_do_link_class(zend_class_entry *ce, zend_string *lc_pa ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); if (!iface) { check_unrecoverable_load_failure(ce); - efree(interfaces); - return FAILURE; + free_alloca(traits_and_interfaces, use_heap); + return NULL; + } + traits_and_interfaces[ce->num_traits + i] = iface; + if (iface) { + UPDATE_IS_CACHEABLE(iface); + } + } + } + +#ifndef ZEND_WIN32 + if (ce->ce_flags & ZEND_ACC_ENUM) { + /* We will add internal methods. */ + is_cacheable = false; + } +#endif + + if (ce->ce_flags & ZEND_ACC_IMMUTABLE) { + if (is_cacheable) { + if (zend_inheritance_cache_get && zend_inheritance_cache_add) { + zend_class_entry *ret = zend_inheritance_cache_get(ce, parent, traits_and_interfaces); + if (ret) { + if (traits_and_interfaces) { + free_alloca(traits_and_interfaces, use_heap); + } + zv = zend_hash_find_ex(CG(class_table), key, 1); + Z_CE_P(zv) = ret; + return ret; + } + } else { + is_cacheable = 0; } - interfaces[num_parent_interfaces + i] = iface; + proto = ce; + } + /* Lazy class loading */ + ce = zend_lazy_class_load(ce); + zv = zend_hash_find_ex(CG(class_table), key, 1); + Z_CE_P(zv) = ce; + if (CG(unlinked_uses) + && zend_hash_index_del(CG(unlinked_uses), (zend_long)(zend_uintptr_t)proto) == SUCCESS) { + ce->ce_flags |= ZEND_ACC_HAS_UNLINKED_USES; } + } else if (ce->ce_flags & ZEND_ACC_FILE_CACHED) { + /* Lazy class loading */ + ce = zend_lazy_class_load(ce); + ce->ce_flags &= ~ZEND_ACC_FILE_CACHED; + zv = zend_hash_find_ex(CG(class_table), key, 1); + Z_CE_P(zv) = ce; + if (CG(unlinked_uses) + && zend_hash_index_del(CG(unlinked_uses), (zend_long)(zend_uintptr_t)proto) == SUCCESS) { + ce->ce_flags |= ZEND_ACC_HAS_UNLINKED_USES; + } + } + + orig_linking_class = CG(current_linking_class); + CG(current_linking_class) = is_cacheable ? ce : NULL; + + if (ce->ce_flags & ZEND_ACC_ENUM) { + /* Only register builtin enum methods during inheritance to avoid persisting them in + * opcache. */ + zend_enum_register_funcs(ce); } if (parent) { @@ -2462,9 +2745,21 @@ ZEND_API zend_result zend_do_link_class(zend_class_entry *ce, zend_string *lc_pa zend_do_inheritance(ce, parent); } if (ce->num_traits) { - zend_do_bind_traits(ce); + zend_do_bind_traits(ce, traits_and_interfaces); } - if (interfaces) { + if (ce->num_interfaces) { + /* Also copy the parent interfaces here, so we don't need to reallocate later. */ + uint32_t num_parent_interfaces = parent ? parent->num_interfaces : 0; + zend_class_entry **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); + } + memcpy(interfaces + num_parent_interfaces, traits_and_interfaces + ce->num_traits, + sizeof(zend_class_entry *) * ce->num_interfaces); + zend_do_implement_interfaces(ce, interfaces); } else if (parent && parent->num_interfaces) { zend_do_inherit_interfaces(ce, parent); @@ -2474,24 +2769,61 @@ ZEND_API zend_result zend_do_link_class(zend_class_entry *ce, zend_string *lc_pa ) { zend_verify_abstract_class(ce); } + if (ce->ce_flags & ZEND_ACC_ENUM) { + zend_verify_enum(ce); + } zend_build_properties_info_table(ce); if (!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)) { ce->ce_flags |= ZEND_ACC_LINKED; - return SUCCESS; + } else { + ce->ce_flags |= ZEND_ACC_NEARLY_LINKED; + if (CG(current_linking_class)) { + ce->ce_flags |= ZEND_ACC_CACHEABLE; + } + load_delayed_classes(); + if (ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE) { + resolve_delayed_variance_obligations(ce); + if (!(ce->ce_flags & ZEND_ACC_LINKED)) { + CG(current_linking_class) = orig_linking_class; + report_variance_errors(ce); + } + } + if (ce->ce_flags & ZEND_ACC_CACHEABLE) { + ce->ce_flags &= ~ZEND_ACC_CACHEABLE; + } else { + CG(current_linking_class) = NULL; + } + } + + if (!CG(current_linking_class)) { + is_cacheable = 0; } + CG(current_linking_class) = orig_linking_class; + + if (is_cacheable) { + HashTable *ht = (HashTable*)ce->inheritance_cache; + zend_class_entry *new_ce; - 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); + ce->inheritance_cache = NULL; + new_ce = zend_inheritance_cache_add(ce, proto, parent, traits_and_interfaces, ht); + if (new_ce) { + zv = zend_hash_find_ex(CG(class_table), key, 1); + ce = new_ce; + Z_CE_P(zv) = ce; + } + if (ht) { + zend_hash_destroy(ht); + FREE_HASHTABLE(ht); } } - return SUCCESS; + if (traits_and_interfaces) { + free_alloca(traits_and_interfaces, use_heap); + } + + return ce; } /* }}} */ @@ -2540,21 +2872,66 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e } /* }}} */ -zend_bool zend_try_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce, zend_string *lcname, zval *delayed_early_binding) /* {{{ */ +zend_class_entry *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); + inheritance_status status; + zend_class_entry *proto = NULL; + zend_class_entry *orig_linking_class; + uint32_t is_cacheable = ce->ce_flags & ZEND_ACC_IMMUTABLE; + + UPDATE_IS_CACHEABLE(parent_ce); + if (is_cacheable) { + if (zend_inheritance_cache_get && zend_inheritance_cache_add) { + zend_class_entry *ret = zend_inheritance_cache_get(ce, parent_ce, NULL); + if (ret) { + 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 NULL; + } + Z_CE_P(delayed_early_binding) = ret; + } else { + if (UNEXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ret) == NULL)) { + return NULL; + } + } + return ret; + } + } else { + is_cacheable = 0; + } + proto = ce; + } + orig_linking_class = CG(current_linking_class); + CG(current_linking_class) = NULL; + status = zend_can_early_bind(ce, parent_ce); + CG(current_linking_class) = orig_linking_class; if (EXPECTED(status != INHERITANCE_UNRESOLVED)) { + if (ce->ce_flags & ZEND_ACC_IMMUTABLE) { + /* Lazy class loading */ + ce = zend_lazy_class_load(ce); + } else if (ce->ce_flags & ZEND_ACC_FILE_CACHED) { + /* Lazy class loading */ + ce = zend_lazy_class_load(ce); + ce->ce_flags &= ~ZEND_ACC_FILE_CACHED; + } + 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; + return NULL; } + Z_CE_P(delayed_early_binding) = ce; } else { if (UNEXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) == NULL)) { - return 0; + return NULL; } } + + orig_linking_class = CG(current_linking_class); + CG(current_linking_class) = is_cacheable ? ce : NULL; + zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS); if (parent_ce && parent_ce->num_interfaces) { zend_do_inherit_interfaces(ce, parent_ce); @@ -2565,8 +2942,28 @@ zend_bool zend_try_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce, } ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE)); ce->ce_flags |= ZEND_ACC_LINKED; - return 1; + + CG(current_linking_class) = orig_linking_class; + + if (is_cacheable) { + HashTable *ht = (HashTable*)ce->inheritance_cache; + zend_class_entry *new_ce; + + ce->inheritance_cache = NULL; + new_ce = zend_inheritance_cache_add(ce, proto, parent_ce, NULL, ht); + if (new_ce) { + zval *zv = zend_hash_find_ex(CG(class_table), lcname, 1); + ce = new_ce; + Z_CE_P(zv) = ce; + } + if (ht) { + zend_hash_destroy(ht); + FREE_HASHTABLE(ht); + } + } + + return ce; } - return 0; + return NULL; } /* }}} */ |