summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--Zend/zend_compile.c4
-rw-r--r--ext/opcache/ZendAccelerator.c106
-rw-r--r--ext/opcache/tests/preload_004.phpt5
-rw-r--r--ext/opcache/tests/preload_009.phpt4
-rw-r--r--ext/opcache/tests/preload_loadable_classes_1.inc18
-rw-r--r--ext/opcache/tests/preload_loadable_classes_1.phpt19
-rw-r--r--ext/opcache/tests/preload_loadable_classes_2.inc6
-rw-r--r--ext/opcache/tests/preload_loadable_classes_2.phpt17
-rw-r--r--ext/opcache/tests/preload_loadable_classes_3.inc5
-rw-r--r--ext/opcache/tests/preload_loadable_classes_3.phpt13
-rw-r--r--ext/opcache/tests/preload_loadable_classes_4.inc3
-rw-r--r--ext/opcache/tests/preload_loadable_classes_4.phpt16
-rw-r--r--ext/opcache/tests/preload_unresolved_prop_type.inc2
-rw-r--r--ext/opcache/tests/preload_unresolved_prop_type.phpt14
-rw-r--r--ext/opcache/tests/preload_unresolved_prop_type_2.inc5
16 files changed, 233 insertions, 6 deletions
diff --git a/NEWS b/NEWS
index afe928926f..8b51908bf6 100644
--- a/NEWS
+++ b/NEWS
@@ -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;
+}