Skip to content

Commit d4c1c60

Browse files
committed
Implement dynamic class const fetch
1 parent f669a55 commit d4c1c60

File tree

9 files changed

+527
-289
lines changed

9 files changed

+527
-289
lines changed

Zend/Optimizer/compact_literals.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,9 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
211211
if (opline->op1_type == IS_CONST) {
212212
LITERAL_INFO(opline->op1.constant, 2);
213213
}
214-
LITERAL_INFO(opline->op2.constant, 1);
214+
if (opline->op2_type == IS_CONST) {
215+
LITERAL_INFO(opline->op2.constant, 1);
216+
}
215217
break;
216218
case ZEND_ASSIGN_STATIC_PROP:
217219
case ZEND_ASSIGN_STATIC_PROP_REF:
@@ -668,7 +670,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
668670
}
669671
break;
670672
case ZEND_FETCH_CLASS_CONSTANT:
671-
if (opline->op1_type == IS_CONST) {
673+
if (opline->op1_type == IS_CONST && opline->op2_type == IS_CONST) {
672674
// op1/op2 class_const
673675
opline->extended_value = add_static_slot(&hash, op_array,
674676
opline->op1.constant,
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
--TEST--
2+
Dynamic class constant fetch
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public const BAR = 'bar';
8+
}
9+
10+
function test($code) {
11+
try {
12+
var_dump(eval($code));
13+
} catch (Throwable $e) {
14+
echo $e->getMessage(), "\n";
15+
}
16+
}
17+
18+
$const_names = [
19+
['', '"BAR"'],
20+
['$bar = "BAR";', '$bar'],
21+
['$ba = "BA"; $r = "R";', '$ba . $r'],
22+
['', 'strtoupper("bar")'],
23+
['', '$barr'],
24+
['$bar = "BAR"; $barRef = &$bar;', '$barRef'],
25+
['', 'strtolower("CLASS")'],
26+
['', '42'],
27+
['$bar = 42;', '$bar'],
28+
['', '[]'],
29+
['$bar = [];', '$bar'],
30+
];
31+
32+
foreach ($const_names as [$prolog, $const_name]) {
33+
test("$prolog return Foo::{{$const_name}};");
34+
test("\$foo = 'Foo'; $prolog return \$foo::{{$const_name}};");
35+
}
36+
37+
?>
38+
--EXPECTF--
39+
string(3) "bar"
40+
string(3) "bar"
41+
string(3) "bar"
42+
string(3) "bar"
43+
string(3) "bar"
44+
string(3) "bar"
45+
string(3) "bar"
46+
string(3) "bar"
47+
48+
Warning: Undefined variable $barr in %s : eval()'d code on line %d
49+
Undefined constant Foo::
50+
51+
Warning: Undefined variable $barr in %s : eval()'d code on line %d
52+
Undefined constant Foo::
53+
string(3) "bar"
54+
string(3) "bar"
55+
Cannot dynamically fetch class constant "class"
56+
Cannot dynamically fetch class constant "class"
57+
Undefined constant Foo::42
58+
Undefined constant Foo::42
59+
Undefined constant Foo::42
60+
Undefined constant Foo::42
61+
62+
Warning: Array to string conversion in %s : eval()'d code on line %d
63+
Undefined constant Foo::Array
64+
65+
Warning: Array to string conversion in %s : eval()'d code on line %d
66+
Undefined constant Foo::Array
67+
68+
Warning: Array to string conversion in %s : eval()'d code on line %d
69+
Undefined constant Foo::Array
70+
71+
Warning: Array to string conversion in %s : eval()'d code on line %d
72+
Undefined constant Foo::Array
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Dynamic class constant fetch in constant expressions
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public const BAR = 'bar';
8+
public const BA = 'BA';
9+
public const R = 'R';
10+
public const CLASS_ = 'class';
11+
public const A = self::{'BAR'};
12+
public const B = self::{'BA' . 'R'};
13+
public const C = self::{self::BA . self::R};
14+
}
15+
16+
var_dump(Foo::A);
17+
var_dump(Foo::B);
18+
var_dump(Foo::C);
19+
20+
?>
21+
--EXPECT--
22+
string(3) "bar"
23+
string(3) "bar"
24+
string(3) "bar"

Zend/zend_compile.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9592,16 +9592,18 @@ static void zend_compile_class_const(znode *result, zend_ast *ast) /* {{{ */
95929592
class_ast = ast->child[0];
95939593
const_ast = ast->child[1];
95949594

9595-
if (class_ast->kind == ZEND_AST_ZVAL) {
9596-
zend_string *resolved_name;
9597-
9598-
resolved_name = zend_resolve_class_name_ast(class_ast);
9599-
if (const_ast->kind == ZEND_AST_ZVAL && zend_try_ct_eval_class_const(&result->u.constant, resolved_name, zend_ast_get_str(const_ast))) {
9600-
result->op_type = IS_CONST;
9595+
if (class_ast->kind == ZEND_AST_ZVAL && const_ast->kind == ZEND_AST_ZVAL) {
9596+
zval *const_zv = zend_ast_get_zval(const_ast);
9597+
if (Z_TYPE_P(const_zv) == IS_STRING) {
9598+
zend_string *const_str = Z_STR_P(const_zv);
9599+
zend_string *resolved_name = zend_resolve_class_name_ast(class_ast);
9600+
if (zend_try_ct_eval_class_const(&result->u.constant, resolved_name, const_str)) {
9601+
result->op_type = IS_CONST;
9602+
zend_string_release_ex(resolved_name, 0);
9603+
return;
9604+
}
96019605
zend_string_release_ex(resolved_name, 0);
9602-
return;
96039606
}
9604-
zend_string_release_ex(resolved_name, 0);
96059607
}
96069608

96079609
zend_compile_class_ref(&class_node, class_ast, ZEND_FETCH_CLASS_EXCEPTION);

Zend/zend_language_parser.y

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,10 @@ class_constant:
13631363
{ $$ = zend_ast_create_class_const_or_name($1, $3); }
13641364
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM identifier
13651365
{ $$ = zend_ast_create_class_const_or_name($1, $3); }
1366+
| class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}'
1367+
{ $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $4); }
1368+
| variable_class_name T_PAAMAYIM_NEKUDOTAYIM '{' expr '}'
1369+
{ $$ = zend_ast_create(ZEND_AST_CLASS_CONST, $1, $4); }
13661370
;
13671371

13681372
optional_expr:

Zend/zend_vm_def.h

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5854,15 +5854,32 @@ ZEND_VM_HOT_HANDLER(99, ZEND_FETCH_CONSTANT, UNUSED|CONST_FETCH, CONST, CACHE_SL
58545854
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
58555855
}
58565856

5857-
ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CONST, CACHE_SLOT)
5857+
// FIXME: Small performance regression due to ANY. Can we make this CONST|ANY?
5858+
ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, ANY, CACHE_SLOT)
58585859
{
58595860
zend_class_entry *ce, *scope;
58605861
zend_class_constant *c;
58615862
zval *value, *zv;
5863+
zend_string *constant_name;
58625864
USE_OPLINE
58635865

58645866
SAVE_OPLINE();
58655867

5868+
if (OP2_TYPE == IS_CONST) {
5869+
constant_name = zval_get_string(RT_CONSTANT(opline, opline->op2));
5870+
} else {
5871+
constant_name = zval_get_string(GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R));
5872+
5873+
if (zend_string_equals_literal_ci(constant_name, "class")) {
5874+
// FIXME: This should probably just be implemented, since it will work for folded constant expressions
5875+
zend_throw_error(NULL, "Cannot dynamically fetch class constant \"class\"");
5876+
ZVAL_UNDEF(EX_VAR(opline->result.var));
5877+
zend_string_release_ex(constant_name, /* persistent */ false);
5878+
FREE_OP2();
5879+
HANDLE_EXCEPTION();
5880+
}
5881+
}
5882+
58665883
do {
58675884
if (OP1_TYPE == IS_CONST) {
58685885
if (EXPECTED(CACHED_PTR(opline->extended_value + sizeof(void*)))) {
@@ -5874,6 +5891,8 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58745891
ce = zend_fetch_class_by_name(Z_STR_P(RT_CONSTANT(opline, opline->op1)), Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1), ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
58755892
if (UNEXPECTED(ce == NULL)) {
58765893
ZVAL_UNDEF(EX_VAR(opline->result.var));
5894+
zend_string_release_ex(constant_name, /* persistent */ false);
5895+
FREE_OP2();
58775896
HANDLE_EXCEPTION();
58785897
}
58795898
}
@@ -5882,6 +5901,8 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58825901
ce = zend_fetch_class(NULL, opline->op1.num);
58835902
if (UNEXPECTED(ce == NULL)) {
58845903
ZVAL_UNDEF(EX_VAR(opline->result.var));
5904+
zend_string_release_ex(constant_name, /* persistent */ false);
5905+
FREE_OP2();
58855906
HANDLE_EXCEPTION();
58865907
}
58875908
} else {
@@ -5893,19 +5914,24 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58935914
}
58945915
}
58955916

5896-
zv = zend_hash_find_known_hash(CE_CONSTANTS_TABLE(ce), Z_STR_P(RT_CONSTANT(opline, opline->op2)));
5917+
zv = zend_hash_find(CE_CONSTANTS_TABLE(ce), constant_name);
5918+
58975919
if (EXPECTED(zv != NULL)) {
58985920
c = Z_PTR_P(zv);
58995921
scope = EX(func)->op_array.scope;
59005922
if (!zend_verify_const_access(c, scope)) {
5901-
zend_throw_error(NULL, "Cannot access %s constant %s::%s", zend_visibility_string(ZEND_CLASS_CONST_FLAGS(c)), ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2)));
5923+
zend_throw_error(NULL, "Cannot access %s constant %s::%s", zend_visibility_string(ZEND_CLASS_CONST_FLAGS(c)), ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
59025924
ZVAL_UNDEF(EX_VAR(opline->result.var));
5925+
zend_string_release_ex(constant_name, /* persistent */ false);
5926+
FREE_OP2();
59035927
HANDLE_EXCEPTION();
59045928
}
59055929

59065930
if (ce->ce_flags & ZEND_ACC_TRAIT) {
5907-
zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2)));
5931+
zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
59085932
ZVAL_UNDEF(EX_VAR(opline->result.var));
5933+
zend_string_release_ex(constant_name, /* persistent */ false);
5934+
FREE_OP2();
59095935
HANDLE_EXCEPTION();
59105936
}
59115937

@@ -5914,27 +5940,35 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
59145940
if (ce->ce_flags & ZEND_ACC_ENUM && ce->enum_backing_type != IS_UNDEF && ce->type == ZEND_USER_CLASS && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
59155941
if (UNEXPECTED(zend_update_class_constants(ce) == FAILURE)) {
59165942
ZVAL_UNDEF(EX_VAR(opline->result.var));
5943+
zend_string_release_ex(constant_name, /* persistent */ false);
5944+
FREE_OP2();
59175945
HANDLE_EXCEPTION();
59185946
}
59195947
}
59205948
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
59215949
zval_update_constant_ex(value, c->ce);
59225950
if (UNEXPECTED(EG(exception) != NULL)) {
59235951
ZVAL_UNDEF(EX_VAR(opline->result.var));
5952+
zend_string_release_ex(constant_name, /* persistent */ false);
5953+
FREE_OP2();
59245954
HANDLE_EXCEPTION();
59255955
}
59265956
}
59275957
CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value);
59285958
} else {
59295959
zend_throw_error(NULL, "Undefined constant %s::%s",
5930-
ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2)));
5960+
ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
59315961
ZVAL_UNDEF(EX_VAR(opline->result.var));
5962+
zend_string_release_ex(constant_name, /* persistent */ false);
5963+
FREE_OP2();
59325964
HANDLE_EXCEPTION();
59335965
}
59345966
} while (0);
59355967

59365968
ZVAL_COPY_OR_DUP(EX_VAR(opline->result.var), value);
59375969

5970+
zend_string_release_ex(constant_name, /* persistent */ false);
5971+
FREE_OP2();
59385972
ZEND_VM_NEXT_OPCODE();
59395973
}
59405974

0 commit comments

Comments
 (0)