diff --git a/UPGRADING b/UPGRADING index 5a8429b6e7633..f51571bed61f1 100644 --- a/UPGRADING +++ b/UPGRADING @@ -57,6 +57,7 @@ PHP 8.3 UPGRADE NOTES "full" => bool "buffer_size" => int See GH-9336 + . class_alias() now supports creating an alias of an internal class. - MBString: . mb_strtolower, mb_strtotitle, and mb_convert_case implement conditional diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index d55c3cfe05816..4ec4091982462 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -30,6 +30,10 @@ PHP 8.3 INTERNALS UPGRADE NOTES for C99 features have been removed and therefore macro definitions from php_config.h have disappeared. Do not use those feature macros. +* Internal class aliases created during request time can now exist in + the class table. zend_register_class_alias_ex() will not increase + the refcount for class aliases and the cleanup function takes this + into account. ======================== 2. Build system changes diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index b5841159bf12c..cf3d9f667da9c 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -1416,8 +1416,7 @@ static void zend_foreach_op_array_helper( void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void *context) { - zend_class_entry *ce; - zend_string *key; + zval *zv; zend_op_array *op_array; zend_foreach_op_array_helper(&script->main_op_array, func, context); @@ -1426,10 +1425,11 @@ void zend_foreach_op_array(zend_script *script, zend_op_array_func_t func, void zend_foreach_op_array_helper(op_array, func, context); } ZEND_HASH_FOREACH_END(); - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&script->class_table, key, ce) { - if (ce->refcount > 1 && !zend_string_equals_ci(key, ce->name)) { + ZEND_HASH_MAP_FOREACH_VAL(&script->class_table, zv) { + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { continue; } + zend_class_entry *ce = Z_CE_P(zv); ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, op_array) { if (op_array->scope == ce && op_array->type == ZEND_USER_FUNCTION @@ -1465,11 +1465,10 @@ static void zend_optimizer_call_registered_passes(zend_script *script, void *ctx ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level) { - zend_class_entry *ce; - zend_string *key; zend_op_array *op_array; zend_string *name; zend_optimizer_ctx ctx; + zval *zv; ctx.arena = zend_arena_create(64 * 1024); ctx.script = script; @@ -1596,10 +1595,11 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l } } - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&script->class_table, key, ce) { - if (ce->refcount > 1 && !zend_string_equals_ci(key, ce->name)) { + ZEND_HASH_MAP_FOREACH_VAL(&script->class_table, zv) { + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { continue; } + zend_class_entry *ce = Z_CE_P(zv); ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->function_table, name, op_array) { if (op_array->scope != ce && op_array->type == ZEND_USER_FUNCTION) { zend_op_array *orig_op_array = diff --git a/Zend/tests/class_alias_006.phpt b/Zend/tests/class_alias_006.phpt index 8fe2ac509aeea..2548d8f143aeb 100644 --- a/Zend/tests/class_alias_006.phpt +++ b/Zend/tests/class_alias_006.phpt @@ -3,12 +3,11 @@ Testing creation of alias to an internal class --FILE-- getMessage() . "\n"; -} +class_alias('stdclass', 'foo'); +$foo = new foo(); +var_dump($foo); ?> --EXPECT-- -class_alias(): Argument #1 ($class) must be a user-defined class name, internal class name given +object(stdClass)#1 (0) { +} diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 905687a00b953..692bfc271b7dc 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3302,13 +3302,15 @@ ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_ lcname = zend_new_interned_string(lcname); + /* We cannot increase the refcount of an internal class during request time. + * 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); + ret = zend_hash_add(CG(class_table), lcname, &zv); zend_string_release_ex(lcname, 0); if (ret) { - if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - ce->refcount++; - } // avoid notifying at MINIT time if (ce->type == ZEND_USER_CLASS) { zend_observer_class_linked_notify(ce, lcname); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 90cfe3ec329ba..1134c92f04a1e 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -1074,16 +1074,11 @@ ZEND_FUNCTION(class_alias) ce = zend_lookup_class_ex(class_name, NULL, !autoload ? ZEND_FETCH_CLASS_NO_AUTOLOAD : 0); if (ce) { - if (ce->type == ZEND_USER_CLASS) { - if (zend_register_class_alias_ex(ZSTR_VAL(alias_name), ZSTR_LEN(alias_name), ce, 0) == SUCCESS) { - RETURN_TRUE; - } else { - zend_error(E_WARNING, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(alias_name)); - RETURN_FALSE; - } + if (zend_register_class_alias_ex(ZSTR_VAL(alias_name), ZSTR_LEN(alias_name), ce, false) == SUCCESS) { + RETURN_TRUE; } else { - zend_argument_value_error(1, "must be a user-defined class name, internal class name given"); - RETURN_THROWS(); + zend_error(E_WARNING, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(alias_name)); + RETURN_FALSE; } } else { zend_error(E_WARNING, "Class \"%s\" not found", ZSTR_VAL(class_name)); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 6d42379b2022e..26d407d0a54e6 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -301,6 +301,12 @@ 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; @@ -323,6 +329,8 @@ ZEND_API void destroy_zend_class(zval *zv) return; } + ZEND_ASSERT(ce->refcount > 0); + if (--ce->refcount > 0) { return; } @@ -516,7 +524,7 @@ void zend_class_add_ref(zval *zv) { zend_class_entry *ce = Z_PTR_P(zv); - if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + if (Z_TYPE_P(zv) != IS_ALIAS_PTR && !(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { ce->refcount++; } } diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 45a39da7da535..f78e06514263f 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -700,15 +700,15 @@ ZEND_FUNCTION(opcache_get_status) } if (zend_hash_num_elements(&ZCSG(preload_script)->script.class_table)) { - zend_class_entry *ce; + zval *zv; zend_string *key; array_init(&scripts); - ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ZCSG(preload_script)->script.class_table, key, ce) { - if (ce->refcount > 1 && !zend_string_equals_ci(key, ce->name)) { + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&ZCSG(preload_script)->script.class_table, key, zv) { + if (Z_TYPE_P(zv) == IS_ALIAS_PTR) { add_next_index_str(&scripts, key); } else { - add_next_index_str(&scripts, ce->name); + add_next_index_str(&scripts, Z_CE_P(zv)->name); } } ZEND_HASH_FOREACH_END(); add_assoc_zval(&statistics, "classes", &scripts);