diff options
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | Zend/zend_compile.c | 4 | ||||
-rw-r--r-- | ext/opcache/ZendAccelerator.c | 106 | ||||
-rw-r--r-- | ext/opcache/tests/preload_004.phpt | 5 | ||||
-rw-r--r-- | ext/opcache/tests/preload_009.phpt | 4 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_1.inc | 18 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_1.phpt | 19 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_2.inc | 6 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_2.phpt | 17 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_3.inc | 5 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_3.phpt | 13 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_4.inc | 3 | ||||
-rw-r--r-- | ext/opcache/tests/preload_loadable_classes_4.phpt | 16 | ||||
-rw-r--r-- | ext/opcache/tests/preload_unresolved_prop_type.inc | 2 | ||||
-rw-r--r-- | ext/opcache/tests/preload_unresolved_prop_type.phpt | 14 | ||||
-rw-r--r-- | ext/opcache/tests/preload_unresolved_prop_type_2.inc | 5 |
16 files changed, 233 insertions, 6 deletions
@@ -36,6 +36,8 @@ PHP NEWS - OPcache: . Fixed $x = (bool)$x; with opcache (should emit undeclared variable notice). (Tyson Andre) + . Fixed bug #78935 (Preloading removes classes that have dependencies). + (Nikita, Dmitry) - PCRE: . Fixed bug #78853 (preg_match() may return integer > 1). (cmb) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 116fc0b7c5..7d35a92ade 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6497,13 +6497,13 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */ if (toplevel /* We currently don't early-bind classes that implement interfaces or use traits */ - && !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) { + && !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS)) + && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) { if (extends_ast) { zend_class_entry *parent_ce = zend_lookup_class_ex( ce->parent_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); 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 895bf82b45..ccd300390f 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3850,6 +3850,108 @@ static void preload_link(void) } ZEND_HASH_FOREACH_END(); } +#ifdef ZEND_WIN32 +static void preload_check_windows_restriction(zend_class_entry *scope, zend_class_entry *ce) { + if (ce && ce->type == ZEND_INTERNAL_CLASS) { + zend_error_noreturn(E_ERROR, + "Class %s uses internal class %s during preloading, which is not supported on Windows", + ZSTR_VAL(scope->name), ZSTR_VAL(ce->name)); + } +} + +static void preload_check_windows_restrictions(zend_class_entry *scope) { + uint32_t i; + + preload_check_windows_restriction(scope, scope->parent); + + for (i = 0; i < scope->num_interfaces; i++) { + preload_check_windows_restriction(scope, scope->interfaces[i]); + } +} +#endif + +static zend_class_entry *preload_load_prop_type(zend_property_info *prop, zend_string *name) { + zend_class_entry *ce; + if (zend_string_equals_literal_ci(name, "self")) { + ce = prop->ce; + } else if (zend_string_equals_literal_ci(name, "parent")) { + ce = prop->ce->parent; + } else { + ce = zend_lookup_class(name); + } + if (ce) { + return ce; + } + + zend_error_noreturn(E_ERROR, + "Failed to load class %s used by typed property %s::$%s during preloading", + ZSTR_VAL(name), ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name)); + return ce; +} + +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; + } + + 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; + } + +#ifdef ZEND_WIN32 + preload_check_windows_restrictions(ce); +#endif + + if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + int result = SUCCESS; + zend_try { + result = zend_update_class_constants(ce); + } zend_catch { + /* Provide some context for the generated error. */ + zend_error_noreturn(E_ERROR, + "Error generated while resolving initializers of class %s during preloading", + ZSTR_VAL(ce->name)); + } zend_end_try(); + if (result == FAILURE) { + /* Just present to be safe: We generally always throw some + * other fatal error as part of update_class_constants(). */ + 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); + } + + 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; + } + } ZEND_HASH_FOREACH_END(); + checked_classes_idx = num_classes; + } +} + static zend_string *preload_resolve_path(zend_string *filename) { if (is_stream_path(ZSTR_VAL(filename))) { @@ -4205,6 +4307,10 @@ static int accel_preload(const char *config) CG(unclean_shutdown) = 1; ret = FAILURE; } + + if (ret == SUCCESS) { + preload_ensure_classes_loadable(); + } } zend_catch { ret = FAILURE; } zend_end_try(); diff --git a/ext/opcache/tests/preload_004.phpt b/ext/opcache/tests/preload_004.phpt index 0441c78883..df92abdc28 100644 --- a/ext/opcache/tests/preload_004.phpt +++ b/ext/opcache/tests/preload_004.phpt @@ -12,5 +12,6 @@ opcache.preload={PWD}/preload_undef_const.inc var_dump(class_exists('Foo')); ?> --EXPECTF-- -Warning: Can't preload class Foo with unresolved initializer for constant A in %spreload_undef_const.inc on line 2 -bool(false) +Fatal error: Undefined class constant 'self::DOES_NOT_EXIST' in Unknown on line 0 + +Fatal error: Error generated while resolving initializers of class Foo during preloading in Unknown on line 0 diff --git a/ext/opcache/tests/preload_009.phpt b/ext/opcache/tests/preload_009.phpt index 84f444768c..1adbf5b006 100644 --- a/ext/opcache/tests/preload_009.phpt +++ b/ext/opcache/tests/preload_009.phpt @@ -13,6 +13,6 @@ var_dump(trait_exists('T')); var_dump(class_exists('Foo')); ?> --EXPECTF-- -Warning: Can't preload class Foo with unresolved initializer for constant C in %spreload_undef_const_2.inc on line 8 +Warning: Use of undefined constant UNDEF - assumed 'UNDEF' (this will throw an Error in a future version of PHP) in Unknown on line 0 +bool(true) bool(true) -bool(false) diff --git a/ext/opcache/tests/preload_loadable_classes_1.inc b/ext/opcache/tests/preload_loadable_classes_1.inc new file mode 100644 index 0000000000..b2bdabae1b --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_1.inc @@ -0,0 +1,18 @@ +<?php + +spl_autoload_register(function($class) { + if ($class == 'Bar') { + class Bar { + const BAZ = 42; + + public self $x; + public Foo $y; + } + } else if ($class == 'Foo') { + class Foo {} + } +}); + +class Test { + const FOO = Bar::BAZ; +} diff --git a/ext/opcache/tests/preload_loadable_classes_1.phpt b/ext/opcache/tests/preload_loadable_classes_1.phpt new file mode 100644 index 0000000000..8673814403 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_1.phpt @@ -0,0 +1,19 @@ +--TEST-- +Preloading: Loadable class checking (1) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_loadable_classes_1.inc +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +<?php +var_dump(class_exists('Test')); +var_dump(class_exists('Bar')); +var_dump(class_exists('Foo')); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/opcache/tests/preload_loadable_classes_2.inc b/ext/opcache/tests/preload_loadable_classes_2.inc new file mode 100644 index 0000000000..d21384dcd4 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_2.inc @@ -0,0 +1,6 @@ +<?php + +class Test { + const X = UNDEF; + const Y = Foo::UNDEF; +} diff --git a/ext/opcache/tests/preload_loadable_classes_2.phpt b/ext/opcache/tests/preload_loadable_classes_2.phpt new file mode 100644 index 0000000000..4a5d2b8a66 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_2.phpt @@ -0,0 +1,17 @@ +--TEST-- +Preloading: Loadable class checking (2) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_loadable_classes_2.inc +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +Unreachable +--EXPECTF-- +Warning: Use of undefined constant UNDEF - assumed 'UNDEF' (this will throw an Error in a future version of PHP) in Unknown on line 0 + +Fatal error: Class 'Foo' not found in Unknown on line 0 + +Fatal error: Error generated while resolving initializers of class Test during preloading in Unknown on line 0 diff --git a/ext/opcache/tests/preload_loadable_classes_3.inc b/ext/opcache/tests/preload_loadable_classes_3.inc new file mode 100644 index 0000000000..d5c550f8c4 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_3.inc @@ -0,0 +1,5 @@ +<?php + +class Test { + protected Foo $prop; +} diff --git a/ext/opcache/tests/preload_loadable_classes_3.phpt b/ext/opcache/tests/preload_loadable_classes_3.phpt new file mode 100644 index 0000000000..a48692960a --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_3.phpt @@ -0,0 +1,13 @@ +--TEST-- +Preloading: Loadable class checking (3) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_loadable_classes_3.inc +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +Unreachable +--EXPECTF-- +Fatal error: Failed to load class Foo used by typed property Test::$prop during preloading in Unknown on line 0 diff --git a/ext/opcache/tests/preload_loadable_classes_4.inc b/ext/opcache/tests/preload_loadable_classes_4.inc new file mode 100644 index 0000000000..162de6eab2 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_4.inc @@ -0,0 +1,3 @@ +<?php + +class Test extends Exception {} diff --git a/ext/opcache/tests/preload_loadable_classes_4.phpt b/ext/opcache/tests/preload_loadable_classes_4.phpt new file mode 100644 index 0000000000..07f0d094ce --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_4.phpt @@ -0,0 +1,16 @@ +--TEST-- +Preloading: Loadable class checking (4) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_loadable_classes_4.inc +--SKIPIF-- +<?php +require_once('skipif.inc'); +if (PHP_OS_FAMILY != 'Windows') die('skip Windows only'); +?> +--FILE-- +Unreachable +--EXPECTF-- +Fatal error: Class Test uses internal class Exception during preloading, which is not supported on Windows in Unknown on line 0 diff --git a/ext/opcache/tests/preload_unresolved_prop_type.inc b/ext/opcache/tests/preload_unresolved_prop_type.inc new file mode 100644 index 0000000000..05f4ee06a3 --- /dev/null +++ b/ext/opcache/tests/preload_unresolved_prop_type.inc @@ -0,0 +1,2 @@ +<?php +opcache_compile_file(__DIR__ . "/preload_unresolved_prop_type_2.inc"); diff --git a/ext/opcache/tests/preload_unresolved_prop_type.phpt b/ext/opcache/tests/preload_unresolved_prop_type.phpt new file mode 100644 index 0000000000..117f36692a --- /dev/null +++ b/ext/opcache/tests/preload_unresolved_prop_type.phpt @@ -0,0 +1,14 @@ +--TEST-- +Preload: Unresolved property type +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_unresolved_prop_type.inc +--SKIPIF-- +<?php require_once('skipif.inc'); ?> +--FILE-- +===DONE=== +--EXPECTF-- +Warning: Can't preload class Test with unresolved property types in %s on line %d +===DONE=== diff --git a/ext/opcache/tests/preload_unresolved_prop_type_2.inc b/ext/opcache/tests/preload_unresolved_prop_type_2.inc new file mode 100644 index 0000000000..c5557d1117 --- /dev/null +++ b/ext/opcache/tests/preload_unresolved_prop_type_2.inc @@ -0,0 +1,5 @@ +<?php + +class Test { + public Unknown $prop; +} |