1
0
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:
Ilija Tovilo
2023-05-10 20:38:33 +02:00
parent 6bd546462c
commit 0600f513b3
7 changed files with 74 additions and 5 deletions

2
NEWS
View File

@@ -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)

View File

@@ -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();

View File

@@ -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) {

View File

@@ -0,0 +1,4 @@
<?php
class Foo {
const BAR = true;
}

View File

@@ -0,0 +1,5 @@
<?php
var_dump(Foo::BAR);
class Foo {
const BAR = true;
}

View 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

View File

@@ -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);
}
}