Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,16 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */

/* Register the derived class */
ce = (zend_class_entry*)Z_PTR_P(zv);
if (ce->ce_flags & ZEND_ACC_LINKED) {
/* The class is already linked, only register the name. */
ZEND_ASSERT(ce->ce_flags & ZEND_ACC_PRELOADED);
zv = zend_hash_add_ptr(EG(class_table), Z_STR_P(lcname), ce);
if (UNEXPECTED(!zv)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
}
return SUCCESS;
}

zv = zend_hash_set_bucket_key(EG(class_table), (Bucket*)zv, Z_STR_P(lcname));
if (UNEXPECTED(!zv)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
Expand Down
129 changes: 93 additions & 36 deletions ext/opcache/ZendAccelerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -3882,51 +3882,108 @@ static zend_class_entry *preload_load_prop_type(zend_property_info *prop, zend_s
return ce;
}

static void preload_ensure_opcode_deps_loadable(zend_function *func) {
zend_op_array *op_array = (zend_op_array *) func;
uint32_t i;

if (func->type != ZEND_USER_FUNCTION) {
return;
}

for (i = 0; i < op_array->last; i++) {
zend_op *opline = &op_array->opcodes[i];
if (opline->opcode == ZEND_DECLARE_ANON_CLASS) {
zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1));
zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), rtd_key);
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
zend_string *parent_lcname = opline->op2_type == IS_CONST
? Z_STR_P(RT_CONSTANT(opline, opline->op2)) : NULL;
/* TODO: Set file and line. We cannot set in_compilation, because that would
* prevent autoloading. */
if (zend_do_link_class(ce, parent_lcname) == FAILURE) {
zend_error(E_ERROR, "Anonymous class linking during preloading failed");
}
}
} else if (opline->opcode == ZEND_DECLARE_CLASS) {
zval *lcname = RT_CONSTANT(opline, opline->op1);
zend_string *rtd_key = Z_STR_P(lcname + 1);
zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), rtd_key);
if (ce) {
zend_string *parent_lcname = opline->op2_type == IS_CONST
? Z_STR_P(RT_CONSTANT(opline, opline->op2)) : NULL;
if (zend_do_link_class(ce, parent_lcname) == FAILURE) {
zend_error(E_ERROR, "Dynamic class linking during preloading failed");
}
}
}
}
}

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;
uint32_t checked_classes_idx = 0, checked_functions_idx = 0;
zend_bool retry = 1;
while (retry) {
uint32_t num_classes, num_functions;
retry = 0;

num_functions = zend_hash_num_elements(EG(function_table));
if (num_functions != checked_functions_idx) {
zend_function *func;
ZEND_HASH_FOREACH_PTR(EG(function_table), func) {
preload_ensure_opcode_deps_loadable(func);
} ZEND_HASH_FOREACH_END();

retry = 1;
checked_functions_idx = num_functions;
}

ZEND_HASH_REVERSE_FOREACH_PTR(EG(class_table), ce) {
if (ce->type == ZEND_INTERNAL_CLASS || _idx == checked_classes_idx) {
break;
}
num_classes = zend_hash_num_elements(EG(class_table));
if (num_classes != checked_classes_idx) {
zend_class_entry *ce;
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;
}
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;
}

if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
if (preload_update_class_constants(ce) == FAILURE) {
zend_error_noreturn(E_ERROR,
"Failed to resolve initializers of class %s during preloading",
ZSTR_VAL(ce->name));
if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
if (preload_update_class_constants(ce) == FAILURE) {
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);
}
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();
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;
}
ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED;
}
} ZEND_HASH_FOREACH_END();
checked_classes_idx = num_classes;

zend_function *func;
ZEND_HASH_FOREACH_PTR(&ce->function_table, func) {
preload_ensure_opcode_deps_loadable(func);
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();

retry = 1;
checked_classes_idx = num_classes;
}
}
}

Expand Down
18 changes: 10 additions & 8 deletions ext/opcache/tests/bug78937_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows
?>
--FILE--
<?php
class Bar {
}
var_dump(foo());
var_dump(bar(true));
?>
--EXPECTF--
Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6

Warning: Can't preload unlinked class class@anonymous: Unknown parent Bar in %spreload_bug78937.inc on line 3

Fatal error: Anonymous class wasn't preloaded in %spreload_bug78937.inc on line 3

Loading AnonDep
Loading FooDep
Loading FooDep2
object(class@anonymous)#1 (0) {
}
object(Foo)#1 (1) {
["id"]=>
int(1)
}
15 changes: 9 additions & 6 deletions ext/opcache/tests/bug78937_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows
--FILE--
<?php
include(__DIR__ . "/preload_bug78937.inc");
class Bar {
}
var_dump(foo());
var_dump(bar(false));
?>
--EXPECTF--
Warning: Can't preload unlinked class Foo: Unknown parent Bar in %spreload_bug78937.inc on line 6

Warning: Can't preload unlinked class class@anonymous: Unknown parent Bar in %spreload_bug78937.inc on line 3
object(class@anonymous)#%d (0) {
Loading AnonDep
Loading FooDep
Loading FooDep2
object(class@anonymous)#2 (0) {
}
object(Foo)#2 (1) {
["id"]=>
int(2)
}
27 changes: 0 additions & 27 deletions ext/opcache/tests/bug78937_3.phpt

This file was deleted.

25 changes: 0 additions & 25 deletions ext/opcache/tests/bug78937_4.phpt

This file was deleted.

26 changes: 0 additions & 26 deletions ext/opcache/tests/bug78937_5.phpt

This file was deleted.

28 changes: 0 additions & 28 deletions ext/opcache/tests/bug78937_6.phpt

This file was deleted.

22 changes: 18 additions & 4 deletions ext/opcache/tests/preload_bug78937.inc
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
<?php
spl_autoload_register(function($class) {
echo "Loading $class\n";
eval("class $class {}");
});

function foo() {
return new class extends Bar {};
return new class extends AnonDep {};
}
function bar() {
class Foo extends Bar {
}
function bar($bool) {
if ($bool) {
class Foo extends FooDep {
public $id = 1;
}
return new Foo;
} else {
class Foo extends FooDep2 {
public $id = 2;
}
return new Foo;
}
}