Skip to content

Commit 8424fa5

Browse files
committed
Implement delyed early binding for classes without parents
Fixes phpGH-8846
1 parent 96d4db7 commit 8424fa5

File tree

6 files changed

+72
-5
lines changed

6 files changed

+72
-5
lines changed

Zend/zend_compile.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8057,8 +8057,11 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
80578057
ce->ce_flags |= ZEND_ACC_LINKED;
80588058
zend_observer_class_linked_notify(ce, lcname);
80598059
return;
8060+
} else {
8061+
goto link_unbound;
80608062
}
80618063
} else if (!extends_ast) {
8064+
link_unbound:
80628065
/* Link unbound simple class */
80638066
zend_build_properties_info_table(ce);
80648067
ce->ce_flags |= ZEND_ACC_LINKED;
@@ -8098,11 +8101,17 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
80988101
zend_add_literal_string(&key);
80998102

81008103
opline->opcode = ZEND_DECLARE_CLASS;
8101-
if (extends_ast && toplevel
8104+
if (toplevel
81028105
&& (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING)
81038106
/* We currently don't early-bind classes that implement interfaces or use traits */
81048107
&& !ce->num_interfaces && !ce->num_traits
81058108
) {
8109+
if (!extends_ast) {
8110+
/* Use empty string for classes without parents to avoid new handler, and special
8111+
* handling of zend_early_binding. */
8112+
opline->op2_type = IS_CONST;
8113+
LITERAL_STR(opline->op2, ZSTR_EMPTY_ALLOC());
8114+
}
81068115
CG(active_op_array)->fn_flags |= ZEND_ACC_EARLY_BINDING;
81078116
opline->opcode = ZEND_DECLARE_CLASS_DELAYED;
81088117
opline->extended_value = zend_alloc_cache_slot();

Zend/zend_inheritance.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3276,8 +3276,17 @@ ZEND_API zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_
32763276
inheritance_status status;
32773277
zend_class_entry *proto = NULL;
32783278
zend_class_entry *orig_linking_class;
3279-
uint32_t is_cacheable = ce->ce_flags & ZEND_ACC_IMMUTABLE;
32803279

3280+
if (ce->ce_flags & ZEND_ACC_LINKED) {
3281+
ZEND_ASSERT(ce->parent == NULL);
3282+
if (UNEXPECTED(!register_early_bound_ce(delayed_early_binding, lcname, ce))) {
3283+
return NULL;
3284+
}
3285+
zend_observer_class_linked_notify(ce, lcname);
3286+
return ce;
3287+
}
3288+
3289+
uint32_t is_cacheable = ce->ce_flags & ZEND_ACC_IMMUTABLE;
32813290
UPDATE_IS_CACHEABLE(parent_ce);
32823291
if (is_cacheable) {
32833292
if (zend_inheritance_cache_get && zend_inheritance_cache_add) {

ext/opcache/tests/gh8846-1.inc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?php
2+
class Foo {
3+
const BAR = true;
4+
}

ext/opcache/tests/gh8846-2.inc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
var_dump(Foo::BAR);
3+
class Foo {
4+
const BAR = true;
5+
}

ext/opcache/tests/gh8846.phpt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
Bug GH-8846: Delayed early binding can be used for classes without parents
3+
--EXTENSIONS--
4+
opcache
5+
--CONFLICTS--
6+
server
7+
--INI--
8+
opcache.validate_timestamps=1
9+
opcache.revalidate_freq=0
10+
--FILE--
11+
<?php
12+
13+
file_put_contents(__DIR__ . '/gh8846-index.php', <<<'PHP'
14+
<?php
15+
if (!@$_GET['skip']) {
16+
include __DIR__ . '/gh8846-1.inc';
17+
}
18+
include __DIR__ . '/gh8846-2.inc';
19+
echo "Ok\n";
20+
PHP);
21+
22+
include 'php_cli_server.inc';
23+
php_cli_server_start('-d opcache.enable=1 -d opcache.enable_cli=1');
24+
25+
echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/gh8846-index.php');
26+
echo "\n";
27+
echo file_get_contents('http://' . PHP_CLI_SERVER_ADDRESS . '/gh8846-index.php?skip=1');
28+
?>
29+
--CLEAN--
30+
<?php
31+
@unlink(__DIR__ . '/gh8846-index.php');
32+
?>
33+
--EXPECTF--
34+
bool(true)
35+
<br />
36+
<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 />
37+
38+
bool(true)
39+
Ok

ext/opcache/zend_accelerator_util_funcs.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,10 @@ static void zend_accel_do_delayed_early_binding(
357357
zval *zv = zend_hash_find_known_hash(EG(class_table), early_binding->rtd_key);
358358
if (zv) {
359359
zend_class_entry *orig_ce = Z_CE_P(zv);
360-
zend_class_entry *parent_ce =
361-
zend_hash_find_ex_ptr(EG(class_table), early_binding->lc_parent_name, 1);
362-
if (parent_ce) {
360+
zend_class_entry *parent_ce = !(orig_ce->ce_flags & ZEND_ACC_LINKED)
361+
? zend_hash_find_ex_ptr(EG(class_table), early_binding->lc_parent_name, 1)
362+
: NULL;
363+
if (parent_ce || (orig_ce->ce_flags & ZEND_ACC_LINKED)) {
363364
ce = zend_try_early_bind(orig_ce, parent_ce, early_binding->lcname, zv);
364365
}
365366
}

0 commit comments

Comments
 (0)