Skip to content

Commit f53b993

Browse files
committed
Fixed bug #78014 (Preloaded classes may depend on non-preloaded classes due to unresolved consts)
1 parent 5f1d76a commit f53b993

File tree

5 files changed

+172
-91
lines changed

5 files changed

+172
-91
lines changed

Zend/zend_compile.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6389,6 +6389,7 @@ zend_op *zend_compile_class_decl(zend_ast *ast, zend_bool toplevel) /* {{{ */
63896389
zend_class_entry *parent_ce = zend_lookup_class_ex(ce->parent_name, NULL, 0);
63906390

63916391
if (parent_ce
6392+
&& !(CG(compiler_options) & ZEND_COMPILE_PRELOAD) /* delay inheritance till preloading */
63926393
&& ((parent_ce->type != ZEND_INTERNAL_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES))
63936394
&& ((parent_ce->type != ZEND_USER_CLASS) || !(CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) || (parent_ce->info.user.filename == ce->info.user.filename))
63946395
) {

ext/opcache/ZendAccelerator.c

Lines changed: 132 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3296,6 +3296,106 @@ static void get_unresolved_initializer(zend_class_entry *ce, const char **kind,
32963296
} ZEND_HASH_FOREACH_END();
32973297
}
32983298

3299+
static zend_bool preload_try_resolve_constants(zend_class_entry *ce)
3300+
{
3301+
zend_bool ok, changed;
3302+
zend_class_constant *c;
3303+
zval *val;
3304+
3305+
EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */
3306+
do {
3307+
ok = 1;
3308+
changed = 0;
3309+
ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) {
3310+
val = &c->value;
3311+
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3312+
if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) {
3313+
changed = 1;
3314+
} else {
3315+
ok = 0;
3316+
}
3317+
}
3318+
} ZEND_HASH_FOREACH_END();
3319+
if (ce->default_properties_count) {
3320+
uint32_t i;
3321+
for (i = 0; i < ce->default_properties_count; i++) {
3322+
val = &ce->default_properties_table[i];
3323+
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3324+
zend_property_info *prop = ce->properties_info_table[i];
3325+
if (UNEXPECTED(zval_update_constant_ex(val, prop->ce) != SUCCESS)) {
3326+
ok = 0;
3327+
}
3328+
}
3329+
}
3330+
}
3331+
if (ce->default_static_members_count) {
3332+
uint32_t count = ce->parent ? ce->default_static_members_count - ce->parent->default_static_members_count : ce->default_static_members_count;
3333+
3334+
val = ce->default_static_members_table + ce->default_static_members_count - 1;
3335+
while (count) {
3336+
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3337+
if (UNEXPECTED(zval_update_constant_ex(val, ce) != SUCCESS)) {
3338+
ok = 0;
3339+
}
3340+
}
3341+
val--;
3342+
count--;
3343+
}
3344+
}
3345+
} while (changed && !ok);
3346+
EG(exception) = NULL;
3347+
3348+
return ok;
3349+
}
3350+
3351+
static zend_bool preload_try_resolve_property_types(zend_class_entry *ce)
3352+
{
3353+
zend_bool ok = 1;
3354+
zend_property_info *prop;
3355+
zend_class_entry *p;
3356+
3357+
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
3358+
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
3359+
zend_string *name, *lcname;
3360+
3361+
if (!ZEND_TYPE_IS_NAME(prop->type)) {
3362+
continue;
3363+
}
3364+
3365+
name = ZEND_TYPE_NAME(prop->type);
3366+
lcname = zend_string_tolower(name);
3367+
p = zend_hash_find_ptr(EG(class_table), lcname);
3368+
zend_string_release(lcname);
3369+
if (!p) {
3370+
ok = 0;
3371+
continue;
3372+
}
3373+
if (p != ce) {
3374+
#ifdef ZEND_WIN32
3375+
/* On Windows we can't link with internal class, because of ASLR */
3376+
if (p->type == ZEND_INTERNAL_CLASS) {
3377+
ok = 0;
3378+
continue;
3379+
}
3380+
#endif
3381+
if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
3382+
ok = 0;
3383+
continue;
3384+
}
3385+
if (!(p->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
3386+
ok = 0;
3387+
continue;
3388+
}
3389+
}
3390+
3391+
zend_string_release(name);
3392+
prop->type = ZEND_TYPE_ENCODE_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type));
3393+
} ZEND_HASH_FOREACH_END();
3394+
}
3395+
3396+
return ok;
3397+
}
3398+
32993399
static void preload_link(void)
33003400
{
33013401
zval *zv;
@@ -3339,6 +3439,12 @@ static void preload_link(void)
33393439
/* On Windows we can't link with internal class, because of ASLR */
33403440
if (parent->type == ZEND_INTERNAL_CLASS) continue;
33413441
#endif
3442+
if (!(parent->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
3443+
continue;
3444+
}
3445+
if (!(parent->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
3446+
continue;
3447+
}
33423448
}
33433449

33443450
if (ce->num_interfaces) {
@@ -3356,6 +3462,10 @@ static void preload_link(void)
33563462
break;
33573463
}
33583464
#endif
3465+
if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
3466+
found = 0;
3467+
break;
3468+
}
33593469
}
33603470
if (!found) continue;
33613471
}
@@ -3387,111 +3497,42 @@ static void preload_link(void)
33873497
/* Set filename & lineno information for inheritance errors */
33883498
CG(in_compilation) = 1;
33893499
CG(compiled_filename) = ce->info.user.filename;
3390-
CG(zend_lineno) = ce->info.user.line_start;
3500+
if (ce->parent_name
3501+
&& !ce->num_interfaces
3502+
&& !ce->num_traits
3503+
&& (parent->type == ZEND_INTERNAL_CLASS
3504+
|| parent->info.user.filename == ce->info.user.filename)) {
3505+
/* simulate early binding */
3506+
CG(zend_lineno) = ce->info.user.line_end;
3507+
} else {
3508+
CG(zend_lineno) = ce->info.user.line_start;
3509+
}
33913510
zend_do_link_class(ce, parent);
33923511
CG(in_compilation) = 0;
33933512
CG(compiled_filename) = NULL;
33943513

33953514
changed = 1;
33963515
}
33973516
}
3398-
} ZEND_HASH_FOREACH_END();
3399-
} while (changed);
3400-
3401-
/* Resolve class constants */
3402-
EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */
3403-
do {
3404-
changed = 0;
3405-
ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) {
3406-
ce = Z_PTR_P(zv);
3407-
if (ce->type == ZEND_INTERNAL_CLASS) {
3408-
break;
3409-
}
3410-
if ((ce->ce_flags & ZEND_ACC_LINKED)
3411-
&& !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
3412-
zend_bool ok = 1;
3413-
zend_class_constant *c;
3414-
zval *val;
3415-
3416-
ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) {
3417-
val = &c->value;
3418-
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3419-
if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) {
3420-
changed = 1;
3421-
} else {
3422-
ok = 0;
3423-
}
3424-
}
3425-
} ZEND_HASH_FOREACH_END();
3426-
if (ce->default_properties_count) {
3427-
uint32_t i;
3428-
for (i = 0; i < ce->default_properties_count; i++) {
3429-
val = &ce->default_properties_table[i];
3430-
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3431-
zend_property_info *prop = ce->properties_info_table[i];
3432-
if (UNEXPECTED(zval_update_constant_ex(val, prop->ce) != SUCCESS)) {
3433-
ok = 0;
3434-
}
3435-
}
3517+
if (ce->ce_flags & ZEND_ACC_LINKED) {
3518+
if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
3519+
if ((ce->ce_flags & ZEND_ACC_TRAIT) /* don't update traits */
3520+
|| preload_try_resolve_constants(ce)) {
3521+
ce->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
3522+
changed = 1;
34363523
}
34373524
}
3438-
if (ce->default_static_members_count) {
3439-
uint32_t count = ce->parent ? ce->default_static_members_count - ce->parent->default_static_members_count : ce->default_static_members_count;
3440-
3441-
val = ce->default_static_members_table + ce->default_static_members_count - 1;
3442-
while (count) {
3443-
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3444-
if (UNEXPECTED(zval_update_constant_ex(val, ce) != SUCCESS)) {
3445-
ok = 0;
3446-
}
3447-
}
3448-
val--;
3449-
count--;
3525+
3526+
if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
3527+
if ((ce->ce_flags & ZEND_ACC_TRAIT) /* don't update traits */
3528+
|| preload_try_resolve_property_types(ce)) {
3529+
ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED;
3530+
changed = 1;
34503531
}
34513532
}
3452-
if (ok) {
3453-
ce->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
3454-
}
34553533
}
34563534
} ZEND_HASH_FOREACH_END();
34573535
} while (changed);
3458-
EG(exception) = NULL;
3459-
3460-
/* Resolve property types */
3461-
ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) {
3462-
ce = Z_PTR_P(zv);
3463-
if (ce->type == ZEND_INTERNAL_CLASS) {
3464-
break;
3465-
}
3466-
if ((ce->ce_flags & ZEND_ACC_LINKED)
3467-
&& !(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) {
3468-
zend_bool ok = 1;
3469-
zend_property_info *prop;
3470-
if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) {
3471-
ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
3472-
zend_string *name, *lcname;
3473-
if (!ZEND_TYPE_IS_NAME(prop->type)) {
3474-
continue;
3475-
}
3476-
3477-
name = ZEND_TYPE_NAME(prop->type);
3478-
lcname = zend_string_tolower(name);
3479-
p = zend_hash_find_ptr(EG(class_table), lcname);
3480-
zend_string_release(lcname);
3481-
if (!p) {
3482-
ok = 0;
3483-
continue;
3484-
}
3485-
3486-
zend_string_release(name);
3487-
prop->type = ZEND_TYPE_ENCODE_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type));
3488-
} ZEND_HASH_FOREACH_END();
3489-
}
3490-
if (ok) {
3491-
ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED;
3492-
}
3493-
}
3494-
} ZEND_HASH_FOREACH_END();
34953536

34963537
/* Move unlinked clases (and with unresilved constants) back to scripts */
34973538
orig_dtor = EG(class_table)->pDestructor;

ext/opcache/tests/bug78014.inc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
class A {
3+
function foo() { return 0; }
4+
}
5+
class B extends A {
6+
const X = UNRESOLVED;
7+
}
8+
class C extends B {
9+
const X = 42;
10+
function foo() { return 42; }
11+
}

ext/opcache/tests/bug78014.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
Bug #78014 (Preloaded classes may depend on non-preloaded classes due to unresolved consts)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.preload={PWD}/preload_bug78014.inc
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
<?php
12+
class B extends A {
13+
function foo(): int { return 24; }
14+
}
15+
$c = new C;
16+
var_dump($c->foo());
17+
?>
18+
--EXPECTF--
19+
Warning: Can't preload unlinked class C in %s on line %d
20+
21+
Warning: Can't preload class B with unresolved initializer for constant X in %s on line %d
22+
23+
Fatal error: Uncaught Error: Class 'C' not found in %sbug78014.php:5
24+
Stack trace:
25+
#0 {main}
26+
thrown in %sbug78014.php on line 5
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__ . "/bug78014.inc");

0 commit comments

Comments
 (0)