From 2dddab01ae6a5427d9f6f38770cea4553e93b033 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Thu, 12 Mar 2020 16:31:24 +0300 Subject: [PATCH 1/4] Avoid "Anonymous class wasn't preloaded" error by lazely loading of not preloaded part of a preloaded script --- Zend/zend.c | 1 + Zend/zend.h | 3 +++ Zend/zend_compile.c | 16 +++++++++++++--- Zend/zend_vm_def.h | 13 +++++++++++-- Zend/zend_vm_execute.h | 13 +++++++++++-- ext/opcache/ZendAccelerator.c | 30 ++++++++++++++++++++++++++++++ ext/opcache/tests/bug78937_1.phpt | 5 ++--- ext/opcache/tests/bug78937_4.phpt | 4 ++-- 8 files changed, 73 insertions(+), 12 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index 97ac3327cab..a9ae03e6242 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -81,6 +81,7 @@ ZEND_API char *(*zend_getenv)(char *name, size_t name_len); ZEND_API zend_string *(*zend_resolve_path)(const char *filename, size_t filename_len); ZEND_API int (*zend_post_startup_cb)(void) = NULL; ZEND_API void (*zend_post_shutdown_cb)(void) = NULL; +ZEND_API int (*zend_preload_autoload)(zend_string *filename) = NULL; void (*zend_on_timeout)(int seconds); diff --git a/Zend/zend.h b/Zend/zend.h index 9aefc8d26a3..94fd9a35597 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -292,6 +292,9 @@ extern ZEND_API zend_string *(*zend_resolve_path)(const char *filename, size_t f extern ZEND_API int (*zend_post_startup_cb)(void); extern ZEND_API void (*zend_post_shutdown_cb)(void); +/* Callback for loading of not preloaded part of the script */ +extern ZEND_API int (*zend_preload_autoload)(zend_string *filename); + ZEND_API ZEND_COLD void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); /* If filename is NULL the default filename is used. */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5661f264396..970011ac088 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1072,11 +1072,21 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */ ce = zend_hash_find_ptr(EG(class_table), Z_STR_P(lcname)); if (ce) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name)); + return FAILURE; } else { - ZEND_ASSERT(EG(current_execute_data)->func->op_array.fn_flags & ZEND_ACC_PRELOADED); - zend_error_noreturn(E_ERROR, "Class %s wasn't preloaded", Z_STRVAL_P(lcname)); + do { + if (zend_preload_autoload + && zend_preload_autoload(EG(current_execute_data)->func->op_array.filename) == SUCCESS) { + zv = zend_hash_find_ex(EG(class_table), Z_STR_P(rtd_key), 1); + if (EXPECTED(zv != NULL)) { + break; + } + } + ZEND_ASSERT(EG(current_execute_data)->func->op_array.fn_flags & ZEND_ACC_PRELOADED); + zend_error_noreturn(E_ERROR, "Class %s wasn't preloaded", Z_STRVAL_P(lcname)); + return FAILURE; + } while (0); } - return FAILURE; } /* Register the derived class */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0282c3c584f..bb6a300a601 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7317,8 +7317,17 @@ ZEND_VM_HANDLER(146, ZEND_DECLARE_ANON_CLASS, ANY, ANY, CACHE_SLOT) zv = zend_hash_find_ex(EG(class_table), rtd_key, 1); if (UNEXPECTED(zv == NULL)) { SAVE_OPLINE(); - ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); - zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded"); + do { + if (zend_preload_autoload + && zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) { + zv = zend_hash_find_ex(EG(class_table), rtd_key, 1); + if (EXPECTED(zv != NULL)) { + break; + } + } + ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); + zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded"); + } while (0); } ZEND_ASSERT(zv != NULL); ce = Z_CE_P(zv); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d79bdb1c1f2..722053bbdc0 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2449,8 +2449,17 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_ANON_CLASS_SPEC_HANDLE zv = zend_hash_find_ex(EG(class_table), rtd_key, 1); if (UNEXPECTED(zv == NULL)) { SAVE_OPLINE(); - ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); - zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded"); + do { + if (zend_preload_autoload + && zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) { + zv = zend_hash_find_ex(EG(class_table), rtd_key, 1); + if (EXPECTED(zv != NULL)) { + break; + } + } + ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); + zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded"); + } while (0); } ZEND_ASSERT(zv != NULL); ce = Z_CE_P(zv); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 758f8fc4bff..3319f719ec4 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4242,6 +4242,34 @@ static void preload_load(void) } } +static int preload_autoload(zend_string *filename) +{ + zend_persistent_script *persistent_script; + zend_op_array *op_array; + + if (zend_hash_exists(&EG(included_files), filename)) { + return FAILURE; + } + + persistent_script = zend_accel_hash_find(&ZCSG(hash), filename); + if (!persistent_script) { + return FAILURE; + } + + op_array = zend_accel_load_script(persistent_script, 1); + if (!op_array) { + return FAILURE; + } + + // TODO: we may need to execute this in some special context ??? + zend_execute(op_array, NULL); + + destroy_op_array(op_array); + efree_size(op_array, sizeof(zend_op_array)); + + return SUCCESS; +} + static int accel_preload(const char *config) { zend_file_handle file_handle; @@ -4534,6 +4562,8 @@ static int accel_preload(const char *config) HANDLE_UNBLOCK_INTERRUPTIONS(); zend_shared_alloc_destroy_xlat_table(); + + zend_preload_autoload = preload_autoload; } else { CG(map_ptr_last) = orig_map_ptr_last; } diff --git a/ext/opcache/tests/bug78937_1.phpt b/ext/opcache/tests/bug78937_1.phpt index bc285f107b9..2745fde72ca 100644 --- a/ext/opcache/tests/bug78937_1.phpt +++ b/ext/opcache/tests/bug78937_1.phpt @@ -20,6 +20,5 @@ var_dump(foo()); Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6 Warning: Can't preload unlinked class class@anonymous: Unknown parent Bar in %spreload_bug78937.inc on line 3 - -Fatal error: Anonymous class wasn't preloaded in %spreload_bug78937.inc on line 3 - +object(class@anonymous)#%d (0) { +} diff --git a/ext/opcache/tests/bug78937_4.phpt b/ext/opcache/tests/bug78937_4.phpt index 2ad86870de8..3652d6de52d 100644 --- a/ext/opcache/tests/bug78937_4.phpt +++ b/ext/opcache/tests/bug78937_4.phpt @@ -21,5 +21,5 @@ var_dump(new Foo); Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6 Warning: Can't preload unlinked class class@anonymous: Unknown parent Bar in %spreload_bug78937.inc on line 3 - -Fatal error: Class foo wasn't preloaded in %spreload_bug78937.inc on line 6 +object(Foo)#%d (0) { +} From 3c6e9bed1a72b510ed7b2d6a4e3b50666e44eb9b Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Thu, 12 Mar 2020 22:19:47 +0300 Subject: [PATCH 2/4] Call global code of preloaded script in global context --- ext/opcache/ZendAccelerator.c | 47 ++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 3319f719ec4..9657c1ec17c 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4246,6 +4246,10 @@ static int preload_autoload(zend_string *filename) { zend_persistent_script *persistent_script; zend_op_array *op_array; + zend_execute_data *old_execute_data; + zend_class_entry *old_fake_scope; + zend_bool do_bailout = 0; + int ret = SUCCESS; if (zend_hash_exists(&EG(included_files), filename)) { return FAILURE; @@ -4256,18 +4260,55 @@ static int preload_autoload(zend_string *filename) return FAILURE; } + zend_hash_add_empty_element(&EG(included_files), filename); + + if (persistent_script->ping_auto_globals_mask) { + zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask); + } + op_array = zend_accel_load_script(persistent_script, 1); if (!op_array) { return FAILURE; } - // TODO: we may need to execute this in some special context ??? - zend_execute(op_array, NULL); + /* Execute in global context */ + old_execute_data = EG(current_execute_data); + EG(current_execute_data) = NULL; + old_fake_scope = EG(fake_scope); + EG(fake_scope) = NULL; + zend_exception_save(); + + zend_try { + zend_execute(op_array, NULL); + } zend_catch { + do_bailout = 1; + } zend_end_try(); + + if (EG(exception)) { + ret = FAILURE; + } + + zend_exception_restore(); + EG(fake_scope) = old_fake_scope; + EG(current_execute_data) = old_execute_data; + while (old_execute_data) { + if (old_execute_data->func && (ZEND_CALL_INFO(old_execute_data) & ZEND_CALL_HAS_SYMBOL_TABLE)) { + if (old_execute_data->symbol_table == &EG(symbol_table)) { + zend_attach_symbol_table(old_execute_data); + } + break; + } + old_execute_data = old_execute_data->prev_execute_data; + } destroy_op_array(op_array); efree_size(op_array, sizeof(zend_op_array)); - return SUCCESS; + if (do_bailout) { + zend_bailout(); + } + + return ret; } static int accel_preload(const char *config) From b6492b44532f4083ace7bfb28fe8807d78ff6606 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Thu, 12 Mar 2020 22:26:16 +0300 Subject: [PATCH 3/4] identation fix --- ext/opcache/ZendAccelerator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 9657c1ec17c..b0b27d79758 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4244,7 +4244,7 @@ static void preload_load(void) static int preload_autoload(zend_string *filename) { - zend_persistent_script *persistent_script; + zend_persistent_script *persistent_script; zend_op_array *op_array; zend_execute_data *old_execute_data; zend_class_entry *old_fake_scope; From c5159b383218968be307fb96f9fd19b2b6e17c90 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Thu, 12 Mar 2020 22:26:30 +0300 Subject: [PATCH 4/4] Check asserts early --- Zend/zend_compile.c | 2 +- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 970011ac088..0f948e000b4 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1075,6 +1075,7 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */ return FAILURE; } else { do { + ZEND_ASSERT(EG(current_execute_data)->func->op_array.fn_flags & ZEND_ACC_PRELOADED); if (zend_preload_autoload && zend_preload_autoload(EG(current_execute_data)->func->op_array.filename) == SUCCESS) { zv = zend_hash_find_ex(EG(class_table), Z_STR_P(rtd_key), 1); @@ -1082,7 +1083,6 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */ break; } } - ZEND_ASSERT(EG(current_execute_data)->func->op_array.fn_flags & ZEND_ACC_PRELOADED); zend_error_noreturn(E_ERROR, "Class %s wasn't preloaded", Z_STRVAL_P(lcname)); return FAILURE; } while (0); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index bb6a300a601..900c21c3d34 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -7318,6 +7318,7 @@ ZEND_VM_HANDLER(146, ZEND_DECLARE_ANON_CLASS, ANY, ANY, CACHE_SLOT) if (UNEXPECTED(zv == NULL)) { SAVE_OPLINE(); do { + ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); if (zend_preload_autoload && zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) { zv = zend_hash_find_ex(EG(class_table), rtd_key, 1); @@ -7325,7 +7326,6 @@ ZEND_VM_HANDLER(146, ZEND_DECLARE_ANON_CLASS, ANY, ANY, CACHE_SLOT) break; } } - ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded"); } while (0); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 722053bbdc0..2fce1be298f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2450,6 +2450,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_ANON_CLASS_SPEC_HANDLE if (UNEXPECTED(zv == NULL)) { SAVE_OPLINE(); do { + ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); if (zend_preload_autoload && zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) { zv = zend_hash_find_ex(EG(class_table), rtd_key, 1); @@ -2457,7 +2458,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_ANON_CLASS_SPEC_HANDLE break; } } - ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED); zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded"); } while (0); }