Skip to content

Commit 5cdea8d

Browse files
committed
Merge branch 'PHP-7.4'
* PHP-7.4: Add support for class_alias to preloading Fixed bug #78935: Check that all linked classes can be preloaded
2 parents 33f7cab + baf3a91 commit 5cdea8d

19 files changed

+271
-7
lines changed

Zend/zend_compile.c

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

66756675
if (toplevel
66766676
/* We currently don't early-bind classes that implement interfaces or use traits */
6677-
&& !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) {
6677+
&& !(ce->ce_flags & (ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))
6678+
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD)) {
66786679
if (extends_ast) {
66796680
zend_class_entry *parent_ce = zend_lookup_class_ex(
66806681
ce->parent_name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD);
66816682

66826683
if (parent_ce
6683-
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) /* delay inheritance till preloading */
66846684
&& ((parent_ce->type != ZEND_INTERNAL_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES))
66856685
&& ((parent_ce->type != ZEND_USER_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) || (parent_ce->info.user.filename == ce->info.user.filename))) {
66866686

ext/opcache/ZendAccelerator.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3935,6 +3935,117 @@ static void preload_link(void)
39353935
} ZEND_HASH_FOREACH_END();
39363936
}
39373937

3938+
#ifdef ZEND_WIN32
3939+
static void preload_check_windows_restriction(zend_class_entry *scope, zend_class_entry *ce) {
3940+
if (ce && ce->type == ZEND_INTERNAL_CLASS) {
3941+
zend_error_noreturn(E_ERROR,
3942+
"Class %s uses internal class %s during preloading, which is not supported on Windows",
3943+
ZSTR_VAL(scope->name), ZSTR_VAL(ce->name));
3944+
}
3945+
}
3946+
3947+
static void preload_check_windows_restrictions(zend_class_entry *scope) {
3948+
uint32_t i;
3949+
3950+
preload_check_windows_restriction(scope, scope->parent);
3951+
3952+
for (i = 0; i < scope->num_interfaces; i++) {
3953+
preload_check_windows_restriction(scope, scope->interfaces[i]);
3954+
}
3955+
}
3956+
#endif
3957+
3958+
static zend_class_entry *preload_load_prop_type(zend_property_info *prop, zend_string *name) {
3959+
zend_class_entry *ce;
3960+
if (zend_string_equals_literal_ci(name, "self")) {
3961+
ce = prop->ce;
3962+
} else if (zend_string_equals_literal_ci(name, "parent")) {
3963+
ce = prop->ce->parent;
3964+
} else {
3965+
ce = zend_lookup_class(name);
3966+
}
3967+
if (ce) {
3968+
return ce;
3969+
}
3970+
3971+
zend_error_noreturn(E_ERROR,
3972+
"Failed to load class %s used by typed property %s::$%s during preloading",
3973+
ZSTR_VAL(name), ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name));
3974+
return ce;
3975+
}
3976+
3977+
static void preload_ensure_classes_loadable() {
3978+
/* Run this in a loop, because additional classes may be loaded while updating constants etc. */
3979+
uint32_t checked_classes_idx = 0;
3980+
while (1) {
3981+
zend_class_entry *ce;
3982+
uint32_t num_classes = zend_hash_num_elements(EG(class_table));
3983+
if (num_classes == checked_classes_idx) {
3984+
return;
3985+
}
3986+
3987+
ZEND_HASH_REVERSE_FOREACH_PTR(EG(class_table), ce) {
3988+
if (ce->type == ZEND_INTERNAL_CLASS || _idx == checked_classes_idx) {
3989+
break;
3990+
}
3991+
3992+
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
3993+
/* Only require that already linked classes are loadable, we'll properly check
3994+
* things when linking additional classes. */
3995+
continue;
3996+
}
3997+
3998+
#ifdef ZEND_WIN32
3999+
preload_check_windows_restrictions(ce);
4000+
#endif
4001+
4002+
if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
4003+
int result = SUCCESS;
4004+
zend_try {
4005+
result = zend_update_class_constants(ce);
4006+
} zend_catch {
4007+
/* Provide some context for the generated error. */
4008+
zend_error_noreturn(E_ERROR,
4009+
"Error generated while resolving initializers of class %s during preloading",
4010+
ZSTR_VAL(ce->name));
4011+
} zend_end_try();
4012+
if (result == FAILURE) {
4013+
/* Just present to be safe: We generally always throw some
4014+
* other fatal error as part of update_class_constants(). */
4015+
zend_error_noreturn(E_ERROR,
4016+
"Failed to resolve initializers of class %s during preloading",
4017+
ZSTR_VAL(ce->name));
4018+
}
4019+
ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED);
4020+
}
4021+
4022+
if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
4023+
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
4024+
zend_property_info *prop;
4025+
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
4026+
if (ZEND_TYPE_HAS_LIST(prop->type)) {
4027+
void **entry;
4028+
ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) {
4029+
if (ZEND_TYPE_LIST_IS_NAME(*entry)) {
4030+
zend_class_entry *ce = preload_load_prop_type(
4031+
prop, ZEND_TYPE_LIST_GET_NAME(*entry));
4032+
*entry = ZEND_TYPE_LIST_ENCODE_CE(ce);
4033+
}
4034+
} ZEND_TYPE_LIST_FOREACH_END();
4035+
} else if (ZEND_TYPE_HAS_NAME(prop->type)) {
4036+
zend_class_entry *ce =
4037+
preload_load_prop_type(prop, ZEND_TYPE_NAME(prop->type));
4038+
ZEND_TYPE_SET_CE(prop->type, ce);
4039+
}
4040+
} ZEND_HASH_FOREACH_END();
4041+
}
4042+
ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED;
4043+
}
4044+
} ZEND_HASH_FOREACH_END();
4045+
checked_classes_idx = num_classes;
4046+
}
4047+
}
4048+
39384049
static zend_string *preload_resolve_path(zend_string *filename)
39394050
{
39404051
if (is_stream_path(ZSTR_VAL(filename))) {
@@ -4290,6 +4401,10 @@ static int accel_preload(const char *config)
42904401
CG(unclean_shutdown) = 1;
42914402
ret = FAILURE;
42924403
}
4404+
4405+
if (ret == SUCCESS) {
4406+
preload_ensure_classes_loadable();
4407+
}
42934408
} zend_catch {
42944409
ret = FAILURE;
42954410
} 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: 3 additions & 3 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
17-
bool(true)
18-
bool(false)
16+
Fatal error: Undefined constant 'UNDEF' in Unknown on line 0
17+
18+
Fatal error: Error generated while resolving initializers of class Foo 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+
class A {}
3+
class_alias(A::class, 'B');
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Bug #78918: Class alias during preloading causes assertion failure
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_class_alias.inc
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
<?php
12+
var_dump(class_exists('A'));
13+
var_dump(class_exists('B'));
14+
?>
15+
--EXPECT--
16+
bool(true)
17+
bool(true)
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
Fatal error: Undefined constant 'UNDEF' in Unknown on line 0
14+
15+
Fatal error: Error generated while resolving initializers of class Test during preloading in Unknown on line 0

0 commit comments

Comments
 (0)