Implement GH-9826: Make class_alias() work with internal classes (#10483)

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.
This commit is contained in:
Niels Dossche 2023-02-22 11:47:32 +01:00 committed by GitHub
parent f4313a81ee
commit 821fc55a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 40 additions and 31 deletions

View File

@ -64,6 +64,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

View File

@ -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

View File

@ -1419,8 +1419,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);
@ -1429,10 +1428,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
@ -1468,11 +1468,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;
@ -1599,10 +1598,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 =

View File

@ -3,12 +3,11 @@ Testing creation of alias to an internal class
--FILE--
<?php
try {
class_alias('stdclass', 'foo');
} catch (ValueError $exception) {
echo $exception->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) {
}

View File

@ -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);

View File

@ -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));

View File

@ -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++;
}
}

View File

@ -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);