From b4179390ad60bebc4249d14445e969a738cdb882 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 16 May 2025 21:12:17 +0200 Subject: [PATCH] Fix GH-18567: Preloading with internal class alias triggers assertion failure The assertion is imprecise now, and the code assumed that from the moment an internal class was encountered that there were only internal classes remaining. This is wrong now, and we still have to continue if we encounter an internal class. We can only skip the remaining iterations if the entry in the hash table is not an alias. --- ext/opcache/ZendAccelerator.c | 35 +++++++++++++++++++++++---- ext/opcache/tests/gh18567.phpt | 27 +++++++++++++++++++++ ext/opcache/tests/preload_gh18567.inc | 2 ++ 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 ext/opcache/tests/gh18567.phpt create mode 100644 ext/opcache/tests/preload_gh18567.inc diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index e3f7ca18151e5..47e0bc08bd5cc 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3522,7 +3522,7 @@ static void preload_shutdown(void) if (EG(class_table)) { ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { zend_class_entry *ce = Z_PTR_P(zv); - if (ce->type == ZEND_INTERNAL_CLASS) { + if (ce->type == ZEND_INTERNAL_CLASS && Z_TYPE_P(zv) != IS_ALIAS_PTR) { break; } } ZEND_HASH_MAP_FOREACH_END_DEL(); @@ -3610,7 +3610,15 @@ static void preload_move_user_classes(HashTable *src, HashTable *dst) 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_ASSERT(ce->type == ZEND_USER_CLASS); + + /* Possible with internal class aliases */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(Z_TYPE(p->val) == IS_ALIAS_PTR); + _zend_hash_append(dst, p->key, &p->val); + zend_hash_del_bucket(src, p); + continue; + } + if (ce->info.user.filename != filename) { filename = ce->info.user.filename; if (filename) { @@ -3904,7 +3912,12 @@ static void preload_link(void) ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM(EG(class_table), key, zv, EG(persistent_classes_count)) { ce = Z_PTR_P(zv); - ZEND_ASSERT(ce->type != ZEND_INTERNAL_CLASS); + + /* Possible with internal class aliases */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_ALIAS_PTR); + continue; + } if (!(ce->ce_flags & (ZEND_ACC_TOP_LEVEL|ZEND_ACC_ANON_CLASS)) || (ce->ce_flags & ZEND_ACC_LINKED)) { @@ -3990,9 +4003,15 @@ static void preload_link(void) ZEND_HASH_MAP_REVERSE_FOREACH_VAL(EG(class_table), zv) { ce = Z_PTR_P(zv); + + /* Possible with internal class aliases */ if (ce->type == ZEND_INTERNAL_CLASS) { - break; + if (Z_TYPE_P(zv) != IS_ALIAS_PTR) { + break; /* can stop already */ + } + 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 */ @@ -4009,7 +4028,13 @@ static void preload_link(void) ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_FROM( EG(class_table), key, zv, EG(persistent_classes_count)) { ce = Z_PTR_P(zv); - ZEND_ASSERT(ce->type != ZEND_INTERNAL_CLASS); + + /* Possible with internal class aliases */ + if (ce->type == ZEND_INTERNAL_CLASS) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_ALIAS_PTR); + continue; + } + if ((ce->ce_flags & (ZEND_ACC_TOP_LEVEL|ZEND_ACC_ANON_CLASS)) && !(ce->ce_flags & ZEND_ACC_LINKED)) { zend_string *lcname = zend_string_tolower(ce->name); diff --git a/ext/opcache/tests/gh18567.phpt b/ext/opcache/tests/gh18567.phpt new file mode 100644 index 0000000000000..e1d2a68af6775 --- /dev/null +++ b/ext/opcache/tests/gh18567.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-18567 (Preloading with internal class alias triggers assertion failure) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.preload={PWD}/preload_gh18567.inc +--EXTENSIONS-- +opcache +--SKIPIF-- + +--FILE-- +getInterfaces()); +?> +--EXPECT-- +array(1) { + ["Stringable"]=> + object(ReflectionClass)#2 (1) { + ["name"]=> + string(10) "Stringable" + } +} diff --git a/ext/opcache/tests/preload_gh18567.inc b/ext/opcache/tests/preload_gh18567.inc new file mode 100644 index 0000000000000..d277e83f83be6 --- /dev/null +++ b/ext/opcache/tests/preload_gh18567.inc @@ -0,0 +1,2 @@ +