Skip to content

Commit 3f86adb

Browse files
committed
Fixed bug #78935: Check that all linked classes can be preloaded
During preloading, check that all classes that have been included as part of the preload script itself (rather than through opcache_compile_file) can actually be preloaded, i.e. satisfy Windows restrictions, have resolved initializers and resolved property types. When resolving initializers and property types, also autoload additional classes. Because of this, the resolution runs in a loop.
1 parent 4313659 commit 3f86adb

16 files changed

+233
-6
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ PHP NEWS
3636
- OPcache:
3737
. Fixed $x = (bool)$x; with opcache (should emit undeclared variable notice).
3838
(Tyson Andre)
39+
. Fixed bug #78935 (Preloading removes classes that have dependencies).
40+
(Nikita, Dmitry)
3941

4042
- PCRE:
4143
. Fixed bug #78853 (preg_match() may return integer > 1). (cmb)

Zend/zend_compile.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6497,13 +6497,13 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
64976497

64986498
if (toplevel
64996499
/* We currently don't early-bind classes that implement interfaces or use traits */
6500-
&& !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) {
6500+
&& !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))
6501+
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
65016502
if (extends_ast) {
65026503
zend_class_entry *parent_ce = zend_lookup_class_ex(
65036504
ce->parent_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
65046505

65056506
if (parent_ce
6506-
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) /* delay inheritance till preloading */
65076507
&& ((parent_ce->type != ZEND_INTERNAL_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES))
65086508
&& ((parent_ce->type != ZEND_USER_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) || (parent_ce->info.user.filename == ce->info.user.filename))) {
65096509

ext/opcache/ZendAccelerator.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3850,6 +3850,108 @@ static void preload_link(void)
38503850
} ZEND_HASH_FOREACH_END();
38513851
}
38523852

3853+
#ifdef ZEND_WIN32
3854+
static void preload_check_windows_restriction(zend_class_entry *scope, zend_class_entry *ce) {
3855+
if (ce && ce->type == ZEND_INTERNAL_CLASS) {
3856+
zend_error_noreturn(E_ERROR,
3857+
"Class %s uses internal class %s during preloading, which is not supported on Windows",
3858+
ZSTR_VAL(scope->name), ZSTR_VAL(ce->name));
3859+
}
3860+
}
3861+
3862+
static void preload_check_windows_restrictions(zend_class_entry *scope) {
3863+
uint32_t i;
3864+
3865+
preload_check_windows_restriction(scope, scope->parent);
3866+
3867+
for (i = 0; i < scope->num_interfaces; i++) {
3868+
preload_check_windows_restriction(scope, scope->interfaces[i]);
3869+
}
3870+
}
3871+
#endif
3872+
3873+
static zend_class_entry *preload_load_prop_type(zend_property_info *prop, zend_string *name) {
3874+
zend_class_entry *ce;
3875+
if (zend_string_equals_literal_ci(name, "self")) {
3876+
ce = prop->ce;
3877+
} else if (zend_string_equals_literal_ci(name, "parent")) {
3878+
ce = prop->ce->parent;
3879+
} else {
3880+
ce = zend_lookup_class(name);
3881+
}
3882+
if (ce) {
3883+
return ce;
3884+
}
3885+
3886+
zend_error_noreturn(E_ERROR,
3887+
"Failed to load class %s used by typed property %s::$%s during preloading",
3888+
ZSTR_VAL(name), ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name));
3889+
return ce;
3890+
}
3891+
3892+
static void preload_ensure_classes_loadable() {
3893+
/* Run this in a loop, because additional classes may be loaded while updating constants etc. */
3894+
uint32_t checked_classes_idx = 0;
3895+
while (1) {
3896+
zend_class_entry *ce;
3897+
uint32_t num_classes = zend_hash_num_elements(EG(class_table));
3898+
if (num_classes == checked_classes_idx) {
3899+
return;
3900+
}
3901+
3902+
ZEND_HASH_REVERSE_FOREACH_PTR(EG(class_table), ce) {
3903+
if (ce->type == ZEND_INTERNAL_CLASS || _idx == checked_classes_idx) {
3904+
break;
3905+
}
3906+
3907+
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
3908+
/* Only require that already linked classes are loadable, we'll properly check
3909+
* things when linking additional classes. */
3910+
continue;
3911+
}
3912+
3913+
#ifdef ZEND_WIN32
3914+
preload_check_windows_restrictions(ce);
3915+
#endif
3916+
3917+
if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
3918+
int result = SUCCESS;
3919+
zend_try {
3920+
result = zend_update_class_constants(ce);
3921+
} zend_catch {
3922+
/* Provide some context for the generated error. */
3923+
zend_error_noreturn(E_ERROR,
3924+
"Error generated while resolving initializers of class %s during preloading",
3925+
ZSTR_VAL(ce->name));
3926+
} zend_end_try();
3927+
if (result == FAILURE) {
3928+
/* Just present to be safe: We generally always throw some
3929+
* other fatal error as part of update_class_constants(). */
3930+
zend_error_noreturn(E_ERROR,
3931+
"Failed to resolve initializers of class %s during preloading",
3932+
ZSTR_VAL(ce->name));
3933+
}
3934+
ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED);
3935+
}
3936+
3937+
if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
3938+
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
3939+
zend_property_info *prop;
3940+
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
3941+
if (ZEND_TYPE_IS_NAME(prop->type)) {
3942+
zend_class_entry *ce =
3943+
preload_load_prop_type(prop, ZEND_TYPE_NAME(prop->type));
3944+
prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type));
3945+
}
3946+
} ZEND_HASH_FOREACH_END();
3947+
}
3948+
ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED;
3949+
}
3950+
} ZEND_HASH_FOREACH_END();
3951+
checked_classes_idx = num_classes;
3952+
}
3953+
}
3954+
38533955
static zend_string *preload_resolve_path(zend_string *filename)
38543956
{
38553957
if (is_stream_path(ZSTR_VAL(filename))) {
@@ -4205,6 +4307,10 @@ static int accel_preload(const char *config)
42054307
CG(unclean_shutdown) = 1;
42064308
ret = FAILURE;
42074309
}
4310+
4311+
if (ret == SUCCESS) {
4312+
preload_ensure_classes_loadable();
4313+
}
42084314
} zend_catch {
42094315
ret = FAILURE;
42104316
} zend_end_try();

ext/opcache/tests/preload_004.phpt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ opcache.preload={PWD}/preload_undef_const.inc
1212
var_dump(class_exists('Foo'));
1313
?>
1414
--EXPECTF--
15-
Warning: Can't preload class Foo with unresolved initializer for constant A in %spreload_undef_const.inc on line 2
16-
bool(false)
15+
Fatal error: Undefined class constant 'self::DOES_NOT_EXIST' in Unknown on line 0
16+
17+
Fatal error: Error generated while resolving initializers of class Foo during preloading in Unknown on line 0

ext/opcache/tests/preload_009.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ var_dump(trait_exists('T'));
1313
var_dump(class_exists('Foo'));
1414
?>
1515
--EXPECTF--
16-
Warning: Can't preload class Foo with unresolved initializer for constant C in %spreload_undef_const_2.inc on line 8
16+
Warning: Use of undefined constant UNDEF - assumed 'UNDEF' (this will throw an Error in a future version of PHP) in Unknown on line 0
17+
bool(true)
1718
bool(true)
18-
bool(false)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
spl_autoload_register(function($class) {
4+
if ($class == 'Bar') {
5+
class Bar {
6+
const BAZ = 42;
7+
8+
public self $x;
9+
public Foo $y;
10+
}
11+
} else if ($class == 'Foo') {
12+
class Foo {}
13+
}
14+
});
15+
16+
class Test {
17+
const FOO = Bar::BAZ;
18+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Preloading: Loadable class checking (1)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_loadable_classes_1.inc
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
<?php
12+
var_dump(class_exists('Test'));
13+
var_dump(class_exists('Bar'));
14+
var_dump(class_exists('Foo'));
15+
?>
16+
--EXPECT--
17+
bool(true)
18+
bool(true)
19+
bool(true)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
class Test {
4+
const X = UNDEF;
5+
const Y = Foo::UNDEF;
6+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Preloading: Loadable class checking (2)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_loadable_classes_2.inc
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
Unreachable
12+
--EXPECTF--
13+
Warning: Use of undefined constant UNDEF - assumed 'UNDEF' (this will throw an Error in a future version of PHP) in Unknown on line 0
14+
15+
Fatal error: Class 'Foo' not found in Unknown on line 0
16+
17+
Fatal error: Error generated while resolving initializers of class Test during preloading in Unknown on line 0
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class Test {
4+
protected Foo $prop;
5+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Preloading: Loadable class checking (3)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_loadable_classes_3.inc
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
Unreachable
12+
--EXPECTF--
13+
Fatal error: Failed to load class Foo used by typed property Test::$prop during preloading in Unknown on line 0
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
class Test extends Exception {}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Preloading: Loadable class checking (4)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_loadable_classes_4.inc
8+
--SKIPIF--
9+
<?php
10+
require_once('skipif.inc');
11+
if (PHP_OS_FAMILY != 'Windows') die('skip Windows only');
12+
?>
13+
--FILE--
14+
Unreachable
15+
--EXPECTF--
16+
Fatal error: Class Test uses internal class Exception during preloading, which is not supported on Windows in Unknown on line 0
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
opcache_compile_file(__DIR__ . "/preload_unresolved_prop_type_2.inc");
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Preload: Unresolved property type
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_unresolved_prop_type.inc
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
===DONE===
12+
--EXPECTF--
13+
Warning: Can't preload class Test with unresolved property types in %s on line %d
14+
===DONE===
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
class Test {
4+
public Unknown $prop;
5+
}

0 commit comments

Comments
 (0)