Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check that all "required" classes can be preloaded #4997

Closed
wants to merge 6 commits into from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions Zend/zend_compile.c
Expand Up @@ -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))) {

Expand Down
106 changes: 106 additions & 0 deletions ext/opcache/ZendAccelerator.c
Expand Up @@ -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))) {
Expand Down Expand Up @@ -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();
Expand Down
5 changes: 3 additions & 2 deletions ext/opcache/tests/preload_004.phpt
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be add a hint to try preloading through opcache_compile_file()?


Fatal error: Error generated while resolving initializers of class Foo during preloading in Unknown on line 0
4 changes: 2 additions & 2 deletions ext/opcache/tests/preload_009.phpt
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a questionable decision. It may be better to throw error with a hint to try preloading through opcache_compile_file(). Anyway, this may be fixed later.

bool(true)
bool(true)
bool(false)
3 changes: 3 additions & 0 deletions ext/opcache/tests/preload_class_alias.inc
@@ -0,0 +1,3 @@
<?php
class A {}
class_alias(A::class, 'B');
17 changes: 17 additions & 0 deletions ext/opcache/tests/preload_class_alias.phpt
@@ -0,0 +1,17 @@
--TEST--
Bug #78918: Class alias during preloading causes assertion failure
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.preload={PWD}/preload_class_alias.inc
--SKIPIF--
<?php require_once('skipif.inc'); ?>
--FILE--
<?php
var_dump(class_exists('A'));
var_dump(class_exists('B'));
?>
--EXPECT--
bool(true)
bool(true)
18 changes: 18 additions & 0 deletions 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;
}
19 changes: 19 additions & 0 deletions 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)
6 changes: 6 additions & 0 deletions ext/opcache/tests/preload_loadable_classes_2.inc
@@ -0,0 +1,6 @@
<?php

class Test {
const X = UNDEF;
const Y = Foo::UNDEF;
}
17 changes: 17 additions & 0 deletions 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
5 changes: 5 additions & 0 deletions ext/opcache/tests/preload_loadable_classes_3.inc
@@ -0,0 +1,5 @@
<?php

class Test {
protected Foo $prop;
}
13 changes: 13 additions & 0 deletions 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
3 changes: 3 additions & 0 deletions ext/opcache/tests/preload_loadable_classes_4.inc
@@ -0,0 +1,3 @@
<?php

class Test extends Exception {}
16 changes: 16 additions & 0 deletions 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
2 changes: 2 additions & 0 deletions ext/opcache/tests/preload_unresolved_prop_type.inc
@@ -0,0 +1,2 @@
<?php
opcache_compile_file(__DIR__ . "/preload_unresolved_prop_type_2.inc");
14 changes: 14 additions & 0 deletions 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===
5 changes: 5 additions & 0 deletions ext/opcache/tests/preload_unresolved_prop_type_2.inc
@@ -0,0 +1,5 @@
<?php

class Test {
public Unknown $prop;
}
6 changes: 6 additions & 0 deletions ext/opcache/zend_persist.c
Expand Up @@ -700,6 +700,12 @@ static void zend_persist_class_entry(zval *zv)
zend_class_entry *ce = Z_PTR_P(zv);

if (ce->type == ZEND_USER_CLASS) {
/* The same zend_class_entry may be reused by class_alias */
zend_class_entry *new_ce = zend_shared_alloc_get_xlat_entry(ce);
if (new_ce) {
Z_PTR_P(zv) = new_ce;
return;
}
if ((ce->ce_flags & ZEND_ACC_LINKED)
&& (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)
&& (ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)
Expand Down
6 changes: 6 additions & 0 deletions ext/opcache/zend_persist_calc.c
Expand Up @@ -352,6 +352,12 @@ static void zend_persist_class_entry_calc(zval *zv)
Bucket *p;

if (ce->type == ZEND_USER_CLASS) {
/* The same zend_class_entry may be reused by class_alias */
if (zend_shared_alloc_get_xlat_entry(ce)) {
return;
}
zend_shared_alloc_register_xlat_entry(ce, ce);

check_property_type_resolution(ce);

ZCG(is_immutable_class) =
Expand Down