From 993a5e9543b7feaff69285f541559e3dfeda26ea Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 6 Jun 2025 16:15:50 -0700 Subject: [PATCH 01/24] Initial zend_class_alias Probably going to have a bunch of failures but I can't find any more locally --- Zend/zend_API.c | 20 ++++-- Zend/zend_builtin_functions.c | 3 +- Zend/zend_class_alias.c | 29 ++++++++ Zend/zend_class_alias.h | 53 ++++++++++++++ Zend/zend_execute_API.c | 6 +- Zend/zend_extensions.c | 8 ++- Zend/zend_observer.c | 5 +- Zend/zend_types.h | 7 +- configure.ac | 1 + ext/opcache/ZendAccelerator.c | 88 ++++++++++++++++------- ext/opcache/zend_accelerator_util_funcs.c | 30 ++++++-- ext/opcache/zend_persist_calc.c | 16 ++++- ext/reflection/php_reflection.c | 9 ++- 13 files changed, 226 insertions(+), 49 deletions(-) create mode 100644 Zend/zend_class_alias.c create mode 100644 Zend/zend_class_alias.h diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e0006e7d7275f..189115119f610 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -34,6 +34,7 @@ #include "zend_enum.h" #include "zend_object_handlers.h" #include "zend_observer.h" +#include "zend_class_alias.h" #include @@ -2543,7 +2544,9 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */ } ZEND_HASH_FOREACH_END(); /* Collect internal classes with static members */ - ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { + zval *ce_or_alias; + ZEND_HASH_MAP_FOREACH_VAL(CG(class_table), ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); if (ce->type == ZEND_INTERNAL_CLASS && ce->default_static_members_count > 0) { class_count++; @@ -2557,7 +2560,8 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */ class_cleanup_handlers[class_count] = NULL; if (class_count) { - ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { + ZEND_HASH_MAP_FOREACH_VAL(CG(class_table), ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); if (ce->type == ZEND_INTERNAL_CLASS && ce->default_static_members_count > 0) { class_cleanup_handlers[--class_count] = ce; @@ -3282,8 +3286,9 @@ static void clean_module_classes(int module_number) /* {{{ */ { /* Child classes may reuse structures from parent classes, so destroy in reverse order. */ Bucket *bucket; + zend_class_entry *ce; ZEND_HASH_REVERSE_FOREACH_BUCKET(EG(class_table), bucket) { - zend_class_entry *ce = Z_CE(bucket->val); + Z_CE_FROM_ZVAL(ce, bucket->val); if (ce->type == ZEND_INTERNAL_CLASS && ce->info.internal.module->module_number == module_number) { zend_hash_del_bucket(EG(class_table), bucket); } @@ -3596,7 +3601,9 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ * Instead of having to deal with differentiating between class types and lifetimes, * we simply don't increase the refcount of a class entry for aliases. */ - ZVAL_ALIAS_PTR(&zv, ce); + zend_class_alias *alias = zend_class_alias_init(ce); + + ZVAL_ALIAS_PTR(&zv, alias); ret = zend_hash_add(CG(class_table), lcname, &zv); zend_string_release_ex(lcname, 0); @@ -3723,11 +3730,12 @@ ZEND_API zend_result zend_disable_class(const char *class_name, size_t class_nam key = zend_string_alloc(class_name_length, 0); zend_str_tolower_copy(ZSTR_VAL(key), class_name, class_name_length); - disabled_class = zend_hash_find_ptr(CG(class_table), key); + zval *disabled_class_or_alias = zend_hash_find(CG(class_table), key); zend_string_release_ex(key, 0); - if (!disabled_class) { + if (!disabled_class_or_alias) { return FAILURE; } + Z_CE_FROM_ZVAL_P(disabled_class, disabled_class_or_alias); /* Will be reset by INIT_CLASS_ENTRY. */ free(disabled_class->interfaces); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 7a07ceadce2e2..c2e5077986454 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -18,6 +18,7 @@ */ #include "zend.h" +#include "zend_class_alias.h" #include "zend_API.h" #include "zend_attributes.h" #include "zend_gc.h" @@ -1398,7 +1399,7 @@ static inline void get_declared_class_impl(INTERNAL_FUNCTION_PARAMETERS, int fla zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(EG(class_table), key, zv) { - ce = Z_PTR_P(zv); + Z_CE_FROM_ZVAL_P(ce, zv); if ((ce->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT)) == flags && key && ZSTR_VAL(key)[0] != 0) { diff --git a/Zend/zend_class_alias.c b/Zend/zend_class_alias.c new file mode 100644 index 0000000000000..a8d4e0c6988cd --- /dev/null +++ b/Zend/zend_class_alias.c @@ -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: Daniel Scherzer | + +----------------------------------------------------------------------+ +*/ + +#include "zend_class_alias.h" + +zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { + zend_class_alias *alias = malloc(sizeof(zend_class_alias)); + // refcount field is only there for compatibility with other structures + GC_SET_REFCOUNT(alias, 1); + + alias->ce = ce; + + return alias; +} diff --git a/Zend/zend_class_alias.h b/Zend/zend_class_alias.h new file mode 100644 index 0000000000000..fdf0c61a6514c --- /dev/null +++ b/Zend/zend_class_alias.h @@ -0,0 +1,53 @@ +/* + +----------------------------------------------------------------------+ + | 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: Daniel Scherzer | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_CLASS_ALIAS_H +#define ZEND_CLASS_ALIAS_H + +#include "zend_types.h" + +struct _zend_class_alias { + zend_refcounted_h gc; + zend_class_entry *ce; +}; + +typedef struct _zend_class_alias zend_class_alias; + +#define Z_CE_FROM_ZVAL_P(_ce, _zv) do { \ + if (EXPECTED(Z_TYPE_P(_zv) == IS_PTR)) { \ + _ce = Z_PTR_P(_zv); \ + } else { \ + ZEND_ASSERT(Z_TYPE_P(_zv) == IS_ALIAS_PTR); \ + _ce = Z_CLASS_ALIAS_P(_zv)->ce; \ + } \ + } while (0) \ + + +#define Z_CE_FROM_ZVAL(_ce, _zv) do { \ + if (EXPECTED(Z_TYPE(_zv) == IS_PTR)) { \ + _ce = Z_PTR(_zv); \ + } else { \ + ZEND_ASSERT(Z_TYPE(_zv) == IS_ALIAS_PTR); \ + _ce = Z_CLASS_ALIAS(_zv)->ce; \ + } \ + } while (0) \ + + +zend_class_alias * zend_class_alias_init(zend_class_entry *ce); + +#endif diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 9a7803e44e66e..c8bfe7468b34d 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -22,6 +22,7 @@ #include #include "zend.h" +#include "zend_class_alias.h" #include "zend_compile.h" #include "zend_execute.h" #include "zend_API.h" @@ -327,7 +328,8 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { - zend_class_entry *ce = Z_PTR_P(zv); + zend_class_entry *ce; + Z_CE_FROM_ZVAL_P(ce, zv); if (ce->default_static_members_count) { zend_cleanup_internal_class_data(ce); @@ -1201,7 +1203,7 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * if (!key) { zend_string_release_ex(lc_name, 0); } - ce = (zend_class_entry*)Z_PTR_P(zv); + Z_CE_FROM_ZVAL_P(ce, zv); if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_LINKED))) { if ((flags & ZEND_FETCH_CLASS_ALLOW_UNLINKED) || ((flags & ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED) && diff --git a/Zend/zend_extensions.c b/Zend/zend_extensions.c index a4e5a38f90d89..72c59626850fa 100644 --- a/Zend/zend_extensions.c +++ b/Zend/zend_extensions.c @@ -17,6 +17,7 @@ +----------------------------------------------------------------------+ */ +#include "zend_class_alias.h" #include "zend_extensions.h" #include "zend_system_id.h" @@ -327,7 +328,9 @@ ZEND_API void zend_init_internal_run_time_cache(void) { if (rt_size) { size_t functions = zend_hash_num_elements(CG(function_table)); zend_class_entry *ce; - ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { + zval *ce_or_alias; + ZEND_HASH_MAP_FOREACH_VAL(CG(class_table), ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); functions += zend_hash_num_elements(&ce->function_table); } ZEND_HASH_FOREACH_END(); @@ -344,7 +347,8 @@ ZEND_API void zend_init_internal_run_time_cache(void) { ptr += rt_size; } } ZEND_HASH_FOREACH_END(); - ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { + ZEND_HASH_MAP_FOREACH_VAL(CG(class_table), ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) { if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL) { ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr); diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c index 15722eb6b2eb3..73712132082f8 100644 --- a/Zend/zend_observer.c +++ b/Zend/zend_observer.c @@ -20,6 +20,7 @@ #include "zend_observer.h" +#include "zend_class_alias.h" #include "zend_extensions.h" #include "zend_llist.h" #include "zend_vm.h" @@ -89,7 +90,9 @@ ZEND_API void zend_observer_post_startup(void) ++zif->T; } ZEND_HASH_FOREACH_END(); zend_class_entry *ce; - ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) { + zval *ce_or_alias; + ZEND_HASH_MAP_FOREACH_VAL(CG(class_table), ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) { ++zif->T; } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 4a6d00b9d73ea..544ccad5c1077 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -99,6 +99,7 @@ typedef struct _zend_resource zend_resource; typedef struct _zend_reference zend_reference; typedef struct _zend_ast_ref zend_ast_ref; typedef struct _zend_ast zend_ast; +typedef struct _zend_class_alias zend_class_alias; typedef int (*compare_func_t)(const void *, const void *); typedef void (*swap_func_t)(void *, void *); @@ -346,6 +347,7 @@ typedef union _zend_value { void *ptr; zend_class_entry *ce; zend_function *func; + zend_class_alias *class_alias; struct { uint32_t w1; uint32_t w2; @@ -1065,6 +1067,9 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define Z_PTR(zval) (zval).value.ptr #define Z_PTR_P(zval_p) Z_PTR(*(zval_p)) +#define Z_CLASS_ALIAS(zval) (zval).value.class_alias +#define Z_CLASS_ALIAS_P(zval_p) Z_CLASS_ALIAS(*(zval_p)) + #define ZVAL_UNDEF(z) do { \ Z_TYPE_INFO_P(z) = IS_UNDEF; \ } while (0) @@ -1277,7 +1282,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { } while (0) #define ZVAL_ALIAS_PTR(z, p) do { \ - Z_PTR_P(z) = (p); \ + Z_CLASS_ALIAS_P(z) = (p); \ Z_TYPE_INFO_P(z) = IS_ALIAS_PTR; \ } while (0) diff --git a/configure.ac b/configure.ac index e4bd8162a2ebc..fba51c381d717 100644 --- a/configure.ac +++ b/configure.ac @@ -1735,6 +1735,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_attributes.c zend_builtin_functions.c zend_call_stack.c + zend_class_alias.c zend_closures.c zend_compile.c zend_constants.c diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index a2b2d0fde8b42..d4a46cc34c3f3 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -22,6 +22,7 @@ #include "main/php.h" #include "main/php_globals.h" #include "zend.h" +#include "zend_class_alias.h" #include "zend_extensions.h" #include "zend_compile.h" #include "ZendAccelerator.h" @@ -682,7 +683,7 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int ZEND_HASH_MAP_FOREACH_BUCKET(CG(class_table), p) { zend_class_entry *ce; - ce = (zend_class_entry*)Z_PTR(p->val); + Z_CE_FROM_ZVAL(ce, p->val); if (p->key) { p->key = new_interned_string(p->key); @@ -3620,8 +3621,9 @@ static void preload_shutdown(void) } if (EG(class_table)) { + zend_class_entry *ce; ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { - zend_class_entry *ce = Z_PTR_P(zv); + Z_CE_FROM_ZVAL_P(ce, zv); if (ce->type == ZEND_INTERNAL_CLASS && Z_TYPE_P(zv) != IS_ALIAS_PTR) { break; } @@ -3709,7 +3711,8 @@ static void preload_move_user_classes(HashTable *src, HashTable *dst) src->pDestructor = NULL; zend_hash_extend(dst, dst->nNumUsed + src->nNumUsed, 0); ZEND_HASH_MAP_FOREACH_BUCKET_FROM(src, p, EG(persistent_classes_count)) { - zend_class_entry *ce = Z_PTR(p->val); + zend_class_entry *ce; + Z_CE_FROM_ZVAL(ce, p->val); /* Possible with internal class aliases */ if (ce->type == ZEND_INTERNAL_CLASS) { @@ -3773,17 +3776,27 @@ static void preload_sort_classes(void *base, size_t count, size_t siz, compare_f while (b1 < end) { try_again: - ce = (zend_class_entry*)Z_PTR(b1->val); + Z_CE_FROM_ZVAL(ce, b1->val); if (ce->parent && (ce->ce_flags & ZEND_ACC_LINKED)) { p = ce->parent; if (p->type == ZEND_USER_CLASS) { b2 = b1 + 1; while (b2 < end) { - if (p == Z_PTR(b2->val)) { - tmp = *b1; - *b1 = *b2; - *b2 = tmp; - goto try_again; + if (Z_TYPE(b2->val) == IS_ALIAS_PTR) { + if (p == Z_CLASS_ALIAS(b2->val)->ce) { + tmp = *b1; + *b1 = *b2; + *b2 = tmp; + goto try_again; + } + } else { + ZEND_ASSERT(Z_TYPE(b2->val) == IS_PTR); + if (p == Z_PTR(b2->val)) { + tmp = *b1; + *b1 = *b2; + *b2 = tmp; + goto try_again; + } } b2++; } @@ -3822,9 +3835,9 @@ static zend_result preload_resolve_deps(preload_error *error, const zend_class_e if (ce->parent_name) { zend_string *key = zend_string_tolower(ce->parent_name); - zend_class_entry *parent = zend_hash_find_ptr(EG(class_table), key); + zval *parent_entry = zend_hash_find(EG(class_table), key); zend_string_release(key); - if (!parent) { + if (!parent_entry) { error->kind = "Unknown parent "; error->name = ZSTR_VAL(ce->parent_name); return FAILURE; @@ -3833,9 +3846,9 @@ static zend_result preload_resolve_deps(preload_error *error, const zend_class_e if (ce->num_interfaces) { for (uint32_t i = 0; i < ce->num_interfaces; i++) { - zend_class_entry *interface = - zend_hash_find_ptr(EG(class_table), ce->interface_names[i].lc_name); - if (!interface) { + zval *interface_entry = + zend_hash_find(EG(class_table), ce->interface_names[i].lc_name); + if (!interface_entry) { error->kind = "Unknown interface "; error->name = ZSTR_VAL(ce->interface_names[i].name); return FAILURE; @@ -3845,9 +3858,9 @@ static zend_result preload_resolve_deps(preload_error *error, const zend_class_e if (ce->num_traits) { for (uint32_t i = 0; i < ce->num_traits; i++) { - zend_class_entry *trait = - zend_hash_find_ptr(EG(class_table), ce->trait_names[i].lc_name); - if (!trait) { + zval *trait_entry = + zend_hash_find(EG(class_table), ce->trait_names[i].lc_name); + if (!trait_entry) { error->kind = "Unknown trait "; error->name = ZSTR_VAL(ce->trait_names[i].name); return FAILURE; @@ -4020,7 +4033,7 @@ static void preload_link(void) changed = false; ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM(EG(class_table), key, zv, EG(persistent_classes_count)) { - ce = Z_PTR_P(zv); + Z_CE_FROM_ZVAL_P(ce, zv); /* Possible with internal class aliases */ if (ce->type == ZEND_INTERNAL_CLASS) { @@ -4083,13 +4096,26 @@ static void preload_link(void) } zend_catch { /* Clear variance obligations that were left behind on bailout. */ if (CG(delayed_variance_obligations)) { + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + zend_hash_index_del( + CG(delayed_variance_obligations), (uintptr_t) Z_CLASS_ALIAS_P(zv)->ce); + } else { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); + zend_hash_index_del( + CG(delayed_variance_obligations), (uintptr_t) Z_CE_P(zv)); + } zend_hash_index_del( CG(delayed_variance_obligations), (uintptr_t) Z_CE_P(zv)); } /* Restore the original class. */ zv = zend_hash_set_bucket_key(EG(class_table), (Bucket*)zv, key); - Z_CE_P(zv) = orig_ce; + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + Z_CLASS_ALIAS_P(zv)->ce = orig_ce; + } else { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); + Z_CE_P(zv) = orig_ce; + } orig_ce->ce_flags &= ~temporary_flags; zend_arena_release(&CG(arena), checkpoint); @@ -4111,8 +4137,7 @@ static void preload_link(void) changed = false; ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { - ce = Z_PTR_P(zv); - + Z_CE_FROM_ZVAL_P(ce, zv); /* Possible with internal class aliases */ if (ce->type == ZEND_INTERNAL_CLASS) { if (Z_TYPE_P(zv) != IS_ALIAS_PTR) { @@ -4136,7 +4161,7 @@ static void preload_link(void) /* Warn for classes that could not be linked. */ ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM( EG(class_table), key, zv, EG(persistent_classes_count)) { - ce = Z_PTR_P(zv); + Z_CE_FROM_ZVAL_P(ce, zv); /* Possible with internal class aliases */ if (ce->type == ZEND_INTERNAL_CLASS) { @@ -4190,7 +4215,11 @@ static void preload_link(void) ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); preload_remove_declares(op_array); } ZEND_HASH_FOREACH_END(); - ZEND_HASH_MAP_FOREACH_PTR_FROM(EG(class_table), ce, EG(persistent_classes_count)) { + zend_string *_unused; + (void)_unused; + // No ZEND_HASH_MAP_FOREACH_VAL_FROM + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM(EG(class_table), _unused, zv, EG(persistent_classes_count)) { + Z_CE_FROM_ZVAL_P(ce, zv); ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { if (op_array->type == ZEND_USER_FUNCTION) { preload_remove_declares(op_array); @@ -4370,18 +4399,21 @@ static void preload_fix_trait_methods(zend_class_entry *ce) static void preload_optimize(zend_persistent_script *script) { zend_class_entry *ce; + zval *zv; zend_persistent_script *tmp_script; zend_shared_alloc_init_xlat_table(); - ZEND_HASH_MAP_FOREACH_PTR(&script->script.class_table, ce) { + ZEND_HASH_MAP_FOREACH_VAL(&script->script.class_table, zv) { + Z_CE_FROM_ZVAL_P(ce, zv); if (ce->ce_flags & ZEND_ACC_TRAIT) { preload_register_trait_methods(ce); } } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_FOREACH_PTR(preload_scripts, tmp_script) { - ZEND_HASH_MAP_FOREACH_PTR(&tmp_script->script.class_table, ce) { + ZEND_HASH_MAP_FOREACH_VAL(&tmp_script->script.class_table, zv) { + Z_CE_FROM_ZVAL_P(ce, zv); if (ce->ce_flags & ZEND_ACC_TRAIT) { preload_register_trait_methods(ce); } @@ -4391,12 +4423,14 @@ static void preload_optimize(zend_persistent_script *script) zend_optimize_script(&script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level); zend_accel_finalize_delayed_early_binding_list(script); - ZEND_HASH_MAP_FOREACH_PTR(&script->script.class_table, ce) { + ZEND_HASH_MAP_FOREACH_VAL(&script->script.class_table, zv) { + Z_CE_FROM_ZVAL_P(ce, zv); preload_fix_trait_methods(ce); } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_FOREACH_PTR(preload_scripts, script) { - ZEND_HASH_MAP_FOREACH_PTR(&script->script.class_table, ce) { + ZEND_HASH_MAP_FOREACH_VAL(&script->script.class_table, zv) { + Z_CE_FROM_ZVAL_P(ce, zv); preload_fix_trait_methods(ce); } ZEND_HASH_FOREACH_END(); } ZEND_HASH_FOREACH_END(); diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 21f056901fd1b..65be23056374c 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -20,6 +20,7 @@ */ #include "zend_API.h" +#include "zend_class_alias.h" #include "zend_constants.h" #include "zend_inheritance.h" #include "zend_accelerator_util_funcs.h" @@ -132,10 +133,14 @@ void zend_accel_move_user_classes(HashTable *src, uint32_t count, zend_script *s p = end - count; for (; p != end; p++) { if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) continue; - ce = Z_PTR(p->val); + Z_CE_FROM_ZVAL(ce, p->val); if (EXPECTED(ce->type == ZEND_USER_CLASS) && EXPECTED(ce->info.user.filename == filename)) { - _zend_hash_append_ptr(dst, p->key, ce); + if (Z_TYPE(p->val) == IS_ALIAS_PTR) { + _zend_hash_append(dst, p->key, &p->val); + } else { + _zend_hash_append_ptr(dst, p->key, ce); + } zend_hash_del_bucket(src, p); } } @@ -219,19 +224,32 @@ static zend_always_inline void _zend_accel_class_hash_copy(HashTable *target, Ha * value. */ continue; } else if (UNEXPECTED(!ZCG(accel_directives).ignore_dups)) { - zend_class_entry *ce1 = Z_PTR(p->val); + zend_class_entry *ce1; + Z_CE_FROM_ZVAL(ce1, p->val); if (!(ce1->ce_flags & ZEND_ACC_ANON_CLASS)) { CG(in_compilation) = 1; zend_set_compiled_filename(ce1->info.user.filename); CG(zend_lineno) = ce1->info.user.line_start; - zend_class_redeclaration_error(E_ERROR, Z_PTR_P(t)); + if (Z_TYPE_P(t) == IS_ALIAS_PTR) { + zend_class_redeclaration_error(E_ERROR, Z_CLASS_ALIAS(p->val)->ce); + } else { + ZEND_ASSERT(Z_TYPE_P(t) == IS_PTR); + zend_class_redeclaration_error(E_ERROR, Z_PTR_P(t)); + } return; } continue; } } else { - zend_class_entry *ce = Z_PTR(p->val); - _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1); + zend_class_entry *ce; + if (Z_TYPE(p->val) == IS_ALIAS_PTR) { + _zend_hash_append_ex(target, p->key, &p->val, 1); + ce = Z_CLASS_ALIAS(p->val)->ce; + } else { + ZEND_ASSERT(Z_TYPE(p->val) == IS_PTR); + ce = Z_PTR(p->val); + _zend_hash_append_ptr_ex(target, p->key, ce, 1); + } if ((ce->ce_flags & ZEND_ACC_LINKED) && ZSTR_VAL(p->key)[0]) { if (ZSTR_HAS_CE_CACHE(ce->name)) { ZSTR_SET_CE_CACHE_EX(ce->name, ce, 0); diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 639d7d5446705..f1fe460d38791 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -27,6 +27,7 @@ #include "zend_operators.h" #include "zend_attributes.h" #include "zend_constants.h" +#include "zend_class_alias.h" #define ADD_DUP_SIZE(m,s) ZCG(current_persistent_script)->size += zend_shared_memdup_size((void*)m, s) #define ADD_SIZE(m) ZCG(current_persistent_script)->size += ZEND_ALIGNED_SIZE(m) @@ -593,6 +594,14 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) } } +static void zend_persist_class_alias_entry_calc(zend_class_alias *alias) +{ + // alias->ce is going to be a pointer to a class entry that will be + // persisted on its own, here we just need to add size for the alias + ADD_SIZE(sizeof(zend_class_alias)); + zend_persist_class_entry_calc(alias->ce); +} + static void zend_accel_persist_class_table_calc(HashTable *class_table) { Bucket *p; @@ -601,7 +610,12 @@ static void zend_accel_persist_class_table_calc(HashTable *class_table) ZEND_HASH_MAP_FOREACH_BUCKET(class_table, p) { ZEND_ASSERT(p->key != NULL); ADD_INTERNED_STRING(p->key); - zend_persist_class_entry_calc(Z_CE(p->val)); + if (Z_TYPE(p->val) == IS_PTR) { + zend_persist_class_entry_calc(Z_CE(p->val)); + } else { + ZEND_ASSERT(Z_TYPE(p->val) == IS_ALIAS_PTR); + zend_persist_class_alias_entry_calc(Z_CLASS_ALIAS(p->val)); + } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 8ef0269481cf7..6e6639280ccc8 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -18,6 +18,7 @@ +----------------------------------------------------------------------+ */ +#include "zend_class_alias.h" #include "zend_compile.h" #include "zend_execute.h" #include "zend_lazy_objects.h" @@ -6790,7 +6791,9 @@ ZEND_METHOD(ReflectionExtension, getClasses) GET_REFLECTION_OBJECT_PTR(module); array_init(return_value); - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(EG(class_table), key, ce) { + zval *ce_or_alias; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(EG(class_table), key, ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); add_extension_class(ce, key, return_value, module, 1); } ZEND_HASH_FOREACH_END(); } @@ -6808,7 +6811,9 @@ ZEND_METHOD(ReflectionExtension, getClassNames) GET_REFLECTION_OBJECT_PTR(module); array_init(return_value); - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(EG(class_table), key, ce) { + zval *ce_or_alias; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(EG(class_table), key, ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); add_extension_class(ce, key, return_value, module, 0); } ZEND_HASH_FOREACH_END(); } From 2083542fd9bd7e414282c72c83ce8d0d60658055 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 6 Jun 2025 16:28:51 -0700 Subject: [PATCH 02/24] Add to windows build --- win32/build/config.w32 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/build/config.w32 b/win32/build/config.w32 index f509e4eae9337..28937a1b19eb0 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -241,7 +241,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.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_lazy_objects.c"); + zend_lazy_objects.c zend_class_alias.h"); 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"); var PHP_ASSEMBLER = PATH_PROG({ From e1a37a8b7b75cba0527265479942ecba1c592886 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 6 Jun 2025 17:08:35 -0700 Subject: [PATCH 03/24] Add #[ClassAlias] --- .../class_alias/parameter_invalid.phpt | 11 ++++ .../class_alias/parameter_nonstring.phpt | 11 ++++ .../class_alias/parameter_required.phpt | 15 +++++ .../class_alias/redeclaration_error.phpt | 11 ++++ .../class_alias/repeated_attribute.phpt | 25 ++++++++ .../class_alias/target_validation.phpt | 11 ++++ .../class_alias/working_example.phpt | 17 +++++ Zend/zend_attributes.c | 63 +++++++++++++++++++ Zend/zend_attributes.stub.php | 11 ++++ Zend/zend_attributes_arginfo.h | 35 ++++++++++- 10 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/attributes/class_alias/parameter_invalid.phpt create mode 100644 Zend/tests/attributes/class_alias/parameter_nonstring.phpt create mode 100644 Zend/tests/attributes/class_alias/parameter_required.phpt create mode 100644 Zend/tests/attributes/class_alias/redeclaration_error.phpt create mode 100644 Zend/tests/attributes/class_alias/repeated_attribute.phpt create mode 100644 Zend/tests/attributes/class_alias/target_validation.phpt create mode 100644 Zend/tests/attributes/class_alias/working_example.phpt diff --git a/Zend/tests/attributes/class_alias/parameter_invalid.phpt b/Zend/tests/attributes/class_alias/parameter_invalid.phpt new file mode 100644 index 0000000000000..028c31414b41e --- /dev/null +++ b/Zend/tests/attributes/class_alias/parameter_invalid.phpt @@ -0,0 +1,11 @@ +--TEST-- +Alias name must be valid +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "never" as a class alias as it is reserved in %s on line %d diff --git a/Zend/tests/attributes/class_alias/parameter_nonstring.phpt b/Zend/tests/attributes/class_alias/parameter_nonstring.phpt new file mode 100644 index 0000000000000..6be25a38e61b9 --- /dev/null +++ b/Zend/tests/attributes/class_alias/parameter_nonstring.phpt @@ -0,0 +1,11 @@ +--TEST-- +Parameter must be a string +--FILE-- + +--EXPECTF-- +Fatal error: ClassAlias::__construct(): Argument #1 ($alias) must be of type string, array given in %s on line %d diff --git a/Zend/tests/attributes/class_alias/parameter_required.phpt b/Zend/tests/attributes/class_alias/parameter_required.phpt new file mode 100644 index 0000000000000..24ea4fe895685 --- /dev/null +++ b/Zend/tests/attributes/class_alias/parameter_required.phpt @@ -0,0 +1,15 @@ +--TEST-- +Parameter is required +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught ArgumentCountError: ClassAlias::__construct() expects exactly 1 argument, 0 given in %s:%d +Stack trace: +#0 %s(%d): ClassAlias->__construct() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias/redeclaration_error.phpt b/Zend/tests/attributes/class_alias/redeclaration_error.phpt new file mode 100644 index 0000000000000..642d08760a645 --- /dev/null +++ b/Zend/tests/attributes/class_alias/redeclaration_error.phpt @@ -0,0 +1,11 @@ +--TEST-- +Cannot redeclare an existing class +--FILE-- + +--EXPECTF-- +Fatal error: Unable to declare alias 'Attribute' for 'Demo' in %s on line %d diff --git a/Zend/tests/attributes/class_alias/repeated_attribute.phpt b/Zend/tests/attributes/class_alias/repeated_attribute.phpt new file mode 100644 index 0000000000000..f2b6fa1617868 --- /dev/null +++ b/Zend/tests/attributes/class_alias/repeated_attribute.phpt @@ -0,0 +1,25 @@ +--TEST-- +Attribute can be repeated +--FILE-- + +--EXPECTF-- +bool(true) +bool(true) +object(Demo)#%d (0) { +} +object(Demo)#%d (0) { +} diff --git a/Zend/tests/attributes/class_alias/target_validation.phpt b/Zend/tests/attributes/class_alias/target_validation.phpt new file mode 100644 index 0000000000000..4dbf6fadc6ce1 --- /dev/null +++ b/Zend/tests/attributes/class_alias/target_validation.phpt @@ -0,0 +1,11 @@ +--TEST-- +Can only be used on classes +--FILE-- + +--EXPECTF-- +Fatal error: Attribute "ClassAlias" cannot target function (allowed targets: class) in %s on line %d diff --git a/Zend/tests/attributes/class_alias/working_example.phpt b/Zend/tests/attributes/class_alias/working_example.phpt new file mode 100644 index 0000000000000..ea1ae4476f395 --- /dev/null +++ b/Zend/tests/attributes/class_alias/working_example.phpt @@ -0,0 +1,17 @@ +--TEST-- +Working usage +--FILE-- + +--EXPECTF-- +bool(true) +object(Demo)#%d (0) { +} diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index c3633801be83e..378222568db22 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -32,6 +32,7 @@ ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; ZEND_API zend_class_entry *zend_ce_override; ZEND_API zend_class_entry *zend_ce_deprecated; ZEND_API zend_class_entry *zend_ce_nodiscard; +ZEND_API zend_class_entry *zend_ce_class_alias; static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; @@ -95,6 +96,46 @@ static void validate_allow_dynamic_properties( scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES; } +static void validate_class_alias( + zend_attribute *attr, uint32_t target, zend_class_entry *scope) +{ + zval alias_obj; + ZVAL_UNDEF(&alias_obj); + zend_result result = zend_get_attribute_object( + &alias_obj, + zend_ce_class_alias, + attr, + scope, + scope->info.user.filename + ); + if (result == FAILURE) { + ZEND_ASSERT(EG(exception)); + return; + } + + zval *alias_name = zend_read_property( + zend_ce_class_alias, + Z_OBJ(alias_obj), + ZEND_STRL("alias"), + false, + NULL + ); + result = zend_register_class_alias_ex( + Z_STRVAL_P(alias_name), + Z_STRLEN_P(alias_name), + scope, + false + ); + if (result == FAILURE) { + zend_error_noreturn(E_ERROR, "Unable to declare alias '%s' for '%s'", + Z_STRVAL_P(alias_name), + ZSTR_VAL(scope->name) + ); + } + zval_ptr_dtor(alias_name); + zval_ptr_dtor(&alias_obj); +} + ZEND_METHOD(Attribute, __construct) { zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL; @@ -217,6 +258,24 @@ ZEND_METHOD(NoDiscard, __construct) } } +ZEND_METHOD(ClassAlias, __construct) +{ + zend_string *alias = NULL; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(alias) + ZEND_PARSE_PARAMETERS_END(); + + zval value; + ZVAL_STR(&value, alias); + zend_update_property(zend_ce_class_alias, Z_OBJ_P(ZEND_THIS), ZEND_STRL("alias"), &value); + + /* The assignment might fail due to 'readonly'. */ + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } +} + static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) { if (attributes) { @@ -548,6 +607,10 @@ void zend_register_attribute_ce(void) zend_ce_nodiscard = register_class_NoDiscard(); attr = zend_mark_internal_attribute(zend_ce_nodiscard); + + zend_ce_class_alias = register_class_ClassAlias(); + attr = zend_mark_internal_attribute(zend_ce_class_alias); + attr->validator = validate_class_alias; } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index fe70de83e4d21..87deb761b901a 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -97,3 +97,14 @@ final class NoDiscard public function __construct(?string $message = null) {} } + +/** + * @strict-properties + */ +#[Attribute(Attribute::TARGET_CLASS|Attribute::IS_REPEATABLE)] +final class ClassAlias +{ + public readonly string $alias; + + public function __construct(string $alias) {} +} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index 14afe40c01adf..32ccd9ada2f6c 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9aee3d8f2ced376f5929048444eaa2529ff90311 */ + * Stub hash: 5c2a6f7f4c9872fa94ae22fa3f2f947388b63a38 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -33,6 +33,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NoDiscard___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ClassAlias___construct, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, alias, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_METHOD(Attribute, __construct); ZEND_METHOD(ReturnTypeWillChange, __construct); ZEND_METHOD(AllowDynamicProperties, __construct); @@ -43,6 +47,7 @@ ZEND_METHOD(SensitiveParameterValue, __debugInfo); ZEND_METHOD(Override, __construct); ZEND_METHOD(Deprecated, __construct); ZEND_METHOD(NoDiscard, __construct); +ZEND_METHOD(ClassAlias, __construct); static const zend_function_entry class_Attribute_methods[] = { ZEND_ME(Attribute, __construct, arginfo_class_Attribute___construct, ZEND_ACC_PUBLIC) @@ -86,6 +91,11 @@ static const zend_function_entry class_NoDiscard_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_ClassAlias_methods[] = { + ZEND_ME(ClassAlias, __construct, arginfo_class_ClassAlias___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_Attribute(void) { zend_class_entry ce, *class_entry; @@ -290,3 +300,26 @@ static zend_class_entry *register_class_NoDiscard(void) return class_entry; } + +static zend_class_entry *register_class_ClassAlias(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ClassAlias", class_ClassAlias_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES); + + zval property_alias_default_value; + ZVAL_UNDEF(&property_alias_default_value); + zend_string *property_alias_name = zend_string_init("alias", sizeof("alias") - 1, 1); + zend_declare_typed_property(class_entry, property_alias_name, &property_alias_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_alias_name); + + zend_string *attribute_name_Attribute_class_ClassAlias_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); + zend_attribute *attribute_Attribute_class_ClassAlias_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_ClassAlias_0, 1); + zend_string_release(attribute_name_Attribute_class_ClassAlias_0); + zval attribute_Attribute_class_ClassAlias_0_arg0; + ZVAL_LONG(&attribute_Attribute_class_ClassAlias_0_arg0, ZEND_ATTRIBUTE_TARGET_CLASS | ZEND_ATTRIBUTE_IS_REPEATABLE); + ZVAL_COPY_VALUE(&attribute_Attribute_class_ClassAlias_0->args[0].value, &attribute_Attribute_class_ClassAlias_0_arg0); + + return class_entry; +} From d3c46c13b43c04e3e0d5d65f40076077b05c9c90 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 6 Jun 2025 17:14:48 -0700 Subject: [PATCH 04/24] Reflection fixes --- ext/reflection/php_reflection.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 6e6639280ccc8..d94b6bd4edf6e 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1218,8 +1218,10 @@ static void _extension_string(smart_str *str, const zend_module_entry *module, c zend_string *key; zend_class_entry *ce; int num_classes = 0; + zval *ce_or_alias; - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(EG(class_table), key, ce) { + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(EG(class_table), key, ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); _extension_class_string(ce, key, &str_classes, ZSTR_VAL(sub_indent), module, &num_classes); } ZEND_HASH_FOREACH_END(); if (num_classes) { @@ -5431,9 +5433,11 @@ ZEND_METHOD(ReflectionClass, getTraitAliases) zend_string *lcname = zend_string_tolower(cur_ref->method_name); for (j = 0; j < ce->num_traits; j++) { - zend_class_entry *trait = - zend_hash_find_ptr(CG(class_table), ce->trait_names[j].lc_name); - ZEND_ASSERT(trait && "Trait must exist"); + zval *trait_entry = + zend_hash_find(CG(class_table), ce->trait_names[j].lc_name); + ZEND_ASSERT(trait_entry && "Trait must exist"); + zend_class_entry *trait; + Z_CE_FROM_ZVAL_P(trait, trait_entry); if (zend_hash_exists(&trait->function_table, lcname)) { class_name = trait->name; break; From 19da859356c149d40926484f06bc6392e6f67570 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Fri, 6 Jun 2025 18:10:50 -0700 Subject: [PATCH 05/24] Save reflection work Need to actually add zend_class_alias->attributes but ran out of time for now --- ...class_alias_listed_as_target-internal.phpt | 11 +++ ...class_alias_listed_as_target-userland.phpt | 31 +++++++ .../must_target_class_alias-internal.phpt | 11 +++ .../must_target_class_alias-userland.phpt | 25 ++++++ .../not_repeatable-internal.phpt | 12 +++ .../not_repeatable-userland.phpt | 24 ++++++ .../repeatable-internal.phpt | 15 ++++ .../repeatable-userland.phpt | 28 +++++++ ...arget_all_targets_class_alias-default.phpt | 25 ++++++ ...rget_all_targets_class_alias-explicit.phpt | 25 ++++++ Zend/zend_attributes.c | 19 ++++- Zend/zend_attributes.h | 7 +- Zend/zend_attributes.stub.php | 6 +- Zend/zend_attributes_arginfo.h | 11 ++- ext/reflection/php_reflection.c | 83 +++++++++++++++++++ ext/reflection/php_reflection.stub.php | 15 ++++ ext/reflection/php_reflection_arginfo.h | 33 +++++++- 17 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt create mode 100644 Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt create mode 100644 Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt create mode 100644 Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt create mode 100644 Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt create mode 100644 Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt create mode 100644 Zend/tests/attributes/class_alias_target/repeatable-internal.phpt create mode 100644 Zend/tests/attributes/class_alias_target/repeatable-userland.phpt create mode 100644 Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt create mode 100644 Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt diff --git a/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt b/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt new file mode 100644 index 0000000000000..4f3bbb6fcb0af --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt @@ -0,0 +1,11 @@ +--TEST-- +Class alias listed in valid targets when used wrong (internal attribute) +--FILE-- + +--EXPECTF-- +Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant, class alias) in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt b/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt new file mode 100644 index 0000000000000..cccedd532a2ab --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt @@ -0,0 +1,31 @@ +--TEST-- +Class alias listed in valid targets when used wrong (userland attribute) +--FILE-- +getAttributes(); +var_dump($attribs); +$attribs[0]->newInstance(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(16) "MyAliasAttribute" + } +} + +Fatal error: Uncaught Error: Attribute "MyAliasAttribute" cannot target class (allowed targets: class alias) in %s:%d +Stack trace: +#0 %s(%d): ReflectionAttribute->newInstance() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt b/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt new file mode 100644 index 0000000000000..3bbc7c1185c24 --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt @@ -0,0 +1,11 @@ +--TEST-- +Error when attribute does not target class alias (internal attribute) +--FILE-- + +--EXPECT-- +NO \ No newline at end of file diff --git a/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt b/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt new file mode 100644 index 0000000000000..6b994757a226e --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt @@ -0,0 +1,25 @@ +--TEST-- +Error when attribute does not target class alias (useland attribute) +--FILE-- +getAttributes(); +var_dump($attribs); +$attribs[0]->newInstance(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" + } +} diff --git a/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt b/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt new file mode 100644 index 0000000000000..ea20f481db716 --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt @@ -0,0 +1,12 @@ +--TEST-- +Validation of attribute repetition (not allowed; internal attribute) +--FILE-- + +--EXPECT-- + +e \ No newline at end of file diff --git a/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt b/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt new file mode 100644 index 0000000000000..516a5243e55a4 --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt @@ -0,0 +1,24 @@ +--TEST-- +Validation of attribute repetition (not allowed; userland attribute) +--FILE-- +getAttributes(); +var_dump($attributes); +$attributes[0]->newInstance(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" + } +} diff --git a/Zend/tests/attributes/class_alias_target/repeatable-internal.phpt b/Zend/tests/attributes/class_alias_target/repeatable-internal.phpt new file mode 100644 index 0000000000000..fe8b1b805952a --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/repeatable-internal.phpt @@ -0,0 +1,15 @@ +--TEST-- +Validation of attribute repetition (is allowed; internal attribute) +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt b/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt new file mode 100644 index 0000000000000..aa2f6511e63f0 --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt @@ -0,0 +1,28 @@ +--TEST-- +Validation of attribute repetition (is allowed; userland attribute) +--FILE-- +getAttributes(); +var_dump($attributes); +$attributes[0]->newInstance(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" + } +} +php: /usr/src/php/Zend/zend_variables.c:143: zval_copy_ctor_func: Assertion `!(zval_gc_flags(((*(zvalue)).value.str)->gc.u.type_info) & (1<<6))' failed. +Aborted (core dumped) + +Termsig=6 diff --git a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt new file mode 100644 index 0000000000000..22abb9504264c --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt @@ -0,0 +1,25 @@ +--TEST-- +Attributes with TARGET_ALL (from the default) can target class aliases +--FILE-- +getAttributes(); +var_dump($attribs); +$attribs[0]->newInstance(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" + } +} diff --git a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt new file mode 100644 index 0000000000000..15772aafe7bc0 --- /dev/null +++ b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt @@ -0,0 +1,25 @@ +--TEST-- +Attributes with TARGET_ALL (from an explicit parameter) can target class aliases +--FILE-- +getAttributes(); +var_dump($attribs); +$attribs[0]->newInstance(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" + } +} diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 378222568db22..1f7e5590ce449 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -261,9 +261,12 @@ ZEND_METHOD(NoDiscard, __construct) ZEND_METHOD(ClassAlias, __construct) { zend_string *alias = NULL; + HashTable *attributes = NULL; - ZEND_PARSE_PARAMETERS_START(1, 1) + ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_STR(alias) + Z_PARAM_OPTIONAL + Z_PARAM_ARRAY_HT(attributes) ZEND_PARSE_PARAMETERS_END(); zval value; @@ -274,6 +277,17 @@ ZEND_METHOD(ClassAlias, __construct) if (UNEXPECTED(EG(exception))) { RETURN_THROWS(); } + + if (attributes == NULL || zend_hash_num_elements(attributes) == 0) { + return; + } + + if (!zend_array_is_list(attributes)) { + zend_throw_error(NULL, + "ClassAlias::__construct(): Argument #2 ($attributes) must be a list, not an associative array" + ); + RETURN_THROWS(); + } } static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) @@ -438,7 +452,8 @@ static const char *target_names[] = { "property", "class constant", "parameter", - "constant" + "constant", + "class alias" }; ZEND_API zend_string *zend_get_attribute_target_names(uint32_t flags) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index a4d6b28c0094a..f5a38abd13010 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -30,9 +30,10 @@ #define ZEND_ATTRIBUTE_TARGET_CLASS_CONST (1<<4) #define ZEND_ATTRIBUTE_TARGET_PARAMETER (1<<5) #define ZEND_ATTRIBUTE_TARGET_CONST (1<<6) -#define ZEND_ATTRIBUTE_TARGET_ALL ((1<<7) - 1) -#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<7) -#define ZEND_ATTRIBUTE_FLAGS ((1<<8) - 1) +#define ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS (1<<7) +#define ZEND_ATTRIBUTE_TARGET_ALL ((1<<8) - 1) +#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<8) +#define ZEND_ATTRIBUTE_FLAGS ((1<<9) - 1) /* Flags for zend_attribute.flags */ #define ZEND_ATTRIBUTE_PERSISTENT (1<<0) diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index 87deb761b901a..de96ff7deaa70 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -19,6 +19,8 @@ final class Attribute const int TARGET_PARAMETER = UNKNOWN; /** @cvalue ZEND_ATTRIBUTE_TARGET_CONST */ const int TARGET_CONSTANT = UNKNOWN; + /** @cvalue ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS */ + const int TARGET_CLASS_ALIAS = UNKNOWN; /** @cvalue ZEND_ATTRIBUTE_TARGET_ALL */ const int TARGET_ALL = UNKNOWN; /** @cvalue ZEND_ATTRIBUTE_IS_REPEATABLE */ @@ -77,7 +79,7 @@ public function __construct() {} /** * @strict-properties */ -#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT)] +#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT|Attribute::TARGET_CLASS_ALIAS)] final class Deprecated { public readonly ?string $message; @@ -106,5 +108,5 @@ final class ClassAlias { public readonly string $alias; - public function __construct(string $alias) {} + public function __construct(string $alias, array $attributes = []) {} } diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index 32ccd9ada2f6c..d957156e92de3 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 5c2a6f7f4c9872fa94ae22fa3f2f947388b63a38 */ + * Stub hash: c5a70b37073a6986229f62d801bddbabfada5474 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -35,6 +35,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ClassAlias___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, alias, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, attributes, IS_ARRAY, 0, "[]") ZEND_END_ARG_INFO() ZEND_METHOD(Attribute, __construct); @@ -145,6 +146,12 @@ static zend_class_entry *register_class_Attribute(void) zend_declare_typed_class_constant(class_entry, const_TARGET_CONSTANT_name, &const_TARGET_CONSTANT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_TARGET_CONSTANT_name); + zval const_TARGET_CLASS_ALIAS_value; + ZVAL_LONG(&const_TARGET_CLASS_ALIAS_value, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS); + zend_string *const_TARGET_CLASS_ALIAS_name = zend_string_init_interned("TARGET_CLASS_ALIAS", sizeof("TARGET_CLASS_ALIAS") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_TARGET_CLASS_ALIAS_name, &const_TARGET_CLASS_ALIAS_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_TARGET_CLASS_ALIAS_name); + zval const_TARGET_ALL_value; ZVAL_LONG(&const_TARGET_ALL_value, ZEND_ATTRIBUTE_TARGET_ALL); zend_string *const_TARGET_ALL_name = zend_string_init_interned("TARGET_ALL", sizeof("TARGET_ALL") - 1, 1); @@ -274,7 +281,7 @@ static zend_class_entry *register_class_Deprecated(void) zend_attribute *attribute_Attribute_class_Deprecated_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_Deprecated_0, 1); zend_string_release(attribute_name_Attribute_class_Deprecated_0); zval attribute_Attribute_class_Deprecated_0_arg0; - ZVAL_LONG(&attribute_Attribute_class_Deprecated_0_arg0, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST); + ZVAL_LONG(&attribute_Attribute_class_Deprecated_0_arg0, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST | ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS); ZVAL_COPY_VALUE(&attribute_Attribute_class_Deprecated_0->args[0].value, &attribute_Attribute_class_Deprecated_0_arg0); return class_entry; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index d94b6bd4edf6e..552505a9e64b7 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -106,6 +106,7 @@ PHPAPI zend_class_entry *reflection_enum_backed_case_ptr; PHPAPI zend_class_entry *reflection_fiber_ptr; PHPAPI zend_class_entry *reflection_constant_ptr; PHPAPI zend_class_entry *reflection_property_hook_type_ptr; +PHPAPI zend_class_entry *reflection_class_alias_ptr; /* Exception throwing macro */ #define _DO_THROW(msg) \ @@ -7847,6 +7848,84 @@ ZEND_METHOD(ReflectionConstant, __toString) RETURN_STR(smart_str_extract(&str)); } +ZEND_METHOD(ReflectionClassAlias, __construct) +{ + zend_string *name; + + zval *object = ZEND_THIS; + reflection_object *intern = Z_REFLECTION_P(object); + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR(name) + ZEND_PARSE_PARAMETERS_END(); + + // First use zend_lookup_class() which will also take care of autoloading, + // but that will always return the underlying class entry + zend_class_entry *ce = zend_lookup_class(name); + if (ce == NULL) { + if (!EG(exception)) { + zend_throw_exception_ex(reflection_exception_ptr, -1, "Class \"%s\" does not exist", ZSTR_VAL(name)); + } + RETURN_THROWS(); + } + + // We now know that the alias exists, find it somewhere in the class_table + zend_string *lc_name; + if (ZSTR_VAL(name)[0] == '\\') { + lc_name = zend_string_alloc(ZSTR_LEN(name) - 1, 0); + zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1); + } else { + lc_name = zend_string_tolower(name); + } + + zval *entry = zend_hash_find(EG(class_table), lc_name); + ZEND_ASSERT(entry != NULL); + + if (Z_TYPE_P(entry) != IS_ALIAS_PTR) { + ZEND_ASSERT(Z_TYPE_P(entry) == IS_PTR); + zend_throw_exception_ex(reflection_exception_ptr, -1, "\"%s\" is not an alias", ZSTR_VAL(name)); + RETURN_THROWS(); + } + + zend_class_alias *alias = Z_CLASS_ALIAS_P(entry); + + zend_string_release_ex(lc_name, /* persistent */ false); + + intern->ptr = alias; + intern->ref_type = REF_TYPE_OTHER; + + zval *name_zv = reflection_prop_name(object); + zval_ptr_dtor(name_zv); + ZVAL_STR_COPY(name_zv, name); +} + +ZEND_METHOD(ReflectionClassAlias, getAttributes) +{ + reflection_object *intern; + zend_class_alias *alias; + + GET_REFLECTION_OBJECT_PTR(alias); + + reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, + NULL, 0, alias->ce, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS, + NULL); +} + +ZEND_METHOD(ReflectionClassAlias, __toString) +{ + reflection_object *intern; + zend_class_alias *alias; + smart_str str = {0}; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(alias); + + smart_str_appends(&str, "TODO ReflectionClassAlias::__toString()"); + // _const_string(&str, ZSTR_VAL(const_->name), &const_->value, ""); + RETURN_STR(smart_str_extract(&str)); +} + PHP_MINIT_FUNCTION(reflection) /* {{{ */ { memcpy(&reflection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); @@ -7952,6 +8031,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_property_hook_type_ptr = register_class_PropertyHookType(); + reflection_class_alias_ptr = register_class_ReflectionClassAlias(reflector_ptr); + reflection_class_alias_ptr->create_object = reflection_objects_new; + reflection_class_alias_ptr->default_object_handlers = &reflection_object_handlers; + REFLECTION_G(key_initialized) = 0; return SUCCESS; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 63518b446ad86..ac0e1575d161d 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -926,3 +926,18 @@ public function __toString(): string {} public function getAttributes(?string $name = null, int $flags = 0): array {} } + +/** + * @strict-properties + * @not-serializable + */ +final class ReflectionClassAlias implements Reflector +{ + public string $name; + + public function __construct(string $name) {} + + public function __toString(): string {} + + public function getAttributes(?string $name = null, int $flags = 0): array {} +} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index d50dc04ae3d15..e5f4d7a4635fb 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: 7a8d126a96f0115783bd20a9adfc6bdc5ee88fda */ + * Stub hash: dbf38d44c7ce5005899a77535bfbb2856ffa14b4 */ 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) @@ -721,6 +721,12 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionConstant_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionClassAlias___construct arginfo_class_ReflectionExtension___construct + +#define arginfo_class_ReflectionClassAlias___toString arginfo_class_ReflectionFunction___toString + +#define arginfo_class_ReflectionClassAlias_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes + ZEND_METHOD(Reflection, getModifierNames); ZEND_METHOD(ReflectionClass, __clone); ZEND_METHOD(ReflectionFunctionAbstract, inNamespace); @@ -990,6 +996,9 @@ ZEND_METHOD(ReflectionConstant, getExtension); ZEND_METHOD(ReflectionConstant, getExtensionName); ZEND_METHOD(ReflectionConstant, __toString); ZEND_METHOD(ReflectionConstant, getAttributes); +ZEND_METHOD(ReflectionClassAlias, __construct); +ZEND_METHOD(ReflectionClassAlias, __toString); +ZEND_METHOD(ReflectionClassAlias, getAttributes); static const zend_function_entry class_Reflection_methods[] = { ZEND_ME(Reflection, getModifierNames, arginfo_class_Reflection_getModifierNames, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) @@ -1362,6 +1371,13 @@ static const zend_function_entry class_ReflectionConstant_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_ReflectionClassAlias_methods[] = { + ZEND_ME(ReflectionClassAlias, __construct, arginfo_class_ReflectionClassAlias___construct, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassAlias, __toString, arginfo_class_ReflectionClassAlias___toString, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassAlias, getAttributes, arginfo_class_ReflectionClassAlias_getAttributes, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_ReflectionException(zend_class_entry *class_entry_Exception) { zend_class_entry ce, *class_entry; @@ -1905,3 +1921,18 @@ static zend_class_entry *register_class_ReflectionConstant(zend_class_entry *cla return class_entry; } + +static zend_class_entry *register_class_ReflectionClassAlias(zend_class_entry *class_entry_Reflector) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionClassAlias", class_ReflectionClassAlias_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); + zend_class_implements(class_entry, 1, class_entry_Reflector); + + zval property_name_default_value; + ZVAL_UNDEF(&property_name_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_NAME), &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + + return class_entry; +} From 55ad1751d8f9925ca5dd94ed41e5d5852c56a715 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 01:10:31 -0700 Subject: [PATCH 06/24] Basic working version! --- Zend/tests/attributes/034_target_values.phpt | 24 +- .../class_alias/attributes_dict.phpt | 19 + .../class_alias/attributes_nonarray.phpt | 14 + .../class_alias/attributes_valid.phpt | 12 + ...rameter_invalid.phpt => name_invalid.phpt} | 0 ...ter_nonstring.phpt => name_nonstring.phpt} | 0 ...meter_required.phpt => name_required.phpt} | 2 +- .../must_target_class_alias-internal.phpt | 4 +- .../must_target_class_alias-userland.phpt | 8 +- .../not_repeatable-internal.phpt | 5 +- .../not_repeatable-userland.phpt | 15 +- .../repeatable-userland.phpt | 13 +- ...arget_all_targets_class_alias-default.phpt | 2 +- ...rget_all_targets_class_alias-explicit.phpt | 2 +- .../constant_listed_as_target-internal.phpt | 2 +- Zend/zend_attributes.c | 343 +++++++++++++++--- Zend/zend_class_alias.c | 3 +- Zend/zend_class_alias.h | 1 + Zend/zend_compile.c | 2 +- Zend/zend_opcode.c | 21 +- ext/reflection/php_reflection.c | 13 +- ...eflectionClassAlias_construct_missing.phpt | 13 + ...lectionClassAlias_construct_non_alias.phpt | 13 + .../ReflectionClassAlias_construct_valid.phpt | 15 + ...lectionClassAlias_getAttributes_empty.phpt | 14 + ...ionClassAlias_getAttributes_non-empty.phpt | 20 + .../tests/ReflectionClassAlias_toString.phpt | 12 + 27 files changed, 496 insertions(+), 96 deletions(-) create mode 100644 Zend/tests/attributes/class_alias/attributes_dict.phpt create mode 100644 Zend/tests/attributes/class_alias/attributes_nonarray.phpt create mode 100644 Zend/tests/attributes/class_alias/attributes_valid.phpt rename Zend/tests/attributes/class_alias/{parameter_invalid.phpt => name_invalid.phpt} (100%) rename Zend/tests/attributes/class_alias/{parameter_nonstring.phpt => name_nonstring.phpt} (100%) rename Zend/tests/attributes/class_alias/{parameter_required.phpt => name_required.phpt} (82%) create mode 100644 ext/reflection/tests/ReflectionClassAlias_construct_missing.phpt create mode 100644 ext/reflection/tests/ReflectionClassAlias_construct_non_alias.phpt create mode 100644 ext/reflection/tests/ReflectionClassAlias_construct_valid.phpt create mode 100644 ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt create mode 100644 ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt create mode 100644 ext/reflection/tests/ReflectionClassAlias_toString.phpt diff --git a/Zend/tests/attributes/034_target_values.phpt b/Zend/tests/attributes/034_target_values.phpt index e56c0c285fbd6..2a06d39ebf05e 100644 --- a/Zend/tests/attributes/034_target_values.phpt +++ b/Zend/tests/attributes/034_target_values.phpt @@ -16,24 +16,26 @@ showFlag("TARGET_PROPERTY", Attribute::TARGET_PROPERTY); showFlag("TARGET_CLASS_CONSTANT", Attribute::TARGET_CLASS_CONSTANT); showFlag("TARGET_PARAMETER", Attribute::TARGET_PARAMETER); showFlag("TARGET_CONSTANT", Attribute::TARGET_CONSTANT); +showFlag("TARGET_CLASS_ALIAS", Attribute::TARGET_CLASS_ALIAS); showFlag("IS_REPEATABLE", Attribute::IS_REPEATABLE); $all = Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS_CONSTANT | Attribute::TARGET_PARAMETER - | Attribute::TARGET_CONSTANT; + | Attribute::TARGET_CONSTANT | Attribute::TARGET_CLASS_ALIAS; var_dump($all, Attribute::TARGET_ALL, $all === Attribute::TARGET_ALL); ?> --EXPECT-- -Attribute::TARGET_CLASS = 1 (127 & 1 === 1) -Attribute::TARGET_FUNCTION = 2 (127 & 2 === 2) -Attribute::TARGET_METHOD = 4 (127 & 4 === 4) -Attribute::TARGET_PROPERTY = 8 (127 & 8 === 8) -Attribute::TARGET_CLASS_CONSTANT = 16 (127 & 16 === 16) -Attribute::TARGET_PARAMETER = 32 (127 & 32 === 32) -Attribute::TARGET_CONSTANT = 64 (127 & 64 === 64) -Attribute::IS_REPEATABLE = 128 (127 & 128 === 0) -int(127) -int(127) +Attribute::TARGET_CLASS = 1 (255 & 1 === 1) +Attribute::TARGET_FUNCTION = 2 (255 & 2 === 2) +Attribute::TARGET_METHOD = 4 (255 & 4 === 4) +Attribute::TARGET_PROPERTY = 8 (255 & 8 === 8) +Attribute::TARGET_CLASS_CONSTANT = 16 (255 & 16 === 16) +Attribute::TARGET_PARAMETER = 32 (255 & 32 === 32) +Attribute::TARGET_CONSTANT = 64 (255 & 64 === 64) +Attribute::TARGET_CLASS_ALIAS = 128 (255 & 128 === 128) +Attribute::IS_REPEATABLE = 256 (255 & 256 === 0) +int(255) +int(255) bool(true) diff --git a/Zend/tests/attributes/class_alias/attributes_dict.phpt b/Zend/tests/attributes/class_alias/attributes_dict.phpt new file mode 100644 index 0000000000000..0d09e3d0bd29c --- /dev/null +++ b/Zend/tests/attributes/class_alias/attributes_dict.phpt @@ -0,0 +1,19 @@ +--TEST-- +Alias attributes must not be associative +--FILE-- + new Deprecated()])] +class Demo {} + +$attr = new ReflectionClass( Demo::class )->getAttributes()[0]; +$attr->newInstance(); + +?> +--EXPECTF-- +Fatal error: Uncaught Error: ClassAlias::__construct(): Argument #2 ($attributes) must be a list, not an associative array in %s:%d +Stack trace: +#0 %s(%d): ClassAlias->__construct('Other', Array) +#1 %s(%d): ReflectionAttribute->newInstance() +#2 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_nonarray.phpt b/Zend/tests/attributes/class_alias/attributes_nonarray.phpt new file mode 100644 index 0000000000000..a0513c9fc7a8c --- /dev/null +++ b/Zend/tests/attributes/class_alias/attributes_nonarray.phpt @@ -0,0 +1,14 @@ +--TEST-- +Alias attributes must be an array +--FILE-- +getAttributes()[0]; +$attr->newInstance(); + +?> +--EXPECTF-- +Fatal error: ClassAlias::__construct(): Argument #2 ($attributes) must be of type array, true given in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_valid.phpt b/Zend/tests/attributes/class_alias/attributes_valid.phpt new file mode 100644 index 0000000000000..8b790c57ae062 --- /dev/null +++ b/Zend/tests/attributes/class_alias/attributes_valid.phpt @@ -0,0 +1,12 @@ +--TEST-- +Alias attributes do not need to exist for declaration +--FILE-- + +--EXPECT-- +Done diff --git a/Zend/tests/attributes/class_alias/parameter_invalid.phpt b/Zend/tests/attributes/class_alias/name_invalid.phpt similarity index 100% rename from Zend/tests/attributes/class_alias/parameter_invalid.phpt rename to Zend/tests/attributes/class_alias/name_invalid.phpt diff --git a/Zend/tests/attributes/class_alias/parameter_nonstring.phpt b/Zend/tests/attributes/class_alias/name_nonstring.phpt similarity index 100% rename from Zend/tests/attributes/class_alias/parameter_nonstring.phpt rename to Zend/tests/attributes/class_alias/name_nonstring.phpt diff --git a/Zend/tests/attributes/class_alias/parameter_required.phpt b/Zend/tests/attributes/class_alias/name_required.phpt similarity index 82% rename from Zend/tests/attributes/class_alias/parameter_required.phpt rename to Zend/tests/attributes/class_alias/name_required.phpt index 24ea4fe895685..2f907925adc4f 100644 --- a/Zend/tests/attributes/class_alias/parameter_required.phpt +++ b/Zend/tests/attributes/class_alias/name_required.phpt @@ -8,7 +8,7 @@ class Demo {} ?> --EXPECTF-- -Fatal error: Uncaught ArgumentCountError: ClassAlias::__construct() expects exactly 1 argument, 0 given in %s:%d +Fatal error: Uncaught ArgumentCountError: ClassAlias::__construct() expects at least 1 argument, 0 given in %s:%d Stack trace: #0 %s(%d): ClassAlias->__construct() #1 {main} diff --git a/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt b/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt index 3bbc7c1185c24..2e741bea2208a 100644 --- a/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt +++ b/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt @@ -7,5 +7,5 @@ Error when attribute does not target class alias (internal attribute) class Demo {} ?> ---EXPECT-- -NO \ No newline at end of file +--EXPECTF-- +Fatal error: Attribute "Override" cannot target class alias (allowed targets: method) in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt b/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt index 6b994757a226e..a6abd649bd15e 100644 --- a/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt +++ b/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt @@ -20,6 +20,12 @@ array(1) { [0]=> object(ReflectionAttribute)#%d (1) { ["name"]=> - string(10) "ClassAlias" + string(19) "MyFunctionAttribute" } } + +Fatal error: Uncaught Error: Attribute "MyFunctionAttribute" cannot target class alias (allowed targets: function) in %s:%d +Stack trace: +#0 %s(%d): ReflectionAttribute->newInstance() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt b/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt index ea20f481db716..ce2abec6d3aec 100644 --- a/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt +++ b/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt @@ -7,6 +7,5 @@ Validation of attribute repetition (not allowed; internal attribute) class Demo {} ?> ---EXPECT-- - -e \ No newline at end of file +--EXPECTF-- +Fatal error: Attribute "Deprecated" must not be repeated in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt b/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt index 516a5243e55a4..5fdbbd8c687b4 100644 --- a/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt +++ b/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt @@ -15,10 +15,21 @@ $attributes[0]->newInstance(); ?> --EXPECTF-- -array(1) { +array(2) { [0]=> object(ReflectionAttribute)#%d (1) { ["name"]=> - string(10) "ClassAlias" + string(11) "MyAttribute" + } + [1]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(11) "MyAttribute" } } + +Fatal error: Uncaught Error: Attribute "MyAttribute" must not be repeated in %s:%d +Stack trace: +#0 %s(%d): ReflectionAttribute->newInstance() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt b/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt index aa2f6511e63f0..305af9358e29e 100644 --- a/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt +++ b/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt @@ -15,14 +15,15 @@ $attributes[0]->newInstance(); ?> --EXPECTF-- -array(1) { +array(2) { [0]=> object(ReflectionAttribute)#%d (1) { ["name"]=> - string(10) "ClassAlias" + string(11) "MyAttribute" + } + [1]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(11) "MyAttribute" } } -php: /usr/src/php/Zend/zend_variables.c:143: zval_copy_ctor_func: Assertion `!(zval_gc_flags(((*(zvalue)).value.str)->gc.u.type_info) & (1<<6))' failed. -Aborted (core dumped) - -Termsig=6 diff --git a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt index 22abb9504264c..00c30aec2d5dd 100644 --- a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt +++ b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt @@ -20,6 +20,6 @@ array(1) { [0]=> object(ReflectionAttribute)#%d (1) { ["name"]=> - string(10) "ClassAlias" + string(11) "MyAttribute" } } diff --git a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt index 15772aafe7bc0..7b11a76aef861 100644 --- a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt +++ b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt @@ -20,6 +20,6 @@ array(1) { [0]=> object(ReflectionAttribute)#%d (1) { ["name"]=> - string(10) "ClassAlias" + string(11) "MyAttribute" } } diff --git a/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt b/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt index b0b88c2f6edab..e39653a00f078 100644 --- a/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt +++ b/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt @@ -8,4 +8,4 @@ class Example {} ?> --EXPECTF-- -Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant) in %s on line %d +Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant, class alias) in %s on line %d diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 1f7e5590ce449..61db55e309e89 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -23,6 +23,7 @@ #include "zend_attributes_arginfo.h" #include "zend_exceptions.h" #include "zend_smart_str.h" +#include "zend_class_alias.h" ZEND_API zend_class_entry *zend_ce_attribute; ZEND_API zend_class_entry *zend_ce_return_type_will_change_attribute; @@ -38,6 +39,120 @@ static zend_object_handlers attributes_object_handlers_sensitive_parameter_value static HashTable internal_attributes; +// zend_compile.c not interface +zend_string *zend_resolve_class_name_ast(zend_ast *ast); + +// Based on zend_compile.c but not part of the interface +void compile_alias_attributes( + HashTable **attributes, zend_ast *ast +) /* {{{ */ { + zend_attribute *attr; + zend_internal_attribute *config; + + zend_ast_list *list = zend_ast_get_list(ast); + uint32_t j; + + ZEND_ASSERT(ast->kind == ZEND_AST_ARRAY); + + for (uint32_t elem_idx = 0; elem_idx < list->children; elem_idx++) { + zend_ast *array_elem = list->child[elem_idx]; + ZEND_ASSERT(array_elem->kind == ZEND_AST_ARRAY_ELEM); + + zend_ast *array_content = array_elem->child[0]; + if (array_content->kind != ZEND_AST_NEW) { + zend_error_noreturn(E_COMPILE_ERROR, + "Attribute must be declared with `new`"); + } + + if (array_content->child[1] && + array_content->child[1]->kind == ZEND_AST_CALLABLE_CONVERT) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot create Closure as attribute argument"); + } + + zend_string *name = zend_resolve_class_name_ast(array_content->child[0]); + zend_string *lcname = zend_string_tolower_ex(name, false); + zend_ast_list *args = array_content->child[1] ? zend_ast_get_list(array_content->child[1]) : NULL; + + config = zend_internal_attribute_get(lcname); + zend_string_release(lcname); + + uint32_t flags = (CG(active_op_array)->fn_flags & ZEND_ACC_STRICT_TYPES) + ? ZEND_ATTRIBUTE_STRICT_TYPES : 0; + attr = zend_add_attribute( + attributes, name, args ? args->children : 0, flags, 0, array_content->lineno); + zend_string_release(name); + + /* Populate arguments */ + if (!args) { + continue; + } + ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); + + bool uses_named_args = 0; + for (j = 0; j < args->children; j++) { + zend_ast **arg_ast_ptr = &args->child[j]; + zend_ast *arg_ast = *arg_ast_ptr; + + if (arg_ast->kind == ZEND_AST_UNPACK) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use unpacking in attribute argument list"); + } + + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0])); + arg_ast_ptr = &arg_ast->child[1]; + uses_named_args = 1; + + for (uint32_t k = 0; k < j; k++) { + if (attr->args[k].name && + zend_string_equals(attr->args[k].name, attr->args[j].name)) { + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s", + ZSTR_VAL(attr->args[j].name)); + } + } + } else if (uses_named_args) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use positional argument after named argument"); + } + + zend_const_expr_to_zval( + &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); + } + } + + if (*attributes != NULL) { + /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ + ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) { + if (attr->offset != 0 || NULL == (config = zend_internal_attribute_get(attr->lcname))) { + continue; + } + + uint32_t target = ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS; + + if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + zend_string *location = zend_get_attribute_target_names(target); + zend_string *allowed = zend_get_attribute_target_names(config->flags); + + zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", + ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) + ); + } + + if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { + if (zend_is_attribute_repeated(*attributes, attr)) { + zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); + } + } + + if (config->validator != NULL) { + config->validator(attr, target, CG(active_class_entry)); + } + } ZEND_HASH_FOREACH_END(); + } +} +/* }}} */ + uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope) { // TODO: More proper signature validation: Too many args, incorrect arg names. @@ -70,6 +185,41 @@ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_ent return ZEND_ATTRIBUTE_TARGET_ALL; } +static zend_execute_data *setup_dummy_call_frame(zend_string *filename, zend_attribute *attribute_data) { + /* Set up dummy call frame that makes it look like the attribute was invoked + * from where it occurs in the code. */ + zend_function dummy_func; + zend_op *opline; + + memset(&dummy_func, 0, sizeof(zend_function)); + + zend_execute_data *call = zend_vm_stack_push_call_frame_ex( + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) + + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) + + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)), + 0, &dummy_func, 0, NULL); + + opline = (zend_op*)(call + 1); + memset(opline, 0, sizeof(zend_op)); + opline->opcode = ZEND_DO_FCALL; + opline->lineno = attribute_data->lineno; + + call->opline = opline; + call->call = NULL; + call->return_value = NULL; + call->func = (zend_function*)(call->opline + 1); + call->prev_execute_data = EG(current_execute_data); + + memset(call->func, 0, sizeof(zend_function)); + call->func->type = ZEND_USER_FUNCTION; + call->func->op_array.fn_flags = + attribute_data->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0; + call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE; + call->func->op_array.filename = filename; + + return call; +} + static void validate_allow_dynamic_properties( zend_attribute *attr, uint32_t target, zend_class_entry *scope) { @@ -99,41 +249,152 @@ static void validate_allow_dynamic_properties( static void validate_class_alias( zend_attribute *attr, uint32_t target, zend_class_entry *scope) { - zval alias_obj; - ZVAL_UNDEF(&alias_obj); - zend_result result = zend_get_attribute_object( - &alias_obj, - zend_ce_class_alias, - attr, - scope, - scope->info.user.filename + // Do NOT construct the attribute yet, that would require any of the + // attributes that are used to exist; at this point, access the alias name + // based on the arguments, and do our own validation + zend_execute_data *call = setup_dummy_call_frame(scope->info.user.filename, attr); + EG(current_execute_data) = call; + + zend_execute_data *constructor_call = zend_vm_stack_push_call_frame( + ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_HAS_THIS, + zend_ce_class_alias->constructor, + attr->argc, + scope ); - if (result == FAILURE) { - ZEND_ASSERT(EG(exception)); + constructor_call->prev_execute_data = EG(current_execute_data); + EG(current_execute_data) = constructor_call; + + if (attr->argc < 1 || attr->argc > 2) { + zend_wrong_parameters_count_error(1, 2); + + EG(current_execute_data) = constructor_call->prev_execute_data; + zend_vm_stack_free_call_frame(constructor_call); + + EG(current_execute_data) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); return; } - zval *alias_name = zend_read_property( - zend_ce_class_alias, - Z_OBJ(alias_obj), - ZEND_STRL("alias"), - false, - NULL - ); - result = zend_register_class_alias_ex( - Z_STRVAL_P(alias_name), - Z_STRLEN_P(alias_name), + zval *found = NULL; + + // Looking for either the first parameter, or if the first parameter + // is named, the 'alias' parameter + if (attr->args[0].name == NULL) { + found = &( attr->args[0].value ); + } else { + for (uint32_t i = 0; i < attr->argc; i++) { + if (zend_string_equals_literal( attr->args[i].name, "alias")) { + found = &( attr->args[i].value ); + break; + } + } + if (found == NULL) { + zend_argument_error(zend_ce_argument_count_error, 1, "not passed"); + EG(current_execute_data) = constructor_call->prev_execute_data; + zend_vm_stack_free_call_frame(constructor_call); + EG(current_execute_data) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + return; + } + } + + zend_string *alias; + if (UNEXPECTED(!zend_parse_arg_str(found, &alias, false, 0))) { + zend_wrong_parameter_error( + ZPP_ERROR_WRONG_ARG, + 1, + "alias", + Z_EXPECTED_STRING, + found + ); + EG(current_execute_data) = constructor_call->prev_execute_data; + zend_vm_stack_free_call_frame(constructor_call); + EG(current_execute_data) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + return; + } + + zend_result result = zend_register_class_alias_ex( + ZSTR_VAL(alias), + ZSTR_LEN(alias), scope, false ); if (result == FAILURE) { zend_error_noreturn(E_ERROR, "Unable to declare alias '%s' for '%s'", - Z_STRVAL_P(alias_name), + ZSTR_VAL(alias), ZSTR_VAL(scope->name) ); + EG(current_execute_data) = constructor_call->prev_execute_data; + zend_vm_stack_free_call_frame(constructor_call); + EG(current_execute_data) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + return; + } + + zend_string *lc_name; + if (ZSTR_VAL(alias)[0] == '\\') { + lc_name = zend_string_alloc(ZSTR_LEN(alias) - 1, 0); + zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(alias) + 1, ZSTR_LEN(alias) - 1); + } else { + lc_name = zend_string_tolower(alias); + } + + zval *entry = zend_hash_find(EG(class_table), lc_name); + zend_string_release_ex(lc_name, /* persistent */ false); + ZEND_ASSERT(entry != NULL); + ZEND_ASSERT(Z_TYPE_P(entry) == IS_ALIAS_PTR); + + zend_class_alias *alias_obj = Z_CLASS_ALIAS_P(entry); + + // Compile attributes + if (attr->argc == 2) { + zval *nested_attribs = NULL; + if (attr->args[0].name == NULL && attr->args[1].name == NULL) { + nested_attribs = &( attr->args[1].value ); + } else { + for (uint32_t i = 0; i < attr->argc; i++) { + if ( attr->args[i].name == NULL ) { + continue; + } + if (zend_string_equals_literal( attr->args[i].name, "alias")) { + continue; + } + if (zend_string_equals_literal( attr->args[i].name, "attributes")) { + nested_attribs = &( attr->args[i].value ); + break; + } + zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(attr->args[i].name)); + EG(current_execute_data) = constructor_call->prev_execute_data; + zend_vm_stack_free_call_frame(constructor_call); + EG(current_execute_data) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + return; + } + } + ZEND_ASSERT(nested_attribs != NULL); + if (UNEXPECTED(!Z_OPT_CONSTANT_P(nested_attribs))) { + zend_wrong_parameter_error( + ZPP_ERROR_WRONG_ARG, + 2, + "attributes", + Z_EXPECTED_ARRAY, + nested_attribs + ); + // Something with an invalid parameter + } else { + zend_ast *attributes_ast = Z_ASTVAL_P(nested_attribs); + compile_alias_attributes(&( alias_obj->attributes), attributes_ast); + } } - zval_ptr_dtor(alias_name); - zval_ptr_dtor(&alias_obj); + + // zval_ptr_dtor(alias_name); + // zval_ptr_dtor(&alias_obj); + EG(current_execute_data) = constructor_call->prev_execute_data; + zend_vm_stack_free_call_frame(constructor_call); + EG(current_execute_data) = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + return; } ZEND_METHOD(Attribute, __construct) @@ -349,6 +610,10 @@ ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, u ZVAL_COPY_OR_DUP(ret, &attr->args[i].value); if (Z_TYPE_P(ret) == IS_CONSTANT_AST) { + // Delayed validation for attributes in class aliases + if (CG(in_compilation) && i == 1 && zend_string_equals(attr->name, zend_ce_class_alias->name)) { + return SUCCESS; + } if (SUCCESS != zval_update_constant_ex(ret, scope)) { zval_ptr_dtor(ret); return FAILURE; @@ -363,37 +628,7 @@ ZEND_API zend_result zend_get_attribute_object(zval *obj, zend_class_entry *attr zend_execute_data *call = NULL; if (filename) { - /* Set up dummy call frame that makes it look like the attribute was invoked - * from where it occurs in the code. */ - zend_function dummy_func; - zend_op *opline; - - memset(&dummy_func, 0, sizeof(zend_function)); - - call = zend_vm_stack_push_call_frame_ex( - ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) + - ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) + - ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)), - 0, &dummy_func, 0, NULL); - - opline = (zend_op*)(call + 1); - memset(opline, 0, sizeof(zend_op)); - opline->opcode = ZEND_DO_FCALL; - opline->lineno = attribute_data->lineno; - - call->opline = opline; - call->call = NULL; - call->return_value = NULL; - call->func = (zend_function*)(call->opline + 1); - call->prev_execute_data = EG(current_execute_data); - - memset(call->func, 0, sizeof(zend_function)); - call->func->type = ZEND_USER_FUNCTION; - call->func->op_array.fn_flags = - attribute_data->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0; - call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE; - call->func->op_array.filename = filename; - + call = setup_dummy_call_frame(filename, attribute_data); EG(current_execute_data) = call; } diff --git a/Zend/zend_class_alias.c b/Zend/zend_class_alias.c index a8d4e0c6988cd..aab09760e41d5 100644 --- a/Zend/zend_class_alias.c +++ b/Zend/zend_class_alias.c @@ -19,11 +19,12 @@ #include "zend_class_alias.h" zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { - zend_class_alias *alias = malloc(sizeof(zend_class_alias)); + zend_class_alias *alias = malloc(sizeof(zend_class_alias)); // refcount field is only there for compatibility with other structures GC_SET_REFCOUNT(alias, 1); alias->ce = ce; + alias->attributes = NULL; return alias; } diff --git a/Zend/zend_class_alias.h b/Zend/zend_class_alias.h index fdf0c61a6514c..1c3e62ca52377 100644 --- a/Zend/zend_class_alias.h +++ b/Zend/zend_class_alias.h @@ -24,6 +24,7 @@ struct _zend_class_alias { zend_refcounted_h gc; zend_class_entry *ce; + HashTable *attributes; }; typedef struct _zend_class_alias zend_class_alias; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 0669d106f15e9..8b5b23f008bb3 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1207,7 +1207,7 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* } /* }}} */ -static zend_string *zend_resolve_class_name_ast(zend_ast *ast) /* {{{ */ +zend_string *zend_resolve_class_name_ast(zend_ast *ast) /* {{{ */ { zval *class_name = zend_ast_get_zval(ast); if (Z_TYPE_P(class_name) != IS_STRING) { diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 6e7d31e15a40f..4ceaf0b22e781 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -28,6 +28,7 @@ #include "zend_sort.h" #include "zend_constants.h" #include "zend_observer.h" +#include "zend_class_alias.h" #include "zend_vm.h" @@ -291,6 +292,20 @@ ZEND_API void zend_cleanup_mutable_class_data(zend_class_entry *ce) ZEND_API void destroy_zend_class(zval *zv) { + /* We don't increase the refcount for class aliases, + * so we don't need to destroy the underlying ->ce here, but we do need + * to free the attributes and the storage for the + * skip the destruction of aliases entirely. */ + if (UNEXPECTED(Z_TYPE_INFO_P(zv) == IS_ALIAS_PTR)) { + zend_class_alias *class_alias = Z_CLASS_ALIAS_P(zv); + + if (class_alias->attributes) { + zend_hash_release(class_alias->attributes); + class_alias->attributes = NULL; + } + return; + } + zend_property_info *prop_info; zend_class_entry *ce = Z_PTR_P(zv); zend_function *fn; @@ -299,12 +314,6 @@ ZEND_API void destroy_zend_class(zval *zv) return; } - /* We don't increase the refcount for class aliases, - * skip the destruction of aliases entirely. */ - if (UNEXPECTED(Z_TYPE_INFO_P(zv) == IS_ALIAS_PTR)) { - return; - } - if (ce->ce_flags & ZEND_ACC_FILE_CACHED) { zend_class_constant *c; zval *p, *end; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 552505a9e64b7..7cf3491a05bff 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -7879,6 +7879,7 @@ ZEND_METHOD(ReflectionClassAlias, __construct) } zval *entry = zend_hash_find(EG(class_table), lc_name); + zend_string_release_ex(lc_name, /* persistent */ false); ZEND_ASSERT(entry != NULL); if (Z_TYPE_P(entry) != IS_ALIAS_PTR) { @@ -7889,8 +7890,6 @@ ZEND_METHOD(ReflectionClassAlias, __construct) zend_class_alias *alias = Z_CLASS_ALIAS_P(entry); - zend_string_release_ex(lc_name, /* persistent */ false); - intern->ptr = alias; intern->ref_type = REF_TYPE_OTHER; @@ -7907,7 +7906,7 @@ ZEND_METHOD(ReflectionClassAlias, getAttributes) GET_REFLECTION_OBJECT_PTR(alias); reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, - NULL, 0, alias->ce, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS, + alias->attributes, 0, alias->ce, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS, NULL); } @@ -7921,8 +7920,12 @@ ZEND_METHOD(ReflectionClassAlias, __toString) GET_REFLECTION_OBJECT_PTR(alias); - smart_str_appends(&str, "TODO ReflectionClassAlias::__toString()"); - // _const_string(&str, ZSTR_VAL(const_->name), &const_->value, ""); + smart_str_append_printf( + &str, + "%s - alias for %s", + Z_STRVAL_P(reflection_prop_name(ZEND_THIS)), + ZSTR_VAL(alias->ce->name) + ); RETURN_STR(smart_str_extract(&str)); } diff --git a/ext/reflection/tests/ReflectionClassAlias_construct_missing.phpt b/ext/reflection/tests/ReflectionClassAlias_construct_missing.phpt new file mode 100644 index 0000000000000..daa661dd40d0d --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_construct_missing.phpt @@ -0,0 +1,13 @@ +--TEST-- +ReflectionClassAlias::__construct() - missing class +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught ReflectionException: Class "missing" does not exist in %s:%d +Stack trace: +#0 %s(%d): ReflectionClassAlias->__construct('missing') +#1 {main} + thrown in %s on line %d diff --git a/ext/reflection/tests/ReflectionClassAlias_construct_non_alias.phpt b/ext/reflection/tests/ReflectionClassAlias_construct_non_alias.phpt new file mode 100644 index 0000000000000..6563bb921689f --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_construct_non_alias.phpt @@ -0,0 +1,13 @@ +--TEST-- +ReflectionClassAlias::__construct() - not an alias +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught ReflectionException: "ReflectionClassAlias" is not an alias in %s:%d +Stack trace: +#0 %s(%d): ReflectionClassAlias->__construct('ReflectionClass...') +#1 {main} + thrown in %s on line %d diff --git a/ext/reflection/tests/ReflectionClassAlias_construct_valid.phpt b/ext/reflection/tests/ReflectionClassAlias_construct_valid.phpt new file mode 100644 index 0000000000000..f0d02ad4be5a7 --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_construct_valid.phpt @@ -0,0 +1,15 @@ +--TEST-- +ReflectionClassAlias::__construct() - with an alias +--FILE-- + +--EXPECTF-- +object(ReflectionClassAlias)#%d (1) { + ["name"]=> + string(22) "MyReflectionClassAlias" +} diff --git a/ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt b/ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt new file mode 100644 index 0000000000000..746c847abdc7b --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt @@ -0,0 +1,14 @@ +--TEST-- +ReflectionClassAlias::__toString() +--FILE-- +getAttributes() ); + +?> +--EXPECT-- +array(0) { +} diff --git a/ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt b/ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt new file mode 100644 index 0000000000000..1dcdcb1402cd0 --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionClassAlias::__toString() +--FILE-- +getAttributes() ); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(16) "MissingAttribute" + } +} diff --git a/ext/reflection/tests/ReflectionClassAlias_toString.phpt b/ext/reflection/tests/ReflectionClassAlias_toString.phpt new file mode 100644 index 0000000000000..20d2cc63cbbf0 --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_toString.phpt @@ -0,0 +1,12 @@ +--TEST-- +ReflectionClassAlias::__toString() +--FILE-- + +--EXPECT-- +MyReflectionClassAlias - alias for ReflectionClassAlias From 59426bc345932b7a321d32baa1eb3e12f51aa3f3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 01:13:42 -0700 Subject: [PATCH 07/24] goto --- Zend/zend_attributes.c | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 61db55e309e89..a16138477f4b0 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -267,12 +267,7 @@ static void validate_class_alias( if (attr->argc < 1 || attr->argc > 2) { zend_wrong_parameters_count_error(1, 2); - EG(current_execute_data) = constructor_call->prev_execute_data; - zend_vm_stack_free_call_frame(constructor_call); - - EG(current_execute_data) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - return; + goto restore_execution_data; } zval *found = NULL; @@ -290,11 +285,7 @@ static void validate_class_alias( } if (found == NULL) { zend_argument_error(zend_ce_argument_count_error, 1, "not passed"); - EG(current_execute_data) = constructor_call->prev_execute_data; - zend_vm_stack_free_call_frame(constructor_call); - EG(current_execute_data) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - return; + goto restore_execution_data; } } @@ -307,11 +298,7 @@ static void validate_class_alias( Z_EXPECTED_STRING, found ); - EG(current_execute_data) = constructor_call->prev_execute_data; - zend_vm_stack_free_call_frame(constructor_call); - EG(current_execute_data) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - return; + goto restore_execution_data; } zend_result result = zend_register_class_alias_ex( @@ -325,11 +312,7 @@ static void validate_class_alias( ZSTR_VAL(alias), ZSTR_VAL(scope->name) ); - EG(current_execute_data) = constructor_call->prev_execute_data; - zend_vm_stack_free_call_frame(constructor_call); - EG(current_execute_data) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - return; + goto restore_execution_data; } zend_string *lc_name; @@ -365,11 +348,7 @@ static void validate_class_alias( break; } zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(attr->args[i].name)); - EG(current_execute_data) = constructor_call->prev_execute_data; - zend_vm_stack_free_call_frame(constructor_call); - EG(current_execute_data) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); - return; + goto restore_execution_data; } } ZEND_ASSERT(nested_attribs != NULL); @@ -388,13 +367,11 @@ static void validate_class_alias( } } - // zval_ptr_dtor(alias_name); - // zval_ptr_dtor(&alias_obj); +restore_execution_data: EG(current_execute_data) = constructor_call->prev_execute_data; zend_vm_stack_free_call_frame(constructor_call); EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_call_frame(call); - return; } ZEND_METHOD(Attribute, __construct) From 973c861bf332fc9d4316cfc34d102f8801d9f08e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 01:22:48 -0700 Subject: [PATCH 08/24] zend_apply_internal_attribute_validation() --- Zend/zend_attributes.c | 51 +++++++++++++++++++++++------------------- Zend/zend_attributes.h | 2 ++ Zend/zend_compile.c | 26 +-------------------- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index a16138477f4b0..26f6db429228b 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -122,36 +122,41 @@ void compile_alias_attributes( } if (*attributes != NULL) { - /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ - ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) { - if (attr->offset != 0 || NULL == (config = zend_internal_attribute_get(attr->lcname))) { - continue; - } + zend_apply_internal_attribute_validation(*attributes, 0, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS); + } +} +/* }}} */ - uint32_t target = ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS; +ZEND_API void zend_apply_internal_attribute_validation(HashTable *attributes, uint32_t offset, uint32_t target) { + zend_attribute *attr; + zend_internal_attribute *config; - if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { - zend_string *location = zend_get_attribute_target_names(target); - zend_string *allowed = zend_get_attribute_target_names(config->flags); + /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ + ZEND_HASH_PACKED_FOREACH_PTR(attributes, attr) { + if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) { + continue; + } - zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", - ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) - ); - } + if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + zend_string *location = zend_get_attribute_target_names(target); + zend_string *allowed = zend_get_attribute_target_names(config->flags); - if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { - if (zend_is_attribute_repeated(*attributes, attr)) { - zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); - } - } + zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", + ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) + ); + } - if (config->validator != NULL) { - config->validator(attr, target, CG(active_class_entry)); + if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { + if (zend_is_attribute_repeated(attributes, attr)) { + zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); } - } ZEND_HASH_FOREACH_END(); - } + } + + if (config->validator != NULL) { + config->validator(attr, target, CG(active_class_entry)); + } + } ZEND_HASH_FOREACH_END(); } -/* }}} */ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope) { diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index f5a38abd13010..8a253b7eb9143 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -96,6 +96,8 @@ ZEND_API zend_attribute *zend_add_attribute( uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope); +ZEND_API void zend_apply_internal_attribute_validation(HashTable *attributes, uint32_t offset, uint32_t target); + END_EXTERN_C() static zend_always_inline zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8b5b23f008bb3..295b043529f60 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7468,31 +7468,7 @@ static void zend_compile_attributes( } if (*attributes != NULL) { - /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ - ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) { - if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) { - continue; - } - - if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { - zend_string *location = zend_get_attribute_target_names(target); - zend_string *allowed = zend_get_attribute_target_names(config->flags); - - zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", - ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) - ); - } - - if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { - if (zend_is_attribute_repeated(*attributes, attr)) { - zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); - } - } - - if (config->validator != NULL) { - config->validator(attr, target, CG(active_class_entry)); - } - } ZEND_HASH_FOREACH_END(); + zend_apply_internal_attribute_validation(*attributes, offset, target); } } /* }}} */ From a9c250015b2d45ff766023b3450bbdd4b5234399 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 01:32:03 -0700 Subject: [PATCH 09/24] zend_attribute_populate_arguments() --- Zend/zend_attributes.c | 77 ++++++++++++++++++++---------------------- Zend/zend_attributes.h | 1 + Zend/zend_compile.c | 35 ++----------------- 3 files changed, 40 insertions(+), 73 deletions(-) diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 26f6db429228b..f7fb90642df3f 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -47,10 +47,8 @@ void compile_alias_attributes( HashTable **attributes, zend_ast *ast ) /* {{{ */ { zend_attribute *attr; - zend_internal_attribute *config; zend_ast_list *list = zend_ast_get_list(ast); - uint32_t j; ZEND_ASSERT(ast->kind == ZEND_AST_ARRAY); @@ -71,12 +69,8 @@ void compile_alias_attributes( } zend_string *name = zend_resolve_class_name_ast(array_content->child[0]); - zend_string *lcname = zend_string_tolower_ex(name, false); zend_ast_list *args = array_content->child[1] ? zend_ast_get_list(array_content->child[1]) : NULL; - config = zend_internal_attribute_get(lcname); - zend_string_release(lcname); - uint32_t flags = (CG(active_op_array)->fn_flags & ZEND_ACC_STRICT_TYPES) ? ZEND_ATTRIBUTE_STRICT_TYPES : 0; attr = zend_add_attribute( @@ -84,40 +78,8 @@ void compile_alias_attributes( zend_string_release(name); /* Populate arguments */ - if (!args) { - continue; - } - ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); - - bool uses_named_args = 0; - for (j = 0; j < args->children; j++) { - zend_ast **arg_ast_ptr = &args->child[j]; - zend_ast *arg_ast = *arg_ast_ptr; - - if (arg_ast->kind == ZEND_AST_UNPACK) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use unpacking in attribute argument list"); - } - - if (arg_ast->kind == ZEND_AST_NAMED_ARG) { - attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0])); - arg_ast_ptr = &arg_ast->child[1]; - uses_named_args = 1; - - for (uint32_t k = 0; k < j; k++) { - if (attr->args[k].name && - zend_string_equals(attr->args[k].name, attr->args[j].name)) { - zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s", - ZSTR_VAL(attr->args[j].name)); - } - } - } else if (uses_named_args) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use positional argument after named argument"); - } - - zend_const_expr_to_zval( - &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); + if (args) { + zend_attribute_populate_arguments(attr, args); } } @@ -158,6 +120,41 @@ ZEND_API void zend_apply_internal_attribute_validation(HashTable *attributes, ui } ZEND_HASH_FOREACH_END(); } +ZEND_API void zend_attribute_populate_arguments(zend_attribute *attr, zend_ast_list *args) { + ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); + + bool uses_named_args = 0; + for (uint32_t j = 0; j < args->children; j++) { + zend_ast **arg_ast_ptr = &args->child[j]; + zend_ast *arg_ast = *arg_ast_ptr; + + if (arg_ast->kind == ZEND_AST_UNPACK) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use unpacking in attribute argument list"); + } + + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0])); + arg_ast_ptr = &arg_ast->child[1]; + uses_named_args = 1; + + for (uint32_t k = 0; k < j; k++) { + if (attr->args[k].name && + zend_string_equals(attr->args[k].name, attr->args[j].name)) { + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s", + ZSTR_VAL(attr->args[j].name)); + } + } + } else if (uses_named_args) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use positional argument after named argument"); + } + + zend_const_expr_to_zval( + &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); + } +} + uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope) { // TODO: More proper signature validation: Too many args, incorrect arg names. diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index 8a253b7eb9143..eaa14b758cadc 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -97,6 +97,7 @@ ZEND_API zend_attribute *zend_add_attribute( uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope); ZEND_API void zend_apply_internal_attribute_validation(HashTable *attributes, uint32_t offset, uint32_t target); +ZEND_API void zend_attribute_populate_arguments(zend_attribute *attr, zend_ast_list *args); END_EXTERN_C() diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 295b043529f60..9be92b43b9196 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7388,7 +7388,7 @@ static void zend_compile_attributes( zend_internal_attribute *config; zend_ast_list *list = zend_ast_get_list(ast); - uint32_t g, i, j; + uint32_t g, i; ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE_LIST); @@ -7431,38 +7431,7 @@ static void zend_compile_attributes( /* Populate arguments */ if (args) { - ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); - - bool uses_named_args = 0; - for (j = 0; j < args->children; j++) { - zend_ast **arg_ast_ptr = &args->child[j]; - zend_ast *arg_ast = *arg_ast_ptr; - - if (arg_ast->kind == ZEND_AST_UNPACK) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use unpacking in attribute argument list"); - } - - if (arg_ast->kind == ZEND_AST_NAMED_ARG) { - attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0])); - arg_ast_ptr = &arg_ast->child[1]; - uses_named_args = 1; - - for (uint32_t k = 0; k < j; k++) { - if (attr->args[k].name && - zend_string_equals(attr->args[k].name, attr->args[j].name)) { - zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s", - ZSTR_VAL(attr->args[j].name)); - } - } - } else if (uses_named_args) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use positional argument after named argument"); - } - - zend_const_expr_to_zval( - &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); - } + zend_attribute_populate_arguments(attr, args); } } } From a5e4547596ea4b9016cbbe718dbb2b9386803cfb Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 01:44:50 -0700 Subject: [PATCH 10/24] Fixes from tests --- Zend/Optimizer/zend_optimizer.c | 20 ++++++++++++++----- .../ReflectionExtension_getClasses_basic.phpt | 7 ++++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 1c58d6b7372fb..dad9d3be697c6 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -30,6 +30,7 @@ #include "zend_call_graph.h" #include "zend_inference.h" #include "zend_dump.h" +#include "zend_class_alias.h" #include "php.h" #ifndef ZEND_OPTIMIZER_MAX_REGISTERED_PASSES @@ -773,7 +774,8 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_ static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename) { - zend_class_entry *ce = Z_PTR_P(ce_zv); + zend_class_entry *ce; + Z_CE_FROM_ZVAL_P(ce, ce_zv); if (ce->ce_flags & ZEND_ACC_PRELOADED) { Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val)); @@ -809,14 +811,22 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename) zend_class_entry *zend_optimizer_get_class_entry( const zend_script *script, const zend_op_array *op_array, zend_string *lcname) { - zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL; - if (ce) { - return ce; + zval *ce_or_alias = script ? zend_hash_find(&script->class_table, lcname) : NULL; + if (ce_or_alias) { + if (EXPECTED(Z_TYPE_P(ce_or_alias) == IS_PTR)) { + return Z_PTR_P(ce_or_alias); + } + ZEND_ASSERT(Z_TYPE_P(ce_or_alias) == IS_ALIAS_PTR); + return Z_CLASS_ALIAS_P(ce_or_alias)->ce; } zval *ce_zv = zend_hash_find(CG(class_table), lcname); if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array ? op_array->filename : NULL)) { - return Z_PTR_P(ce_zv); + if (EXPECTED(Z_TYPE_P(ce_zv) == IS_PTR)) { + return Z_PTR_P(ce_zv); + } + ZEND_ASSERT(Z_TYPE_P(ce_zv) == IS_ALIAS_PTR); + return Z_CLASS_ALIAS_P(ce_zv)->ce; } if (op_array && op_array->scope && zend_string_equals_ci(op_array->scope->name, lcname)) { diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 8ba243a503bd0..e665446699326 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECTF-- -array(26) { +array(27) { ["ReflectionException"]=> object(ReflectionClass)#%d (1) { ["name"]=> @@ -139,4 +139,9 @@ array(26) { ["name"]=> string(16) "PropertyHookType" } + ["ReflectionClassAlias"]=> + object(ReflectionClass)#%d (1) { + ["name"]=> + string(20) "ReflectionClassAlias" + } } From 2adfea923c27ebca584f4f42d821816a15aaecf2 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 01:45:43 -0700 Subject: [PATCH 11/24] Fix windows file --- win32/build/config.w32 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 28937a1b19eb0..e8b4381d306c5 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -241,7 +241,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.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_lazy_objects.c zend_class_alias.h"); + zend_lazy_objects.c zend_class_alias.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"); var PHP_ASSEMBLER = PATH_PROG({ From d38277bbc4c85d7052054e6d8e8566e7fc354d45 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 14:07:16 -0700 Subject: [PATCH 12/24] Add deprecation Not yet wired to emit anything yet though --- Zend/zend_attributes.c | 10 ++++++++++ Zend/zend_class_alias.c | 1 + Zend/zend_class_alias.h | 1 + ext/reflection/php_reflection.c | 14 +++++++++++++- ext/reflection/php_reflection.stub.php | 2 ++ ext/reflection/php_reflection_arginfo.h | 6 +++++- .../ReflectionClassAlias_isDeprecated.phpt | 18 ++++++++++++++++++ ...flectionClassAlias_toString_deprecated.phpt | 13 +++++++++++++ 8 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt create mode 100644 ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index f7fb90642df3f..20c5c9f2d1648 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -366,6 +366,16 @@ static void validate_class_alias( } else { zend_ast *attributes_ast = Z_ASTVAL_P(nested_attribs); compile_alias_attributes(&( alias_obj->attributes), attributes_ast); + + zend_attribute *deprecated_attribute = zend_get_attribute_str( + alias_obj->attributes, + "deprecated", + strlen("deprecated") + ); + + if (deprecated_attribute) { + alias_obj->alias_flags |= ZEND_ACC_DEPRECATED; + } } } diff --git a/Zend/zend_class_alias.c b/Zend/zend_class_alias.c index aab09760e41d5..2994732fb0c77 100644 --- a/Zend/zend_class_alias.c +++ b/Zend/zend_class_alias.c @@ -25,6 +25,7 @@ zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { alias->ce = ce; alias->attributes = NULL; + alias->alias_flags = 0; return alias; } diff --git a/Zend/zend_class_alias.h b/Zend/zend_class_alias.h index 1c3e62ca52377..42741c7146357 100644 --- a/Zend/zend_class_alias.h +++ b/Zend/zend_class_alias.h @@ -25,6 +25,7 @@ struct _zend_class_alias { zend_refcounted_h gc; zend_class_entry *ce; HashTable *attributes; + uint32_t alias_flags; }; typedef struct _zend_class_alias zend_class_alias; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 7cf3491a05bff..9c611881e4043 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -7922,13 +7922,25 @@ ZEND_METHOD(ReflectionClassAlias, __toString) smart_str_append_printf( &str, - "%s - alias for %s", + "%s - %salias for %s", Z_STRVAL_P(reflection_prop_name(ZEND_THIS)), + (alias->alias_flags & ZEND_ACC_DEPRECATED) ? "deprecated " : "", ZSTR_VAL(alias->ce->name) ); RETURN_STR(smart_str_extract(&str)); } +ZEND_METHOD(ReflectionClassAlias, isDeprecated) +{ + reflection_object *intern; + zend_class_alias *alias; + + ZEND_PARSE_PARAMETERS_NONE(); + + GET_REFLECTION_OBJECT_PTR(alias); + RETURN_BOOL(alias->alias_flags & ZEND_ACC_DEPRECATED); +} + PHP_MINIT_FUNCTION(reflection) /* {{{ */ { memcpy(&reflection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index ac0e1575d161d..edf1f4affcf75 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -939,5 +939,7 @@ public function __construct(string $name) {} public function __toString(): string {} + public function isDeprecated(): bool {} + public function getAttributes(?string $name = null, int $flags = 0): array {} } diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index e5f4d7a4635fb..ffab8395ea16f 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: dbf38d44c7ce5005899a77535bfbb2856ffa14b4 */ + * Stub hash: 13417094b53f28c193b21ca0d0eac88df18e7aa7 */ 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) @@ -725,6 +725,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassAlias___toString arginfo_class_ReflectionFunction___toString +#define arginfo_class_ReflectionClassAlias_isDeprecated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionClassAlias_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes ZEND_METHOD(Reflection, getModifierNames); @@ -998,6 +1000,7 @@ ZEND_METHOD(ReflectionConstant, __toString); ZEND_METHOD(ReflectionConstant, getAttributes); ZEND_METHOD(ReflectionClassAlias, __construct); ZEND_METHOD(ReflectionClassAlias, __toString); +ZEND_METHOD(ReflectionClassAlias, isDeprecated); ZEND_METHOD(ReflectionClassAlias, getAttributes); static const zend_function_entry class_Reflection_methods[] = { @@ -1374,6 +1377,7 @@ static const zend_function_entry class_ReflectionConstant_methods[] = { static const zend_function_entry class_ReflectionClassAlias_methods[] = { ZEND_ME(ReflectionClassAlias, __construct, arginfo_class_ReflectionClassAlias___construct, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassAlias, __toString, arginfo_class_ReflectionClassAlias___toString, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClassAlias, isDeprecated, arginfo_class_ReflectionClassAlias_isDeprecated, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassAlias, getAttributes, arginfo_class_ReflectionClassAlias_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt b/ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt new file mode 100644 index 0000000000000..4c27321782692 --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt @@ -0,0 +1,18 @@ +--TEST-- +ReflectionClassAlias::isDeprecated() +--FILE-- +isDeprecated() ); + +$r = new ReflectionClassAlias( 'NewAlias' ); +var_dump( $r->isDeprecated() ); +?> +--EXPECT-- +bool(true) +bool(false) diff --git a/ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt b/ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt new file mode 100644 index 0000000000000..656cf874bb24e --- /dev/null +++ b/ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt @@ -0,0 +1,13 @@ +--TEST-- +ReflectionClassAlias::__toString() for a deprecated alias +--FILE-- + +--EXPECT-- +Other - deprecated alias for Demo From 009cdf12084b84c16618a75b091814d1525cd47d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 14:25:30 -0700 Subject: [PATCH 13/24] Autoload with zvals --- Zend/zend_execute.h | 2 +- Zend/zend_execute_API.c | 7 +++++-- ext/spl/php_spl.c | 12 ++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index cf15c9e3b2db5..4d8dd3962d9d8 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -35,7 +35,7 @@ ZEND_API extern void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); /* The lc_name may be stack allocated! */ -ZEND_API extern zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); +ZEND_API extern zval *(*zend_autoload)(zend_string *name, zend_string *lc_name); void init_executor(void); void shutdown_executor(void); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index c8bfe7468b34d..cda7d8bbc0eb8 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -52,7 +52,7 @@ ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data); ZEND_API void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value); -ZEND_API zend_class_entry *(*zend_autoload)(zend_string *name, zend_string *lc_name); +ZEND_API zval *(*zend_autoload)(zend_string *name, zend_string *lc_name); #ifdef ZEND_WIN32 ZEND_TLS HANDLE tq_timer = NULL; @@ -1270,7 +1270,10 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * EG(filename_override) = NULL; EG(lineno_override) = -1; zend_exception_save(); - ce = zend_autoload(autoload_name, lc_name); + zval *ce_zval = zend_autoload(autoload_name, lc_name); + if (ce_zval) { + ce = Z_PTR_P(ce_zval); + } zend_exception_restore(); EG(filename_override) = previous_filename; EG(lineno_override) = previous_lineno; diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 1149be29bd46e..6d8ceff1340fa 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -414,7 +414,7 @@ static bool autoload_func_info_equals( && alfi1->closure == alfi2->closure; } -static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) { +static zval *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) { if (!spl_autoload_functions) { return NULL; } @@ -444,13 +444,9 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri break; } - if (ZSTR_HAS_CE_CACHE(class_name) && ZSTR_GET_CE_CACHE(class_name)) { - return (zend_class_entry*)ZSTR_GET_CE_CACHE(class_name); - } else { - zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); - if (ce) { - return ce; - } + zval *ce_ptr = zend_hash_find(EG(class_table), lc_name); + if (ce_ptr) { + return ce_ptr; } zend_hash_move_forward_ex(spl_autoload_functions, &pos); From eaf2b3c4153f7a29a5409d5c1a7a60b1e709f9bc Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 15:00:48 -0700 Subject: [PATCH 14/24] Emit deprecation warnings! --- .../class_alias/access_constant.phpt | 16 +++++++++ .../class_alias/access_static_prop.phpt | 15 +++++++++ .../class_alias/call_static_method.phpt | 18 ++++++++++ .../deprecated/class_alias/messages.phpt | 33 +++++++++++++++++++ .../class_alias/use_constructor.phpt | 18 ++++++++++ Zend/zend_class_alias.c | 19 +++++++++++ Zend/zend_class_alias.h | 1 + Zend/zend_compile.c | 25 ++++++++++++-- Zend/zend_execute.c | 2 +- Zend/zend_execute_API.c | 24 +++++++++++++- ext/reflection/php_reflection.c | 5 +-- 11 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 Zend/tests/attributes/deprecated/class_alias/access_constant.phpt create mode 100644 Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt create mode 100644 Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt create mode 100644 Zend/tests/attributes/deprecated/class_alias/messages.phpt create mode 100644 Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt diff --git a/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt b/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt new file mode 100644 index 0000000000000..d2db12119459c --- /dev/null +++ b/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt @@ -0,0 +1,16 @@ +--TEST-- +#[\Deprecated]: Class alias - access a constant +--FILE-- + +--EXPECTF-- +Deprecated: Alias is deprecated in %s on line %d +int(1) diff --git a/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt b/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt new file mode 100644 index 0000000000000..3354b3e9bd22b --- /dev/null +++ b/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\Deprecated]: Class alias - access a static variable +--FILE-- + +--EXPECTF-- +Deprecated: Alias is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt b/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt new file mode 100644 index 0000000000000..ae5fe3b34e1d3 --- /dev/null +++ b/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\Deprecated]: Class alias - call a static method +--FILE-- + +--EXPECTF-- +Deprecated: Alias is deprecated in %s on line %d +Clazz::doNothing diff --git a/Zend/tests/attributes/deprecated/class_alias/messages.phpt b/Zend/tests/attributes/deprecated/class_alias/messages.phpt new file mode 100644 index 0000000000000..3c9477d2552f6 --- /dev/null +++ b/Zend/tests/attributes/deprecated/class_alias/messages.phpt @@ -0,0 +1,33 @@ +--TEST-- +#[\Deprecated]: Class alias - all messages have details +--FILE-- + +--EXPECTF-- +Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d +int(1) + +Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d + +Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d + +Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt b/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt new file mode 100644 index 0000000000000..1338880e5f1e1 --- /dev/null +++ b/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\Deprecated]: Class alias - use the constructor +--FILE-- + +--EXPECTF-- +Deprecated: Alias is deprecated in %s on line %d +Clazz::__construct diff --git a/Zend/zend_class_alias.c b/Zend/zend_class_alias.c index 2994732fb0c77..d3d105071271b 100644 --- a/Zend/zend_class_alias.c +++ b/Zend/zend_class_alias.c @@ -17,6 +17,9 @@ */ #include "zend_class_alias.h" +#include "zend_errors.h" +#include "zend_string.h" +#include "zend.h" zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { zend_class_alias *alias = malloc(sizeof(zend_class_alias)); @@ -29,3 +32,19 @@ zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { return alias; } + +ZEND_COLD zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix); + +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_alias(const zend_class_alias *alias) { + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (get_deprecation_suffix_from_attribute(alias->attributes, NULL, &message_suffix) == FAILURE) { + return; + } + + zend_error_unchecked(E_USER_DEPRECATED, "Alias is deprecated%S", + message_suffix + ); + + zend_string_release(message_suffix); +} diff --git a/Zend/zend_class_alias.h b/Zend/zend_class_alias.h index 42741c7146357..b8d1fdc333d4f 100644 --- a/Zend/zend_class_alias.h +++ b/Zend/zend_class_alias.h @@ -51,5 +51,6 @@ typedef struct _zend_class_alias zend_class_alias; zend_class_alias * zend_class_alias_init(zend_class_entry *ce); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_alias(const zend_class_alias *alias); #endif diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9be92b43b9196..a1f27eaaf5785 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_class_alias.h" #define SET_NODE(target, src) do { \ target ## _type = (src)->op_type; \ @@ -1875,8 +1876,23 @@ static bool zend_try_ct_eval_class_const(zval *zv, zend_string *class_name, zend if (class_name_refers_to_active_ce(class_name, fetch_type)) { cc = zend_hash_find_ptr(&CG(active_class_entry)->constants_table, name); } else if (fetch_type == ZEND_FETCH_CLASS_DEFAULT && !(CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION)) { - zend_class_entry *ce = zend_hash_find_ptr_lc(CG(class_table), class_name); - if (ce) { + zend_string *lc_key = zend_string_tolower(class_name); + zval *ce_or_alias = zend_hash_find(CG(class_table), lc_key); + zend_string_release(lc_key); + + if (ce_or_alias) { + zend_class_entry *ce; + if (Z_TYPE_P(ce_or_alias) == IS_ALIAS_PTR) { + zend_class_alias *alias = Z_CLASS_ALIAS_P(ce_or_alias); + if (alias->alias_flags & ZEND_ACC_DEPRECATED) { + // Cannot evaluate at compile time + return 0; + } + ce = alias->ce; + } else { + ZEND_ASSERT(Z_TYPE_P(ce_or_alias) == IS_PTR); + ce = Z_PTR_P(ce_or_alias); + } cc = zend_hash_find_ptr(&ce->constants_table, name); } else { return 0; @@ -5340,7 +5356,10 @@ static void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type zend_class_entry *ce = NULL; if (opline->op1_type == IS_CONST) { zend_string *lcname = Z_STR_P(CT_CONSTANT(opline->op1) + 1); - ce = zend_hash_find_ptr(CG(class_table), lcname); + zval *ce_or_alias = zend_hash_find(CG(class_table), lcname); + if (ce_or_alias) { + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); + } if (ce) { if (zend_compile_ignore_class(ce, CG(active_op_array)->filename)) { ce = NULL; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 5d8d9f4caeb86..0c3be0572136b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1795,7 +1795,7 @@ ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void) zend_throw_error(NULL, "%s", msg); } -ZEND_COLD static zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix) +ZEND_COLD zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix) { *message_suffix = ZSTR_EMPTY_ALLOC(); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index cda7d8bbc0eb8..7cf7ae7e01b12 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1204,6 +1204,11 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * zend_string_release_ex(lc_name, 0); } Z_CE_FROM_ZVAL_P(ce, zv); + zend_class_alias *alias = NULL; + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + ce_cache = 0; + alias = Z_CLASS_ALIAS_P(zv); + } if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_LINKED))) { if ((flags & ZEND_FETCH_CLASS_ALLOW_UNLINKED) || ((flags & ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED) && @@ -1213,6 +1218,9 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * zend_hash_init(CG(unlinked_uses), 0, NULL, NULL, 0); } zend_hash_index_add_empty_element(CG(unlinked_uses), (zend_long)(uintptr_t)ce); + if (!(flags & ZEND_FETCH_CLASS_SILENT) && alias && (alias->alias_flags & ZEND_ACC_DEPRECATED)) { + zend_deprecated_class_alias(alias); + } return ce; } return NULL; @@ -1223,6 +1231,9 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * (!CG(in_compilation) || (ce->ce_flags & ZEND_ACC_IMMUTABLE))) { SET_CE_CACHE(ce_cache, ce); } + if (!(flags & ZEND_FETCH_CLASS_SILENT) && alias && (alias->alias_flags & ZEND_ACC_DEPRECATED)) { + zend_deprecated_class_alias(alias); + } return ce; } @@ -1271,8 +1282,16 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * EG(lineno_override) = -1; zend_exception_save(); zval *ce_zval = zend_autoload(autoload_name, lc_name); + zend_class_alias *alias = NULL; if (ce_zval) { - ce = Z_PTR_P(ce_zval); + if (Z_TYPE_P(ce_zval) == IS_ALIAS_PTR) { + ce_cache = 0; + alias = Z_CLASS_ALIAS_P(ce_zval); + ce = alias->ce; + } else { + ZEND_ASSERT(Z_TYPE_P(ce_zval) == IS_PTR); + ce = Z_PTR_P(ce_zval); + } } zend_exception_restore(); EG(filename_override) = previous_filename; @@ -1290,6 +1309,9 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * SET_CE_CACHE(ce_cache, ce); } } + if (!(flags & ZEND_FETCH_CLASS_SILENT) && alias && (alias->alias_flags & ZEND_ACC_DEPRECATED)) { + zend_deprecated_class_alias(alias); + } return ce; } /* }}} */ diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 9c611881e4043..36d94ae9e0a50 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -7860,8 +7860,9 @@ ZEND_METHOD(ReflectionClassAlias, __construct) ZEND_PARSE_PARAMETERS_END(); // First use zend_lookup_class() which will also take care of autoloading, - // but that will always return the underlying class entry - zend_class_entry *ce = zend_lookup_class(name); + // but that will always return the underlying class entry; don't complain + // about deprecations here + zend_class_entry *ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_SILENT); if (ce == NULL) { if (!EG(exception)) { zend_throw_exception_ex(reflection_exception_ptr, -1, "Class \"%s\" does not exist", ZSTR_VAL(name)); From cc2d2c331964205aa16e4fcf0487ddda680b158b Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 15:08:28 -0700 Subject: [PATCH 15/24] Names in deprecation warnings --- .../deprecated/class_alias/access_constant.phpt | 2 +- .../deprecated/class_alias/access_static_prop.phpt | 2 +- .../deprecated/class_alias/call_static_method.phpt | 2 +- .../tests/attributes/deprecated/class_alias/messages.phpt | 8 ++++---- .../deprecated/class_alias/use_constructor.phpt | 2 +- Zend/zend_API.c | 3 +++ Zend/zend_class_alias.c | 5 ++++- Zend/zend_class_alias.h | 1 + Zend/zend_opcode.c | 4 ++++ 9 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt b/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt index d2db12119459c..8b3885d964204 100644 --- a/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt +++ b/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt @@ -12,5 +12,5 @@ var_dump(MyAlias::TEST); ?> --EXPECTF-- -Deprecated: Alias is deprecated in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d int(1) diff --git a/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt b/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt index 3354b3e9bd22b..530c92125a9bf 100644 --- a/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt +++ b/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt @@ -12,4 +12,4 @@ MyAlias::$v = true; ?> --EXPECTF-- -Deprecated: Alias is deprecated in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt b/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt index ae5fe3b34e1d3..bbaa89215bc8c 100644 --- a/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt +++ b/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt @@ -14,5 +14,5 @@ MyAlias::doNothing(); ?> --EXPECTF-- -Deprecated: Alias is deprecated in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d Clazz::doNothing diff --git a/Zend/tests/attributes/deprecated/class_alias/messages.phpt b/Zend/tests/attributes/deprecated/class_alias/messages.phpt index 3c9477d2552f6..08ff0fdda74e7 100644 --- a/Zend/tests/attributes/deprecated/class_alias/messages.phpt +++ b/Zend/tests/attributes/deprecated/class_alias/messages.phpt @@ -23,11 +23,11 @@ MyAlias::doNothing(); ?> --EXPECTF-- -Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d int(1) -Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d -Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d -Deprecated: Alias is deprecated since 8.4, don't use the alias in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt b/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt index 1338880e5f1e1..1e09c3a0b7cfd 100644 --- a/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt +++ b/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt @@ -14,5 +14,5 @@ $o = new MyAlias(); ?> --EXPECTF-- -Deprecated: Alias is deprecated in %s on line %d +Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d Clazz::__construct diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 189115119f610..03fcfb3932c21 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3603,6 +3603,9 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ */ zend_class_alias *alias = zend_class_alias_init(ce); + zend_string *original_name = zend_string_init(name, name_len, persistent); + alias->name = original_name; + ZVAL_ALIAS_PTR(&zv, alias); ret = zend_hash_add(CG(class_table), lcname, &zv); diff --git a/Zend/zend_class_alias.c b/Zend/zend_class_alias.c index d3d105071271b..7a2db3fe47b11 100644 --- a/Zend/zend_class_alias.c +++ b/Zend/zend_class_alias.c @@ -27,6 +27,7 @@ zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { GC_SET_REFCOUNT(alias, 1); alias->ce = ce; + alias->name = NULL; alias->attributes = NULL; alias->alias_flags = 0; @@ -42,7 +43,9 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_alias(const zend_cla return; } - zend_error_unchecked(E_USER_DEPRECATED, "Alias is deprecated%S", + zend_error_unchecked(E_USER_DEPRECATED, "Alias %s for class %s is deprecated%S", + ZSTR_VAL(alias->name), + ZSTR_VAL(alias->ce->name), message_suffix ); diff --git a/Zend/zend_class_alias.h b/Zend/zend_class_alias.h index b8d1fdc333d4f..6ca62eb2fa940 100644 --- a/Zend/zend_class_alias.h +++ b/Zend/zend_class_alias.h @@ -24,6 +24,7 @@ struct _zend_class_alias { zend_refcounted_h gc; zend_class_entry *ce; + zend_string *name; HashTable *attributes; uint32_t alias_flags; }; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 4ceaf0b22e781..a4f4ef90133fc 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -303,6 +303,10 @@ ZEND_API void destroy_zend_class(zval *zv) zend_hash_release(class_alias->attributes); class_alias->attributes = NULL; } + if (class_alias->name) { + zend_string_release(class_alias->name); + class_alias->name = NULL; + } return; } From f66b280696c3acf57087a39c7110911bbbc0ee62 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 22:22:52 -0700 Subject: [PATCH 16/24] Fixes --- Zend/Optimizer/zend_optimizer.c | 13 +++++++++++++ Zend/zend_API.c | 2 ++ Zend/zend_execute_API.c | 3 +++ Zend/zend_opcode.c | 4 ++-- ext/opcache/ZendAccelerator.c | 8 ++++++++ ext/opcache/jit/zend_jit.c | 4 +++- ext/opcache/zend_accelerator_util_funcs.c | 16 ++++++++++------ ext/opcache/zend_file_cache.c | 2 ++ ext/opcache/zend_persist.c | 23 ++++++++++++++++++++++- ext/opcache/zend_persist_calc.c | 5 +++++ 10 files changed, 70 insertions(+), 10 deletions(-) diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index dad9d3be697c6..a880312867281 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -784,6 +784,13 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename) return false; } } + // Ignore deprecated aliases so that they are fetched at runtime + if (Z_TYPE_P(ce_zv) == IS_ALIAS_PTR) { + zend_class_alias *alias = Z_CLASS_ALIAS_P(ce_zv); + if (alias->alias_flags & ZEND_ACC_DEPRECATED) { + return true; + } + } return ce->type == ZEND_USER_CLASS && (!ce->info.user.filename || ce->info.user.filename != filename); } @@ -817,6 +824,12 @@ zend_class_entry *zend_optimizer_get_class_entry( return Z_PTR_P(ce_or_alias); } ZEND_ASSERT(Z_TYPE_P(ce_or_alias) == IS_ALIAS_PTR); + zend_class_alias *alias = Z_CLASS_ALIAS_P(ce_or_alias); + if (alias->alias_flags & ZEND_ACC_DEPRECATED) { + // Pretend that the class cannot be found so that it gets looked + // up at runtime + return NULL; + } return Z_CLASS_ALIAS_P(ce_or_alias)->ce; } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 03fcfb3932c21..79c663eea77db 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3617,6 +3617,8 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ } return SUCCESS; } + + zend_string_release(original_name); return FAILURE; } /* }}} */ diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 7cf7ae7e01b12..7bf7849549f8f 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -328,6 +328,9 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + continue; + } zend_class_entry *ce; Z_CE_FROM_ZVAL_P(ce, zv); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index a4f4ef90133fc..1706ba81130e6 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -301,11 +301,11 @@ ZEND_API void destroy_zend_class(zval *zv) if (class_alias->attributes) { zend_hash_release(class_alias->attributes); - class_alias->attributes = NULL; + // class_alias->attributes = NULL; } if (class_alias->name) { zend_string_release(class_alias->name); - class_alias->name = NULL; + // class_alias->name = NULL; } return; } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index d4a46cc34c3f3..df3250421c01e 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4146,6 +4146,14 @@ static void preload_link(void) continue; } + // Not preloading class constants for deprecated aliases + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + zend_class_alias *class_alias = Z_CLASS_ALIAS_P(zv); + if (class_alias->alias_flags & ZEND_ACC_DEPRECATED) { + continue; + } + } + if ((ce->ce_flags & ZEND_ACC_LINKED) && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { /* don't update traits */ CG(in_compilation) = true; /* prevent autoloading */ diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 355178d9d51ed..8c6b9acc0dab8 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -570,7 +570,9 @@ static zend_class_entry* zend_get_known_class(const zend_op_array *op_array, con ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING); class_name = Z_STR_P(zv); - ce = zend_lookup_class_ex(class_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + // ZEND_FETCH_CLASS_SILENT - ignore alias deprecation + // TODO but we want to not cache the ce if i is a deprecated alias + ce = zend_lookup_class_ex(class_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); if (ce && (ce->type == ZEND_INTERNAL_CLASS || ce->info.user.filename != op_array->filename)) { ce = NULL; } diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 65be23056374c..2b790bfd817df 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -365,15 +365,19 @@ static void zend_accel_do_delayed_early_binding( CG(in_compilation) = 1; for (uint32_t i = 0; i < persistent_script->num_early_bindings; i++) { zend_early_binding *early_binding = &persistent_script->early_bindings[i]; - zend_class_entry *ce = zend_hash_find_ex_ptr(EG(class_table), early_binding->lcname, 1); - if (!ce) { + zval *ce_or_alias = zend_hash_find_ex(EG(class_table), early_binding->lcname, 1); + if (!ce_or_alias) { zval *zv = zend_hash_find_known_hash(EG(class_table), early_binding->rtd_key); + zend_class_entry *ce = NULL; if (zv) { - zend_class_entry *orig_ce = Z_CE_P(zv); - zend_class_entry *parent_ce = !(orig_ce->ce_flags & ZEND_ACC_LINKED) - ? zend_hash_find_ex_ptr(EG(class_table), early_binding->lc_parent_name, 1) + zend_class_entry *orig_ce; + Z_CE_FROM_ZVAL_P(orig_ce, zv); + zval *parent_ce_or_alias = !(orig_ce->ce_flags & ZEND_ACC_LINKED) + ? zend_hash_find_ex(EG(class_table), early_binding->lc_parent_name, 1) : NULL; - if (parent_ce || (orig_ce->ce_flags & ZEND_ACC_LINKED)) { + if (parent_ce_or_alias || (orig_ce->ce_flags & ZEND_ACC_LINKED)) { + zend_class_entry *parent_ce; + Z_CE_FROM_ZVAL_P(parent_ce, parent_ce_or_alias); ce = zend_try_early_bind(orig_ce, parent_ce, early_binding->lcname, zv); } } diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index fee90e42b574f..80d5bb510b4c7 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -765,6 +765,7 @@ static void zend_file_cache_serialize_class(zval *zv, zend_file_cache_metainfo *info, void *buf) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zend_class_entry *ce; SERIALIZE_PTR(Z_PTR_P(zv)); @@ -1619,6 +1620,7 @@ static void zend_file_cache_unserialize_class_constant(zval * zend_persistent_script *script, void *buf) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); if (!IS_UNSERIALIZED(Z_PTR_P(zv))) { zend_class_constant *c; diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 202cd73c90422..2aabcbfb4614c 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -29,6 +29,7 @@ #include "zend_operators.h" #include "zend_interfaces.h" #include "zend_attributes.h" +#include "zend_class_alias.h" #ifdef HAVE_JIT # include "Optimizer/zend_func_info.h" @@ -909,6 +910,21 @@ static void zend_persist_class_constant(zval *zv) zend_persist_type(&c->type); } +zend_class_alias *zend_persist_class_alias_entry(zend_class_alias *orig_alias) +{ + Bucket *p; + zend_class_alias *alias = orig_alias; + + alias = zend_shared_memdup_put(alias, sizeof(zend_class_alias)); + alias->ce = zend_persist_class_entry(alias->ce); + // zend_accel_store_string(alias->name); + if (alias->attributes) { + alias->attributes = zend_persist_attributes(alias->attributes); + } + + return alias; +} + zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) { Bucket *p; @@ -1296,7 +1312,12 @@ static void zend_accel_persist_class_table(HashTable *class_table) ZEND_HASH_MAP_FOREACH_BUCKET(class_table, p) { ZEND_ASSERT(p->key != NULL); zend_accel_store_interned_string(p->key); - Z_CE(p->val) = zend_persist_class_entry(Z_CE(p->val)); + if (Z_TYPE(p->val) == IS_ALIAS_PTR) { + Z_CLASS_ALIAS(p->val) = zend_persist_class_alias_entry(Z_CLASS_ALIAS(p->val)); + } else { + ZEND_ASSERT(Z_TYPE(p->val) == IS_PTR); + Z_CE(p->val) = zend_persist_class_entry(Z_CE(p->val)); + } } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_FOREACH_BUCKET(class_table, p) { if (EXPECTED(Z_TYPE(p->val) != IS_ALIAS_PTR)) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index f1fe460d38791..41c16789c30c7 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -599,6 +599,11 @@ static void zend_persist_class_alias_entry_calc(zend_class_alias *alias) // alias->ce is going to be a pointer to a class entry that will be // persisted on its own, here we just need to add size for the alias ADD_SIZE(sizeof(zend_class_alias)); + // And the things that the alias holds directly + ADD_INTERNED_STRING(alias->name); + if (alias->attributes) { + zend_persist_attributes_calc(alias->attributes); + } zend_persist_class_entry_calc(alias->ce); } From 9d83e4aace8bd3f649290922410cc516edd54ef7 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 7 Jun 2025 22:26:55 -0700 Subject: [PATCH 17/24] Unused variable --- ext/opcache/zend_persist.c | 1 - 1 file changed, 1 deletion(-) diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 2aabcbfb4614c..7c5f3e053daa3 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -912,7 +912,6 @@ static void zend_persist_class_constant(zval *zv) zend_class_alias *zend_persist_class_alias_entry(zend_class_alias *orig_alias) { - Bucket *p; zend_class_alias *alias = orig_alias; alias = zend_shared_memdup_put(alias, sizeof(zend_class_alias)); From 16e4da3390034d6debe68b6ce2b9f7781c760035 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 9 Jun 2025 16:30:30 -0700 Subject: [PATCH 18/24] Missing free --- Zend/zend_opcode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 1706ba81130e6..e6fa22ee792b7 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -307,6 +307,7 @@ ZEND_API void destroy_zend_class(zval *zv) zend_string_release(class_alias->name); // class_alias->name = NULL; } + free(class_alias); return; } From 0ef14f0ef151d2aea7ad479106e6f1c276798dbb Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 9 Jun 2025 16:30:40 -0700 Subject: [PATCH 19/24] More tests --- .../attributes_array_contains_non-object.phpt | 17 ++++++++++++ .../attributes_array_non-object.phpt | 17 ++++++++++++ ...runtime_attributes_validation_missing.phpt | 26 +++++++++++++++++++ ...e_attributes_validation_non-attribute.phpt | 26 +++++++++++++++++++ Zend/zend_attributes.c | 25 ++++++++++++------ 5 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt create mode 100644 Zend/tests/attributes/class_alias/attributes_array_non-object.phpt create mode 100644 Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt create mode 100644 Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt diff --git a/Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt b/Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt new file mode 100644 index 0000000000000..4da3ef4c723a7 --- /dev/null +++ b/Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt @@ -0,0 +1,17 @@ +--TEST-- +#[ClassAlias] - attributes parameter validated at compile time (non-object in array) +--FILE-- +getAttributes()[0]; +var_dump( $attrib ); + +$attrib->newInstance(); + +?> +--EXPECTF-- +Fatal error: Attribute must be declared with `new` in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_array_non-object.phpt b/Zend/tests/attributes/class_alias/attributes_array_non-object.phpt new file mode 100644 index 0000000000000..12ecda3574ca0 --- /dev/null +++ b/Zend/tests/attributes/class_alias/attributes_array_non-object.phpt @@ -0,0 +1,17 @@ +--TEST-- +#[ClassAlias] - attributes parameter validated at compile time (array can be evaluated) +--FILE-- +getAttributes()[0]; +var_dump( $attrib ); + +$attrib->newInstance(); + +?> +--EXPECTF-- +Fatal error: ClassAlias::__construct(): Argument #2 ($attributes) must be an array of objects in %s on line %d diff --git a/Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt b/Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt new file mode 100644 index 0000000000000..4f1e2412b66ea --- /dev/null +++ b/Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt @@ -0,0 +1,26 @@ +--TEST-- +#[ClassAlias] - attributes parameter validated at runtime (missing class) +--FILE-- +getAttributes()[0]; +var_dump( $attrib ); + +$attrib->newInstance(); + +?> +--EXPECTF-- +object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" +} + +Fatal error: Uncaught Error: Class "MissingAttribute" not found in %s:%d +Stack trace: +#0 %s(%d): ReflectionAttribute->newInstance() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt b/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt new file mode 100644 index 0000000000000..aae32fd975a4e --- /dev/null +++ b/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt @@ -0,0 +1,26 @@ +--TEST-- +#[ClassAlias] - attributes parameter value types not validated when constructing ClassAlias +--FILE-- +getAttributes()[0]; +var_dump( $attrib ); + +var_dump( $attrib->newInstance() ); + +?> +--EXPECTF-- +object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "ClassAlias" +} +object(ClassAlias)#%d (1) { + ["alias"]=> + string(5) "Other" +} diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 20c5c9f2d1648..636fcd45c1367 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -355,14 +355,23 @@ static void validate_class_alias( } ZEND_ASSERT(nested_attribs != NULL); if (UNEXPECTED(!Z_OPT_CONSTANT_P(nested_attribs))) { - zend_wrong_parameter_error( - ZPP_ERROR_WRONG_ARG, - 2, - "attributes", - Z_EXPECTED_ARRAY, - nested_attribs - ); - // Something with an invalid parameter + // If it is an array, then it must be an array that can be evaluated + // already + if (Z_TYPE_P(nested_attribs) == IS_ARRAY) { + zend_argument_type_error( + 2, + "must be an array of objects" + ); + } else { + zend_wrong_parameter_error( + ZPP_ERROR_WRONG_ARG, + 2, + "attributes", + Z_EXPECTED_ARRAY, + nested_attribs + ); + // Something with an invalid parameter + } } else { zend_ast *attributes_ast = Z_ASTVAL_P(nested_attribs); compile_alias_attributes(&( alias_obj->attributes), attributes_ast); From 966ceab5d99b1bf265b2c26bd1453838311b59f0 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 9 Jun 2025 16:44:55 -0700 Subject: [PATCH 20/24] Store attributes --- .../runtime_attributes_validation_non-attribute.phpt | 8 +++++++- Zend/zend_attributes.c | 4 ++++ Zend/zend_attributes.stub.php | 2 ++ Zend/zend_attributes_arginfo.h | 8 +++++++- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt b/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt index aae32fd975a4e..b95f9929a4754 100644 --- a/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt +++ b/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt @@ -20,7 +20,13 @@ object(ReflectionAttribute)#%d (1) { ["name"]=> string(10) "ClassAlias" } -object(ClassAlias)#%d (1) { +object(ClassAlias)#%d (2) { ["alias"]=> string(5) "Other" + ["attributes"]=> + array(1) { + [0]=> + object(NonAttribute)#%d (0) { + } + } } diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 636fcd45c1367..8f733b3eff334 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -538,6 +538,8 @@ ZEND_METHOD(ClassAlias, __construct) } if (attributes == NULL || zend_hash_num_elements(attributes) == 0) { + ZVAL_EMPTY_ARRAY(&value); + zend_update_property(zend_ce_class_alias, Z_OBJ_P(ZEND_THIS), ZEND_STRL("attributes"), &value); return; } @@ -547,6 +549,8 @@ ZEND_METHOD(ClassAlias, __construct) ); RETURN_THROWS(); } + ZVAL_ARR(&value, attributes); + zend_update_property(zend_ce_class_alias, Z_OBJ_P(ZEND_THIS), ZEND_STRL("attributes"), &value); } static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index de96ff7deaa70..d217bad206f6d 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -108,5 +108,7 @@ final class ClassAlias { public readonly string $alias; + public readonly array $attributes; + public function __construct(string $alias, array $attributes = []) {} } diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index d957156e92de3..6b6f3a1aafc87 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c5a70b37073a6986229f62d801bddbabfada5474 */ + * Stub hash: 0114c2f6665c4e10e01d721481aa875e5a092900 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -321,6 +321,12 @@ static zend_class_entry *register_class_ClassAlias(void) zend_declare_typed_property(class_entry, property_alias_name, &property_alias_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_alias_name); + zval property_attributes_default_value; + ZVAL_UNDEF(&property_attributes_default_value); + zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1); + zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); + zend_string_release(property_attributes_name); + zend_string *attribute_name_Attribute_class_ClassAlias_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); zend_attribute *attribute_Attribute_class_ClassAlias_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_ClassAlias_0, 1); zend_string_release(attribute_name_Attribute_class_ClassAlias_0); From 31ecd01d89b9a7560a8778d66f596afd4e3f3adc Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 9 Jun 2025 19:37:12 -0700 Subject: [PATCH 21/24] Try --- Zend/Optimizer/zend_optimizer.c | 11 ++++++++--- .../attributes/class_alias/attributes_dict.phpt | 2 ++ Zend/zend_API.c | 1 + Zend/zend_attributes.c | 9 +++++---- Zend/zend_execute_API.c | 7 ++++--- Zend/zend_opcode.c | 4 ++++ ext/opcache/ZendAccelerator.c | 13 +++++++++---- ext/opcache/zend_file_cache.c | 1 + ext/opcache/zend_persist.c | 6 ++++++ 9 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index a880312867281..4b9980748d78f 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -776,6 +776,9 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename) { zend_class_entry *ce; Z_CE_FROM_ZVAL_P(ce, ce_zv); + if (Z_TYPE_P(ce_zv) == IS_ALIAS_PTR) { + return true; + } if (ce->ce_flags & ZEND_ACC_PRELOADED) { Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val)); @@ -830,7 +833,8 @@ zend_class_entry *zend_optimizer_get_class_entry( // up at runtime return NULL; } - return Z_CLASS_ALIAS_P(ce_or_alias)->ce; + return NULL; + // return Z_CLASS_ALIAS_P(ce_or_alias)->ce; } zval *ce_zv = zend_hash_find(CG(class_table), lcname); @@ -839,7 +843,8 @@ zend_class_entry *zend_optimizer_get_class_entry( return Z_PTR_P(ce_zv); } ZEND_ASSERT(Z_TYPE_P(ce_zv) == IS_ALIAS_PTR); - return Z_CLASS_ALIAS_P(ce_zv)->ce; + return NULL; + // return Z_CLASS_ALIAS_P(ce_zv)->ce; } if (op_array && op_array->scope && zend_string_equals_ci(op_array->scope->name, lcname)) { @@ -882,7 +887,7 @@ const zend_class_constant *zend_fetch_class_const_info( } else { zval *ce_zv = zend_hash_find(EG(class_table), Z_STR_P(op1 + 1)); if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array->filename)) { - ce = Z_PTR_P(ce_zv); + Z_CE_FROM_ZVAL_P(ce, ce_zv); } } } diff --git a/Zend/tests/attributes/class_alias/attributes_dict.phpt b/Zend/tests/attributes/class_alias/attributes_dict.phpt index 0d09e3d0bd29c..7d582370e3c90 100644 --- a/Zend/tests/attributes/class_alias/attributes_dict.phpt +++ b/Zend/tests/attributes/class_alias/attributes_dict.phpt @@ -6,6 +6,8 @@ Alias attributes must not be associative #[ClassAlias('Other', ['test' => new Deprecated()])] class Demo {} +class_alias( 'Demo', 'Other2' ); + $attr = new ReflectionClass( Demo::class )->getAttributes()[0]; $attr->newInstance(); diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 79c663eea77db..38af53c2a4427 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3619,6 +3619,7 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ } zend_string_release(original_name); + free(alias); return FAILURE; } /* }}} */ diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index 8f733b3eff334..c49dc3c3ff56e 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -303,6 +303,11 @@ static void validate_class_alias( goto restore_execution_data; } + // if (CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION) { + // // Opcache, don't register the alias + // goto restore_execution_data; + // } + zend_result result = zend_register_class_alias_ex( ZSTR_VAL(alias), ZSTR_LEN(alias), @@ -612,10 +617,6 @@ ZEND_API zend_result zend_get_attribute_value(zval *ret, zend_attribute *attr, u ZVAL_COPY_OR_DUP(ret, &attr->args[i].value); if (Z_TYPE_P(ret) == IS_CONSTANT_AST) { - // Delayed validation for attributes in class aliases - if (CG(in_compilation) && i == 1 && zend_string_equals(attr->name, zend_ce_class_alias->name)) { - return SUCCESS; - } if (SUCCESS != zval_update_constant_ex(ret, scope)) { zval_ptr_dtor(ret); return FAILURE; diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 7bf7849549f8f..7a5dd252f0fd9 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -328,9 +328,10 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) } } ZEND_HASH_FOREACH_END(); ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { - if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { - continue; - } + // CHECK + // if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { + // continue; + // } zend_class_entry *ce; Z_CE_FROM_ZVAL_P(ce, zv); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index e6fa22ee792b7..50eb16a8da789 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -299,6 +299,10 @@ ZEND_API void destroy_zend_class(zval *zv) if (UNEXPECTED(Z_TYPE_INFO_P(zv) == IS_ALIAS_PTR)) { zend_class_alias *class_alias = Z_CLASS_ALIAS_P(zv); + if (class_alias->alias_flags & ZEND_ACC_IMMUTABLE) { + return; + } + if (class_alias->attributes) { zend_hash_release(class_alias->attributes); // class_alias->attributes = NULL; diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index df3250421c01e..0642f46d8f411 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -681,9 +681,16 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int /* class table hash keys, class names, properties, methods, constants, etc */ ZEND_HASH_MAP_FOREACH_BUCKET(CG(class_table), p) { - zend_class_entry *ce; + zend_class_entry *ce = NULL; - Z_CE_FROM_ZVAL(ce, p->val); + if (EXPECTED(Z_TYPE(p->val) == IS_PTR)) { + ce = Z_PTR(p->val); + } else { + ZEND_ASSERT(Z_TYPE(p->val) == IS_ALIAS_PTR); + zend_class_alias *alias = Z_PTR(p->val); + alias->name = new_interned_string(alias->name); + ce = alias->ce; + } if (p->key) { p->key = new_interned_string(p->key); @@ -4104,8 +4111,6 @@ static void preload_link(void) zend_hash_index_del( CG(delayed_variance_obligations), (uintptr_t) Z_CE_P(zv)); } - zend_hash_index_del( - CG(delayed_variance_obligations), (uintptr_t) Z_CE_P(zv)); } /* Restore the original class. */ diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 80d5bb510b4c7..4a0a2f4e490c5 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -1646,6 +1646,7 @@ static void zend_file_cache_unserialize_class(zval *zv, zend_persistent_script *script, void *buf) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zend_class_entry *ce; UNSERIALIZE_PTR(Z_PTR_P(zv)); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 7c5f3e053daa3..87cb45185a380 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -921,6 +921,12 @@ zend_class_alias *zend_persist_class_alias_entry(zend_class_alias *orig_alias) alias->attributes = zend_persist_attributes(alias->attributes); } + if (EXPECTED(!ZCG(current_persistent_script)->corrupted)) { + alias->alias_flags |= ZEND_ACC_IMMUTABLE; + } else { + alias->alias_flags |= ZEND_ACC_FILE_CACHED; + } + return alias; } From 9dd407cd037a182b7f93d6c3363aeedc916d185a Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 14 Jun 2025 22:08:08 -0700 Subject: [PATCH 22/24] Revert attributes/name/etc. Just switching to a zend_class_alias object should be easier --- Zend/Optimizer/zend_optimizer.c | 22 +- Zend/tests/attributes/034_target_values.phpt | 24 +- .../attributes_array_contains_non-object.phpt | 17 - .../attributes_array_non-object.phpt | 17 - .../class_alias/attributes_dict.phpt | 21 - .../class_alias/attributes_nonarray.phpt | 14 - .../class_alias/attributes_valid.phpt | 12 - .../attributes/class_alias/name_invalid.phpt | 11 - .../class_alias/name_nonstring.phpt | 11 - .../attributes/class_alias/name_required.phpt | 15 - .../class_alias/redeclaration_error.phpt | 11 - .../class_alias/repeated_attribute.phpt | 25 -- ...runtime_attributes_validation_missing.phpt | 26 -- ...e_attributes_validation_non-attribute.phpt | 32 -- .../class_alias/target_validation.phpt | 11 - .../class_alias/working_example.phpt | 17 - ...class_alias_listed_as_target-internal.phpt | 11 - ...class_alias_listed_as_target-userland.phpt | 31 -- .../must_target_class_alias-internal.phpt | 11 - .../must_target_class_alias-userland.phpt | 31 -- .../not_repeatable-internal.phpt | 11 - .../not_repeatable-userland.phpt | 35 -- .../repeatable-internal.phpt | 15 - .../repeatable-userland.phpt | 29 -- ...arget_all_targets_class_alias-default.phpt | 25 -- ...rget_all_targets_class_alias-explicit.phpt | 25 -- .../constant_listed_as_target-internal.phpt | 2 +- .../class_alias/access_constant.phpt | 16 - .../class_alias/access_static_prop.phpt | 15 - .../class_alias/call_static_method.phpt | 18 - .../deprecated/class_alias/messages.phpt | 33 -- .../class_alias/use_constructor.phpt | 18 - Zend/zend_API.c | 4 - Zend/zend_attributes.c | 380 ++---------------- Zend/zend_attributes.h | 10 +- Zend/zend_attributes.stub.php | 17 +- Zend/zend_attributes_arginfo.h | 50 +-- Zend/zend_class_alias.c | 22 - Zend/zend_class_alias.h | 3 - Zend/zend_compile.c | 75 +++- Zend/zend_execute.c | 2 +- Zend/zend_execute_API.c | 14 - Zend/zend_opcode.c | 8 - ext/opcache/ZendAccelerator.c | 18 +- ext/opcache/jit/zend_jit.c | 4 +- ext/opcache/zend_persist.c | 4 - ext/opcache/zend_persist_calc.c | 5 - ext/reflection/php_reflection.c | 26 +- ext/reflection/php_reflection.stub.php | 4 - ext/reflection/php_reflection_arginfo.h | 10 +- ...lectionClassAlias_getAttributes_empty.phpt | 14 - ...ionClassAlias_getAttributes_non-empty.phpt | 20 - .../ReflectionClassAlias_isDeprecated.phpt | 18 - ...lectionClassAlias_toString_deprecated.phpt | 13 - 54 files changed, 117 insertions(+), 1216 deletions(-) delete mode 100644 Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt delete mode 100644 Zend/tests/attributes/class_alias/attributes_array_non-object.phpt delete mode 100644 Zend/tests/attributes/class_alias/attributes_dict.phpt delete mode 100644 Zend/tests/attributes/class_alias/attributes_nonarray.phpt delete mode 100644 Zend/tests/attributes/class_alias/attributes_valid.phpt delete mode 100644 Zend/tests/attributes/class_alias/name_invalid.phpt delete mode 100644 Zend/tests/attributes/class_alias/name_nonstring.phpt delete mode 100644 Zend/tests/attributes/class_alias/name_required.phpt delete mode 100644 Zend/tests/attributes/class_alias/redeclaration_error.phpt delete mode 100644 Zend/tests/attributes/class_alias/repeated_attribute.phpt delete mode 100644 Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt delete mode 100644 Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt delete mode 100644 Zend/tests/attributes/class_alias/target_validation.phpt delete mode 100644 Zend/tests/attributes/class_alias/working_example.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/repeatable-internal.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/repeatable-userland.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt delete mode 100644 Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt delete mode 100644 Zend/tests/attributes/deprecated/class_alias/access_constant.phpt delete mode 100644 Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt delete mode 100644 Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt delete mode 100644 Zend/tests/attributes/deprecated/class_alias/messages.phpt delete mode 100644 Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt delete mode 100644 ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt delete mode 100644 ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt delete mode 100644 ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt delete mode 100644 ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 4b9980748d78f..92c48dc3d807b 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -776,9 +776,6 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename) { zend_class_entry *ce; Z_CE_FROM_ZVAL_P(ce, ce_zv); - if (Z_TYPE_P(ce_zv) == IS_ALIAS_PTR) { - return true; - } if (ce->ce_flags & ZEND_ACC_PRELOADED) { Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val)); @@ -787,13 +784,6 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename) return false; } } - // Ignore deprecated aliases so that they are fetched at runtime - if (Z_TYPE_P(ce_zv) == IS_ALIAS_PTR) { - zend_class_alias *alias = Z_CLASS_ALIAS_P(ce_zv); - if (alias->alias_flags & ZEND_ACC_DEPRECATED) { - return true; - } - } return ce->type == ZEND_USER_CLASS && (!ce->info.user.filename || ce->info.user.filename != filename); } @@ -827,14 +817,7 @@ zend_class_entry *zend_optimizer_get_class_entry( return Z_PTR_P(ce_or_alias); } ZEND_ASSERT(Z_TYPE_P(ce_or_alias) == IS_ALIAS_PTR); - zend_class_alias *alias = Z_CLASS_ALIAS_P(ce_or_alias); - if (alias->alias_flags & ZEND_ACC_DEPRECATED) { - // Pretend that the class cannot be found so that it gets looked - // up at runtime - return NULL; - } - return NULL; - // return Z_CLASS_ALIAS_P(ce_or_alias)->ce; + return Z_CLASS_ALIAS_P(ce_or_alias)->ce; } zval *ce_zv = zend_hash_find(CG(class_table), lcname); @@ -843,8 +826,7 @@ zend_class_entry *zend_optimizer_get_class_entry( return Z_PTR_P(ce_zv); } ZEND_ASSERT(Z_TYPE_P(ce_zv) == IS_ALIAS_PTR); - return NULL; - // return Z_CLASS_ALIAS_P(ce_zv)->ce; + return Z_CLASS_ALIAS_P(ce_zv)->ce; } if (op_array && op_array->scope && zend_string_equals_ci(op_array->scope->name, lcname)) { diff --git a/Zend/tests/attributes/034_target_values.phpt b/Zend/tests/attributes/034_target_values.phpt index 2a06d39ebf05e..e56c0c285fbd6 100644 --- a/Zend/tests/attributes/034_target_values.phpt +++ b/Zend/tests/attributes/034_target_values.phpt @@ -16,26 +16,24 @@ showFlag("TARGET_PROPERTY", Attribute::TARGET_PROPERTY); showFlag("TARGET_CLASS_CONSTANT", Attribute::TARGET_CLASS_CONSTANT); showFlag("TARGET_PARAMETER", Attribute::TARGET_PARAMETER); showFlag("TARGET_CONSTANT", Attribute::TARGET_CONSTANT); -showFlag("TARGET_CLASS_ALIAS", Attribute::TARGET_CLASS_ALIAS); showFlag("IS_REPEATABLE", Attribute::IS_REPEATABLE); $all = Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY | Attribute::TARGET_CLASS_CONSTANT | Attribute::TARGET_PARAMETER - | Attribute::TARGET_CONSTANT | Attribute::TARGET_CLASS_ALIAS; + | Attribute::TARGET_CONSTANT; var_dump($all, Attribute::TARGET_ALL, $all === Attribute::TARGET_ALL); ?> --EXPECT-- -Attribute::TARGET_CLASS = 1 (255 & 1 === 1) -Attribute::TARGET_FUNCTION = 2 (255 & 2 === 2) -Attribute::TARGET_METHOD = 4 (255 & 4 === 4) -Attribute::TARGET_PROPERTY = 8 (255 & 8 === 8) -Attribute::TARGET_CLASS_CONSTANT = 16 (255 & 16 === 16) -Attribute::TARGET_PARAMETER = 32 (255 & 32 === 32) -Attribute::TARGET_CONSTANT = 64 (255 & 64 === 64) -Attribute::TARGET_CLASS_ALIAS = 128 (255 & 128 === 128) -Attribute::IS_REPEATABLE = 256 (255 & 256 === 0) -int(255) -int(255) +Attribute::TARGET_CLASS = 1 (127 & 1 === 1) +Attribute::TARGET_FUNCTION = 2 (127 & 2 === 2) +Attribute::TARGET_METHOD = 4 (127 & 4 === 4) +Attribute::TARGET_PROPERTY = 8 (127 & 8 === 8) +Attribute::TARGET_CLASS_CONSTANT = 16 (127 & 16 === 16) +Attribute::TARGET_PARAMETER = 32 (127 & 32 === 32) +Attribute::TARGET_CONSTANT = 64 (127 & 64 === 64) +Attribute::IS_REPEATABLE = 128 (127 & 128 === 0) +int(127) +int(127) bool(true) diff --git a/Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt b/Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt deleted file mode 100644 index 4da3ef4c723a7..0000000000000 --- a/Zend/tests/attributes/class_alias/attributes_array_contains_non-object.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#[ClassAlias] - attributes parameter validated at compile time (non-object in array) ---FILE-- -getAttributes()[0]; -var_dump( $attrib ); - -$attrib->newInstance(); - -?> ---EXPECTF-- -Fatal error: Attribute must be declared with `new` in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_array_non-object.phpt b/Zend/tests/attributes/class_alias/attributes_array_non-object.phpt deleted file mode 100644 index 12ecda3574ca0..0000000000000 --- a/Zend/tests/attributes/class_alias/attributes_array_non-object.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -#[ClassAlias] - attributes parameter validated at compile time (array can be evaluated) ---FILE-- -getAttributes()[0]; -var_dump( $attrib ); - -$attrib->newInstance(); - -?> ---EXPECTF-- -Fatal error: ClassAlias::__construct(): Argument #2 ($attributes) must be an array of objects in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_dict.phpt b/Zend/tests/attributes/class_alias/attributes_dict.phpt deleted file mode 100644 index 7d582370e3c90..0000000000000 --- a/Zend/tests/attributes/class_alias/attributes_dict.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -Alias attributes must not be associative ---FILE-- - new Deprecated()])] -class Demo {} - -class_alias( 'Demo', 'Other2' ); - -$attr = new ReflectionClass( Demo::class )->getAttributes()[0]; -$attr->newInstance(); - -?> ---EXPECTF-- -Fatal error: Uncaught Error: ClassAlias::__construct(): Argument #2 ($attributes) must be a list, not an associative array in %s:%d -Stack trace: -#0 %s(%d): ClassAlias->__construct('Other', Array) -#1 %s(%d): ReflectionAttribute->newInstance() -#2 {main} - thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_nonarray.phpt b/Zend/tests/attributes/class_alias/attributes_nonarray.phpt deleted file mode 100644 index a0513c9fc7a8c..0000000000000 --- a/Zend/tests/attributes/class_alias/attributes_nonarray.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -Alias attributes must be an array ---FILE-- -getAttributes()[0]; -$attr->newInstance(); - -?> ---EXPECTF-- -Fatal error: ClassAlias::__construct(): Argument #2 ($attributes) must be of type array, true given in %s on line %d diff --git a/Zend/tests/attributes/class_alias/attributes_valid.phpt b/Zend/tests/attributes/class_alias/attributes_valid.phpt deleted file mode 100644 index 8b790c57ae062..0000000000000 --- a/Zend/tests/attributes/class_alias/attributes_valid.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -Alias attributes do not need to exist for declaration ---FILE-- - ---EXPECT-- -Done diff --git a/Zend/tests/attributes/class_alias/name_invalid.phpt b/Zend/tests/attributes/class_alias/name_invalid.phpt deleted file mode 100644 index 028c31414b41e..0000000000000 --- a/Zend/tests/attributes/class_alias/name_invalid.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Alias name must be valid ---FILE-- - ---EXPECTF-- -Fatal error: Cannot use "never" as a class alias as it is reserved in %s on line %d diff --git a/Zend/tests/attributes/class_alias/name_nonstring.phpt b/Zend/tests/attributes/class_alias/name_nonstring.phpt deleted file mode 100644 index 6be25a38e61b9..0000000000000 --- a/Zend/tests/attributes/class_alias/name_nonstring.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Parameter must be a string ---FILE-- - ---EXPECTF-- -Fatal error: ClassAlias::__construct(): Argument #1 ($alias) must be of type string, array given in %s on line %d diff --git a/Zend/tests/attributes/class_alias/name_required.phpt b/Zend/tests/attributes/class_alias/name_required.phpt deleted file mode 100644 index 2f907925adc4f..0000000000000 --- a/Zend/tests/attributes/class_alias/name_required.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Parameter is required ---FILE-- - ---EXPECTF-- -Fatal error: Uncaught ArgumentCountError: ClassAlias::__construct() expects at least 1 argument, 0 given in %s:%d -Stack trace: -#0 %s(%d): ClassAlias->__construct() -#1 {main} - thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias/redeclaration_error.phpt b/Zend/tests/attributes/class_alias/redeclaration_error.phpt deleted file mode 100644 index 642d08760a645..0000000000000 --- a/Zend/tests/attributes/class_alias/redeclaration_error.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Cannot redeclare an existing class ---FILE-- - ---EXPECTF-- -Fatal error: Unable to declare alias 'Attribute' for 'Demo' in %s on line %d diff --git a/Zend/tests/attributes/class_alias/repeated_attribute.phpt b/Zend/tests/attributes/class_alias/repeated_attribute.phpt deleted file mode 100644 index f2b6fa1617868..0000000000000 --- a/Zend/tests/attributes/class_alias/repeated_attribute.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -Attribute can be repeated ---FILE-- - ---EXPECTF-- -bool(true) -bool(true) -object(Demo)#%d (0) { -} -object(Demo)#%d (0) { -} diff --git a/Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt b/Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt deleted file mode 100644 index 4f1e2412b66ea..0000000000000 --- a/Zend/tests/attributes/class_alias/runtime_attributes_validation_missing.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -#[ClassAlias] - attributes parameter validated at runtime (missing class) ---FILE-- -getAttributes()[0]; -var_dump( $attrib ); - -$attrib->newInstance(); - -?> ---EXPECTF-- -object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(10) "ClassAlias" -} - -Fatal error: Uncaught Error: Class "MissingAttribute" not found in %s:%d -Stack trace: -#0 %s(%d): ReflectionAttribute->newInstance() -#1 {main} - thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt b/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt deleted file mode 100644 index b95f9929a4754..0000000000000 --- a/Zend/tests/attributes/class_alias/runtime_attributes_validation_non-attribute.phpt +++ /dev/null @@ -1,32 +0,0 @@ ---TEST-- -#[ClassAlias] - attributes parameter value types not validated when constructing ClassAlias ---FILE-- -getAttributes()[0]; -var_dump( $attrib ); - -var_dump( $attrib->newInstance() ); - -?> ---EXPECTF-- -object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(10) "ClassAlias" -} -object(ClassAlias)#%d (2) { - ["alias"]=> - string(5) "Other" - ["attributes"]=> - array(1) { - [0]=> - object(NonAttribute)#%d (0) { - } - } -} diff --git a/Zend/tests/attributes/class_alias/target_validation.phpt b/Zend/tests/attributes/class_alias/target_validation.phpt deleted file mode 100644 index 4dbf6fadc6ce1..0000000000000 --- a/Zend/tests/attributes/class_alias/target_validation.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Can only be used on classes ---FILE-- - ---EXPECTF-- -Fatal error: Attribute "ClassAlias" cannot target function (allowed targets: class) in %s on line %d diff --git a/Zend/tests/attributes/class_alias/working_example.phpt b/Zend/tests/attributes/class_alias/working_example.phpt deleted file mode 100644 index ea1ae4476f395..0000000000000 --- a/Zend/tests/attributes/class_alias/working_example.phpt +++ /dev/null @@ -1,17 +0,0 @@ ---TEST-- -Working usage ---FILE-- - ---EXPECTF-- -bool(true) -object(Demo)#%d (0) { -} diff --git a/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt b/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt deleted file mode 100644 index 4f3bbb6fcb0af..0000000000000 --- a/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-internal.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Class alias listed in valid targets when used wrong (internal attribute) ---FILE-- - ---EXPECTF-- -Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant, class alias) in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt b/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt deleted file mode 100644 index cccedd532a2ab..0000000000000 --- a/Zend/tests/attributes/class_alias_target/class_alias_listed_as_target-userland.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -Class alias listed in valid targets when used wrong (userland attribute) ---FILE-- -getAttributes(); -var_dump($attribs); -$attribs[0]->newInstance(); - -?> ---EXPECTF-- -array(1) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(16) "MyAliasAttribute" - } -} - -Fatal error: Uncaught Error: Attribute "MyAliasAttribute" cannot target class (allowed targets: class alias) in %s:%d -Stack trace: -#0 %s(%d): ReflectionAttribute->newInstance() -#1 {main} - thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt b/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt deleted file mode 100644 index 2e741bea2208a..0000000000000 --- a/Zend/tests/attributes/class_alias_target/must_target_class_alias-internal.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Error when attribute does not target class alias (internal attribute) ---FILE-- - ---EXPECTF-- -Fatal error: Attribute "Override" cannot target class alias (allowed targets: method) in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt b/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt deleted file mode 100644 index a6abd649bd15e..0000000000000 --- a/Zend/tests/attributes/class_alias_target/must_target_class_alias-userland.phpt +++ /dev/null @@ -1,31 +0,0 @@ ---TEST-- -Error when attribute does not target class alias (useland attribute) ---FILE-- -getAttributes(); -var_dump($attribs); -$attribs[0]->newInstance(); - -?> ---EXPECTF-- -array(1) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(19) "MyFunctionAttribute" - } -} - -Fatal error: Uncaught Error: Attribute "MyFunctionAttribute" cannot target class alias (allowed targets: function) in %s:%d -Stack trace: -#0 %s(%d): ReflectionAttribute->newInstance() -#1 {main} - thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt b/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt deleted file mode 100644 index ce2abec6d3aec..0000000000000 --- a/Zend/tests/attributes/class_alias_target/not_repeatable-internal.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Validation of attribute repetition (not allowed; internal attribute) ---FILE-- - ---EXPECTF-- -Fatal error: Attribute "Deprecated" must not be repeated in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt b/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt deleted file mode 100644 index 5fdbbd8c687b4..0000000000000 --- a/Zend/tests/attributes/class_alias_target/not_repeatable-userland.phpt +++ /dev/null @@ -1,35 +0,0 @@ ---TEST-- -Validation of attribute repetition (not allowed; userland attribute) ---FILE-- -getAttributes(); -var_dump($attributes); -$attributes[0]->newInstance(); - -?> ---EXPECTF-- -array(2) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(11) "MyAttribute" - } - [1]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(11) "MyAttribute" - } -} - -Fatal error: Uncaught Error: Attribute "MyAttribute" must not be repeated in %s:%d -Stack trace: -#0 %s(%d): ReflectionAttribute->newInstance() -#1 {main} - thrown in %s on line %d diff --git a/Zend/tests/attributes/class_alias_target/repeatable-internal.phpt b/Zend/tests/attributes/class_alias_target/repeatable-internal.phpt deleted file mode 100644 index fe8b1b805952a..0000000000000 --- a/Zend/tests/attributes/class_alias_target/repeatable-internal.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -Validation of attribute repetition (is allowed; internal attribute) ---EXTENSIONS-- -zend_test ---FILE-- - ---EXPECT-- -Done diff --git a/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt b/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt deleted file mode 100644 index 305af9358e29e..0000000000000 --- a/Zend/tests/attributes/class_alias_target/repeatable-userland.phpt +++ /dev/null @@ -1,29 +0,0 @@ ---TEST-- -Validation of attribute repetition (is allowed; userland attribute) ---FILE-- -getAttributes(); -var_dump($attributes); -$attributes[0]->newInstance(); - -?> ---EXPECTF-- -array(2) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(11) "MyAttribute" - } - [1]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(11) "MyAttribute" - } -} diff --git a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt deleted file mode 100644 index 00c30aec2d5dd..0000000000000 --- a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-default.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -Attributes with TARGET_ALL (from the default) can target class aliases ---FILE-- -getAttributes(); -var_dump($attribs); -$attribs[0]->newInstance(); - -?> ---EXPECTF-- -array(1) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(11) "MyAttribute" - } -} diff --git a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt b/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt deleted file mode 100644 index 7b11a76aef861..0000000000000 --- a/Zend/tests/attributes/class_alias_target/target_all_targets_class_alias-explicit.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -Attributes with TARGET_ALL (from an explicit parameter) can target class aliases ---FILE-- -getAttributes(); -var_dump($attribs); -$attribs[0]->newInstance(); - -?> ---EXPECTF-- -array(1) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(11) "MyAttribute" - } -} diff --git a/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt b/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt index e39653a00f078..b0b88c2f6edab 100644 --- a/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt +++ b/Zend/tests/attributes/constants/constant_listed_as_target-internal.phpt @@ -8,4 +8,4 @@ class Example {} ?> --EXPECTF-- -Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant, class alias) in %s on line %d +Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant) in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt b/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt deleted file mode 100644 index 8b3885d964204..0000000000000 --- a/Zend/tests/attributes/deprecated/class_alias/access_constant.phpt +++ /dev/null @@ -1,16 +0,0 @@ ---TEST-- -#[\Deprecated]: Class alias - access a constant ---FILE-- - ---EXPECTF-- -Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d -int(1) diff --git a/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt b/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt deleted file mode 100644 index 530c92125a9bf..0000000000000 --- a/Zend/tests/attributes/deprecated/class_alias/access_static_prop.phpt +++ /dev/null @@ -1,15 +0,0 @@ ---TEST-- -#[\Deprecated]: Class alias - access a static variable ---FILE-- - ---EXPECTF-- -Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt b/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt deleted file mode 100644 index bbaa89215bc8c..0000000000000 --- a/Zend/tests/attributes/deprecated/class_alias/call_static_method.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -#[\Deprecated]: Class alias - call a static method ---FILE-- - ---EXPECTF-- -Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d -Clazz::doNothing diff --git a/Zend/tests/attributes/deprecated/class_alias/messages.phpt b/Zend/tests/attributes/deprecated/class_alias/messages.phpt deleted file mode 100644 index 08ff0fdda74e7..0000000000000 --- a/Zend/tests/attributes/deprecated/class_alias/messages.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -#[\Deprecated]: Class alias - all messages have details ---FILE-- - ---EXPECTF-- -Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d -int(1) - -Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d - -Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d - -Deprecated: Alias MyAlias for class Clazz is deprecated since 8.4, don't use the alias in %s on line %d diff --git a/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt b/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt deleted file mode 100644 index 1e09c3a0b7cfd..0000000000000 --- a/Zend/tests/attributes/deprecated/class_alias/use_constructor.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -#[\Deprecated]: Class alias - use the constructor ---FILE-- - ---EXPECTF-- -Deprecated: Alias MyAlias for class Clazz is deprecated in %s on line %d -Clazz::__construct diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 38af53c2a4427..09d9e69f5a55b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3603,9 +3603,6 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ */ zend_class_alias *alias = zend_class_alias_init(ce); - zend_string *original_name = zend_string_init(name, name_len, persistent); - alias->name = original_name; - ZVAL_ALIAS_PTR(&zv, alias); ret = zend_hash_add(CG(class_table), lcname, &zv); @@ -3618,7 +3615,6 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ return SUCCESS; } - zend_string_release(original_name); free(alias); return FAILURE; } diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index c49dc3c3ff56e..c3633801be83e 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -23,7 +23,6 @@ #include "zend_attributes_arginfo.h" #include "zend_exceptions.h" #include "zend_smart_str.h" -#include "zend_class_alias.h" ZEND_API zend_class_entry *zend_ce_attribute; ZEND_API zend_class_entry *zend_ce_return_type_will_change_attribute; @@ -33,128 +32,11 @@ ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; ZEND_API zend_class_entry *zend_ce_override; ZEND_API zend_class_entry *zend_ce_deprecated; ZEND_API zend_class_entry *zend_ce_nodiscard; -ZEND_API zend_class_entry *zend_ce_class_alias; static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; static HashTable internal_attributes; -// zend_compile.c not interface -zend_string *zend_resolve_class_name_ast(zend_ast *ast); - -// Based on zend_compile.c but not part of the interface -void compile_alias_attributes( - HashTable **attributes, zend_ast *ast -) /* {{{ */ { - zend_attribute *attr; - - zend_ast_list *list = zend_ast_get_list(ast); - - ZEND_ASSERT(ast->kind == ZEND_AST_ARRAY); - - for (uint32_t elem_idx = 0; elem_idx < list->children; elem_idx++) { - zend_ast *array_elem = list->child[elem_idx]; - ZEND_ASSERT(array_elem->kind == ZEND_AST_ARRAY_ELEM); - - zend_ast *array_content = array_elem->child[0]; - if (array_content->kind != ZEND_AST_NEW) { - zend_error_noreturn(E_COMPILE_ERROR, - "Attribute must be declared with `new`"); - } - - if (array_content->child[1] && - array_content->child[1]->kind == ZEND_AST_CALLABLE_CONVERT) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot create Closure as attribute argument"); - } - - zend_string *name = zend_resolve_class_name_ast(array_content->child[0]); - zend_ast_list *args = array_content->child[1] ? zend_ast_get_list(array_content->child[1]) : NULL; - - uint32_t flags = (CG(active_op_array)->fn_flags & ZEND_ACC_STRICT_TYPES) - ? ZEND_ATTRIBUTE_STRICT_TYPES : 0; - attr = zend_add_attribute( - attributes, name, args ? args->children : 0, flags, 0, array_content->lineno); - zend_string_release(name); - - /* Populate arguments */ - if (args) { - zend_attribute_populate_arguments(attr, args); - } - } - - if (*attributes != NULL) { - zend_apply_internal_attribute_validation(*attributes, 0, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS); - } -} -/* }}} */ - -ZEND_API void zend_apply_internal_attribute_validation(HashTable *attributes, uint32_t offset, uint32_t target) { - zend_attribute *attr; - zend_internal_attribute *config; - - /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ - ZEND_HASH_PACKED_FOREACH_PTR(attributes, attr) { - if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) { - continue; - } - - if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { - zend_string *location = zend_get_attribute_target_names(target); - zend_string *allowed = zend_get_attribute_target_names(config->flags); - - zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", - ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) - ); - } - - if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { - if (zend_is_attribute_repeated(attributes, attr)) { - zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); - } - } - - if (config->validator != NULL) { - config->validator(attr, target, CG(active_class_entry)); - } - } ZEND_HASH_FOREACH_END(); -} - -ZEND_API void zend_attribute_populate_arguments(zend_attribute *attr, zend_ast_list *args) { - ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); - - bool uses_named_args = 0; - for (uint32_t j = 0; j < args->children; j++) { - zend_ast **arg_ast_ptr = &args->child[j]; - zend_ast *arg_ast = *arg_ast_ptr; - - if (arg_ast->kind == ZEND_AST_UNPACK) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use unpacking in attribute argument list"); - } - - if (arg_ast->kind == ZEND_AST_NAMED_ARG) { - attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0])); - arg_ast_ptr = &arg_ast->child[1]; - uses_named_args = 1; - - for (uint32_t k = 0; k < j; k++) { - if (attr->args[k].name && - zend_string_equals(attr->args[k].name, attr->args[j].name)) { - zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s", - ZSTR_VAL(attr->args[j].name)); - } - } - } else if (uses_named_args) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot use positional argument after named argument"); - } - - zend_const_expr_to_zval( - &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); - } -} - uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope) { // TODO: More proper signature validation: Too many args, incorrect arg names. @@ -187,41 +69,6 @@ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_ent return ZEND_ATTRIBUTE_TARGET_ALL; } -static zend_execute_data *setup_dummy_call_frame(zend_string *filename, zend_attribute *attribute_data) { - /* Set up dummy call frame that makes it look like the attribute was invoked - * from where it occurs in the code. */ - zend_function dummy_func; - zend_op *opline; - - memset(&dummy_func, 0, sizeof(zend_function)); - - zend_execute_data *call = zend_vm_stack_push_call_frame_ex( - ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) + - ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) + - ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)), - 0, &dummy_func, 0, NULL); - - opline = (zend_op*)(call + 1); - memset(opline, 0, sizeof(zend_op)); - opline->opcode = ZEND_DO_FCALL; - opline->lineno = attribute_data->lineno; - - call->opline = opline; - call->call = NULL; - call->return_value = NULL; - call->func = (zend_function*)(call->opline + 1); - call->prev_execute_data = EG(current_execute_data); - - memset(call->func, 0, sizeof(zend_function)); - call->func->type = ZEND_USER_FUNCTION; - call->func->op_array.fn_flags = - attribute_data->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0; - call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE; - call->func->op_array.filename = filename; - - return call; -} - static void validate_allow_dynamic_properties( zend_attribute *attr, uint32_t target, zend_class_entry *scope) { @@ -248,158 +95,6 @@ static void validate_allow_dynamic_properties( scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES; } -static void validate_class_alias( - zend_attribute *attr, uint32_t target, zend_class_entry *scope) -{ - // Do NOT construct the attribute yet, that would require any of the - // attributes that are used to exist; at this point, access the alias name - // based on the arguments, and do our own validation - zend_execute_data *call = setup_dummy_call_frame(scope->info.user.filename, attr); - EG(current_execute_data) = call; - - zend_execute_data *constructor_call = zend_vm_stack_push_call_frame( - ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC | ZEND_CALL_HAS_THIS, - zend_ce_class_alias->constructor, - attr->argc, - scope - ); - constructor_call->prev_execute_data = EG(current_execute_data); - EG(current_execute_data) = constructor_call; - - if (attr->argc < 1 || attr->argc > 2) { - zend_wrong_parameters_count_error(1, 2); - - goto restore_execution_data; - } - - zval *found = NULL; - - // Looking for either the first parameter, or if the first parameter - // is named, the 'alias' parameter - if (attr->args[0].name == NULL) { - found = &( attr->args[0].value ); - } else { - for (uint32_t i = 0; i < attr->argc; i++) { - if (zend_string_equals_literal( attr->args[i].name, "alias")) { - found = &( attr->args[i].value ); - break; - } - } - if (found == NULL) { - zend_argument_error(zend_ce_argument_count_error, 1, "not passed"); - goto restore_execution_data; - } - } - - zend_string *alias; - if (UNEXPECTED(!zend_parse_arg_str(found, &alias, false, 0))) { - zend_wrong_parameter_error( - ZPP_ERROR_WRONG_ARG, - 1, - "alias", - Z_EXPECTED_STRING, - found - ); - goto restore_execution_data; - } - - // if (CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION) { - // // Opcache, don't register the alias - // goto restore_execution_data; - // } - - zend_result result = zend_register_class_alias_ex( - ZSTR_VAL(alias), - ZSTR_LEN(alias), - scope, - false - ); - if (result == FAILURE) { - zend_error_noreturn(E_ERROR, "Unable to declare alias '%s' for '%s'", - ZSTR_VAL(alias), - ZSTR_VAL(scope->name) - ); - goto restore_execution_data; - } - - zend_string *lc_name; - if (ZSTR_VAL(alias)[0] == '\\') { - lc_name = zend_string_alloc(ZSTR_LEN(alias) - 1, 0); - zend_str_tolower_copy(ZSTR_VAL(lc_name), ZSTR_VAL(alias) + 1, ZSTR_LEN(alias) - 1); - } else { - lc_name = zend_string_tolower(alias); - } - - zval *entry = zend_hash_find(EG(class_table), lc_name); - zend_string_release_ex(lc_name, /* persistent */ false); - ZEND_ASSERT(entry != NULL); - ZEND_ASSERT(Z_TYPE_P(entry) == IS_ALIAS_PTR); - - zend_class_alias *alias_obj = Z_CLASS_ALIAS_P(entry); - - // Compile attributes - if (attr->argc == 2) { - zval *nested_attribs = NULL; - if (attr->args[0].name == NULL && attr->args[1].name == NULL) { - nested_attribs = &( attr->args[1].value ); - } else { - for (uint32_t i = 0; i < attr->argc; i++) { - if ( attr->args[i].name == NULL ) { - continue; - } - if (zend_string_equals_literal( attr->args[i].name, "alias")) { - continue; - } - if (zend_string_equals_literal( attr->args[i].name, "attributes")) { - nested_attribs = &( attr->args[i].value ); - break; - } - zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(attr->args[i].name)); - goto restore_execution_data; - } - } - ZEND_ASSERT(nested_attribs != NULL); - if (UNEXPECTED(!Z_OPT_CONSTANT_P(nested_attribs))) { - // If it is an array, then it must be an array that can be evaluated - // already - if (Z_TYPE_P(nested_attribs) == IS_ARRAY) { - zend_argument_type_error( - 2, - "must be an array of objects" - ); - } else { - zend_wrong_parameter_error( - ZPP_ERROR_WRONG_ARG, - 2, - "attributes", - Z_EXPECTED_ARRAY, - nested_attribs - ); - // Something with an invalid parameter - } - } else { - zend_ast *attributes_ast = Z_ASTVAL_P(nested_attribs); - compile_alias_attributes(&( alias_obj->attributes), attributes_ast); - - zend_attribute *deprecated_attribute = zend_get_attribute_str( - alias_obj->attributes, - "deprecated", - strlen("deprecated") - ); - - if (deprecated_attribute) { - alias_obj->alias_flags |= ZEND_ACC_DEPRECATED; - } - } - } - -restore_execution_data: - EG(current_execute_data) = constructor_call->prev_execute_data; - zend_vm_stack_free_call_frame(constructor_call); - EG(current_execute_data) = call->prev_execute_data; - zend_vm_stack_free_call_frame(call); -} - ZEND_METHOD(Attribute, __construct) { zend_long flags = ZEND_ATTRIBUTE_TARGET_ALL; @@ -522,42 +217,6 @@ ZEND_METHOD(NoDiscard, __construct) } } -ZEND_METHOD(ClassAlias, __construct) -{ - zend_string *alias = NULL; - HashTable *attributes = NULL; - - ZEND_PARSE_PARAMETERS_START(1, 2) - Z_PARAM_STR(alias) - Z_PARAM_OPTIONAL - Z_PARAM_ARRAY_HT(attributes) - ZEND_PARSE_PARAMETERS_END(); - - zval value; - ZVAL_STR(&value, alias); - zend_update_property(zend_ce_class_alias, Z_OBJ_P(ZEND_THIS), ZEND_STRL("alias"), &value); - - /* The assignment might fail due to 'readonly'. */ - if (UNEXPECTED(EG(exception))) { - RETURN_THROWS(); - } - - if (attributes == NULL || zend_hash_num_elements(attributes) == 0) { - ZVAL_EMPTY_ARRAY(&value); - zend_update_property(zend_ce_class_alias, Z_OBJ_P(ZEND_THIS), ZEND_STRL("attributes"), &value); - return; - } - - if (!zend_array_is_list(attributes)) { - zend_throw_error(NULL, - "ClassAlias::__construct(): Argument #2 ($attributes) must be a list, not an associative array" - ); - RETURN_THROWS(); - } - ZVAL_ARR(&value, attributes); - zend_update_property(zend_ce_class_alias, Z_OBJ_P(ZEND_THIS), ZEND_STRL("attributes"), &value); -} - static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) { if (attributes) { @@ -631,7 +290,37 @@ ZEND_API zend_result zend_get_attribute_object(zval *obj, zend_class_entry *attr zend_execute_data *call = NULL; if (filename) { - call = setup_dummy_call_frame(filename, attribute_data); + /* Set up dummy call frame that makes it look like the attribute was invoked + * from where it occurs in the code. */ + zend_function dummy_func; + zend_op *opline; + + memset(&dummy_func, 0, sizeof(zend_function)); + + call = zend_vm_stack_push_call_frame_ex( + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_execute_data), sizeof(zval)) + + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_op), sizeof(zval)) + + ZEND_MM_ALIGNED_SIZE_EX(sizeof(zend_function), sizeof(zval)), + 0, &dummy_func, 0, NULL); + + opline = (zend_op*)(call + 1); + memset(opline, 0, sizeof(zend_op)); + opline->opcode = ZEND_DO_FCALL; + opline->lineno = attribute_data->lineno; + + call->opline = opline; + call->call = NULL; + call->return_value = NULL; + call->func = (zend_function*)(call->opline + 1); + call->prev_execute_data = EG(current_execute_data); + + memset(call->func, 0, sizeof(zend_function)); + call->func->type = ZEND_USER_FUNCTION; + call->func->op_array.fn_flags = + attribute_data->flags & ZEND_ATTRIBUTE_STRICT_TYPES ? ZEND_ACC_STRICT_TYPES : 0; + call->func->op_array.fn_flags |= ZEND_ACC_CALL_VIA_TRAMPOLINE; + call->func->op_array.filename = filename; + EG(current_execute_data) = call; } @@ -690,8 +379,7 @@ static const char *target_names[] = { "property", "class constant", "parameter", - "constant", - "class alias" + "constant" }; ZEND_API zend_string *zend_get_attribute_target_names(uint32_t flags) @@ -860,10 +548,6 @@ void zend_register_attribute_ce(void) zend_ce_nodiscard = register_class_NoDiscard(); attr = zend_mark_internal_attribute(zend_ce_nodiscard); - - zend_ce_class_alias = register_class_ClassAlias(); - attr = zend_mark_internal_attribute(zend_ce_class_alias); - attr->validator = validate_class_alias; } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index eaa14b758cadc..a4d6b28c0094a 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -30,10 +30,9 @@ #define ZEND_ATTRIBUTE_TARGET_CLASS_CONST (1<<4) #define ZEND_ATTRIBUTE_TARGET_PARAMETER (1<<5) #define ZEND_ATTRIBUTE_TARGET_CONST (1<<6) -#define ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS (1<<7) -#define ZEND_ATTRIBUTE_TARGET_ALL ((1<<8) - 1) -#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<8) -#define ZEND_ATTRIBUTE_FLAGS ((1<<9) - 1) +#define ZEND_ATTRIBUTE_TARGET_ALL ((1<<7) - 1) +#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<7) +#define ZEND_ATTRIBUTE_FLAGS ((1<<8) - 1) /* Flags for zend_attribute.flags */ #define ZEND_ATTRIBUTE_PERSISTENT (1<<0) @@ -96,9 +95,6 @@ ZEND_API zend_attribute *zend_add_attribute( uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_entry *scope); -ZEND_API void zend_apply_internal_attribute_validation(HashTable *attributes, uint32_t offset, uint32_t target); -ZEND_API void zend_attribute_populate_arguments(zend_attribute *attr, zend_ast_list *args); - END_EXTERN_C() static zend_always_inline zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc) diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index d217bad206f6d..fe70de83e4d21 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -19,8 +19,6 @@ final class Attribute const int TARGET_PARAMETER = UNKNOWN; /** @cvalue ZEND_ATTRIBUTE_TARGET_CONST */ const int TARGET_CONSTANT = UNKNOWN; - /** @cvalue ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS */ - const int TARGET_CLASS_ALIAS = UNKNOWN; /** @cvalue ZEND_ATTRIBUTE_TARGET_ALL */ const int TARGET_ALL = UNKNOWN; /** @cvalue ZEND_ATTRIBUTE_IS_REPEATABLE */ @@ -79,7 +77,7 @@ public function __construct() {} /** * @strict-properties */ -#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT|Attribute::TARGET_CLASS_ALIAS)] +#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION|Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_CONSTANT)] final class Deprecated { public readonly ?string $message; @@ -99,16 +97,3 @@ final class NoDiscard public function __construct(?string $message = null) {} } - -/** - * @strict-properties - */ -#[Attribute(Attribute::TARGET_CLASS|Attribute::IS_REPEATABLE)] -final class ClassAlias -{ - public readonly string $alias; - - public readonly array $attributes; - - public function __construct(string $alias, array $attributes = []) {} -} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index 6b6f3a1aafc87..14afe40c01adf 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 0114c2f6665c4e10e01d721481aa875e5a092900 */ + * Stub hash: 9aee3d8f2ced376f5929048444eaa2529ff90311 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -33,11 +33,6 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NoDiscard___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 1, "null") ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ClassAlias___construct, 0, 0, 1) - ZEND_ARG_TYPE_INFO(0, alias, IS_STRING, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, attributes, IS_ARRAY, 0, "[]") -ZEND_END_ARG_INFO() - ZEND_METHOD(Attribute, __construct); ZEND_METHOD(ReturnTypeWillChange, __construct); ZEND_METHOD(AllowDynamicProperties, __construct); @@ -48,7 +43,6 @@ ZEND_METHOD(SensitiveParameterValue, __debugInfo); ZEND_METHOD(Override, __construct); ZEND_METHOD(Deprecated, __construct); ZEND_METHOD(NoDiscard, __construct); -ZEND_METHOD(ClassAlias, __construct); static const zend_function_entry class_Attribute_methods[] = { ZEND_ME(Attribute, __construct, arginfo_class_Attribute___construct, ZEND_ACC_PUBLIC) @@ -92,11 +86,6 @@ static const zend_function_entry class_NoDiscard_methods[] = { ZEND_FE_END }; -static const zend_function_entry class_ClassAlias_methods[] = { - ZEND_ME(ClassAlias, __construct, arginfo_class_ClassAlias___construct, ZEND_ACC_PUBLIC) - ZEND_FE_END -}; - static zend_class_entry *register_class_Attribute(void) { zend_class_entry ce, *class_entry; @@ -146,12 +135,6 @@ static zend_class_entry *register_class_Attribute(void) zend_declare_typed_class_constant(class_entry, const_TARGET_CONSTANT_name, &const_TARGET_CONSTANT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_TARGET_CONSTANT_name); - zval const_TARGET_CLASS_ALIAS_value; - ZVAL_LONG(&const_TARGET_CLASS_ALIAS_value, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS); - zend_string *const_TARGET_CLASS_ALIAS_name = zend_string_init_interned("TARGET_CLASS_ALIAS", sizeof("TARGET_CLASS_ALIAS") - 1, 1); - zend_declare_typed_class_constant(class_entry, const_TARGET_CLASS_ALIAS_name, &const_TARGET_CLASS_ALIAS_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); - zend_string_release(const_TARGET_CLASS_ALIAS_name); - zval const_TARGET_ALL_value; ZVAL_LONG(&const_TARGET_ALL_value, ZEND_ATTRIBUTE_TARGET_ALL); zend_string *const_TARGET_ALL_name = zend_string_init_interned("TARGET_ALL", sizeof("TARGET_ALL") - 1, 1); @@ -281,7 +264,7 @@ static zend_class_entry *register_class_Deprecated(void) zend_attribute *attribute_Attribute_class_Deprecated_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_Deprecated_0, 1); zend_string_release(attribute_name_Attribute_class_Deprecated_0); zval attribute_Attribute_class_Deprecated_0_arg0; - ZVAL_LONG(&attribute_Attribute_class_Deprecated_0_arg0, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST | ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS); + ZVAL_LONG(&attribute_Attribute_class_Deprecated_0_arg0, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION | ZEND_ATTRIBUTE_TARGET_CLASS_CONST | ZEND_ATTRIBUTE_TARGET_CONST); ZVAL_COPY_VALUE(&attribute_Attribute_class_Deprecated_0->args[0].value, &attribute_Attribute_class_Deprecated_0_arg0); return class_entry; @@ -307,32 +290,3 @@ static zend_class_entry *register_class_NoDiscard(void) return class_entry; } - -static zend_class_entry *register_class_ClassAlias(void) -{ - zend_class_entry ce, *class_entry; - - INIT_CLASS_ENTRY(ce, "ClassAlias", class_ClassAlias_methods); - class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES); - - zval property_alias_default_value; - ZVAL_UNDEF(&property_alias_default_value); - zend_string *property_alias_name = zend_string_init("alias", sizeof("alias") - 1, 1); - zend_declare_typed_property(class_entry, property_alias_name, &property_alias_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); - zend_string_release(property_alias_name); - - zval property_attributes_default_value; - ZVAL_UNDEF(&property_attributes_default_value); - zend_string *property_attributes_name = zend_string_init("attributes", sizeof("attributes") - 1, 1); - zend_declare_typed_property(class_entry, property_attributes_name, &property_attributes_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); - zend_string_release(property_attributes_name); - - zend_string *attribute_name_Attribute_class_ClassAlias_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); - zend_attribute *attribute_Attribute_class_ClassAlias_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_ClassAlias_0, 1); - zend_string_release(attribute_name_Attribute_class_ClassAlias_0); - zval attribute_Attribute_class_ClassAlias_0_arg0; - ZVAL_LONG(&attribute_Attribute_class_ClassAlias_0_arg0, ZEND_ATTRIBUTE_TARGET_CLASS | ZEND_ATTRIBUTE_IS_REPEATABLE); - ZVAL_COPY_VALUE(&attribute_Attribute_class_ClassAlias_0->args[0].value, &attribute_Attribute_class_ClassAlias_0_arg0); - - return class_entry; -} diff --git a/Zend/zend_class_alias.c b/Zend/zend_class_alias.c index 7a2db3fe47b11..ddadcbce78582 100644 --- a/Zend/zend_class_alias.c +++ b/Zend/zend_class_alias.c @@ -17,8 +17,6 @@ */ #include "zend_class_alias.h" -#include "zend_errors.h" -#include "zend_string.h" #include "zend.h" zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { @@ -27,27 +25,7 @@ zend_class_alias * zend_class_alias_init(zend_class_entry *ce) { GC_SET_REFCOUNT(alias, 1); alias->ce = ce; - alias->name = NULL; - alias->attributes = NULL; alias->alias_flags = 0; return alias; } - -ZEND_COLD zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix); - -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_alias(const zend_class_alias *alias) { - zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); - - if (get_deprecation_suffix_from_attribute(alias->attributes, NULL, &message_suffix) == FAILURE) { - return; - } - - zend_error_unchecked(E_USER_DEPRECATED, "Alias %s for class %s is deprecated%S", - ZSTR_VAL(alias->name), - ZSTR_VAL(alias->ce->name), - message_suffix - ); - - zend_string_release(message_suffix); -} diff --git a/Zend/zend_class_alias.h b/Zend/zend_class_alias.h index 6ca62eb2fa940..e087332713ce0 100644 --- a/Zend/zend_class_alias.h +++ b/Zend/zend_class_alias.h @@ -24,8 +24,6 @@ struct _zend_class_alias { zend_refcounted_h gc; zend_class_entry *ce; - zend_string *name; - HashTable *attributes; uint32_t alias_flags; }; @@ -52,6 +50,5 @@ typedef struct _zend_class_alias zend_class_alias; zend_class_alias * zend_class_alias_init(zend_class_entry *ce); -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_alias(const zend_class_alias *alias); #endif diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a1f27eaaf5785..de1c28310d457 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1208,7 +1208,7 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* } /* }}} */ -zend_string *zend_resolve_class_name_ast(zend_ast *ast) /* {{{ */ +static zend_string *zend_resolve_class_name_ast(zend_ast *ast) /* {{{ */ { zval *class_name = zend_ast_get_zval(ast); if (Z_TYPE_P(class_name) != IS_STRING) { @@ -1882,17 +1882,7 @@ static bool zend_try_ct_eval_class_const(zval *zv, zend_string *class_name, zend if (ce_or_alias) { zend_class_entry *ce; - if (Z_TYPE_P(ce_or_alias) == IS_ALIAS_PTR) { - zend_class_alias *alias = Z_CLASS_ALIAS_P(ce_or_alias); - if (alias->alias_flags & ZEND_ACC_DEPRECATED) { - // Cannot evaluate at compile time - return 0; - } - ce = alias->ce; - } else { - ZEND_ASSERT(Z_TYPE_P(ce_or_alias) == IS_PTR); - ce = Z_PTR_P(ce_or_alias); - } + Z_CE_FROM_ZVAL_P(ce, ce_or_alias); cc = zend_hash_find_ptr(&ce->constants_table, name); } else { return 0; @@ -7407,7 +7397,7 @@ static void zend_compile_attributes( zend_internal_attribute *config; zend_ast_list *list = zend_ast_get_list(ast); - uint32_t g, i; + uint32_t g, i, j; ZEND_ASSERT(ast->kind == ZEND_AST_ATTRIBUTE_LIST); @@ -7450,13 +7440,68 @@ static void zend_compile_attributes( /* Populate arguments */ if (args) { - zend_attribute_populate_arguments(attr, args); + ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST); + + bool uses_named_args = 0; + for (j = 0; j < args->children; j++) { + zend_ast **arg_ast_ptr = &args->child[j]; + zend_ast *arg_ast = *arg_ast_ptr; + + if (arg_ast->kind == ZEND_AST_UNPACK) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use unpacking in attribute argument list"); + } + + if (arg_ast->kind == ZEND_AST_NAMED_ARG) { + attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0])); + arg_ast_ptr = &arg_ast->child[1]; + uses_named_args = 1; + + for (uint32_t k = 0; k < j; k++) { + if (attr->args[k].name && + zend_string_equals(attr->args[k].name, attr->args[j].name)) { + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s", + ZSTR_VAL(attr->args[j].name)); + } + } + } else if (uses_named_args) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot use positional argument after named argument"); + } + + zend_const_expr_to_zval( + &attr->args[j].value, arg_ast_ptr, /* allow_dynamic */ true); + } } } } if (*attributes != NULL) { - zend_apply_internal_attribute_validation(*attributes, offset, target); + /* Validate attributes in a secondary loop (needed to detect repeated attributes). */ + ZEND_HASH_PACKED_FOREACH_PTR(*attributes, attr) { + if (attr->offset != offset || NULL == (config = zend_internal_attribute_get(attr->lcname))) { + continue; + } + + if (!(target & (config->flags & ZEND_ATTRIBUTE_TARGET_ALL))) { + zend_string *location = zend_get_attribute_target_names(target); + zend_string *allowed = zend_get_attribute_target_names(config->flags); + + zend_error_noreturn(E_ERROR, "Attribute \"%s\" cannot target %s (allowed targets: %s)", + ZSTR_VAL(attr->name), ZSTR_VAL(location), ZSTR_VAL(allowed) + ); + } + + if (!(config->flags & ZEND_ATTRIBUTE_IS_REPEATABLE)) { + if (zend_is_attribute_repeated(*attributes, attr)) { + zend_error_noreturn(E_ERROR, "Attribute \"%s\" must not be repeated", ZSTR_VAL(attr->name)); + } + } + + if (config->validator != NULL) { + config->validator(attr, target, CG(active_class_entry)); + } + } ZEND_HASH_FOREACH_END(); } } /* }}} */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 0c3be0572136b..5d8d9f4caeb86 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1795,7 +1795,7 @@ ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void) zend_throw_error(NULL, "%s", msg); } -ZEND_COLD zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix) +ZEND_COLD static zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix) { *message_suffix = ZSTR_EMPTY_ALLOC(); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 7a5dd252f0fd9..adcea50373912 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1208,11 +1208,6 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * zend_string_release_ex(lc_name, 0); } Z_CE_FROM_ZVAL_P(ce, zv); - zend_class_alias *alias = NULL; - if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { - ce_cache = 0; - alias = Z_CLASS_ALIAS_P(zv); - } if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_LINKED))) { if ((flags & ZEND_FETCH_CLASS_ALLOW_UNLINKED) || ((flags & ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED) && @@ -1222,9 +1217,6 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * zend_hash_init(CG(unlinked_uses), 0, NULL, NULL, 0); } zend_hash_index_add_empty_element(CG(unlinked_uses), (zend_long)(uintptr_t)ce); - if (!(flags & ZEND_FETCH_CLASS_SILENT) && alias && (alias->alias_flags & ZEND_ACC_DEPRECATED)) { - zend_deprecated_class_alias(alias); - } return ce; } return NULL; @@ -1235,9 +1227,6 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * (!CG(in_compilation) || (ce->ce_flags & ZEND_ACC_IMMUTABLE))) { SET_CE_CACHE(ce_cache, ce); } - if (!(flags & ZEND_FETCH_CLASS_SILENT) && alias && (alias->alias_flags & ZEND_ACC_DEPRECATED)) { - zend_deprecated_class_alias(alias); - } return ce; } @@ -1313,9 +1302,6 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * SET_CE_CACHE(ce_cache, ce); } } - if (!(flags & ZEND_FETCH_CLASS_SILENT) && alias && (alias->alias_flags & ZEND_ACC_DEPRECATED)) { - zend_deprecated_class_alias(alias); - } return ce; } /* }}} */ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 50eb16a8da789..af91e985236a7 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -303,14 +303,6 @@ ZEND_API void destroy_zend_class(zval *zv) return; } - if (class_alias->attributes) { - zend_hash_release(class_alias->attributes); - // class_alias->attributes = NULL; - } - if (class_alias->name) { - zend_string_release(class_alias->name); - // class_alias->name = NULL; - } free(class_alias); return; } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 0642f46d8f411..d1359baa0ae5a 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -682,15 +682,7 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int /* class table hash keys, class names, properties, methods, constants, etc */ ZEND_HASH_MAP_FOREACH_BUCKET(CG(class_table), p) { zend_class_entry *ce = NULL; - - if (EXPECTED(Z_TYPE(p->val) == IS_PTR)) { - ce = Z_PTR(p->val); - } else { - ZEND_ASSERT(Z_TYPE(p->val) == IS_ALIAS_PTR); - zend_class_alias *alias = Z_PTR(p->val); - alias->name = new_interned_string(alias->name); - ce = alias->ce; - } + Z_CE_FROM_ZVAL(ce, p->val); if (p->key) { p->key = new_interned_string(p->key); @@ -4151,14 +4143,6 @@ static void preload_link(void) continue; } - // Not preloading class constants for deprecated aliases - if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { - zend_class_alias *class_alias = Z_CLASS_ALIAS_P(zv); - if (class_alias->alias_flags & ZEND_ACC_DEPRECATED) { - continue; - } - } - if ((ce->ce_flags & ZEND_ACC_LINKED) && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { if (!(ce->ce_flags & ZEND_ACC_TRAIT)) { /* don't update traits */ CG(in_compilation) = true; /* prevent autoloading */ diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 8c6b9acc0dab8..355178d9d51ed 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -570,9 +570,7 @@ static zend_class_entry* zend_get_known_class(const zend_op_array *op_array, con ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING); class_name = Z_STR_P(zv); - // ZEND_FETCH_CLASS_SILENT - ignore alias deprecation - // TODO but we want to not cache the ce if i is a deprecated alias - ce = zend_lookup_class_ex(class_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + ce = zend_lookup_class_ex(class_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); if (ce && (ce->type == ZEND_INTERNAL_CLASS || ce->info.user.filename != op_array->filename)) { ce = NULL; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 87cb45185a380..ed1338d90f49b 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -916,10 +916,6 @@ zend_class_alias *zend_persist_class_alias_entry(zend_class_alias *orig_alias) alias = zend_shared_memdup_put(alias, sizeof(zend_class_alias)); alias->ce = zend_persist_class_entry(alias->ce); - // zend_accel_store_string(alias->name); - if (alias->attributes) { - alias->attributes = zend_persist_attributes(alias->attributes); - } if (EXPECTED(!ZCG(current_persistent_script)->corrupted)) { alias->alias_flags |= ZEND_ACC_IMMUTABLE; diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 41c16789c30c7..f1fe460d38791 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -599,11 +599,6 @@ static void zend_persist_class_alias_entry_calc(zend_class_alias *alias) // alias->ce is going to be a pointer to a class entry that will be // persisted on its own, here we just need to add size for the alias ADD_SIZE(sizeof(zend_class_alias)); - // And the things that the alias holds directly - ADD_INTERNED_STRING(alias->name); - if (alias->attributes) { - zend_persist_attributes_calc(alias->attributes); - } zend_persist_class_entry_calc(alias->ce); } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 36d94ae9e0a50..143ab124f268f 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -7899,18 +7899,6 @@ ZEND_METHOD(ReflectionClassAlias, __construct) ZVAL_STR_COPY(name_zv, name); } -ZEND_METHOD(ReflectionClassAlias, getAttributes) -{ - reflection_object *intern; - zend_class_alias *alias; - - GET_REFLECTION_OBJECT_PTR(alias); - - reflect_attributes(INTERNAL_FUNCTION_PARAM_PASSTHRU, - alias->attributes, 0, alias->ce, ZEND_ATTRIBUTE_TARGET_CLASS_ALIAS, - NULL); -} - ZEND_METHOD(ReflectionClassAlias, __toString) { reflection_object *intern; @@ -7923,25 +7911,13 @@ ZEND_METHOD(ReflectionClassAlias, __toString) smart_str_append_printf( &str, - "%s - %salias for %s", + "%s - alias for %s", Z_STRVAL_P(reflection_prop_name(ZEND_THIS)), - (alias->alias_flags & ZEND_ACC_DEPRECATED) ? "deprecated " : "", ZSTR_VAL(alias->ce->name) ); RETURN_STR(smart_str_extract(&str)); } -ZEND_METHOD(ReflectionClassAlias, isDeprecated) -{ - reflection_object *intern; - zend_class_alias *alias; - - ZEND_PARSE_PARAMETERS_NONE(); - - GET_REFLECTION_OBJECT_PTR(alias); - RETURN_BOOL(alias->alias_flags & ZEND_ACC_DEPRECATED); -} - PHP_MINIT_FUNCTION(reflection) /* {{{ */ { memcpy(&reflection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index edf1f4affcf75..f0b7efac57ad4 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -938,8 +938,4 @@ final class ReflectionClassAlias implements Reflector public function __construct(string $name) {} public function __toString(): string {} - - public function isDeprecated(): bool {} - - public function getAttributes(?string $name = null, int $flags = 0): array {} } diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index ffab8395ea16f..907bc299f9c64 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: 13417094b53f28c193b21ca0d0eac88df18e7aa7 */ + * Stub hash: 8daecbf3ce2d4389db49cd6c5e58d7c62cae52c0 */ 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) @@ -725,10 +725,6 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClassAlias___toString arginfo_class_ReflectionFunction___toString -#define arginfo_class_ReflectionClassAlias_isDeprecated arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType - -#define arginfo_class_ReflectionClassAlias_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes - ZEND_METHOD(Reflection, getModifierNames); ZEND_METHOD(ReflectionClass, __clone); ZEND_METHOD(ReflectionFunctionAbstract, inNamespace); @@ -1000,8 +996,6 @@ ZEND_METHOD(ReflectionConstant, __toString); ZEND_METHOD(ReflectionConstant, getAttributes); ZEND_METHOD(ReflectionClassAlias, __construct); ZEND_METHOD(ReflectionClassAlias, __toString); -ZEND_METHOD(ReflectionClassAlias, isDeprecated); -ZEND_METHOD(ReflectionClassAlias, getAttributes); static const zend_function_entry class_Reflection_methods[] = { ZEND_ME(Reflection, getModifierNames, arginfo_class_Reflection_getModifierNames, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) @@ -1377,8 +1371,6 @@ static const zend_function_entry class_ReflectionConstant_methods[] = { static const zend_function_entry class_ReflectionClassAlias_methods[] = { ZEND_ME(ReflectionClassAlias, __construct, arginfo_class_ReflectionClassAlias___construct, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClassAlias, __toString, arginfo_class_ReflectionClassAlias___toString, ZEND_ACC_PUBLIC) - ZEND_ME(ReflectionClassAlias, isDeprecated, arginfo_class_ReflectionClassAlias_isDeprecated, ZEND_ACC_PUBLIC) - ZEND_ME(ReflectionClassAlias, getAttributes, arginfo_class_ReflectionClassAlias_getAttributes, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt b/ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt deleted file mode 100644 index 746c847abdc7b..0000000000000 --- a/ext/reflection/tests/ReflectionClassAlias_getAttributes_empty.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -ReflectionClassAlias::__toString() ---FILE-- -getAttributes() ); - -?> ---EXPECT-- -array(0) { -} diff --git a/ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt b/ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt deleted file mode 100644 index 1dcdcb1402cd0..0000000000000 --- a/ext/reflection/tests/ReflectionClassAlias_getAttributes_non-empty.phpt +++ /dev/null @@ -1,20 +0,0 @@ ---TEST-- -ReflectionClassAlias::__toString() ---FILE-- -getAttributes() ); - -?> ---EXPECTF-- -array(1) { - [0]=> - object(ReflectionAttribute)#%d (1) { - ["name"]=> - string(16) "MissingAttribute" - } -} diff --git a/ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt b/ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt deleted file mode 100644 index 4c27321782692..0000000000000 --- a/ext/reflection/tests/ReflectionClassAlias_isDeprecated.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -ReflectionClassAlias::isDeprecated() ---FILE-- -isDeprecated() ); - -$r = new ReflectionClassAlias( 'NewAlias' ); -var_dump( $r->isDeprecated() ); -?> ---EXPECT-- -bool(true) -bool(false) diff --git a/ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt b/ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt deleted file mode 100644 index 656cf874bb24e..0000000000000 --- a/ext/reflection/tests/ReflectionClassAlias_toString_deprecated.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -ReflectionClassAlias::__toString() for a deprecated alias ---FILE-- - ---EXPECT-- -Other - deprecated alias for Demo From ad1f55c3c81af602537e500a812b6780aaa2f053 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 16 Jun 2025 01:35:53 -0700 Subject: [PATCH 23/24] Z_TYPE_P --- Zend/zend_opcode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index af91e985236a7..0c96532bcc89a 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -296,7 +296,7 @@ ZEND_API void destroy_zend_class(zval *zv) * so we don't need to destroy the underlying ->ce here, but we do need * to free the attributes and the storage for the * skip the destruction of aliases entirely. */ - if (UNEXPECTED(Z_TYPE_INFO_P(zv) == IS_ALIAS_PTR)) { + if (UNEXPECTED(Z_TYPE_P(zv) == IS_ALIAS_PTR)) { zend_class_alias *class_alias = Z_CLASS_ALIAS_P(zv); if (class_alias->alias_flags & ZEND_ACC_IMMUTABLE) { From eaa7d0d15397595fe2ed7fad085e0cadc81dd279 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Mon, 16 Jun 2025 01:59:56 -0700 Subject: [PATCH 24/24] Cache --- Zend/zend_execute_API.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index adcea50373912..6bb952ded66fb 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1278,7 +1278,6 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string * zend_class_alias *alias = NULL; if (ce_zval) { if (Z_TYPE_P(ce_zval) == IS_ALIAS_PTR) { - ce_cache = 0; alias = Z_CLASS_ALIAS_P(ce_zval); ce = alias->ce; } else {