From f53b9939fe4f66e1a539a42d607fefbe45be6239 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 20 May 2019 18:15:38 +0300 Subject: [PATCH] Fixed bug #78014 (Preloaded classes may depend on non-preloaded classes due to unresolved consts) --- Zend/zend_compile.c | 1 + ext/opcache/ZendAccelerator.c | 223 +++++++++++++++---------- ext/opcache/tests/bug78014.inc | 11 ++ ext/opcache/tests/bug78014.phpt | 26 +++ ext/opcache/tests/preload_bug78014.inc | 2 + 5 files changed, 172 insertions(+), 91 deletions(-) create mode 100644 ext/opcache/tests/bug78014.inc create mode 100644 ext/opcache/tests/bug78014.phpt create mode 100644 ext/opcache/tests/preload_bug78014.inc diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 303aefcf9d..b55f9c595f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6389,6 +6389,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */ zend_class_entry *parent_ce = zend_lookup_class_ex(ce->parent_name, NULL, 0); if (parent_ce + && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) /* delay inheritance till preloading */ && ((parent_ce->type != ZEND_INTERNAL_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES)) && ((parent_ce->type != ZEND_USER_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) || (parent_ce->info.user.filename == ce->info.user.filename)) ) { diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 58baf76e5f..e28db6361d 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3296,6 +3296,106 @@ static void get_unresolved_initializer(zend_class_entry *ce, const char **kind, } ZEND_HASH_FOREACH_END(); } +static zend_bool preload_try_resolve_constants(zend_class_entry *ce) +{ + zend_bool ok, changed; + zend_class_constant *c; + zval *val; + + EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */ + do { + ok = 1; + changed = 0; + ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) { + val = &c->value; + if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) { + changed = 1; + } else { + ok = 0; + } + } + } ZEND_HASH_FOREACH_END(); + if (ce->default_properties_count) { + uint32_t i; + for (i = 0; i < ce->default_properties_count; i++) { + val = &ce->default_properties_table[i]; + if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + zend_property_info *prop = ce->properties_info_table[i]; + if (UNEXPECTED(zval_update_constant_ex(val, prop->ce) != SUCCESS)) { + ok = 0; + } + } + } + } + if (ce->default_static_members_count) { + uint32_t count = ce->parent ? ce->default_static_members_count - ce->parent->default_static_members_count : ce->default_static_members_count; + + val = ce->default_static_members_table + ce->default_static_members_count - 1; + while (count) { + if (Z_TYPE_P(val) == IS_CONSTANT_AST) { + if (UNEXPECTED(zval_update_constant_ex(val, ce) != SUCCESS)) { + ok = 0; + } + } + val--; + count--; + } + } + } while (changed && !ok); + EG(exception) = NULL; + + return ok; +} + +static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) +{ + zend_bool ok = 1; + zend_property_info *prop; + zend_class_entry *p; + + if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { + zend_string *name, *lcname; + + if (!ZEND_TYPE_IS_NAME(prop->type)) { + continue; + } + + name = ZEND_TYPE_NAME(prop->type); + lcname = zend_string_tolower(name); + p = zend_hash_find_ptr(EG(class_table), lcname); + zend_string_release(lcname); + if (!p) { + ok = 0; + continue; + } + if (p != ce) { +#ifdef ZEND_WIN32 + /* On Windows we can't link with internal class, because of ASLR */ + if (p->type == ZEND_INTERNAL_CLASS) { + ok = 0; + continue; + } +#endif + if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + ok = 0; + continue; + } + if (!(p->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + ok = 0; + continue; + } + } + + zend_string_release(name); + prop->type = ZEND_TYPE_ENCODE_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type)); + } ZEND_HASH_FOREACH_END(); + } + + return ok; +} + static void preload_link(void) { zval *zv; @@ -3339,6 +3439,12 @@ static void preload_link(void) /* On Windows we can't link with internal class, because of ASLR */ if (parent->type == ZEND_INTERNAL_CLASS) continue; #endif + if (!(parent->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + continue; + } + if (!(parent->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + continue; + } } if (ce->num_interfaces) { @@ -3356,6 +3462,10 @@ static void preload_link(void) break; } #endif + if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + found = 0; + break; + } } if (!found) continue; } @@ -3387,7 +3497,16 @@ static void preload_link(void) /* Set filename & lineno information for inheritance errors */ CG(in_compilation) = 1; CG(compiled_filename) = ce->info.user.filename; - CG(zend_lineno) = ce->info.user.line_start; + if (ce->parent_name + && !ce->num_interfaces + && !ce->num_traits + && (parent->type == ZEND_INTERNAL_CLASS + || parent->info.user.filename == ce->info.user.filename)) { + /* simulate early binding */ + CG(zend_lineno) = ce->info.user.line_end; + } else { + CG(zend_lineno) = ce->info.user.line_start; + } zend_do_link_class(ce, parent); CG(in_compilation) = 0; CG(compiled_filename) = NULL; @@ -3395,103 +3514,25 @@ static void preload_link(void) changed = 1; } } - } ZEND_HASH_FOREACH_END(); - } while (changed); - - /* Resolve class constants */ - EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */ - do { - changed = 0; - ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) { - ce = Z_PTR_P(zv); - if (ce->type == ZEND_INTERNAL_CLASS) { - break; - } - if ((ce->ce_flags & ZEND_ACC_LINKED) - && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { - zend_bool ok = 1; - zend_class_constant *c; - zval *val; - - ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) { - val = &c->value; - if (Z_TYPE_P(val) == IS_CONSTANT_AST) { - if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) { - changed = 1; - } else { - ok = 0; - } - } - } ZEND_HASH_FOREACH_END(); - if (ce->default_properties_count) { - uint32_t i; - for (i = 0; i < ce->default_properties_count; i++) { - val = &ce->default_properties_table[i]; - if (Z_TYPE_P(val) == IS_CONSTANT_AST) { - zend_property_info *prop = ce->properties_info_table[i]; - if (UNEXPECTED(zval_update_constant_ex(val, prop->ce) != SUCCESS)) { - ok = 0; - } - } + if (ce->ce_flags & ZEND_ACC_LINKED) { + if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + if ((ce->ce_flags & ZEND_ACC_TRAIT) /* don't update traits */ + || preload_try_resolve_constants(ce)) { + ce->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED; + changed = 1; } } - if (ce->default_static_members_count) { - uint32_t count = ce->parent ? ce->default_static_members_count - ce->parent->default_static_members_count : ce->default_static_members_count; - - val = ce->default_static_members_table + ce->default_static_members_count - 1; - while (count) { - if (Z_TYPE_P(val) == IS_CONSTANT_AST) { - if (UNEXPECTED(zval_update_constant_ex(val, ce) != SUCCESS)) { - ok = 0; - } - } - val--; - count--; + + if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + if ((ce->ce_flags & ZEND_ACC_TRAIT) /* don't update traits */ + || preload_try_resolve_property_types(ce)) { + ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED; + changed = 1; } } - if (ok) { - ce->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED; - } } } ZEND_HASH_FOREACH_END(); } while (changed); - EG(exception) = NULL; - - /* Resolve property types */ - ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) { - ce = Z_PTR_P(zv); - if (ce->type == ZEND_INTERNAL_CLASS) { - break; - } - if ((ce->ce_flags & ZEND_ACC_LINKED) - && !(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { - zend_bool ok = 1; - zend_property_info *prop; - if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { - ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - zend_string *name, *lcname; - if (!ZEND_TYPE_IS_NAME(prop->type)) { - continue; - } - - name = ZEND_TYPE_NAME(prop->type); - lcname = zend_string_tolower(name); - p = zend_hash_find_ptr(EG(class_table), lcname); - zend_string_release(lcname); - if (!p) { - ok = 0; - continue; - } - - zend_string_release(name); - prop->type = ZEND_TYPE_ENCODE_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type)); - } ZEND_HASH_FOREACH_END(); - } - if (ok) { - ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED; - } - } - } ZEND_HASH_FOREACH_END(); /* Move unlinked clases (and with unresilved constants) back to scripts */ orig_dtor = EG(class_table)->pDestructor; diff --git a/ext/opcache/tests/bug78014.inc b/ext/opcache/tests/bug78014.inc new file mode 100644 index 0000000000..51a1196476 --- /dev/null +++ b/ext/opcache/tests/bug78014.inc @@ -0,0 +1,11 @@ + +--FILE-- +foo()); +?> +--EXPECTF-- +Warning: Can't preload unlinked class C in %s on line %d + +Warning: Can't preload class B with unresolved initializer for constant X in %s on line %d + +Fatal error: Uncaught Error: Class 'C' not found in %sbug78014.php:5 +Stack trace: +#0 {main} + thrown in %sbug78014.php on line 5 diff --git a/ext/opcache/tests/preload_bug78014.inc b/ext/opcache/tests/preload_bug78014.inc new file mode 100644 index 0000000000..84cc1d90c8 --- /dev/null +++ b/ext/opcache/tests/preload_bug78014.inc @@ -0,0 +1,2 @@ +