summaryrefslogtreecommitdiff
path: root/Zend/zend_inheritance.c
diff options
context:
space:
mode:
Diffstat (limited to 'Zend/zend_inheritance.c')
-rw-r--r--Zend/zend_inheritance.c627
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;
}
/* }}} */