Skip to content

Commit e348735

Browse files
committed
Implement dynamic class const fetch
1 parent 3044af2 commit e348735

File tree

7 files changed

+481
-279
lines changed

7 files changed

+481
-279
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Dynamic class constant fetch
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public const BAR = 'bar';
8+
}
9+
10+
$bar = 'BAR';
11+
$ba = 'BA';
12+
$r = 'R';
13+
14+
var_dump(Foo::{'BAR'});
15+
var_dump(Foo::{$bar});
16+
var_dump(Foo::{$ba . $r});
17+
var_dump(Foo::{strtoupper('bar')});
18+
19+
try {
20+
var_dump(Foo::{$barr});
21+
} catch (Throwable $e) {
22+
echo $e->getMessage(), "\n";
23+
}
24+
25+
$barRef = &$bar;
26+
var_dump(Foo::{$bar});
27+
28+
try {
29+
var_dump(Foo::{strtolower('CLASS')});
30+
} catch (Throwable $e) {
31+
echo $e->getMessage(), "\n";
32+
}
33+
34+
?>
35+
--EXPECTF--
36+
string(3) "bar"
37+
string(3) "bar"
38+
string(3) "bar"
39+
string(3) "bar"
40+
41+
Warning: Undefined variable $barr in %s on line %d
42+
Undefined constant Foo::
43+
string(3) "bar"
44+
Cannot dynamically fetch class constant "class"
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_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: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5853,15 +5853,40 @@ ZEND_VM_HOT_HANDLER(99, ZEND_FETCH_CONSTANT, UNUSED|CONST_FETCH, CONST, CACHE_SL
58535853
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
58545854
}
58555855

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

58635865
SAVE_OPLINE();
58645866

5867+
if (OP2_TYPE == IS_CONST) {
5868+
// FIXME: Unreachable for now, check if we can add this specialization
5869+
constant_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
5870+
} else {
5871+
zval *op2 = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R);
5872+
if (!try_convert_to_string(op2)) {
5873+
zend_throw_error(NULL, "Illegal offset type");
5874+
ZVAL_UNDEF(EX_VAR(opline->result.var));
5875+
FREE_OP2();
5876+
HANDLE_EXCEPTION();
5877+
}
5878+
constant_name = Z_STR_P(op2);
5879+
// FIXME: Not sure when hashes are calculated and why this is necessary only here
5880+
zend_string_hash_val(constant_name);
5881+
5882+
if (zend_string_equals_literal_ci(constant_name, "class")) {
5883+
// FIXME: This should probably just be implemented, since it will work for folded constant expressions
5884+
zend_throw_error(NULL, "Cannot dynamically fetch class constant \"class\"");
5885+
FREE_OP2();
5886+
HANDLE_EXCEPTION();
5887+
}
5888+
}
5889+
58655890
do {
58665891
if (OP1_TYPE == IS_CONST) {
58675892
if (EXPECTED(CACHED_PTR(opline->extended_value + sizeof(void*)))) {
@@ -5873,6 +5898,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58735898
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);
58745899
if (UNEXPECTED(ce == NULL)) {
58755900
ZVAL_UNDEF(EX_VAR(opline->result.var));
5901+
FREE_OP2();
58765902
HANDLE_EXCEPTION();
58775903
}
58785904
}
@@ -5881,6 +5907,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58815907
ce = zend_fetch_class(NULL, opline->op1.num);
58825908
if (UNEXPECTED(ce == NULL)) {
58835909
ZVAL_UNDEF(EX_VAR(opline->result.var));
5910+
FREE_OP2();
58845911
HANDLE_EXCEPTION();
58855912
}
58865913
} else {
@@ -5892,19 +5919,21 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58925919
}
58935920
}
58945921

5895-
zv = zend_hash_find_known_hash(CE_CONSTANTS_TABLE(ce), Z_STR_P(RT_CONSTANT(opline, opline->op2)));
5922+
zv = zend_hash_find_known_hash(CE_CONSTANTS_TABLE(ce), constant_name);
58965923
if (EXPECTED(zv != NULL)) {
58975924
c = Z_PTR_P(zv);
58985925
scope = EX(func)->op_array.scope;
58995926
if (!zend_verify_const_access(c, scope)) {
5900-
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)));
5927+
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));
59015928
ZVAL_UNDEF(EX_VAR(opline->result.var));
5929+
FREE_OP2();
59025930
HANDLE_EXCEPTION();
59035931
}
59045932

59055933
if (ce->ce_flags & ZEND_ACC_TRAIT) {
5906-
zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2)));
5934+
zend_throw_error(NULL, "Cannot access trait constant %s::%s directly", ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
59075935
ZVAL_UNDEF(EX_VAR(opline->result.var));
5936+
FREE_OP2();
59085937
HANDLE_EXCEPTION();
59095938
}
59105939

@@ -5913,27 +5942,31 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
59135942
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)) {
59145943
if (UNEXPECTED(zend_update_class_constants(ce) == FAILURE)) {
59155944
ZVAL_UNDEF(EX_VAR(opline->result.var));
5945+
FREE_OP2();
59165946
HANDLE_EXCEPTION();
59175947
}
59185948
}
59195949
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
59205950
zval_update_constant_ex(value, c->ce);
59215951
if (UNEXPECTED(EG(exception) != NULL)) {
59225952
ZVAL_UNDEF(EX_VAR(opline->result.var));
5953+
FREE_OP2();
59235954
HANDLE_EXCEPTION();
59245955
}
59255956
}
59265957
CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value);
59275958
} else {
59285959
zend_throw_error(NULL, "Undefined constant %s::%s",
5929-
ZSTR_VAL(ce->name), Z_STRVAL_P(RT_CONSTANT(opline, opline->op2)));
5960+
ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
59305961
ZVAL_UNDEF(EX_VAR(opline->result.var));
5962+
FREE_OP2();
59315963
HANDLE_EXCEPTION();
59325964
}
59335965
} while (0);
59345966

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

5969+
FREE_OP2();
59375970
ZEND_VM_NEXT_OPCODE();
59385971
}
59395972

0 commit comments

Comments
 (0)