From 436f00bce979a3200de6bb74e9e65683192f9acb Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 1 Feb 2023 21:45:01 +0100 Subject: [PATCH] Implement GH-9826: Make class_alias() work with internal classes We can't increase the refcount of internal classes during request time. To work around this problem we simply don't refcount aliases anymore and add a check in the destruction to skip aliases entirely. There were also some checks which checked for an alias implicitly by comparing the refcount, these have been replaced by checking the type of the zval instead. --- UPGRADING | 1 + UPGRADING.INTERNALS | 4 ++++ Zend/Optimizer/zend_optimizer.c | 16 ++++++++-------- Zend/tests/class_alias_006.phpt | 11 +++++------ Zend/zend_API.c | 8 +++++--- Zend/zend_builtin_functions.c | 13 ++++--------- Zend/zend_opcode.c | 10 +++++++++- ext/opcache/zend_accelerator_module.c | 8 ++++---- 8 files changed, 40 insertions(+), 31 deletions(-) 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);