From 2aa3c7c449a808ab52fc93b8506efd84f16fde63 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 11 Mar 2020 16:36:16 +0100 Subject: [PATCH 1/2] Fix bug #79349 --- ext/opcache/ZendAccelerator.c | 121 +++++++++++++++++-------- ext/opcache/tests/bug78937_1.phpt | 12 ++- ext/opcache/tests/bug78937_2.phpt | 11 ++- ext/opcache/tests/bug78937_3.phpt | 16 ++-- ext/opcache/tests/bug78937_4.phpt | 25 ----- ext/opcache/tests/bug78937_5.phpt | 26 ------ ext/opcache/tests/bug78937_6.phpt | 28 ------ ext/opcache/tests/preload_bug78937.inc | 10 +- 8 files changed, 115 insertions(+), 134 deletions(-) delete mode 100644 ext/opcache/tests/bug78937_4.phpt delete mode 100644 ext/opcache/tests/bug78937_5.phpt delete mode 100644 ext/opcache/tests/bug78937_6.phpt diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 758f8fc4bff51..b29dab25126b3 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3882,51 +3882,100 @@ static zend_class_entry *preload_load_prop_type(zend_property_info *prop, zend_s return ce; } +static void preload_ensure_opcode_deps_loadable(zend_function *func) { + zend_op_array *op_array = (zend_op_array *) func; + uint32_t i; + + if (func->type != ZEND_USER_FUNCTION) { + return; + } + + for (i = 0; i < op_array->last; i++) { + zend_op *opline = &op_array->opcodes[i]; + if (opline->opcode == ZEND_DECLARE_ANON_CLASS) { + zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1)); + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), rtd_key); + if (!(ce->ce_flags & ZEND_ACC_LINKED)) { + zend_string *parent_lcname = opline->op2_type == IS_CONST + ? Z_STR_P(RT_CONSTANT(opline, opline->op2)) : NULL; + /* TODO: Set file and line. We cannot set in_compilation, because that would + * prevent autoloading. */ + if (zend_do_link_class(ce, parent_lcname) == FAILURE) { + zend_error(E_ERROR, "Anonymous class linking during preloading failed"); + } + } + } + /* TODO: For non-anonymous dynamically declared classes we would have to preload the + * class under the RTD key and then make sure the hash bucket gets reset to the RTD + * key after the request. */ + } +} + static void preload_ensure_classes_loadable() { /* Run this in a loop, because additional classes may be loaded while updating constants etc. */ - uint32_t checked_classes_idx = 0; - while (1) { - zend_class_entry *ce; - uint32_t num_classes = zend_hash_num_elements(EG(class_table)); - if (num_classes == checked_classes_idx) { - return; + uint32_t checked_classes_idx = 0, checked_functions_idx = 0; + zend_bool retry = 1; + while (retry) { + uint32_t num_classes, num_functions; + retry = 0; + + num_functions = zend_hash_num_elements(EG(function_table)); + if (num_functions != checked_functions_idx) { + zend_function *func; + ZEND_HASH_FOREACH_PTR(EG(function_table), func) { + preload_ensure_opcode_deps_loadable(func); + } ZEND_HASH_FOREACH_END(); + + retry = 1; + checked_functions_idx = num_functions; } - ZEND_HASH_REVERSE_FOREACH_PTR(EG(class_table), ce) { - if (ce->type == ZEND_INTERNAL_CLASS || _idx == checked_classes_idx) { - break; - } + num_classes = zend_hash_num_elements(EG(class_table)); + if (num_classes != checked_classes_idx) { + zend_class_entry *ce; + ZEND_HASH_REVERSE_FOREACH_PTR(EG(class_table), ce) { + if (ce->type == ZEND_INTERNAL_CLASS || _idx == checked_classes_idx) { + break; + } - if (!(ce->ce_flags & ZEND_ACC_LINKED)) { - /* Only require that already linked classes are loadable, we'll properly check - * things when linking additional classes. */ - continue; - } + if (!(ce->ce_flags & ZEND_ACC_LINKED)) { + /* Only require that already linked classes are loadable, we'll properly check + * things when linking additional classes. */ + continue; + } - if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { - if (preload_update_class_constants(ce) == FAILURE) { - zend_error_noreturn(E_ERROR, - "Failed to resolve initializers of class %s during preloading", - ZSTR_VAL(ce->name)); + if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + if (preload_update_class_constants(ce) == FAILURE) { + zend_error_noreturn(E_ERROR, + "Failed to resolve initializers of class %s during preloading", + ZSTR_VAL(ce->name)); + } + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED); } - ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED); - } - if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { - if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { - zend_property_info *prop; - ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_class_entry *ce = - preload_load_prop_type(prop, ZEND_TYPE_NAME(prop->type)); - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); - } - } ZEND_HASH_FOREACH_END(); + if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_property_info *prop; + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { + if (ZEND_TYPE_IS_NAME(prop->type)) { + zend_class_entry *ce = + preload_load_prop_type(prop, ZEND_TYPE_NAME(prop->type)); + prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); + } + } ZEND_HASH_FOREACH_END(); + } + ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED; } - ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED; - } - } ZEND_HASH_FOREACH_END(); - checked_classes_idx = num_classes; + + zend_function *func; + ZEND_HASH_FOREACH_PTR(&ce->function_table, func) { + preload_ensure_opcode_deps_loadable(func); + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + retry = 1; + checked_classes_idx = num_classes; + } } } diff --git a/ext/opcache/tests/bug78937_1.phpt b/ext/opcache/tests/bug78937_1.phpt index bc285f107b98d..2bf9a9d0b9d79 100644 --- a/ext/opcache/tests/bug78937_1.phpt +++ b/ext/opcache/tests/bug78937_1.phpt @@ -12,14 +12,16 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows ?> --FILE-- --EXPECTF-- -Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6 +Loading AnonDep -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 +Warning: Can't preload unlinked class Foo: Unknown parent FooDep in %spreload_bug78937.inc on line 11 +object(class@anonymous)#1 (0) { +} +Fatal error: Class foo wasn't preloaded in %spreload_bug78937.inc on line 11 diff --git a/ext/opcache/tests/bug78937_2.phpt b/ext/opcache/tests/bug78937_2.phpt index a20c07d231e3f..e159cf7207a4b 100644 --- a/ext/opcache/tests/bug78937_2.phpt +++ b/ext/opcache/tests/bug78937_2.phpt @@ -13,13 +13,16 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows --FILE-- --EXPECTF-- -Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6 +Loading AnonDep -Warning: Can't preload unlinked class class@anonymous: Unknown parent Bar in %spreload_bug78937.inc on line 3 -object(class@anonymous)#%d (0) { +Warning: Can't preload unlinked class Foo: Unknown parent FooDep in %spreload_bug78937.inc on line 11 +object(class@anonymous)#2 (0) { +} +object(Foo)#2 (0) { } diff --git a/ext/opcache/tests/bug78937_3.phpt b/ext/opcache/tests/bug78937_3.phpt index 16f7b80a4035f..4ce2db368f00b 100644 --- a/ext/opcache/tests/bug78937_3.phpt +++ b/ext/opcache/tests/bug78937_3.phpt @@ -14,14 +14,14 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows --EXPECTF-- -Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6 +Loading AnonDep -Warning: Can't preload unlinked class class@anonymous: Unknown parent Bar in %spreload_bug78937.inc on line 3 - -Fatal error: Uncaught Error: Class 'Bar' not found in %spreload_bug78937.inc:3 -Stack trace: -#0 %sbug78937_3.php(3): foo() -#1 {main} - thrown in %spreload_bug78937.inc on line 3 +Warning: Can't preload unlinked class Foo: Unknown parent FooDep in %spreload_bug78937.inc on line 11 +object(class@anonymous)#2 (0) { +} +Loading FooDep +object(Foo)#2 (0) { +} diff --git a/ext/opcache/tests/bug78937_4.phpt b/ext/opcache/tests/bug78937_4.phpt deleted file mode 100644 index 2ad86870de85b..0000000000000 --- a/ext/opcache/tests/bug78937_4.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -Bug #78937.4 (Preloading unlinkable anonymous class can segfault) ---INI-- -opcache.enable=1 -opcache.enable_cli=1 -opcache.optimization_level=-1 -opcache.preload={PWD}/preload_bug78937.inc ---SKIPIF-- - ---FILE-- - ---EXPECTF-- -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 diff --git a/ext/opcache/tests/bug78937_5.phpt b/ext/opcache/tests/bug78937_5.phpt deleted file mode 100644 index 3502699750e39..0000000000000 --- a/ext/opcache/tests/bug78937_5.phpt +++ /dev/null @@ -1,26 +0,0 @@ ---TEST-- -Bug #78937.5 (Preloading unlinkable anonymous class can segfault) ---INI-- -opcache.enable=1 -opcache.enable_cli=1 -opcache.optimization_level=-1 -opcache.preload={PWD}/preload_bug78937.inc ---SKIPIF-- - ---FILE-- - ---EXPECTF-- -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 -object(Foo)#%d (0) { -} diff --git a/ext/opcache/tests/bug78937_6.phpt b/ext/opcache/tests/bug78937_6.phpt deleted file mode 100644 index ec1cc2d2772a5..0000000000000 --- a/ext/opcache/tests/bug78937_6.phpt +++ /dev/null @@ -1,28 +0,0 @@ ---TEST-- -Bug #78937.6 (Preloading unlinkable anonymous class can segfault) ---INI-- -opcache.enable=1 -opcache.enable_cli=1 -opcache.optimization_level=-1 -opcache.preload={PWD}/preload_bug78937.inc ---SKIPIF-- - ---FILE-- - ---EXPECTF-- -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: Uncaught Error: Class 'Bar' not found in %spreload_bug78937.inc:6 -Stack trace: -#0 %sbug78937_6.php(3): bar() -#1 {main} - thrown in %spreload_bug78937.inc on line 6 diff --git a/ext/opcache/tests/preload_bug78937.inc b/ext/opcache/tests/preload_bug78937.inc index 7da2dc3227194..6e4d38dcba496 100644 --- a/ext/opcache/tests/preload_bug78937.inc +++ b/ext/opcache/tests/preload_bug78937.inc @@ -1,8 +1,14 @@ Date: Wed, 11 Mar 2020 17:11:00 +0100 Subject: [PATCH 2/2] Also handle the dynamic class case --- Zend/zend_compile.c | 10 ++++++++++ ext/opcache/ZendAccelerator.c | 14 ++++++++++--- ext/opcache/tests/bug78937_1.phpt | 14 ++++++------- ext/opcache/tests/bug78937_2.phpt | 12 ++++++------ ext/opcache/tests/bug78937_3.phpt | 27 -------------------------- ext/opcache/tests/preload_bug78937.inc | 16 +++++++++++---- 6 files changed, 46 insertions(+), 47 deletions(-) delete mode 100644 ext/opcache/tests/bug78937_3.phpt diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 5661f26439663..57517b3c64e0e 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1081,6 +1081,16 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */ /* Register the derived class */ ce = (zend_class_entry*)Z_PTR_P(zv); + if (ce->ce_flags & ZEND_ACC_LINKED) { + /* The class is already linked, only register the name. */ + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_PRELOADED); + zv = zend_hash_add_ptr(EG(class_table), Z_STR_P(lcname), ce); + if (UNEXPECTED(!zv)) { + 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 SUCCESS; + } + zv = zend_hash_set_bucket_key(EG(class_table), (Bucket*)zv, Z_STR_P(lcname)); if (UNEXPECTED(!zv)) { 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)); diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index b29dab25126b3..801c975cb9667 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3904,10 +3904,18 @@ static void preload_ensure_opcode_deps_loadable(zend_function *func) { zend_error(E_ERROR, "Anonymous class linking during preloading failed"); } } + } else if (opline->opcode == ZEND_DECLARE_CLASS) { + zval *lcname = RT_CONSTANT(opline, opline->op1); + zend_string *rtd_key = Z_STR_P(lcname + 1); + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), rtd_key); + if (ce) { + zend_string *parent_lcname = opline->op2_type == IS_CONST + ? Z_STR_P(RT_CONSTANT(opline, opline->op2)) : NULL; + if (zend_do_link_class(ce, parent_lcname) == FAILURE) { + zend_error(E_ERROR, "Dynamic class linking during preloading failed"); + } + } } - /* TODO: For non-anonymous dynamically declared classes we would have to preload the - * class under the RTD key and then make sure the hash bucket gets reset to the RTD - * key after the request. */ } } diff --git a/ext/opcache/tests/bug78937_1.phpt b/ext/opcache/tests/bug78937_1.phpt index 2bf9a9d0b9d79..bb0e3108f2607 100644 --- a/ext/opcache/tests/bug78937_1.phpt +++ b/ext/opcache/tests/bug78937_1.phpt @@ -12,16 +12,16 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows ?> --FILE-- --EXPECTF-- Loading AnonDep - -Warning: Can't preload unlinked class Foo: Unknown parent FooDep in %spreload_bug78937.inc on line 11 +Loading FooDep +Loading FooDep2 object(class@anonymous)#1 (0) { } - -Fatal error: Class foo wasn't preloaded in %spreload_bug78937.inc on line 11 +object(Foo)#1 (1) { + ["id"]=> + int(1) +} diff --git a/ext/opcache/tests/bug78937_2.phpt b/ext/opcache/tests/bug78937_2.phpt index e159cf7207a4b..4944dfbdc9f96 100644 --- a/ext/opcache/tests/bug78937_2.phpt +++ b/ext/opcache/tests/bug78937_2.phpt @@ -13,16 +13,16 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows --FILE-- --EXPECTF-- Loading AnonDep - -Warning: Can't preload unlinked class Foo: Unknown parent FooDep in %spreload_bug78937.inc on line 11 +Loading FooDep +Loading FooDep2 object(class@anonymous)#2 (0) { } -object(Foo)#2 (0) { +object(Foo)#2 (1) { + ["id"]=> + int(2) } diff --git a/ext/opcache/tests/bug78937_3.phpt b/ext/opcache/tests/bug78937_3.phpt deleted file mode 100644 index 4ce2db368f00b..0000000000000 --- a/ext/opcache/tests/bug78937_3.phpt +++ /dev/null @@ -1,27 +0,0 @@ ---TEST-- -Bug #78937.3 (Preloading unlinkable anonymous class can segfault) ---INI-- -opcache.enable=1 -opcache.enable_cli=1 -opcache.optimization_level=-1 -opcache.preload={PWD}/preload_bug78937.inc ---SKIPIF-- - ---FILE-- - ---EXPECTF-- -Loading AnonDep - -Warning: Can't preload unlinked class Foo: Unknown parent FooDep in %spreload_bug78937.inc on line 11 -object(class@anonymous)#2 (0) { -} -Loading FooDep -object(Foo)#2 (0) { -} diff --git a/ext/opcache/tests/preload_bug78937.inc b/ext/opcache/tests/preload_bug78937.inc index 6e4d38dcba496..b6368c75d7d42 100644 --- a/ext/opcache/tests/preload_bug78937.inc +++ b/ext/opcache/tests/preload_bug78937.inc @@ -7,8 +7,16 @@ spl_autoload_register(function($class) { function foo() { return new class extends AnonDep {}; } -function bar() { - class Foo extends FooDep { - } - return new Foo; +function bar($bool) { + if ($bool) { + class Foo extends FooDep { + public $id = 1; + } + return new Foo; + } else { + class Foo extends FooDep2 { + public $id = 2; + } + return new Foo; + } }