diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 2c3aaae065997..fe1c25bda2cc8 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -282,7 +282,7 @@ static inline bool can_elide_list_type( zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type)); zend_class_entry *ce = zend_optimizer_get_class_entry(script, op_array, lcname); zend_string_release(lcname); - bool result = ce && safe_instanceof(use_info->ce, ce); + bool result = ce && !ce->required_scope && safe_instanceof(use_info->ce, ce); if (result == !is_intersection) { return result; } diff --git a/Zend/tests/errmsg/errmsg_027.phpt b/Zend/tests/errmsg/errmsg_027.phpt index 3d96ec27b4d64..a30e1269453f9 100644 --- a/Zend/tests/errmsg/errmsg_027.phpt +++ b/Zend/tests/errmsg/errmsg_027.phpt @@ -13,4 +13,4 @@ class test { echo "Done\n"; ?> --EXPECTF-- -Fatal error: Class declarations may not be nested in %s on line %d +Fatal error: Class declarations may not be declared inside functions in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..bb05c40a2ffe1 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -164,6 +164,10 @@ struct _zend_class_entry { HashTable properties_info; HashTable constants_table; + zend_class_entry *required_scope; + zend_class_entry *lexical_scope; + bool required_scope_absolute; + ZEND_MAP_PTR_DEF(zend_class_mutable_data*, mutable_data); zend_inheritance_cache_entry *inheritance_cache; diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 7023bf1a36b4b..16e5b74cd6754 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1816,6 +1816,27 @@ static zend_always_inline zend_result _object_and_properties_init(zval *arg, zen return FAILURE; } + if (class_type->required_scope) { + const zend_class_entry *scope = zend_get_executed_scope(); + if (UNEXPECTED(scope == NULL)) { + zend_type_error("Cannot instantiate class %s from the global scope", ZSTR_VAL(class_type->name)); + ZVAL_NULL(arg); + Z_OBJ_P(arg) = NULL; + return FAILURE; + } + + if (class_type->required_scope_absolute) { + if (scope != class_type->required_scope && scope->lexical_scope != class_type->required_scope) { + zend_type_error("Cannot instantiate private class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name)); + ZVAL_NULL(arg); + Z_OBJ_P(arg) = NULL; + return FAILURE; + } + } else if (!instanceof_function(scope, class_type->required_scope) && !instanceof_function(scope->lexical_scope, class_type->required_scope)) { + zend_type_error("Cannot instantiate protected class %s from scope %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name)); + } + } + if (UNEXPECTED(!(class_type->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { if (UNEXPECTED(zend_update_class_constants(class_type) != SUCCESS)) { ZVAL_NULL(arg); @@ -2898,7 +2919,222 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arg_info_toString, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() -static zend_always_inline void zend_normalize_internal_type(zend_type *type) { +static HashTable *interned_type_tree = NULL; + +// todo: move to zend_types.h +#define ADD_TO_TREE(list, count, value) \ + do { \ + list = erealloc(list, sizeof(zend_type) * (count + 1)); \ + list[count++] = value; \ + } while (0) + +static int compare_simple_types(const zend_type a, const zend_type b) { + const uint32_t a_mask = ZEND_TYPE_FULL_MASK(a); + const uint32_t b_mask = ZEND_TYPE_FULL_MASK(b); + + if (a_mask != b_mask) { + return a_mask < b_mask ? -1 : 1; + } + + const bool a_has_name = ZEND_TYPE_HAS_NAME(a); + const bool b_has_name = ZEND_TYPE_HAS_NAME(b); + + if (a_has_name && b_has_name) { + const zend_string *a_name = ZEND_TYPE_NAME(a); + const zend_string *b_name = ZEND_TYPE_NAME(b); + const int cmp = ZSTR_VAL(a_name) == ZSTR_VAL(b_name); + if (cmp != 0) { + return cmp; + } + } + + const bool a_nullable = ZEND_TYPE_ALLOW_NULL(a); + const bool b_nullable = ZEND_TYPE_ALLOW_NULL(b); + + if (a_nullable != b_nullable) { + return a_nullable ? 1 : -1; + } + + // Types are equal + return 0; +} + +static int compare_type_nodes(const void *a_, const void *b_) { + const zend_type_node *a = *(zend_type_node **)a_; + const zend_type_node *b = *(zend_type_node **)b_; + + if (a->kind != b->kind) { + return (int)a->kind - (int)b->kind; + } + + if (a->kind == ZEND_TYPE_SIMPLE) { + return compare_simple_types(a->simple_type, b->simple_type); + } + + if (a->compound.num_types != b->compound.num_types) { + return (int)a->compound.num_types - (int)b->compound.num_types; + } + + for (uint32_t i = 0; i < a->compound.num_types; i++) { + const int cmp = compare_type_nodes(&a->compound.types[i], &b->compound.types[i]); + if (cmp != 0) { + return cmp; + } + } + + return 0; +} + +zend_ulong zend_type_node_hash(const zend_type_node *node) { + zend_ulong hash = 2166136261u; // FNV-1a offset basis + + hash ^= (zend_ulong)node->kind; + hash *= 16777619; + + switch (node->kind) { + case ZEND_TYPE_SIMPLE: { + const zend_type type = node->simple_type; + hash ^= (zend_ulong)ZEND_TYPE_FULL_MASK(type); + hash *= 16777619; + + if (ZEND_TYPE_HAS_NAME(type)) { + zend_string *name = ZEND_TYPE_NAME(type); + hash ^= zend_string_hash_val(name); + hash *= 16777619; + } + + break; + } + + case ZEND_TYPE_UNION: + case ZEND_TYPE_INTERSECTION: { + for (uint32_t i = 0; i < node->compound.num_types; ++i) { + const zend_ulong child_hash = zend_type_node_hash(node->compound.types[i]); + hash ^= child_hash; + hash *= 16777619; + } + break; + } + } + + return hash; +} + +bool zend_type_node_equals(const zend_type_node *a, const zend_type_node *b) { + if (a == b) return true; + if (a->kind != b->kind) return false; + + if (a->kind == ZEND_TYPE_SIMPLE) { + const zend_type at = a->simple_type; + const zend_type bt = b->simple_type; + + if (ZEND_TYPE_FULL_MASK(at) != ZEND_TYPE_FULL_MASK(bt)) { + return false; + } + + const bool a_has_name = ZEND_TYPE_HAS_NAME(at); + const bool b_has_name = ZEND_TYPE_HAS_NAME(bt); + if (a_has_name != b_has_name) { + return false; + } + + if (a_has_name) { + const zend_string *a_name = ZEND_TYPE_NAME(at); + const zend_string *b_name = ZEND_TYPE_NAME(bt); + if (!zend_string_equals(a_name, b_name)) { + return false; + } + } + + return true; + } + + // Compound type: union or intersection + if (a->compound.num_types != b->compound.num_types) { + return false; + } + + for (uint32_t i = 0; i < a->compound.num_types; ++i) { + if (!zend_type_node_equals(a->compound.types[i], b->compound.types[i])) { + return false; + } + } + + return true; +} + +static zend_type_node *intern_type_node(zend_type_node *node) { + const zend_ulong hash = zend_type_node_hash(node); + zend_type_node *existing; + + if (interned_type_tree == NULL) { + interned_type_tree = pemalloc(sizeof(HashTable), 1); + zend_hash_init(interned_type_tree, 64, NULL, NULL, 1); + } + + if ((existing = zend_hash_index_find_ptr(interned_type_tree, hash))) { + if (zend_type_node_equals(existing, node)) { + pefree(node, 1); + return existing; // reuse interned node + } + } + + zend_hash_index_add_new_ptr(interned_type_tree, hash, node); + return node; +} + +ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) { + if (type.type_mask == 0) { + return NULL; + } + + if (!ZEND_TYPE_HAS_LIST(type)) { + zend_type_node *node = pemalloc(sizeof(zend_type_node), 1); + node->kind = ZEND_TYPE_SIMPLE; + node->simple_type = type; + return intern_type_node(node); + } + + zend_type_list *list = ZEND_TYPE_LIST(type); + const zend_type_node_kind kind = ZEND_TYPE_IS_INTERSECTION(type) ? + ZEND_TYPE_INTERSECTION : ZEND_TYPE_UNION; + + zend_type_node **children = NULL; + uint32_t num_children = 0; + + zend_type *subtype; + + ZEND_TYPE_LIST_FOREACH(list, subtype) { + zend_type_node *child = zend_type_to_interned_tree(*subtype); + + if (child->kind == kind) { + for (uint32_t i = 0; child->compound.num_types; i++) { + ADD_TO_TREE(children, num_children, child->compound.types[i]); + } + } else { + ADD_TO_TREE(children, num_children, child); + } + } ZEND_TYPE_LIST_FOREACH_END(); + + qsort(children, num_children, sizeof(zend_type_node*), compare_type_nodes); + + size_t deduped_count = 0; + for (size_t i = 0; i < num_children; i++) { + if (i == 0 || compare_type_nodes(&children[i], &children[i - 1]) != 0) { + children[deduped_count++] = children[i]; + } + } + + zend_type_node *node = pemalloc(sizeof(zend_type_node), 1); + node->kind = kind; + node->compound.num_types = deduped_count; + node->compound.types = pemalloc(sizeof(zend_type_node *) * deduped_count, 1); + memcpy(node->compound.types, children, sizeof(zend_type_node *) * deduped_count); + + return intern_type_node(node); +} + +static zend_always_inline zend_type_node *zend_normalize_internal_type(zend_type *type) { ZEND_ASSERT(!ZEND_TYPE_HAS_LITERAL_NAME(*type)); if (ZEND_TYPE_PURE_MASK(*type) != MAY_BE_ANY) { ZEND_ASSERT(!ZEND_TYPE_CONTAINS_CODE(*type, IS_RESOURCE) && "resource is not allowed in a zend_type"); @@ -2921,6 +3157,8 @@ static zend_always_inline void zend_normalize_internal_type(zend_type *type) { } ZEND_TYPE_FOREACH_END(); } } ZEND_TYPE_FOREACH_END(); + + return zend_type_to_interned_tree(*type); } /* registers all functions in *library_functions in the function hash */ @@ -3190,7 +3428,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend new_arg_info[i].type = legacy_iterable; } - zend_normalize_internal_type(&new_arg_info[i].type); + new_arg_info[i].type_tree = zend_normalize_internal_type(&new_arg_info[i].type); } } @@ -4665,7 +4903,9 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z property_info->type = type; if (is_persistent_class(ce)) { - zend_normalize_internal_type(&property_info->type); + property_info->type_tree = zend_normalize_internal_type(&property_info->type); + } else { + property_info->type_tree = zend_type_to_interned_tree(property_info->type); } zend_hash_update_ptr(&ce->properties_info, name, property_info); diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 6aeffce25d8e5..3beb40408fdcf 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -128,46 +128,46 @@ typedef struct _zend_fcall_info_cache { /* Arginfo structures without type information */ #define ZEND_ARG_INFO(pass_by_ref, name) \ - { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value) \ - { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) \ - { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Arginfo structures with simple type information */ #define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value) \ - { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Arginfo structures with complex type information */ #define ZEND_ARG_TYPE_MASK(pass_by_ref, name, type_mask, default_value) \ - { #name, ZEND_TYPE_INIT_MASK(type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_MASK(type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask, default_value) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Arginfo structures with object type information */ #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, class_name, allow_null) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, class_name, allow_null, default_value) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value }, + { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value }, #define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, class_name, allow_null) \ - { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL }, /* Legacy arginfo structures */ #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) \ - { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL }, + { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX2(name, return_reference, required_num_args, class_name, allow_null, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ { (const char*)(uintptr_t)(required_num_args), \ - ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX2(name, return_reference, required_num_args, class_name, allow_null, 0) @@ -180,7 +180,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX2(name, return_reference, required_num_args, type, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_MASK(type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_MASK(type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(name, return_reference, required_num_args, type) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX2(name, return_reference, required_num_args, type, 0) @@ -190,7 +190,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX2(name, return_reference, required_num_args, class_name, type, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(name, return_reference, required_num_args, class_name, type) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX2(name, return_reference, required_num_args, class_name, type, 0) @@ -200,7 +200,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, is_tentative_return_type) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, 0) @@ -213,7 +213,7 @@ typedef struct _zend_fcall_info_cache { #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0, 0)), NULL }, + { (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0, 0)), NULL, NULL }, #define ZEND_BEGIN_ARG_INFO(name, _unused) \ ZEND_BEGIN_ARG_INFO_EX(name, {}, ZEND_RETURN_VALUE, -1) #define ZEND_END_ARG_INFO() }; @@ -448,6 +448,8 @@ ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const ze ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type); ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type); +ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type); + static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) { if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) { zend_class_mutable_data *mutable_data = diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1cba9ef221997..e8dbe3cec5ce2 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -38,6 +38,7 @@ #include "zend_call_stack.h" #include "zend_frameless_function.h" #include "zend_property_hooks.h" +#include "zend_namespaces.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -424,6 +425,7 @@ void zend_init_compiler_data_structures(void) /* {{{ */ zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op)); zend_stack_init(&CG(short_circuiting_opnums), sizeof(uint32_t)); CG(active_class_entry) = NULL; + CG(nested_class_queue) = NULL; CG(in_compilation) = 0; CG(skip_shebang) = 0; @@ -893,12 +895,12 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token } break; case T_READONLY: - if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) { + if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP || target == ZEND_MODIFIER_TARGET_NESTED_CLASS) { return ZEND_ACC_READONLY; } break; case T_ABSTRACT: - if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_PROPERTY) { + if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_NESTED_CLASS) { return ZEND_ACC_ABSTRACT; } break; @@ -906,7 +908,8 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token if (target == ZEND_MODIFIER_TARGET_METHOD || target == ZEND_MODIFIER_TARGET_CONSTANT || target == ZEND_MODIFIER_TARGET_PROPERTY - || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { + || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK + || target == ZEND_MODIFIER_TARGET_NESTED_CLASS) { return ZEND_ACC_FINAL; } break; @@ -943,6 +946,8 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token member = "parameter"; } else if (target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { member = "property hook"; + } else if (target == ZEND_MODIFIER_TARGET_NESTED_CLASS) { + member = "nested class"; } else { ZEND_UNREACHABLE(); } @@ -1050,6 +1055,37 @@ uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifi return 0; } } + if (target == ZEND_MODIFIER_TARGET_NESTED_CLASS) { + if ((flags & ZEND_ACC_PPP_MASK) && (new_flag & ZEND_ACC_PPP_MASK)) { + zend_throw_exception(zend_ce_compile_error, + "Multiple access type modifiers are not allowed", 0); + return 0; + } + + if ((flags & ZEND_ACC_STATIC) || (new_flag & ZEND_ACC_STATIC)) { + zend_throw_exception(zend_ce_compile_error, + "Static inner classes are not allowed", 0); + return 0; + } + + if ((flags & ZEND_ACC_PUBLIC_SET) || (new_flag & ZEND_ACC_PUBLIC_SET)) { + zend_throw_exception(zend_ce_compile_error, + "Public(set) inner classes are not allowed", 0); + return 0; + } + + if ((flags & ZEND_ACC_PROTECTED_SET) || (new_flag & ZEND_ACC_PROTECTED_SET)) { + zend_throw_exception(zend_ce_compile_error, + "Protected(set) inner classes are not allowed", 0); + return 0; + } + + if ((flags & ZEND_ACC_PRIVATE_SET) || (new_flag & ZEND_ACC_PRIVATE_SET)) { + zend_throw_exception(zend_ce_compile_error, + "Private(set) inner classes are not allowed", 0); + return 0; + } + } return new_flags; } /* }}} */ @@ -1144,6 +1180,32 @@ static zend_string *zend_resolve_const_name(zend_string *name, uint32_t type, bo name, type, is_fully_qualified, 1, FC(imports_const)); } +zend_string *zend_resolve_class_in_scope(zend_string *name, const zend_class_entry *scope) +{ + const zend_class_entry *current_scope = scope; + + while (current_scope && current_scope->type != ZEND_NAMESPACE_CLASS) { + zend_string *try_name = zend_string_concat3( + ZSTR_VAL(current_scope->name), ZSTR_LEN(current_scope->name), + "\\", 1, + ZSTR_VAL(name), ZSTR_LEN(name)); + + zend_string *lc_try_name = zend_string_tolower(try_name); + + bool has_seen = zend_have_seen_symbol(lc_try_name, ZEND_SYMBOL_CLASS); + zend_string_release(lc_try_name); + + if (has_seen) { + return try_name; + } + zend_string_release(try_name); + + current_scope = current_scope->lexical_scope; + } + + return NULL; +} + static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* {{{ */ { char *compound; @@ -1202,6 +1264,13 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* } } + if (CG(active_class_entry)) { + zend_string *nested_name = zend_resolve_class_in_scope(name, CG(active_class_entry)); + if (nested_name) { + return nested_name; + } + } + /* If not fully qualified and not an alias, prepend the current namespace */ return zend_prefix_with_ns(name); } @@ -7348,6 +7417,7 @@ static zend_type zend_compile_typename_ex( } ast->attr = orig_ast_attr; + return type; } /* }}} */ @@ -7584,6 +7654,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 } else { arg_infos->type = (zend_type) ZEND_TYPE_INIT_CODE(fallback_return_type, 0, 0); } + arg_infos->type_tree = zend_type_to_interned_tree(arg_infos->type); arg_infos++; op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; @@ -7830,6 +7901,8 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 &prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER); } } + + arg_info->type_tree = zend_type_to_interned_tree(arg_info->type); } /* These are assigned at the end to avoid uninitialized memory in case of an error */ @@ -8980,10 +9053,9 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */ } /* }}} */ -static void zend_compile_implements(zend_ast *ast) /* {{{ */ +static void zend_compile_implements(zend_ast *ast, zend_class_entry *ce) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); - zend_class_entry *ce = CG(active_class_entry); zend_class_name *interface_names; uint32_t i; @@ -9041,6 +9113,42 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } +static void zend_defer_class_decl(zend_ast *ast) +{ + ZEND_ASSERT(CG(active_class_entry)); + + if (CG(active_op_array)->function_name) { + zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be declared inside functions"); + } + + if (CG(nested_class_queue) == NULL) { + ALLOC_HASHTABLE(CG(nested_class_queue)); + zend_hash_init(CG(nested_class_queue), 8, NULL, ZVAL_PTR_DTOR, 0); + } + + zend_hash_next_index_insert_ptr(CG(nested_class_queue), ast); +} + +static void zend_scan_nested_class_decl(zend_ast *ast, const zend_string *prefix) +{ + const zend_ast_list *list = zend_ast_get_list(ast); + for (int i = 0; i < list->children; i++) { + ast = list->child[i]; + if (ast->kind == ZEND_AST_CLASS) { + const zend_ast_decl *decl = (zend_ast_decl *)ast; + zend_string *name = zend_string_concat3( + ZSTR_VAL(prefix), ZSTR_LEN(prefix), + "\\", 1, + ZSTR_VAL(decl->name), ZSTR_LEN(decl->name)); + zend_string *lc_name = zend_string_tolower(name); + zend_register_seen_symbol(lc_name, ZEND_SYMBOL_CLASS); + zend_string_release(lc_name); + zend_scan_nested_class_decl(decl->child[2], name); + zend_string_release(name); + } + } +} + static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; @@ -9057,10 +9165,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) if (EXPECTED((decl->flags & ZEND_ACC_ANON_CLASS) == 0)) { zend_string *unqualified_name = decl->name; - if (CG(active_class_entry)) { - zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be nested"); - } - const char *type = "a class name"; if (decl->flags & ZEND_ACC_ENUM) { type = "an enum name"; @@ -9070,7 +9174,34 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) type = "a trait name"; } zend_assert_valid_class_name(unqualified_name, type); - name = zend_prefix_with_ns(unqualified_name); + + if (CG(active_class_entry)) { + name = zend_string_concat3( + ZSTR_VAL(CG(active_class_entry)->name), ZSTR_LEN(CG(active_class_entry)->name), + "\\", 1, + ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name)); + + /* configure the class from the modifiers */ + decl->flags |= decl->attr & ZEND_ACC_FINAL; + if (decl->attr & ZEND_ACC_ABSTRACT) { + decl->flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; + } + if (decl->attr & ZEND_ACC_READONLY) { + decl->flags |= ZEND_ACC_READONLY_CLASS | ZEND_ACC_NO_DYNAMIC_PROPERTIES; + } + + int propFlags = decl->attr & ZEND_ACC_PPP_MASK; + decl->attr &= ~(ZEND_ACC_PPP_MASK | ZEND_ACC_FINAL | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT); + + ce->required_scope = propFlags & (ZEND_ACC_PRIVATE | ZEND_ACC_PROTECTED) ? CG(active_class_entry) : NULL; + ce->required_scope_absolute = propFlags & ZEND_ACC_PRIVATE ? true : false; + ce->lexical_scope = CG(active_class_entry); + } else { + name = zend_prefix_with_ns(unqualified_name); + ce->required_scope = NULL; + ce->lexical_scope = zend_resolve_namespace(FC(current_namespace)); + } + name = zend_new_interned_string(name); lcname = zend_string_tolower(name); @@ -9088,6 +9219,8 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* Find an anon class name that is not in use yet. */ name = NULL; lcname = NULL; + ce->required_scope = NULL; + ce->lexical_scope = CG(active_class_entry) ? CG(active_class_entry) : zend_resolve_namespace(FC(current_namespace)); do { zend_tmp_string_release(name); zend_tmp_string_release(lcname); @@ -9129,16 +9262,16 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_resolve_const_class_name_reference(extends_ast, "class name"); } + if (implements_ast) { + zend_compile_implements(implements_ast, ce); + } + CG(active_class_entry) = ce; if (decl->child[3]) { zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0); } - if (implements_ast) { - zend_compile_implements(implements_ast); - } - if (ce->ce_flags & ZEND_ACC_ENUM) { if (enum_backing_type_ast != NULL) { zend_compile_enum_backing_type(ce, enum_backing_type_ast); @@ -9147,6 +9280,9 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_enum_register_props(ce); } + if (ce->lexical_scope->type == ZEND_NAMESPACE_CLASS) { + zend_scan_nested_class_decl(stmt_ast, name); + } zend_compile_stmt(stmt_ast); /* Reset lineno for final opcodes and errors */ @@ -9156,8 +9292,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_verify_abstract_class(ce); } - CG(active_class_entry) = original_ce; - if (toplevel) { ce->ce_flags |= ZEND_ACC_TOP_LEVEL; } @@ -9178,7 +9312,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) && !zend_compile_ignore_class(parent_ce, ce->info.user.filename)) { if (zend_try_early_bind(ce, parent_ce, lcname, NULL)) { zend_string_release(lcname); - return; + goto compile_nested_classes; } } } else if (EXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL)) { @@ -9187,7 +9321,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_inheritance_check_override(ce); ce->ce_flags |= ZEND_ACC_LINKED; zend_observer_class_linked_notify(ce, lcname); - return; + goto compile_nested_classes; } else { goto link_unbound; } @@ -9257,6 +9391,24 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) opline->result.opline_num = -1; } } + compile_nested_classes: + + if (CG(nested_class_queue) == NULL) { + CG(active_class_entry) = original_ce; + return; + } + + HashTable *queue = CG(nested_class_queue); + CG(nested_class_queue) = NULL; + + ZEND_HASH_FOREACH_PTR(queue, ast) { + zend_compile_class_decl(NULL, ast, true); + } ZEND_HASH_FOREACH_END(); + + CG(active_class_entry) = original_ce; + + zend_hash_destroy(queue); + FREE_HASHTABLE(queue); } /* }}} */ @@ -11547,6 +11699,10 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ zend_compile_use_trait(ast); break; case ZEND_AST_CLASS: + if (CG(active_class_entry)) { + zend_defer_class_decl(ast); + break; + } zend_compile_class_decl(NULL, ast, 0); break; case ZEND_AST_GROUP_USE: diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 224a68be749cb..44e5959649fba 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -452,6 +452,7 @@ typedef struct _zend_property_info { HashTable *attributes; zend_class_entry *ce; zend_type type; + zend_type_node *type_tree; const zend_property_info *prototype; zend_function **hooks; } zend_property_info; @@ -479,6 +480,7 @@ typedef struct _zend_class_constant { typedef struct _zend_internal_arg_info { const char *name; zend_type type; + zend_type_node *type_tree; const char *default_value; } zend_internal_arg_info; @@ -486,6 +488,7 @@ typedef struct _zend_internal_arg_info { typedef struct _zend_arg_info { zend_string *name; zend_type type; + zend_type_node *type_tree; zend_string *default_value; } zend_arg_info; @@ -497,6 +500,7 @@ typedef struct _zend_arg_info { typedef struct _zend_internal_function_info { uintptr_t required_num_args; zend_type type; + zend_type_node *type_tree; const char *default_value; } zend_internal_function_info; @@ -894,6 +898,7 @@ typedef enum { ZEND_MODIFIER_TARGET_CONSTANT, ZEND_MODIFIER_TARGET_CPP, ZEND_MODIFIER_TARGET_PROPERTY_HOOK, + ZEND_MODIFIER_TARGET_NESTED_CLASS, } zend_modifier_target; /* Used during AST construction */ @@ -1060,6 +1065,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_INTERNAL_CLASS 1 #define ZEND_USER_CLASS 2 +#define ZEND_NAMESPACE_CLASS 3 #define ZEND_EVAL (1<<0) #define ZEND_INCLUDE (1<<1) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6b6af2c225f79..6771a740886e4 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1045,8 +1045,45 @@ static zend_always_inline bool i_zend_check_property_type(const zend_property_in return zend_verify_scalar_type_hint(type_mask, property, strict, 0); } +static zend_result zend_check_type_visibility(const zend_class_entry *ce, const zend_property_info *info, uint32_t current_visibility) +{ + /* public classes are always visible */ + if (!ce->required_scope) { + return SUCCESS; + } + + /* a protected class is visible if it is a subclass of the lexical scope + * and the current visibility is protected or private */ + if (!ce->required_scope_absolute && instanceof_function(info->ce, ce->required_scope)) { + if (current_visibility & ZEND_ACC_PUBLIC) { + zend_type_error("Cannot declare protected class %s to a public property in %s::%s", ZSTR_VAL(ce->name), ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name)); + return FAILURE; + } + + return SUCCESS; + } + + /* a private class is visible if it is the same class as the lexical scope and the current visibility is private */ + if (ce->required_scope_absolute && ce->required_scope == info->ce) { + if (current_visibility < ZEND_ACC_PRIVATE) { + zend_type_error("Cannot declare private class %s to a %s property in %s::%s", ZSTR_VAL(ce->name), zend_visibility_string(current_visibility), ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name)); + return FAILURE; + } + + return SUCCESS; + } + + zend_type_error("Cannot declare %s to weaker visible property %s::%s", ZSTR_VAL(ce->name), ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name)); + return FAILURE; +} + static zend_always_inline bool i_zend_verify_property_type(const zend_property_info *info, zval *property, bool strict) { + if (Z_TYPE_P(property) == IS_OBJECT && zend_check_type_visibility(Z_OBJCE_P(property), info, info->flags)) { + zend_verify_property_type_error(info, property); + return 0; + } + if (i_zend_check_property_type(info, property, strict)) { return 1; } @@ -1140,71 +1177,58 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( return ce; } -static bool zend_check_intersection_type_from_cache_slot(zend_type_list *intersection_type_list, - zend_class_entry *arg_ce, void ***cache_slot_ptr) +static int zend_type_node_matches(const zend_type_node *node, zval *zv) { - void **cache_slot = *cache_slot_ptr; - zend_class_entry *ce; - zend_type *list_type; - bool status = true; - ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) { - /* Only check classes if the type might be valid */ - if (status) { - ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* If type is not an instance of one of the types taking part in the - * intersection it cannot be a valid instance of the whole intersection type. */ - if (!ce || !instanceof_function(arg_ce, ce)) { - status = false; + switch (node->kind) { + case ZEND_TYPE_SIMPLE: { + return 2; + } + + case ZEND_TYPE_UNION: { + for (uint32_t i = 0; i < node->compound.num_types; i++) { + if (zend_type_node_matches(node->compound.types[i], zv)) { + return 1; + } } + return 0; } - PROGRESS_CACHE_SLOT(); - } ZEND_TYPE_LIST_FOREACH_END(); - if (HAVE_CACHE_SLOT) { - *cache_slot_ptr = cache_slot; + + case ZEND_TYPE_INTERSECTION: { + for (uint32_t i = 0; i < node->compound.num_types; i++) { + if (!zend_type_node_matches(node->compound.types[i], zv)) { + return 0; + } + } + return 1; + } + + default: + return 0; } - return status; } + static zend_always_inline bool zend_check_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) { - uint32_t type_mask; + if (EXPECTED(type_tree != NULL) && type_tree->kind != ZEND_TYPE_SIMPLE) { + const int result = zend_type_node_matches(type_tree, arg); + if (result < 2) { + return result; + } + } + if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { - zend_class_entry *ce; - if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { - zend_type *list_type; - if (ZEND_TYPE_IS_INTERSECTION(*type)) { - return zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg), &cache_slot); - } else { - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (ZEND_TYPE_IS_INTERSECTION(*list_type)) { - if (zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg), &cache_slot)) { - return true; - } - /* The cache_slot is progressed in zend_check_intersection_type_from_cache_slot() */ - } else { - ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type)); - ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* Instance of a single type part of a union is sufficient to pass the type check */ - if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { - return true; - } - PROGRESS_CACHE_SLOT(); - } - } ZEND_TYPE_LIST_FOREACH_END(); - } - } else { - ce = zend_fetch_ce_from_cache_slot(cache_slot, type); - /* If we have a CE we check if it satisfies the type constraint, - * otherwise it will check if a standard type satisfies it. */ - if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { - return true; - } + const zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, type); + /* If we have a CE we check if it satisfies the type constraint, + * otherwise it will check if a standard type satisfies it. */ + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; } } - type_mask = ZEND_TYPE_FULL_MASK(*type); + const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) { return 1; @@ -1232,7 +1256,7 @@ static zend_always_inline bool zend_check_type_slow( } static zend_always_inline bool zend_check_type( - zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, + zend_type *type, zend_type_node *type_tree, zval *arg, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal) { zend_reference *ref = NULL; @@ -1247,14 +1271,14 @@ static zend_always_inline bool zend_check_type( return 1; } - return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal); + return zend_check_type_slow(type, type_tree, arg, ref, cache_slot, is_return_type, is_internal); } ZEND_API bool zend_check_user_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type) + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type) { return zend_check_type_slow( - type, arg, ref, cache_slot, is_return_type, /* is_internal */ false); + type, type_tree, arg, ref, cache_slot, is_return_type, /* is_internal */ false); } static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) @@ -1265,7 +1289,7 @@ static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint cur_arg_info = &zf->common.arg_info[arg_num-1]; if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) { + && UNEXPECTED(!zend_check_type(&cur_arg_info->type, cur_arg_info->type_tree, arg, cache_slot, zf->common.scope, 0, 0))) { zend_verify_arg_error(zf, cur_arg_info, arg_num, arg); return 0; } @@ -1277,7 +1301,7 @@ static zend_always_inline bool zend_verify_variadic_arg_type( zend_function *zf, zend_arg_info *arg_info, uint32_t arg_num, zval *arg, void **cache_slot) { ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type)); - if (UNEXPECTED(!zend_check_type(&arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) { + if (UNEXPECTED(!zend_check_type(&arg_info->type, arg_info->type_tree, arg, cache_slot, zf->common.scope, 0, 0))) { zend_verify_arg_error(zf, arg_info, arg_num, arg); return 0; } @@ -1302,7 +1326,7 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ } if (ZEND_TYPE_IS_SET(cur_arg_info->type) - && UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) { + && UNEXPECTED(!zend_check_type(&cur_arg_info->type, cur_arg_info->type_tree, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) { return 0; } arg++; @@ -1508,7 +1532,7 @@ ZEND_API bool zend_verify_internal_return_type(zend_function *zf, zval *ret) return 1; } - if (UNEXPECTED(!zend_check_type(&ret_info->type, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) { + if (UNEXPECTED(!zend_check_type(&ret_info->type, ret_info->type_tree, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) { zend_verify_internal_return_error(zf, ret); return 0; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 3b8ed89ec4f38..5e40229591c67 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -105,7 +105,7 @@ ZEND_API ZEND_COLD void zend_verify_never_error( const zend_function *zf); ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); ZEND_API bool zend_check_user_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type); + zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type); #if ZEND_DEBUG ZEND_API bool zend_internal_call_should_throw(zend_function *fbc, zend_execute_data *call); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 1f55521fb72f1..7a46a2a074bc3 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -39,6 +39,7 @@ #include "zend_observer.h" #include "zend_call_stack.h" #include "zend_frameless_function.h" +#include "zend_namespaces.h" #ifdef HAVE_SYS_TIME_H #include #endif @@ -148,6 +149,8 @@ void init_executor(void) /* {{{ */ EG(function_table) = CG(function_table); EG(class_table) = CG(class_table); + EG(namespaces) = NULL; + EG(global_namespace) = NULL; EG(in_autoload) = NULL; EG(error_handling) = EH_NORMAL; @@ -496,6 +499,8 @@ void shutdown_executor(void) /* {{{ */ FREE_HASHTABLE(*EG(symtable_cache_ptr)); } + zend_destroy_namespaces(); + zend_hash_destroy(&EG(included_files)); zend_stack_destroy(&EG(user_error_handlers_error_reporting)); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 079bfb99caccf..2e205c9288609 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -85,6 +85,7 @@ struct _zend_compiler_globals { zend_stack loop_var_stack; zend_class_entry *active_class_entry; + HashTable *nested_class_queue; zend_string *compiled_filename; @@ -191,6 +192,8 @@ struct _zend_executor_globals { HashTable *function_table; /* function symbol table */ HashTable *class_table; /* class table */ HashTable *zend_constants; /* constants table */ + HashTable *namespaces; /* namespace table */ + zend_class_entry *global_namespace; zval *vm_stack_top; zval *vm_stack_end; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 9483a83b4e955..3c8785477971b 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -285,10 +285,10 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type enum_declaration_statement enum_backing_type enum_case enum_case_expr %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body -%type optional_parameter_list +%type optional_parameter_list nested_class_statement %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers -%type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers +%type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers nested_class_modifiers %type class_modifiers class_modifier anonymous_class_modifiers anonymous_class_modifiers_optional use_type backup_fn_flags %type backup_lex_pos @@ -628,6 +628,14 @@ class_modifier: | T_READONLY { $$ = ZEND_ACC_READONLY_CLASS|ZEND_ACC_NO_DYNAMIC_PROPERTIES; } ; +nested_class_modifiers: + non_empty_member_modifiers + { $$ = zend_modifier_list_to_flags(ZEND_MODIFIER_TARGET_NESTED_CLASS, $1); + if (!$$) { YYERROR; } } + | %empty + { $$ = ZEND_ACC_PUBLIC; } +; + trait_declaration_statement: T_TRAIT { $$ = CG(zend_lineno); } T_STRING backup_doc_comment '{' class_statement_list '}' @@ -943,6 +951,10 @@ class_statement_list: { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); } ; +nested_class_statement: + T_CLASS T_STRING { $$ = CG(zend_lineno); } extends_from implements_list backup_doc_comment '{' class_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $3, $6, zend_ast_get_str($2), $4, $5, $8, NULL, NULL); } +; attributed_class_statement: property_modifiers optional_type_without_static property_list ';' @@ -962,6 +974,7 @@ attributed_class_statement: { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1 | $12, $2, $5, zend_ast_get_str($4), $7, NULL, $11, $9, NULL); CG(extra_fn_flags) = $10; } | enum_case { $$ = $1; } + | nested_class_modifiers nested_class_statement { $$ = $2; $$->attr = $1; } ; class_statement: diff --git a/Zend/zend_namespaces.c b/Zend/zend_namespaces.c new file mode 100644 index 0000000000000..a85bb197d5336 --- /dev/null +++ b/Zend/zend_namespaces.c @@ -0,0 +1,105 @@ +/* ++----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | 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 | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rob Landers | + | | + +----------------------------------------------------------------------+ +*/ + +#include "zend_namespaces.h" +#include "zend_API.h" +#include "zend_hash.h" + +zend_class_entry *create_namespace(zend_string *name) { + zend_class_entry *ns = pemalloc(sizeof(zend_class_entry), 0); + zend_initialize_class_data(ns, 1); + ns->type = ZEND_NAMESPACE_CLASS; + ns->ce_flags |= ZEND_ACC_UNINSTANTIABLE; + ns->name = name; + + return ns; +} + +static zend_class_entry *insert_namespace(const zend_string *name, zend_string *lc_name) { + zend_class_entry *parent_ns = EG(global_namespace); + zend_class_entry *ns = parent_ns; + const char *start = ZSTR_VAL(name); + const char *end = start + ZSTR_LEN(name); + const char *pos = start; + size_t len = 0; + + while (pos <= end) { + if (pos == end || *pos == '\\') { + len = pos - start; + zend_string *needle = zend_string_init(ZSTR_VAL(lc_name), len, 0); + + ns = zend_hash_find_ptr(EG(namespaces), needle); + + if (!ns) { + zend_string *full_name = zend_string_init_interned(ZSTR_VAL(name), len, 1); + ns = create_namespace(full_name); + ns->lexical_scope = parent_ns; + zend_hash_add_ptr(EG(namespaces), needle, ns); + } + zend_string_release(needle); + + parent_ns = ns; + } + pos ++; + } + + return ns; +} + +zend_class_entry *zend_resolve_namespace(zend_string *name) { + if (EG(global_namespace) == NULL) { + EG(global_namespace) = create_namespace(zend_empty_string); + EG(global_namespace)->lexical_scope = NULL; + ALLOC_HASHTABLE(EG(namespaces)); + zend_hash_init(EG(namespaces), 8, NULL, ZEND_CLASS_DTOR, 0); + zend_hash_add_ptr(EG(namespaces), zend_empty_string, EG(global_namespace)); + } + + if (name == NULL || ZSTR_LEN(name) == 0) { + return EG(global_namespace); + } + + zend_string *lc_name = zend_string_tolower(name); + zend_class_entry *ns = zend_hash_find_ptr(EG(namespaces), lc_name); + + if (!ns) { + ns = insert_namespace(name, lc_name); + } + + zend_string_release(lc_name); + + return ns; +} + +zend_class_entry *zend_lookup_namespace(zend_string *name) { + zend_string *lc_name = zend_string_tolower(name); + zend_class_entry *ns = zend_hash_find_ptr(EG(namespaces), lc_name); + zend_string_release(lc_name); + + return ns; +} + +void zend_destroy_namespaces(void) { + if (EG(namespaces) != NULL) { + zend_hash_destroy(EG(namespaces)); + FREE_HASHTABLE(EG(namespaces)); + EG(namespaces) = NULL; + EG(global_namespace) = NULL; + } +} diff --git a/Zend/zend_namespaces.h b/Zend/zend_namespaces.h new file mode 100644 index 0000000000000..1ef876d80019c --- /dev/null +++ b/Zend/zend_namespaces.h @@ -0,0 +1,29 @@ +/* ++----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | 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 | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Rob Landers | + | | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_NAMESPACES_H +#define ZEND_NAMESPACES_H + +#include "zend_compile.h" + +ZEND_API zend_class_entry *zend_resolve_namespace(zend_string *name); +ZEND_API zend_class_entry *zend_lookup_namespace(zend_string *name); +ZEND_API void zend_destroy_namespaces(void); + +#endif //ZEND_NAMESPACES_H diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 3b3ecfc590d2a..70de86788593d 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -381,6 +381,7 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c if (flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) { zend_class_entry *scope = get_fake_or_executed_scope(); +check_lexical_scope: if (property_info->ce != scope) { if (flags & ZEND_ACC_CHANGED) { zend_property_info *p = zend_get_parent_private_property(scope, ce, member); @@ -402,6 +403,10 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c goto dynamic; } else { wrong: + if (scope && scope->lexical_scope && scope->lexical_scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + goto check_lexical_scope; + } /* Information was available, but we were denied access. Error out. */ if (!silent) { zend_bad_property_access(property_info, ce, member); @@ -1818,6 +1823,8 @@ ZEND_API zend_function *zend_std_get_method(zend_object **obj_ptr, zend_string * /* Check access level */ if (fbc->op_array.fn_flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) { scope = zend_get_executed_scope(); + zend_class_entry *original_scope = scope; +check_lexical_scope: if (fbc->common.scope != scope) { if (fbc->op_array.fn_flags & ZEND_ACC_CHANGED) { @@ -1835,7 +1842,11 @@ ZEND_API zend_function *zend_std_get_method(zend_object **obj_ptr, zend_string * if (zobj->ce->__call) { fbc = zend_get_user_call_function(zobj->ce, method_name); } else { - zend_bad_method_call(fbc, method_name, scope); + if (scope && scope->lexical_scope && scope->lexical_scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + goto check_lexical_scope; + } + zend_bad_method_call(fbc, method_name, original_scope); fbc = NULL; } } @@ -1894,12 +1905,20 @@ ZEND_API zend_function *zend_std_get_static_method(zend_class_entry *ce, zend_st fbc = Z_FUNC_P(func); if (!(fbc->op_array.fn_flags & ZEND_ACC_PUBLIC)) { zend_class_entry *scope = zend_get_executed_scope(); + zend_class_entry *original_scope = scope; + +check_lexical_scope: if (UNEXPECTED(fbc->common.scope != scope)) { if (UNEXPECTED(fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fbc), scope))) { zend_function *fallback_fbc = get_static_method_fallback(ce, function_name); if (!fallback_fbc) { - zend_bad_method_call(fbc, function_name, scope); + if (scope && scope->lexical_scope && scope->lexical_scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + goto check_lexical_scope; + } + + zend_bad_method_call(fbc, function_name, original_scope); } fbc = fallback_fbc; } @@ -1976,10 +1995,15 @@ ZEND_API zval *zend_std_get_static_property_with_info(zend_class_entry *ce, zend if (!(property_info->flags & ZEND_ACC_PUBLIC)) { zend_class_entry *scope = get_fake_or_executed_scope(); +check_lexical_scope: if (property_info->ce != scope) { if (UNEXPECTED(property_info->flags & ZEND_ACC_PRIVATE) || UNEXPECTED(!is_protected_compatible_scope(property_info->ce, scope))) { if (type != BP_VAR_IS) { + if (scope && scope->lexical_scope && scope->lexical_scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + goto check_lexical_scope; + } zend_bad_property_access(property_info, ce, property_name); } return NULL; @@ -2060,10 +2084,16 @@ ZEND_API zend_function *zend_std_get_constructor(zend_object *zobj) /* {{{ */ if (constructor) { if (UNEXPECTED(!(constructor->op_array.fn_flags & ZEND_ACC_PUBLIC))) { zend_class_entry *scope = get_fake_or_executed_scope(); + zend_class_entry *original_scope = scope; +check_lexical_scope: if (UNEXPECTED(constructor->common.scope != scope)) { if (UNEXPECTED(constructor->op_array.fn_flags & ZEND_ACC_PRIVATE) || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(constructor), scope))) { - zend_bad_constructor_call(constructor, scope); + if (scope && scope->lexical_scope && scope->lexical_scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + goto check_lexical_scope; + } + zend_bad_constructor_call(constructor, original_scope); zend_object_store_ctor_failed(zobj); constructor = NULL; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index f32ae13e06793..372d3b90927ac 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -432,6 +432,7 @@ ZEND_API void destroy_zend_class(zval *zv) } break; case ZEND_INTERNAL_CLASS: + case ZEND_NAMESPACE_CLASS: if (ce->doc_comment) { zend_string_release_ex(ce->doc_comment, 1); } @@ -527,7 +528,11 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->attributes) { zend_hash_release(ce->attributes); } - free(ce); + if (ce->type == ZEND_NAMESPACE_CLASS) { + pefree(ce, 0); + } else { + free(ce); + } break; } } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index f839cec3b3667..89d9c3908cc49 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -142,6 +142,27 @@ typedef struct { zend_type types[1]; } zend_type_list; +typedef struct _zend_type_node zend_type_node; + +typedef enum { + ZEND_TYPE_SIMPLE, + ZEND_TYPE_UNION, + ZEND_TYPE_INTERSECTION, +} zend_type_node_kind; + +struct _zend_type_node { + zend_type_node_kind kind; + + union { + zend_type simple_type; + + struct { + uint32_t num_types; + zend_type_node **types; + } compound; + }; +}; + #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 #define _ZEND_TYPE_MASK ((1u << 25) - 1) /* Only one of these bits may be set. */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 8770cab3826d8..f7e882a98f55a 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4427,7 +4427,25 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + + if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) { + if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) { + zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } else { + zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) { + zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } + } + + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index e890f94cb08e2..f828ecc438a97 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -10799,7 +10799,25 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + + if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) { + if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) { + zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } else { + zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) { + zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } + } + + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -21536,7 +21554,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + + if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) { + if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) { + zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } else { + zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) { + zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } + } + + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -30012,7 +30048,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + + if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) { + if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) { + zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } else { + zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) { + zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } + } + + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -37802,7 +37856,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + + if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) { + if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) { + zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } else { + zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) { + zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } + } + + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -50603,7 +50675,25 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { + + if (Z_TYPE_P(retval_ptr) == IS_OBJECT && Z_OBJCE_P(retval_ptr)->required_scope) { + if (EX(func)->common.fn_flags & ZEND_ACC_PUBLIC) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute) { + zend_type_error("Public method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } else { + zend_type_error("Public method %s cannot return protected class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } else if (EX(func)->common.fn_flags & ZEND_ACC_PROTECTED) { + if (Z_OBJCE_P(retval_ptr)->required_scope_absolute && Z_OBJCE_P(retval_ptr)->required_scope != EX(func)->common.scope) { + zend_type_error("Protected method %s cannot return private class %s", ZSTR_VAL(EX(func)->common.function_name), ZSTR_VAL(Z_OBJCE_P(retval_ptr)->name)); + HANDLE_EXCEPTION(); + } + } + } + + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/configure.ac b/configure.ac index 01d9ded69b920..e1f99ab8e287d 100644 --- a/configure.ac +++ b/configure.ac @@ -1757,6 +1757,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_opcode.c zend_operators.c zend_property_hooks.c + zend_namespaces.c zend_ptr_stack.c zend_signal.c zend_smart_str.c diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 177feea3afa6e..308f06d31bb03 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1902,7 +1902,7 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg const zend_op *opline = EX(opline); void **cache_slot = CACHE_ADDR(opline->extended_value); bool ret = zend_check_user_type_slow( - &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false); + &arg_info->type, arg_info->type_tree, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false); if (UNEXPECTED(!ret)) { zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg); return 0; @@ -1919,7 +1919,7 @@ static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_a } } if (UNEXPECTED(!zend_check_user_type_slow( - &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { + &arg_info->type, arg_info->type_tree, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { zend_verify_return_error((zend_function*)op_array, arg); } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 3d45c63a98781..5f6fbecd2ac94 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -1121,6 +1121,27 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) return ce; } +void zend_update_ce_scopes(zend_class_entry *ce) +{ + if (ce->required_scope) { + zend_class_entry *required_scope = ce->required_scope; + + zend_class_entry *r = zend_shared_alloc_get_xlat_entry(required_scope); + if (r) { + ce->required_scope = r; + } + } + + if (ce->lexical_scope) { + zend_class_entry *lexical_scope = ce->lexical_scope; + + zend_class_entry *l = zend_shared_alloc_get_xlat_entry(lexical_scope); + if (l) { + ce->lexical_scope = l; + } + } +} + void zend_update_parent_ce(zend_class_entry *ce) { if (ce->ce_flags & ZEND_ACC_LINKED) { @@ -1294,6 +1315,7 @@ static void zend_accel_persist_class_table(HashTable *class_table) if (EXPECTED(Z_TYPE(p->val) != IS_ALIAS_PTR)) { ce = Z_PTR(p->val); zend_update_parent_ce(ce); + zend_update_ce_scopes(ce); } } ZEND_HASH_FOREACH_END(); #ifdef HAVE_JIT diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bc8ffbdd8bd8e..b9190113d01cf 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4075,6 +4075,55 @@ static void add_class_vars(zend_class_entry *ce, bool statics, zval *return_valu } /* }}} */ +/* {{{ Returns whether the class is private */ +ZEND_METHOD(ReflectionClass, isPrivate) +{ + reflection_object *intern; + zend_class_entry *ce; + + ZEND_PARSE_PARAMETERS_NONE(); + GET_REFLECTION_OBJECT_PTR(ce); + RETURN_BOOL(ce->required_scope && ce->required_scope_absolute); +} +/* }}} */ + +/* {{{ Returns true if the class is protected */ +ZEND_METHOD(ReflectionClass, isProtected) +{ + reflection_object *intern; + zend_class_entry *ce; + + ZEND_PARSE_PARAMETERS_NONE(); + GET_REFLECTION_OBJECT_PTR(ce); + RETURN_BOOL(ce->required_scope && !ce->required_scope_absolute); +} +/* }}} */ + +/* {{{ Returns true if the class is public */ +ZEND_METHOD(ReflectionClass, isPublic) +{ + reflection_object *intern; + zend_class_entry *ce; + + ZEND_PARSE_PARAMETERS_NONE(); + GET_REFLECTION_OBJECT_PTR(ce); + RETURN_BOOL(!ce->required_scope); +} +/* }}} */ + +/* {{{ Returns whether the current class is an inner class */ +ZEND_METHOD(ReflectionClass, isInnerClass) +{ + reflection_object *intern; + zend_class_entry *ce; + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(ce); + + RETURN_BOOL(ce->lexical_scope && ce->lexical_scope->type != ZEND_NAMESPACE_CLASS); +} +/* }}} */ + /* {{{ Returns an associative array containing all static property values of the class */ ZEND_METHOD(ReflectionClass, getStaticProperties) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index be511d7ee14cd..15b506683ed04 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -432,6 +432,14 @@ public function getNamespaceName(): string {} public function getShortName(): string {} public function getAttributes(?string $name = null, int $flags = 0): array {} + + public function isInnerClass(): bool {} + + public function isPrivate(): bool {} + + public function isPublic(): bool {} + + public function isProtected(): bool {} } class ReflectionObject extends ReflectionClass diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index d78a685dde9c9..962da4160ef52 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3c6be99bb36965139464925a618cb0bf03affa62 */ + * Stub hash: 192b737230cefd6dd019062664e7deb49f920645 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -366,6 +366,14 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionClass_isInnerClass arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionClass_isPrivate arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionClass_isPublic arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionClass_isProtected arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionObject___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) ZEND_END_ARG_INFO() @@ -847,6 +855,10 @@ ZEND_METHOD(ReflectionClass, inNamespace); ZEND_METHOD(ReflectionClass, getNamespaceName); ZEND_METHOD(ReflectionClass, getShortName); ZEND_METHOD(ReflectionClass, getAttributes); +ZEND_METHOD(ReflectionClass, isInnerClass); +ZEND_METHOD(ReflectionClass, isPrivate); +ZEND_METHOD(ReflectionClass, isPublic); +ZEND_METHOD(ReflectionClass, isProtected); ZEND_METHOD(ReflectionObject, __construct); ZEND_METHOD(ReflectionProperty, __construct); ZEND_METHOD(ReflectionProperty, __toString); @@ -1139,6 +1151,10 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, getNamespaceName, arginfo_class_ReflectionClass_getNamespaceName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getShortName, arginfo_class_ReflectionClass_getShortName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getAttributes, arginfo_class_ReflectionClass_getAttributes, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isInnerClass, arginfo_class_ReflectionClass_isInnerClass, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isPrivate, arginfo_class_ReflectionClass_isPrivate, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isPublic, arginfo_class_ReflectionClass_isPublic, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isProtected, arginfo_class_ReflectionClass_isProtected, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index fd5d83e917419..d505878bc1d9e 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -30,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [64] { + - Methods [68] { Method [ private method __clone ] { - Parameters [0] { @@ -514,5 +514,33 @@ Class [ class ReflectionClass implements Stringable, Refle } - Return [ array ] } + + Method [ public method isInnerClass ] { + + - Parameters [0] { + } + - Return [ bool ] + } + + Method [ public method isPrivate ] { + + - Parameters [0] { + } + - Return [ bool ] + } + + Method [ public method isPublic ] { + + - Parameters [0] { + } + - Return [ bool ] + } + + Method [ public method isProtected ] { + + - Parameters [0] { + } + - Return [ bool ] + } } } diff --git a/ext/reflection/tests/bug74454.phpt b/ext/reflection/tests/bug74454.phpt index 272409339c479..f1116becf6ce8 100644 --- a/ext/reflection/tests/bug74454.phpt +++ b/ext/reflection/tests/bug74454.phpt @@ -14,4 +14,4 @@ function load_file() { } ?> --EXPECT-- -ParseError: syntax error, unexpected token "if", expecting "function" +ParseError: syntax error, unexpected token "if", expecting "class" diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index e3f87ee1e1636..725396d3421d2 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -1202,7 +1202,7 @@ static void register_ZendTestClass_dnf_property(zend_class_entry *ce) { // The types are upgraded to DNF types in `register_dynamic_function_entries()` static zend_internal_arg_info arginfo_zend_test_internal_dnf_arguments[] = { // first entry is a zend_internal_function_info (see zend_compile.h): {argument_count, return_type, unused} - {(const char*)(uintptr_t)(1), {0}, NULL}, + {(const char*)(uintptr_t)(1), {0}, NULL, NULL}, {"arg", {0}, NULL} }; diff --git a/tests/classes/inner_classes/access_modifiers_001.phpt b/tests/classes/inner_classes/access_modifiers_001.phpt new file mode 100644 index 0000000000000..ac1ce71cd3d40 --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_001.phpt @@ -0,0 +1,13 @@ +--TEST-- +multiple access modifiers +--FILE-- + +--EXPECTF-- +Fatal error: Multiple access type modifiers are not allowed in %s on line %d diff --git a/tests/classes/inner_classes/access_modifiers_002.phpt b/tests/classes/inner_classes/access_modifiers_002.phpt new file mode 100644 index 0000000000000..9c9eee89387b0 --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_002.phpt @@ -0,0 +1,13 @@ +--TEST-- +invalid inner class +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected identifier "int", expecting "class" in %s on line %d diff --git a/tests/classes/inner_classes/access_modifiers_003.phpt b/tests/classes/inner_classes/access_modifiers_003.phpt new file mode 100644 index 0000000000000..f6cd8bdd3eab1 --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_003.phpt @@ -0,0 +1,13 @@ +--TEST-- +static access modifiers +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the static modifier on a nested class in %s on line %d diff --git a/tests/classes/inner_classes/access_modifiers_004.phpt b/tests/classes/inner_classes/access_modifiers_004.phpt new file mode 100644 index 0000000000000..78ba58bfcc64a --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_004.phpt @@ -0,0 +1,13 @@ +--TEST-- +public(set) inner class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the public(set) modifier on a nested class in %s on line %d diff --git a/tests/classes/inner_classes/access_modifiers_005.phpt b/tests/classes/inner_classes/access_modifiers_005.phpt new file mode 100644 index 0000000000000..96966f0dc384e --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_005.phpt @@ -0,0 +1,13 @@ +--TEST-- +protected(set) inner class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the protected(set) modifier on a nested class in %s on line %d diff --git a/tests/classes/inner_classes/access_modifiers_006.phpt b/tests/classes/inner_classes/access_modifiers_006.phpt new file mode 100644 index 0000000000000..4599ea7cfd7d9 --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_006.phpt @@ -0,0 +1,13 @@ +--TEST-- +private(set) inner class +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the private(set) modifier on a nested class in %s on line %d diff --git a/tests/classes/inner_classes/access_modifiers_007.phpt b/tests/classes/inner_classes/access_modifiers_007.phpt new file mode 100644 index 0000000000000..88b9071b958cc --- /dev/null +++ b/tests/classes/inner_classes/access_modifiers_007.phpt @@ -0,0 +1,27 @@ +--TEST-- +abstract inner classes +--FILE-- +isAbstract()); +new Outer\Inner(); +?> +--EXPECTF-- +object(Extended)#1 (0) { +} +bool(true) + +Fatal error: Uncaught Error: Cannot instantiate abstract class Outer\Inner in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/aliases.phpt b/tests/classes/inner_classes/aliases.phpt new file mode 100644 index 0000000000000..3b1fe64e403a8 --- /dev/null +++ b/tests/classes/inner_classes/aliases.phpt @@ -0,0 +1,18 @@ +--TEST-- +aliases cannot escape +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: Cannot instantiate class Outer\Inner from the global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/autoload_001.phpt b/tests/classes/inner_classes/autoload_001.phpt new file mode 100644 index 0000000000000..dc18c02c8b9de --- /dev/null +++ b/tests/classes/inner_classes/autoload_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +ensure autoloading works +--FILE-- +x, ' ', $point->y, "\n"; +?> +--EXPECT-- +autoload(inner_classes\Point) +1 2 diff --git a/tests/classes/inner_classes/autoload_002.phpt b/tests/classes/inner_classes/autoload_002.phpt new file mode 100644 index 0000000000000..505ab8bfea8ae --- /dev/null +++ b/tests/classes/inner_classes/autoload_002.phpt @@ -0,0 +1,19 @@ +--TEST-- +ensure private autoloading works +--FILE-- + +--EXPECTF-- +autoload(inner_classes\Line) + +Fatal error: Uncaught TypeError: Cannot instantiate class inner_classes\Line from the global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/enum_usage.phpt b/tests/classes/inner_classes/enum_usage.phpt new file mode 100644 index 0000000000000..00b2c5f755a55 --- /dev/null +++ b/tests/classes/inner_classes/enum_usage.phpt @@ -0,0 +1,16 @@ +--TEST-- +usage in an enum +--FILE-- + +--EXPECT-- +object(Outer\Inner)#1 (0) { +} +bool(true) diff --git a/tests/classes/inner_classes/errors_001.phpt b/tests/classes/inner_classes/errors_001.phpt new file mode 100644 index 0000000000000..d03109ea5af51 --- /dev/null +++ b/tests/classes/inner_classes/errors_001.phpt @@ -0,0 +1,12 @@ +--TEST-- +no outer class +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Class "Outer\Inner" not found in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/errors_002.phpt b/tests/classes/inner_classes/errors_002.phpt new file mode 100644 index 0000000000000..883310c6705d0 --- /dev/null +++ b/tests/classes/inner_classes/errors_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +inner class not found +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Class "Outer\Inner" not found in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/inheritance.phpt b/tests/classes/inner_classes/inheritance.phpt new file mode 100644 index 0000000000000..b104070169a0a --- /dev/null +++ b/tests/classes/inner_classes/inheritance.phpt @@ -0,0 +1,20 @@ +--TEST-- +circular inheritance +--SKIPIF-- + +--FILE-- + +--EXPECT-- diff --git a/tests/classes/inner_classes/inner_classes.inc b/tests/classes/inner_classes/inner_classes.inc new file mode 100644 index 0000000000000..793804cf6606e --- /dev/null +++ b/tests/classes/inner_classes/inner_classes.inc @@ -0,0 +1,9 @@ +getMessage()}\n"; +} + +try { + var_dump(new Outer\ProtectedInner()); +} catch (Throwable $e) { + echo "Failed to instantiate Outer\ProtectedInner: {$e->getMessage()}\n"; +} + +try { + var_dump(new Outer\PublicInner()); +} catch (Throwable $e) { + echo "Failed to instantiate Outer\PublicInner: {$e->getMessage()}\n"; +} + +class Other { + public function testPrivate() { + var_dump(new Outer\PrivateInner()); + } + public function testProtected() { + var_dump(new Outer\ProtectedInner()); + } + public function testPublic() { + var_dump(new Outer\PublicInner()); + } +} + +$other = new Other(); +foreach (['Private', 'Protected', 'Public'] as $type) { + try { + $other->{"test$type"}(); + } catch(Throwable $e) { + echo "Failed to instantiate Outer\\$type: {$e->getMessage()}\n"; + } +} + +class Child extends Outer { + public function testPrivate() { + var_dump(new Outer\PrivateInner()); + } + public function testProtected() { + var_dump(new Outer\ProtectedInner()); + } + public function testPublic() { + var_dump(new Outer\PublicInner()); + } +} + +$other = new Child(); +foreach (['Private', 'Protected', 'Public'] as $type) { + try { + $other->{"test$type"}(); + } catch(Throwable $e) { + echo "Failed to instantiate Outer\\$type: {$e->getMessage()}\n"; + } +} + +?> +--EXPECT-- +Failed to instantiate Outer\PrivateInner: Cannot instantiate class Outer\PrivateInner from the global scope +Failed to instantiate Outer\ProtectedInner: Cannot instantiate class Outer\ProtectedInner from the global scope +object(Outer\PublicInner)#1 (0) { +} +Failed to instantiate Outer\Private: Cannot instantiate private class Outer\PrivateInner from scope Other +Failed to instantiate Outer\Protected: Cannot instantiate protected class Outer\ProtectedInner from scope Other +object(Outer\PublicInner)#3 (0) { +} +Failed to instantiate Outer\Private: Cannot instantiate private class Outer\PrivateInner from scope Child +object(Outer\ProtectedInner)#2 (0) { +} +object(Outer\PublicInner)#2 (0) { +} diff --git a/tests/classes/inner_classes/interface_usage.phpt b/tests/classes/inner_classes/interface_usage.phpt new file mode 100644 index 0000000000000..e863e7b71bb88 --- /dev/null +++ b/tests/classes/inner_classes/interface_usage.phpt @@ -0,0 +1,21 @@ +--TEST-- +usage in an interface +--FILE-- + +--EXPECT-- +object(Outer\Inner)#1 (0) { +} +bool(true) +bool(false) diff --git a/tests/classes/inner_classes/readonly.phpt b/tests/classes/inner_classes/readonly.phpt new file mode 100644 index 0000000000000..3fb85996605a3 --- /dev/null +++ b/tests/classes/inner_classes/readonly.phpt @@ -0,0 +1,23 @@ +--TEST-- +readonly should work +--FILE-- +t = 42; +var_dump($foo); +?> +--EXPECTF-- +object(Outer\Inner)#1 (1) { + ["t"]=> + int(1) +} + +Fatal error: Uncaught Error: Cannot modify readonly property Outer\Inner::$t in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/reflection_001.phpt b/tests/classes/inner_classes/reflection_001.phpt new file mode 100644 index 0000000000000..4872d1514cdcc --- /dev/null +++ b/tests/classes/inner_classes/reflection_001.phpt @@ -0,0 +1,64 @@ +--TEST-- +reflection on inner classes +--FILE-- +getName()); + var_dump($ref->getShortName()); + var_dump($ref->isInnerClass()); + var_dump($ref->isPrivate()); + var_dump($ref->isProtected()); + var_dump($ref->isPublic()); +} + +details(Outer::class); +details('n\s\Outer\Middle\Inner'); +details('n\s\Outer\PrivateMiddle'); +details('n\s\Outer\ProtectedMiddle'); + +?> +--EXPECT-- +Details for n\s\Outer +string(9) "n\s\Outer" +string(5) "Outer" +bool(false) +bool(false) +bool(false) +bool(true) +Details for n\s\Outer\Middle\Inner +string(22) "n\s\Outer\Middle\Inner" +string(5) "Inner" +bool(true) +bool(false) +bool(false) +bool(true) +Details for n\s\Outer\PrivateMiddle +string(23) "n\s\Outer\PrivateMiddle" +string(13) "PrivateMiddle" +bool(true) +bool(true) +bool(false) +bool(false) +Details for n\s\Outer\ProtectedMiddle +string(25) "n\s\Outer\ProtectedMiddle" +string(15) "ProtectedMiddle" +bool(true) +bool(false) +bool(true) +bool(false) diff --git a/tests/classes/inner_classes/resolution_001.phpt b/tests/classes/inner_classes/resolution_001.phpt new file mode 100644 index 0000000000000..5425e76de304f --- /dev/null +++ b/tests/classes/inner_classes/resolution_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +resolution of outer scopes +--FILE-- + +--EXPECT-- +1 diff --git a/tests/classes/inner_classes/resolution_002.phpt b/tests/classes/inner_classes/resolution_002.phpt new file mode 100644 index 0000000000000..144dd11160167 --- /dev/null +++ b/tests/classes/inner_classes/resolution_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +shadowing +--FILE-- + +--EXPECT-- +Foo\Bar\Outer\Inner: 1 +Foo\Bar\Outer\Middle: 1 diff --git a/tests/classes/inner_classes/return_types_001.phpt b/tests/classes/inner_classes/return_types_001.phpt new file mode 100644 index 0000000000000..4f2e5806def02 --- /dev/null +++ b/tests/classes/inner_classes/return_types_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +test return types +--FILE-- + +--EXPECT-- +object(Outer\Inner)#1 (0) { +} diff --git a/tests/classes/inner_classes/return_types_002.phpt b/tests/classes/inner_classes/return_types_002.phpt new file mode 100644 index 0000000000000..cbd043a815d1f --- /dev/null +++ b/tests/classes/inner_classes/return_types_002.phpt @@ -0,0 +1,36 @@ +--TEST-- +private inner class +--FILE-- +getInner(); + } +} + +class Foo extends Outer { + public function getInner(): Outer\Inner { + var_dump(parent::getInner2()); + return new Outer\Inner(); + } +} + +$outer = new Foo(); +var_dump($outer->getInner()); +?> +--EXPECTF-- +object(Outer\Inner)#2 (0) { +} + +Fatal error: Uncaught TypeError: Cannot instantiate private class Outer\Inner from scope Foo in %s:%d +Stack trace: +#0 %s(%d): Foo->getInner() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/return_types_003.phpt b/tests/classes/inner_classes/return_types_003.phpt new file mode 100644 index 0000000000000..7b4b392167e0a --- /dev/null +++ b/tests/classes/inner_classes/return_types_003.phpt @@ -0,0 +1,33 @@ +--TEST-- +protected inner class +--FILE-- +getInner()); +var_dump(new Outer\Inner()); +?> +--EXPECTF-- +object(Outer\Inner)#2 (0) { +} + +Fatal error: Uncaught TypeError: Public method getInner cannot return protected class Outer\Inner in %s:%d +Stack trace: +#0 %s(%d): Foo->getInner() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/return_types_004.phpt b/tests/classes/inner_classes/return_types_004.phpt new file mode 100644 index 0000000000000..a5c8635031aa6 --- /dev/null +++ b/tests/classes/inner_classes/return_types_004.phpt @@ -0,0 +1,26 @@ +--TEST-- +private return types +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: Public method getInner cannot return private class Outer\Inner in %s:%d +Stack trace: +#0 %s(%d): Outer::getInner() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/return_types_005.phpt b/tests/classes/inner_classes/return_types_005.phpt new file mode 100644 index 0000000000000..dbb4f15ddcc24 --- /dev/null +++ b/tests/classes/inner_classes/return_types_005.phpt @@ -0,0 +1,26 @@ +--TEST-- +protected return types +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: Public method getInner cannot return protected class Outer\Inner in %s:%d +Stack trace: +#0 %s(%d): Outer::getInner() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/return_types_006.phpt b/tests/classes/inner_classes/return_types_006.phpt new file mode 100644 index 0000000000000..d530496a28b35 --- /dev/null +++ b/tests/classes/inner_classes/return_types_006.phpt @@ -0,0 +1,21 @@ +--TEST-- +returning private inner from inner method +--FILE-- +test(); } +} + +$foo = new Outer()->test(); +var_dump($foo); +?> +--EXPECT-- +object(Outer\PrivateInner)#3 (0) { +} diff --git a/tests/classes/inner_classes/simple_declaration_001.phpt b/tests/classes/inner_classes/simple_declaration_001.phpt new file mode 100644 index 0000000000000..93e9b16f4b241 --- /dev/null +++ b/tests/classes/inner_classes/simple_declaration_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +simple declaration +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/tests/classes/inner_classes/simple_declaration_002.phpt b/tests/classes/inner_classes/simple_declaration_002.phpt new file mode 100644 index 0000000000000..d129f17a7b0a5 --- /dev/null +++ b/tests/classes/inner_classes/simple_declaration_002.phpt @@ -0,0 +1,13 @@ +--TEST-- +nested inside function +--FILE-- + +--EXPECTF-- +Fatal error: Class declarations may not be declared inside functions in %s on line %d diff --git a/tests/classes/inner_classes/simple_declaration_003.phpt b/tests/classes/inner_classes/simple_declaration_003.phpt new file mode 100644 index 0000000000000..1d8c59fd64ef2 --- /dev/null +++ b/tests/classes/inner_classes/simple_declaration_003.phpt @@ -0,0 +1,20 @@ +--TEST-- +basic nested classes +--FILE-- +test(); +?> +--EXPECT-- +Foo\Outer\Middle\Inner diff --git a/tests/classes/inner_classes/simple_declaration_004.phpt b/tests/classes/inner_classes/simple_declaration_004.phpt new file mode 100644 index 0000000000000..92dde65e9fcfe --- /dev/null +++ b/tests/classes/inner_classes/simple_declaration_004.phpt @@ -0,0 +1,38 @@ +--TEST-- +scope resolution access +--FILE-- + +--EXPECT-- +object(Outer\Middle)#1 (0) { +} +object(Outer\Middle)#1 (0) { +} +object(Outer2\Middle)#1 (0) { +} diff --git a/tests/classes/inner_classes/simple_declaration_005.phpt b/tests/classes/inner_classes/simple_declaration_005.phpt new file mode 100644 index 0000000000000..0e28a13d44b53 --- /dev/null +++ b/tests/classes/inner_classes/simple_declaration_005.phpt @@ -0,0 +1,34 @@ +--TEST-- +failed inheritance +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of Outer2::testSelf(): Outer2\Middle must be compatible with Outer::testSelf(): Outer\Middle in %s on line %d diff --git a/tests/classes/inner_classes/static_variables.phpt b/tests/classes/inner_classes/static_variables.phpt new file mode 100644 index 0000000000000..debe1a5198e2c --- /dev/null +++ b/tests/classes/inner_classes/static_variables.phpt @@ -0,0 +1,30 @@ +--TEST-- +::class, statics, and inner classes +--FILE-- + +--EXPECT-- +string(12) "Outer\Middle" +string(3) "foo" +int(42) +string(18) "Outer\Middle\Inner" +string(3) "foo" +int(42) diff --git a/tests/classes/inner_classes/trait_usage_001.phpt b/tests/classes/inner_classes/trait_usage_001.phpt new file mode 100644 index 0000000000000..6e618e1a09387 --- /dev/null +++ b/tests/classes/inner_classes/trait_usage_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +usage inside a trait +--FILE-- + +--EXPECT-- +object(Outer\Inner)#1 (0) { +} +bool(true) +bool(false) diff --git a/tests/classes/inner_classes/visibility_001.phpt b/tests/classes/inner_classes/visibility_001.phpt new file mode 100644 index 0000000000000..2f13eb4c93f5c --- /dev/null +++ b/tests/classes/inner_classes/visibility_001.phpt @@ -0,0 +1,25 @@ +--TEST-- +outer class visibility +--FILE-- +illegal = new Inner(); + } +} + +$x = new Outer(); +$x->test(); + +var_dump($x); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: Cannot declare private class Outer\Inner to a public property in Outer::illegal in %s:%d +Stack trace: +#0 %s(%d): Outer->test() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/visibility_002.phpt b/tests/classes/inner_classes/visibility_002.phpt new file mode 100644 index 0000000000000..7444229361281 --- /dev/null +++ b/tests/classes/inner_classes/visibility_002.phpt @@ -0,0 +1,30 @@ +--TEST-- +accessing outer class private vars +--FILE-- +illegal = $this; + } + } + private Inner $illegal; + + public function test(): void { + new Inner()->test($this); + } +} + +$x = new Outer(); +$x->test(); + +var_dump($x); + +?> +--EXPECT-- +object(Outer)#1 (1) { + ["illegal":"Outer":private]=> + object(Outer\Inner)#2 (0) { + } +} diff --git a/tests/classes/inner_classes/visibility_003.phpt b/tests/classes/inner_classes/visibility_003.phpt new file mode 100644 index 0000000000000..07cbf29767ca0 --- /dev/null +++ b/tests/classes/inner_classes/visibility_003.phpt @@ -0,0 +1,30 @@ +--TEST-- +accessing outer protected vars +--FILE-- +illegal = $this; + } + } + private Inner $illegal; + + public function test(): void { + new Inner()->test($this); + } +} + +$x = new Outer(); +$x->test(); + +var_dump($x); + +?> +--EXPECT-- +object(Outer)#1 (1) { + ["illegal":"Outer":private]=> + object(Outer\Inner)#2 (0) { + } +} diff --git a/tests/classes/inner_classes/visibility_004.phpt b/tests/classes/inner_classes/visibility_004.phpt new file mode 100644 index 0000000000000..5f6b64f3f276b --- /dev/null +++ b/tests/classes/inner_classes/visibility_004.phpt @@ -0,0 +1,25 @@ +--TEST-- +outer class visibility +--FILE-- +illegal = new Inner(); + } +} + +$x = new Outer(); +$x->test(); + +var_dump($x); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: Cannot declare protected class Outer\Inner to a public property in Outer::illegal in %s:%d +Stack trace: +#0 %s(%d): Outer->test() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/visibility_005.phpt b/tests/classes/inner_classes/visibility_005.phpt new file mode 100644 index 0000000000000..8624c36245220 --- /dev/null +++ b/tests/classes/inner_classes/visibility_005.phpt @@ -0,0 +1,27 @@ +--TEST-- +accessing outer private methods +--FILE-- +test(); + } + } + } +} +new Outer\Middle\Inner()->test(); +?> +--EXPECT-- +Outer\Middle::test +Outer::test diff --git a/tests/classes/inner_classes/visibility_006.phpt b/tests/classes/inner_classes/visibility_006.phpt new file mode 100644 index 0000000000000..de15166bb551b --- /dev/null +++ b/tests/classes/inner_classes/visibility_006.phpt @@ -0,0 +1,28 @@ +--TEST-- +scope doesn't bypass scope +--FILE-- +test(); + } + } + } +} +new Outer\Middle\Inner()->testit(); +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to undefined method Outer\Middle\Inner::test() in %s:%d +Stack trace: +#0 %s(%d): Outer\Middle\Inner->testit() +#1 {main} + thrown in %s on line %d diff --git a/tests/classes/inner_classes/visibility_007.phpt b/tests/classes/inner_classes/visibility_007.phpt new file mode 100644 index 0000000000000..3da65bface64a --- /dev/null +++ b/tests/classes/inner_classes/visibility_007.phpt @@ -0,0 +1,27 @@ +--TEST-- +accessing outer protected methods +--FILE-- +test(); + } + } + } +} +new Outer\Middle\Inner()->test(); +?> +--EXPECT-- +Outer\Middle::test +Outer::test diff --git a/tests/classes/inner_classes/visibility_008.phpt b/tests/classes/inner_classes/visibility_008.phpt new file mode 100644 index 0000000000000..cbb8657fc632e --- /dev/null +++ b/tests/classes/inner_classes/visibility_008.phpt @@ -0,0 +1,30 @@ +--TEST-- +accessing sibling methods +--FILE-- +test(); + } + } +} +new Other\Inner()->test(); +?> +--EXPECT-- +Outer\Middle::test +Outer::test diff --git a/tests/classes/inner_classes/visibility_009.phpt b/tests/classes/inner_classes/visibility_009.phpt new file mode 100644 index 0000000000000..3c267d109984b --- /dev/null +++ b/tests/classes/inner_classes/visibility_009.phpt @@ -0,0 +1,38 @@ +--TEST-- +deeply nested property visibility +--FILE-- +i = 42; + var_dump($foo); + $foo = new Middle(); + $foo->i = 42; + var_dump($foo); + } + } + } +} +Outer\Middle\Inner::test(); +?> +--EXPECT-- +int(5) +int(42) +object(Outer)#1 (1) { + ["i":"Outer":private]=> + int(42) +} +object(Outer\Middle)#2 (1) { + ["i":"Outer\Middle":private]=> + int(42) +} diff --git a/tests/classes/inner_classes/visibility_010.phpt b/tests/classes/inner_classes/visibility_010.phpt new file mode 100644 index 0000000000000..b6e94ddf7d359 --- /dev/null +++ b/tests/classes/inner_classes/visibility_010.phpt @@ -0,0 +1,41 @@ +--TEST-- +constructors +--FILE-- +name = $builder->name; + $this->email = $builder->email; + } + + public readonly final class Builder { + public function __construct(public private(set) string|null $name = null, public private(set) string|null $email = null) {} + + public function withEmail(string $email): self { + return new self($this->name, $email); + } + + public function withName(string $name): self { + return new self($name, $this->email); + } + + public function build(): User { + return new User($this); + } + } +} + +$user = new User\Builder()->withName('Rob')->withEmail('rob@example.com')->build(); +var_dump($user); +?> +--EXPECT-- +object(User)#2 (2) { + ["name"]=> + string(3) "Rob" + ["email"]=> + string(15) "rob@example.com" +} diff --git a/win32/build/config.w32 b/win32/build/config.w32 index f82ed73efe3bd..85efc27c6fb4f 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -240,7 +240,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \ zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \ zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \ - zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c \ + zend_enum.c zend_fibers.c zend_atomic.c zend_hrtime.c zend_frameless_function.c zend_property_hooks.c zend_namespaces.c \ zend_lazy_objects.c"); ADD_SOURCES("Zend\\Optimizer", "zend_optimizer.c pass1.c pass3.c optimize_func_calls.c block_pass.c optimize_temp_vars_5.c nop_removal.c compact_literals.c zend_cfg.c zend_dfg.c dfa_pass.c zend_ssa.c zend_inference.c zend_func_info.c zend_call_graph.c zend_dump.c escape_analysis.c compact_vars.c dce.c sccp.c scdf.c");