Skip to content

Commit 0600f51

Browse files
committed
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
1 parent 6bd5464 commit 0600f51

File tree

7 files changed

+74
-5
lines changed

7 files changed

+74
-5
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ PHP NEWS
3535
has inherited it from its parent). (ilutov)
3636
. Fix bug GH-11154 (Negative indices on empty array don't affect next chosen
3737
index). (ColinHDev)
38+
. Fix bug GH-8846 (Implement delayed early binding for classes without
39+
parents). (ilutov)
3840

3941
- Date:
4042
. Implement More Appropriate Date/Time Exceptions RFC. (Derick)

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)