mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
Implement delayed early binding for classes without parents
Normally, we add classes without parents (and no interfaces or traits) directly to the class map, early binding the class. However, if the same class has already been registered, we would instead just add a ZEND_DECLARE_CLASS instruction and let the handler throw a duplicate class declaration exception. However, with opcache, if on the next request the files are included in the opposite order, we won't perform early binding. To fix this, create a ZEND_DECLARE_CLASS_DELAYED instruction instead and handle classes without parents accordingly, skipping any linking for classes that are already linked in delayed early binding. Fixes GH-8846
This commit is contained in:
2
NEWS
2
NEWS
@@ -35,6 +35,8 @@ PHP NEWS
|
||||
has inherited it from its parent). (ilutov)
|
||||
. Fix bug GH-11154 (Negative indices on empty array don't affect next chosen
|
||||
index). (ColinHDev)
|
||||
. Fix bug GH-8846 (Implement delayed early binding for classes without
|
||||
parents). (ilutov)
|
||||
|
||||
- Date:
|
||||
. Implement More Appropriate Date/Time Exceptions RFC. (Derick)
|
||||
|
||||
@@ -8057,8 +8057,11 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
|
||||
ce->ce_flags |= ZEND_ACC_LINKED;
|
||||
zend_observer_class_linked_notify(ce, lcname);
|
||||
return;
|
||||
} else {
|
||||
goto link_unbound;
|
||||
}
|
||||
} else if (!extends_ast) {
|
||||
link_unbound:
|
||||
/* Link unbound simple class */
|
||||
zend_build_properties_info_table(ce);
|
||||
ce->ce_flags |= ZEND_ACC_LINKED;
|
||||
@@ -8098,11 +8101,17 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
|
||||
zend_add_literal_string(&key);
|
||||
|
||||
opline->opcode = ZEND_DECLARE_CLASS;
|
||||
if (extends_ast && toplevel
|
||||
if (toplevel
|
||||
&& (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING)
|
||||
/* We currently don't early-bind classes that implement interfaces or use traits */
|
||||
&& !ce->num_interfaces && !ce->num_traits
|
||||
) {
|
||||
if (!extends_ast) {
|
||||
/* Use empty string for classes without parents to avoid new handler, and special
|
||||
* handling of zend_early_binding. */
|
||||
opline->op2_type = IS_CONST;
|
||||
LITERAL_STR(opline->op2, ZSTR_EMPTY_ALLOC());
|
||||
}
|
||||
CG(active_op_array)->fn_flags |= ZEND_ACC_EARLY_BINDING;
|
||||
opline->opcode = ZEND_DECLARE_CLASS_DELAYED;
|
||||
opline->extended_value = zend_alloc_cache_slot();
|
||||
|
||||
@@ -3276,8 +3276,17 @@ ZEND_API zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_
|
||||
inheritance_status status;
|
||||
zend_class_entry *proto = NULL;
|
||||
zend_class_entry *orig_linking_class;
|
||||
uint32_t is_cacheable = ce->ce_flags & ZEND_ACC_IMMUTABLE;
|
||||
|
||||
if (ce->ce_flags & ZEND_ACC_LINKED) {
|
||||
ZEND_ASSERT(ce->parent == NULL);
|
||||
if (UNEXPECTED(!register_early_bound_ce(delayed_early_binding, lcname, ce))) {
|
||||
return NULL;
|
||||
}
|
||||
zend_observer_class_linked_notify(ce, lcname);
|
||||
return ce;
|
||||
}
|
||||
|
||||
uint32_t is_cacheable = ce->ce_flags & ZEND_ACC_IMMUTABLE;
|
||||
UPDATE_IS_CACHEABLE(parent_ce);
|
||||
if (is_cacheable) {
|
||||
if (zend_inheritance_cache_get && zend_inheritance_cache_add) {
|
||||
|
||||
4
ext/opcache/tests/gh8846-1.inc
Normal file
4
ext/opcache/tests/gh8846-1.inc
Normal file
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
class Foo {
|
||||
const BAR = true;
|
||||
}
|
||||
5
ext/opcache/tests/gh8846-2.inc
Normal file
5
ext/opcache/tests/gh8846-2.inc
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
var_dump(Foo::BAR);
|
||||
class Foo {
|
||||
const BAR = true;
|
||||
}
|
||||
39
ext/opcache/tests/gh8846.phpt
Normal file
39
ext/opcache/tests/gh8846.phpt
Normal file
@@ -0,0 +1,39 @@
|
||||
--TEST--
|
||||
Bug GH-8846: Delayed early binding can be used for classes without parents
|
||||
--EXTENSIONS--
|
||||
opcache
|
||||
--CONFLICTS--
|
||||
server
|
||||
--INI--
|
||||
opcache.validate_timestamps=1
|
||||
opcache.revalidate_freq=0
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
file_put_contents(__DIR__ . '/gh8846-index.php', <<<'PHP'
|
||||
<?php
|
||||
if (!@$_GET['skip']) {
|
||||
include __DIR__ . '/gh8846-1.inc';
|
||||
}
|
||||
include __DIR__ . '/gh8846-2.inc';
|
||||
echo "Ok\n";
|
||||
PHP);
|
||||
|
||||
include 'php_cli_server.inc';
|
||||
php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1');
|
||||
|
||||
echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/gh8846-index.php');
|
||||
echo "\n";
|
||||
echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/gh8846-index.php?skip=1');
|
||||
?>
|
||||
--CLEAN--
|
||||
<?php
|
||||
@unlink(__DIR__ . '/gh8846-index.php');
|
||||
?>
|
||||
--EXPECTF--
|
||||
bool(true)
|
||||
<br />
|
||||
<b>Fatal error</b>: Cannot declare class Foo, because the name is already in use in <b>%sgh8846-2.inc</b> on line <b>%d</b><br />
|
||||
|
||||
bool(true)
|
||||
Ok
|
||||
@@ -357,9 +357,10 @@ static void zend_accel_do_delayed_early_binding(
|
||||
zval *zv = zend_hash_find_known_hash(EG(class_table), early_binding->rtd_key);
|
||||
if (zv) {
|
||||
zend_class_entry *orig_ce = Z_CE_P(zv);
|
||||
zend_class_entry *parent_ce =
|
||||
zend_hash_find_ex_ptr(EG(class_table), early_binding->lc_parent_name, 1);
|
||||
if (parent_ce) {
|
||||
zend_class_entry *parent_ce = !(orig_ce->ce_flags & ZEND_ACC_LINKED)
|
||||
? zend_hash_find_ex_ptr(EG(class_table), early_binding->lc_parent_name, 1)
|
||||
: NULL;
|
||||
if (parent_ce || (orig_ce->ce_flags & ZEND_ACC_LINKED)) {
|
||||
ce = zend_try_early_bind(orig_ce, parent_ce, early_binding->lcname, zv);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user