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.c b/Zend/zend.c index 2d8a0f455f8b4..8180869b785ee 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -18,6 +18,7 @@ */ #include "zend.h" + #include "zend_extensions.h" #include "zend_modules.h" #include "zend_constants.h" @@ -41,6 +42,7 @@ #include "Optimizer/zend_optimizer.h" #include "php.h" #include "php_globals.h" +#include "zend_namespaces.h" // FIXME: Breaks the declaration of the function below #undef zenderror @@ -1228,6 +1230,7 @@ void zend_shutdown(void) /* {{{ */ zend_unload_modules(); zend_optimizer_shutdown(); + startup_done = false; } /* }}} */ @@ -1349,6 +1352,8 @@ ZEND_API void zend_deactivate(void) /* {{{ */ /* shutdown_executor() takes care of its own bailout handling */ shutdown_executor(); + zend_destroy_namespaces(); + zend_try { zend_ini_deactivate(); } zend_end_try(); diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..912008d43d997 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; + char 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..3f81d83d8a61b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1816,6 +1816,30 @@ 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_throw_error(NULL, "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_throw_error(NULL, "Cannot instantiate private class %s from %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_throw_error(NULL, "Cannot instantiate protected class %s from %s", ZSTR_VAL(class_type->name), ZSTR_VAL(scope->name)); + ZVAL_NULL(arg); + Z_OBJ_P(arg) = NULL; + return FAILURE; + } + } + if (UNEXPECTED(!(class_type->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) { if (UNEXPECTED(zend_update_class_constants(class_type) != SUCCESS)) { ZVAL_NULL(arg); diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 9348c35f6cc07..557a8c669cf23 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -134,6 +134,7 @@ enum _zend_ast_kind { ZEND_AST_YIELD, ZEND_AST_COALESCE, ZEND_AST_ASSIGN_COALESCE, + ZEND_AST_INNER_CLASS, ZEND_AST_STATIC, ZEND_AST_WHILE, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 1cba9ef221997..db5e92500b54d 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; \ @@ -893,12 +894,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_INNER_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_INNER_CLASS) { return ZEND_ACC_ABSTRACT; } break; @@ -906,6 +907,7 @@ 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_INNER_CLASS || target == ZEND_MODIFIER_TARGET_PROPERTY_HOOK) { return ZEND_ACC_FINAL; } @@ -943,6 +945,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_INNER_CLASS) { + member = "inner class"; } else { ZEND_UNREACHABLE(); } @@ -1050,6 +1054,37 @@ uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifi return 0; } } + if (target == ZEND_MODIFIER_TARGET_INNER_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 +1179,61 @@ static zend_string *zend_resolve_const_name(zend_string *name, uint32_t type, bo name, type, is_fully_qualified, 1, FC(imports_const)); } +static zend_string *get_namespace_from_scope(const zend_class_entry *scope) { + ZEND_ASSERT(scope != NULL); + while (scope && scope->lexical_scope && scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + } + return zend_string_copy(scope->name); +} + +static zend_string *get_scoped_name(zend_string *ns, zend_string *name) { + // remove the matching prefix from name + name = zend_string_tolower(name); + if (ns && ZSTR_LEN(ns) && ZSTR_LEN(name) > ZSTR_LEN(ns) + 1 && + memcmp(ZSTR_VAL(name), ZSTR_VAL(ns), ZSTR_LEN(ns)) == 0 && + ZSTR_VAL(name)[ZSTR_LEN(ns)] == '\\') { + zend_string *ret = zend_string_init(ZSTR_VAL(name) + ZSTR_LEN(ns) + 1, ZSTR_LEN(name) - ZSTR_LEN(ns) - 1, 0); + zend_string_release(name); + return ret; + } + return name; +} + +zend_string *zend_resolve_class_in_scope(zend_string *name, const zend_class_entry *scope) { + zend_string *ns_name = get_namespace_from_scope(scope); + zend_string *original_suffix = get_scoped_name(ns_name, name); + zend_string_release(ns_name); + + const zend_class_entry *current_scope = scope; + + // Traverse upwards in lexical scope, stop at namespace boundary + while (current_scope && current_scope->type != ZEND_NAMESPACE_CLASS) { + // build fully-qualified name: current_scope->name + "\\" + original_suffix + zend_string *try_name = zend_string_concat3( + ZSTR_VAL(current_scope->name), ZSTR_LEN(current_scope->name), + "\\", 1, + ZSTR_VAL(original_suffix), ZSTR_LEN(original_suffix) + ); + + 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) { + zend_string_release(original_suffix); + return try_name; + } + zend_string_release(try_name); + + current_scope = current_scope->lexical_scope; + } + + zend_string_release(original_suffix); + return NULL; +} + static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* {{{ */ { char *compound; @@ -1202,6 +1292,13 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* } } + if (CG(active_class_entry)) { + zend_string *import_name = zend_resolve_class_in_scope(name, CG(active_class_entry)); + if (import_name) { + return import_name; + } + } + /* If not fully qualified and not an alias, prepend the current namespace */ return zend_prefix_with_ns(name); } @@ -1760,8 +1857,45 @@ static uint32_t zend_get_class_fetch_type_ast(zend_ast *name_ast) /* {{{ */ } /* }}} */ +static bool zend_try_compile_const_expr_resolve_class_name(zval *zv, zend_ast *class_ast); + +static zend_string *zend_resolve_nested_class_name(zend_ast *ast) /* {{{ */ +{ + ZEND_ASSERT(ast->kind == ZEND_AST_INNER_CLASS); + + zend_ast *outer_class = ast->child[0]; + zend_ast *inner_class = ast->child[1]; + + zend_string *outer_name, *inner_name, *full_name; + + if (outer_class->kind == ZEND_AST_INNER_CLASS) { + outer_name = zend_resolve_nested_class_name(outer_class); + } else { + zval outer_class_name; + zend_try_compile_const_expr_resolve_class_name(&outer_class_name, outer_class); + outer_name = Z_STR(outer_class_name); + } + + inner_name = zend_ast_get_str(inner_class); + + full_name = zend_string_concat3( + ZSTR_VAL(outer_name), ZSTR_LEN(outer_name), + "\\", 1, + ZSTR_VAL(inner_name), ZSTR_LEN(inner_name) + ); + + zend_string_release(outer_name); + + return full_name; +} +/* }}} */ + static zend_string *zend_resolve_const_class_name_reference(zend_ast *ast, const char *type) { + if (ast->kind == ZEND_AST_INNER_CLASS) { + return zend_resolve_nested_class_name(ast); + } + zend_string *class_name = zend_ast_get_str(ast); if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type_ast(ast)) { zend_error_noreturn(E_COMPILE_ERROR, @@ -1792,6 +1926,11 @@ static bool zend_try_compile_const_expr_resolve_class_name(zval *zv, zend_ast *c uint32_t fetch_type; zval *class_name; + if (class_ast->kind == ZEND_AST_INNER_CLASS) { + ZVAL_STR(zv, zend_resolve_nested_class_name(class_ast)); + return 1; + } + if (class_ast->kind != ZEND_AST_ZVAL) { return 0; } @@ -2891,10 +3030,12 @@ static void zend_compile_class_ref(znode *result, zend_ast *name_ast, uint32_t f if (ZEND_FETCH_CLASS_DEFAULT == fetch_type) { result->op_type = IS_CONST; ZVAL_STR(&result->u.constant, zend_resolve_class_name_ast(name_ast)); + } else if (fetch_flags & ZEND_FETCH_CLASS_OUTER && fetch_type == ZEND_FETCH_CLASS_STATIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot use the static modifier on an inner class"); } else { zend_ensure_valid_class_fetch_type(fetch_type); result->op_type = IS_UNUSED; - result->u.op.num = fetch_type | fetch_flags; + result->u.op.num = fetch_type | (fetch_flags & ~ZEND_FETCH_CLASS_OUTER); } } /* }}} */ @@ -7316,6 +7457,9 @@ static zend_type zend_compile_typename_ex( ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT; ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; } + } else if (ast->kind == ZEND_AST_INNER_CLASS) { + zend_string *name = zend_resolve_nested_class_name(ast); + return (zend_type) ZEND_TYPE_INIT_CLASS(name, /* allow null */ false, 0); } else { type = zend_compile_single_typename(ast); } @@ -8980,10 +9124,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 +9184,44 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } +HashTable *inner_class_queue = NULL; + +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 (inner_class_queue == NULL) { + ALLOC_HASHTABLE(inner_class_queue); + zend_hash_init(inner_class_queue, 8, NULL, ZVAL_PTR_DTOR, 0); + } + + zend_hash_next_index_insert_ptr(inner_class_queue, ast); +} + +static void zend_scan_nested_class_decl(zend_ast *ast) { + ZEND_ASSERT(CG(active_class_entry)); + + 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(CG(active_class_entry)->name), ZSTR_LEN(CG(active_class_entry)->name), + "\\", 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(name); + zend_string_release(lc_name); + } + } +} + static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; @@ -9057,10 +9238,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 +9247,51 @@ 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) && CG(active_op_array)->function_name) { + zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be declared inside functions"); + } + + if (CG(active_class_entry)) { + // rename the inner class so we may reference it by name + 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 current ce->flags for a nested class. This should only include: + // - final + // - readonly + // - abstract + 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; + } + + // configure for a nested class. This should only include: + // - public + // - private + // - protected + int propFlags = decl->attr & (ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE); + // remove the flags from attrs + decl->attr &= ~(ZEND_ACC_PUBLIC|ZEND_ACC_PROTECTED|ZEND_ACC_PRIVATE|ZEND_ACC_FINAL|ZEND_ACC_ABSTRACT|ZEND_ACC_READONLY); + + // if a class is private or protected, we need to require the correct scope + 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); + + // ensure the class is treated as a top-level class and not an anon class + toplevel = true; + } 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 +9309,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 = NULL; do { zend_tmp_string_release(name); zend_tmp_string_release(lcname); @@ -9129,16 +9352,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 +9370,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_enum_register_props(ce); } + zend_scan_nested_class_decl(stmt_ast); zend_compile_stmt(stmt_ast); /* Reset lineno for final opcodes and errors */ @@ -9156,8 +9380,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 +9400,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_inner_classes; } } } else if (EXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL)) { @@ -9187,7 +9409,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_inner_classes; } else { goto link_unbound; } @@ -9257,6 +9479,24 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) opline->result.opline_num = -1; } } + compile_inner_classes: + + if (inner_class_queue == NULL) { + CG(active_class_entry) = original_ce; + return; + } + + HashTable *queue = inner_class_queue; + inner_class_queue = NULL; + + ZEND_HASH_FOREACH_PTR(queue, ast) { + zend_compile_class_decl(NULL, ast, 0); + } ZEND_HASH_FOREACH_END(); + + CG(active_class_entry) = original_ce; + + zend_hash_destroy(queue); + FREE_HASHTABLE(queue); } /* }}} */ @@ -11547,6 +11787,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..916c0154371b0 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -894,6 +894,7 @@ typedef enum { ZEND_MODIFIER_TARGET_CONSTANT, ZEND_MODIFIER_TARGET_CPP, ZEND_MODIFIER_TARGET_PROPERTY_HOOK, + ZEND_MODIFIER_TARGET_INNER_CLASS, } zend_modifier_target; /* Used during AST construction */ @@ -1020,6 +1021,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FETCH_CLASS_AUTO 4 #define ZEND_FETCH_CLASS_INTERFACE 5 #define ZEND_FETCH_CLASS_TRAIT 6 +#define ZEND_FETCH_CLASS_OUTER 7 #define ZEND_FETCH_CLASS_MASK 0x0f #define ZEND_FETCH_CLASS_NO_AUTOLOAD 0x80 #define ZEND_FETCH_CLASS_SILENT 0x0100 @@ -1060,6 +1062,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 4 #define ZEND_EVAL (1<<0) #define ZEND_INCLUDE (1<<1) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6b6af2c225f79..36a9174c5dc0c 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_always_inline bool zend_check_class_visibility(const zend_class_entry *ce, const zend_property_info *info, uint32_t current_visibility) { + // a public class is always visible + if (!ce->required_scope) { + return 1; + } + + // 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) { + if (current_visibility & ZEND_ACC_PUBLIC) { + zend_type_error("Cannot assign protected %s to higher visibile property %s::%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(info->ce->name), + zend_get_unmangled_property_name(info->name)); + return 0; + } + + return 1; + } + + // 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 && current_visibility & ZEND_ACC_PRIVATE) { + return 1; + } + + zend_type_error("Cannot assign private %s to higher visibile property %s::%s", + ZSTR_VAL(ce->name), + ZSTR_VAL(info->ce->name), + zend_get_unmangled_property_name(info->name)); + + return 0; +} + 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_class_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; } @@ -1196,6 +1233,23 @@ static zend_always_inline bool zend_check_type_slow( } } else { ce = zend_fetch_ce_from_cache_slot(cache_slot, type); + + // verify that the class is being used in the correct scope + if (ce && ce->required_scope) { + zend_class_entry *scope = zend_get_executed_scope(); + if (ce->required_scope_absolute && scope != ce->required_scope) { + if (scope == NULL) { + zend_error(E_ERROR, "Private inner class %s cannot be used as a type declaration in the global scope", ZSTR_VAL(ce->name)); + } + + zend_error(E_ERROR, "Private inner class %s cannot be used as a type declaration in the scope of %s", ZSTR_VAL(ce->name), ZSTR_VAL(scope->name)); + } else if (scope == NULL) { + zend_error(E_ERROR, "Protected inner class %s cannot be used as a type declaration in the global scope", ZSTR_VAL(ce->name)); + } else if (!instanceof_function(scope, ce->required_scope)) { + zend_error(E_ERROR, "Protected inner class %s cannot be used as a type declaration in the scope of %s", ZSTR_VAL(ce->name), ZSTR_VAL(scope->name)); + } + } + /* 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)) { diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 3b8ed89ec4f38..932ded88ee53f 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -49,6 +49,7 @@ ZEND_API void execute_ex(zend_execute_data *execute_data); ZEND_API void execute_internal(zend_execute_data *execute_data, zval *return_value); ZEND_API bool zend_is_valid_class_name(zend_string *name); ZEND_API zend_class_entry *zend_lookup_class(zend_string *name); +ZEND_API zend_class_entry *zend_lookup_class_in_scope(zend_string *name, const zend_class_entry *scope); ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *lcname, uint32_t flags); ZEND_API zend_class_entry *zend_get_called_scope(zend_execute_data *ex); ZEND_API zend_object *zend_get_this_object(zend_execute_data *ex); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 1f55521fb72f1..3e4ec6bb04405 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -20,6 +20,7 @@ #include #include +#include #include "zend.h" #include "zend_compile.h" @@ -1166,6 +1167,62 @@ ZEND_API bool zend_is_valid_class_name(zend_string *name) { return 1; } +static zend_string *get_namespace_from_scope(const zend_class_entry *scope) { + ZEND_ASSERT(scope != NULL); + while (scope && scope->lexical_scope && scope->type != ZEND_NAMESPACE_CLASS) { + scope = scope->lexical_scope; + } + return zend_string_copy(scope->name); +} + +static zend_string *get_scoped_name(zend_string *ns, zend_string *name) { + // remove the matching prefix from name + name = zend_string_tolower(name); + if (ns && ZSTR_LEN(ns) && ZSTR_LEN(name) > ZSTR_LEN(ns) + 1 && + memcmp(ZSTR_VAL(name), ZSTR_VAL(ns), ZSTR_LEN(ns)) == 0 && + ZSTR_VAL(name)[ZSTR_LEN(ns)] == '\\') { + zend_string *ret = zend_string_init(ZSTR_VAL(name) + ZSTR_LEN(ns) + 1, ZSTR_LEN(name) - ZSTR_LEN(ns) - 1, 0); + zend_string_release(name); + return ret; + } + return name; +} + +zend_class_entry *zend_lookup_class_in_scope(zend_string *name, const zend_class_entry *scope) { + zend_class_entry *ce = NULL; + zend_string *ns_name = get_namespace_from_scope(scope); + zend_string *original_suffix = get_scoped_name(ns_name, name); + zend_string_release(ns_name); + + const zend_class_entry *current_scope = scope; + + // Traverse upwards in lexical scope, stop at namespace boundary + while (current_scope && current_scope->type != ZEND_NAMESPACE_CLASS) { + // build fully-qualified name: current_scope->name + "\\" + original_suffix + zend_string *try_name = zend_string_concat3( + ZSTR_VAL(current_scope->name), ZSTR_LEN(current_scope->name), + "\\", 1, + ZSTR_VAL(original_suffix), ZSTR_LEN(original_suffix) + ); + + zend_string *lc_try_name = zend_string_tolower(try_name); + zend_string_release(try_name); + + ce = zend_hash_find_ptr(EG(class_table), lc_try_name); + zend_string_release(lc_try_name); + + if (ce) { + zend_string_release(original_suffix); + return ce; + } + + current_scope = current_scope->lexical_scope; + } + + zend_string_release(original_suffix); + return NULL; +} + ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *key, uint32_t flags) /* {{{ */ { zend_class_entry *ce = NULL; @@ -1182,6 +1239,30 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * } } + if (!zend_is_compiling()) { + const zend_class_entry *scope = zend_get_executed_scope(); + if (scope) { + ce = zend_lookup_class_in_scope(name, scope); + if (ce) { + if (ce_cache) { + SET_CE_CACHE(ce_cache, ce); + } + return ce; + } + } + } else if (CG(active_class_entry)) { + const zend_class_entry *scope = CG(active_class_entry); + if (scope) { + ce = zend_lookup_class_in_scope(name, scope); + if (ce) { + if (ce_cache) { + SET_CE_CACHE(ce_cache, ce); + } + return ce; + } + } + } + if (key) { lc_name = key; } else { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index bcccf978e2a01..84a5435596135 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -280,8 +280,12 @@ static zend_class_entry *lookup_class_ex( return ce; } - ce = zend_lookup_class_ex( - name, NULL, ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD); + ce = zend_lookup_class_in_scope(name, scope); + + if (!ce) { + ce = zend_lookup_class_ex( + name, NULL, ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD); + } if (!CG(in_compilation) || in_preload) { if (ce) { @@ -3531,9 +3535,13 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_LINKED)); if (ce->parent_name) { - parent = zend_fetch_class_by_name( - ce->parent_name, lc_parent_name, - ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); + parent = zend_lookup_class_in_scope(ce->parent_name, ce); + + if (!parent) { + parent = zend_fetch_class_by_name( + ce->parent_name, lc_parent_name, + ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); + } if (!parent) { check_unrecoverable_load_failure(ce); return NULL; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 9483a83b4e955..b4f5ff789db74 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 inner_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 inner_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; } ; +inner_class_modifiers: + non_empty_member_modifiers + { $$ = zend_modifier_list_to_flags(ZEND_MODIFIER_TARGET_INNER_CLASS, $1); + if (!$$) { YYERROR; } } + | %empty + { $$ = ZEND_ACC_PUBLIC; } +; + trait_declaration_statement: T_TRAIT { $$ = CG(zend_lineno); } T_STRING backup_doc_comment '{' class_statement_list '}' @@ -866,9 +874,9 @@ type_expr_without_static: ; type_without_static: - T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); } - | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); } - | name { $$ = $1; } + T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); } + | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); } + | name { $$ = $1; } ; union_type_without_static_element: @@ -943,6 +951,10 @@ class_statement_list: { $$ = zend_ast_create_list(0, ZEND_AST_STMT_LIST); } ; +inner_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,8 @@ 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; } + | inner_class_modifiers inner_class_statement + { $$ = $2; $$->attr = $1; } ; class_statement: @@ -1414,9 +1428,9 @@ class_name: ; class_name_reference: - class_name { $$ = $1; } - | new_variable { $$ = $1; } - | '(' expr ')' { $$ = $2; } + class_name { $$ = $1; } + | new_variable { $$ = $1; } + | '(' expr ')' { $$ = $2; } ; backticks_expr: diff --git a/Zend/zend_namespaces.c b/Zend/zend_namespaces.c new file mode 100644 index 0000000000000..b415ce61fa995 --- /dev/null +++ b/Zend/zend_namespaces.c @@ -0,0 +1,111 @@ +/* ++----------------------------------------------------------------------+ + | 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 + +#include "zend_API.h" +#include "zend_hash.h" + +static zend_class_entry *global_namespace = NULL; +static HashTable namespaces; + +static zend_class_entry *create_namespace(zend_string *interned_name) { + zend_class_entry *ns = malloc(sizeof(zend_class_entry)); + zend_initialize_class_data(ns, 1); + ns->type = ZEND_NAMESPACE_CLASS; + ns->ce_flags |= ZEND_ACC_UNINSTANTIABLE; + + ns->name = zend_string_copy(interned_name); + + return ns; +} + +static zend_class_entry *insert_namespace(const zend_string *name) { + zend_class_entry *ns = NULL; + zend_class_entry *parent_ns = global_namespace; + 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(name), len, 0); + + ns = zend_hash_find_ptr(&namespaces, needle); + + if (!ns) { + zend_string *interned_name = zend_new_interned_string(needle); + ns = create_namespace(interned_name); + ns->parent = parent_ns; + zend_hash_add_ptr(&namespaces, interned_name, ns); + } + zend_string_release(needle); + + parent_ns = ns; + } + pos++; + } + + return ns; +} + +zend_class_entry *zend_resolve_namespace(zend_string *name) { + if (global_namespace == NULL) { + global_namespace = create_namespace(zend_string_copy(zend_empty_string)); + global_namespace->lexical_scope = NULL; + zend_hash_init(&namespaces, 8, NULL, ZEND_CLASS_DTOR, 1); + zend_hash_add_ptr(&namespaces, zend_empty_string, global_namespace); + } + + if (name == NULL || ZSTR_LEN(name) == 0) { + return global_namespace; + } + + zend_string *lc_name = zend_string_tolower(name); + zend_class_entry *ns = zend_hash_find_ptr(&namespaces, lc_name); + + if (!ns) { + ns = insert_namespace(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(&namespaces, lc_name); + zend_string_release(lc_name); + + return ns; +} + +void zend_destroy_namespaces(void) { + if (global_namespace == NULL) { + return; + } + + zend_hash_destroy(&namespaces); + global_namespace = NULL; +} diff --git a/Zend/zend_namespaces.h b/Zend/zend_namespaces.h new file mode 100644 index 0000000000000..ce64ce5462e1a --- /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); +void zend_destroy_namespaces(void); +ZEND_API zend_class_entry *zend_lookup_namespace(zend_string *name); + +#endif //ZEND_NAMESPACES_H diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index f2c2886eeb2be..4e253b9427c98 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); @@ -1815,6 +1820,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) { @@ -1832,7 +1839,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; } } @@ -1891,12 +1902,19 @@ 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; } @@ -1973,10 +1991,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; @@ -2057,9 +2080,15 @@ 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(); +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))) { + 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, scope); zend_object_store_ctor_failed(zobj); constructor = NULL; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index f32ae13e06793..20b2d70cdec1a 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); } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 8770cab3826d8..be154ddf8a8e0 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4427,6 +4427,24 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } SAVE_OPLINE(); + + 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, 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..1f87aacdb444f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -10799,6 +10799,24 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } SAVE_OPLINE(); + + 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, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); @@ -21536,6 +21554,24 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } SAVE_OPLINE(); + + 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, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); @@ -30012,6 +30048,24 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } SAVE_OPLINE(); + + 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, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); @@ -37802,6 +37856,24 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } SAVE_OPLINE(); + + 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, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); @@ -50603,6 +50675,24 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } SAVE_OPLINE(); + + 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, 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/zend_persist.c b/ext/opcache/zend_persist.c index 3d45c63a98781..771101462b510 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_required_scope(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_required_scope(ce); } } ZEND_HASH_FOREACH_END(); #ifdef HAVE_JIT diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bc8ffbdd8bd8e..eb600535a96bf 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4075,6 +4075,52 @@ static void add_class_vars(zend_class_entry *ce, bool statics, zval *return_valu } /* }}} */ +/* {{{ 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 true if 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 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..6b03c6deb03c8 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 isProtected(): bool {} + + public function isPublic(): bool {} } class ReflectionObject extends ReflectionClass diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index d78a685dde9c9..9630754e93c27 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: be0cae939aa05863ce2206767bbb923ea9951dd7 */ 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_isProtected arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + +#define arginfo_class_ReflectionClass_isPublic 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, isProtected); +ZEND_METHOD(ReflectionClass, isPublic); 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, isProtected, arginfo_class_ReflectionClass_isProtected, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, isPublic, arginfo_class_ReflectionClass_isPublic, 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..2a77633245d03 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 isProtected ] { + + - Parameters [0] { + } + - Return [ bool ] + } + + Method [ public method isPublic ] { + + - 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/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..3dd79008daa60 --- /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 inner 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..a4e86484ed9ec --- /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 inner 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..82d7f0e93676c --- /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 inner 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..41266b10b453f --- /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 inner 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/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..8365942a86db2 --- /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 Error: 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..a545b6e499e90 --- /dev/null +++ b/tests/classes/inner_classes/inheritance.phpt @@ -0,0 +1,14 @@ +--TEST-- +circular inheritance +--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 @@ + +--EXPECT-- +object(Outer\Inner)#1 (0) { +} +bool(true) +bool(false) diff --git a/tests/classes/inner_classes/readonly_001.phpt b/tests/classes/inner_classes/readonly_001.phpt new file mode 100644 index 0000000000000..3fb85996605a3 --- /dev/null +++ b/tests/classes/inner_classes/readonly_001.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..0965778ff190f --- /dev/null +++ b/tests/classes/inner_classes/reflection_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +reflection on inner classes +--FILE-- +getName()); +var_dump($ref->getShortName()); +var_dump($ref->isInnerClass()); +var_dump($outer->isInnerClass()); +var_dump($ref->isPrivate()); +var_dump($ref->isProtected()); +var_dump($ref->isPublic()); +?> +--EXPECT-- +string(22) "n\s\Outer\Middle\Inner" +string(5) "Inner" +bool(true) +bool(false) +bool(false) +bool(false) +bool(true) 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..160cf8f0205fd --- /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 Error: Cannot instantiate private class Outer\Inner from 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..49427a492422e --- /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.phpt b/tests/classes/inner_classes/trait_usage.phpt new file mode 100644 index 0000000000000..6e618e1a09387 --- /dev/null +++ b/tests/classes/inner_classes/trait_usage.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..a1ac42e6e9736 --- /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 assign private Outer\Inner to higher visibile property 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..c655bbe2c8b29 --- /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 assign protected Outer\Inner to higher visibile property 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");