Skip to content

Commit

Permalink
Implement delyed early binding for classes without parents
Browse files Browse the repository at this point in the history
  • Loading branch information
iluuu1994 committed May 12, 2023
1 parent 96d4db7 commit 66764f5
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 5 deletions.
11 changes: 10 additions & 1 deletion Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
11 changes: 10 additions & 1 deletion Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions ext/opcache/tests/gh8846-1.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?php
class Foo {
const BAR = true;
}
5 changes: 5 additions & 0 deletions ext/opcache/tests/gh8846-2.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php
var_dump(Foo::BAR);
class Foo {
const BAR = true;
}
39 changes: 39 additions & 0 deletions ext/opcache/tests/gh8846.phpt
Original file line number Diff line number Diff line change
@@ -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
7 changes: 4 additions & 3 deletions ext/opcache/zend_accelerator_util_funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down

0 comments on commit 66764f5

Please sign in to comment.