Skip to content

Commit c02e1b1

Browse files
committed
PoC: Add support BP_VAR_IS in class constants
1 parent 1de148f commit c02e1b1

File tree

5 files changed

+209
-111
lines changed

5 files changed

+209
-111
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
Dynamic class constant fetch BP_VAR_IS
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
public const BAR = 'bar';
8+
}
9+
$foo = new Foo();
10+
11+
var_dump(Foo::BAR ?? throw new Exception('Unreachable'));
12+
var_dump(Foo::BAZ ?? 'Default');
13+
var_dump(Foo::BAZ->qux ?? 'Default');
14+
15+
try {
16+
// BP_VAR_IS is not supported on the LHS of class constants
17+
var_dump($foo->bar::BAZ ?? 'Default');
18+
} catch (Throwable $e) {
19+
echo $e->getMessage(), "\n";
20+
}
21+
22+
?>
23+
--EXPECTF--
24+
string(3) "bar"
25+
string(7) "Default"
26+
string(7) "Default"
27+
28+
Warning: Undefined property: Foo::$bar in %s on line %d
29+
Class name must be a valid object or a string

Zend/zend_compile.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9579,7 +9579,7 @@ static void zend_compile_const(znode *result, zend_ast *ast) /* {{{ */
95799579
}
95809580
/* }}} */
95819581

9582-
static void zend_compile_class_const(znode *result, zend_ast *ast) /* {{{ */
9582+
static void zend_compile_class_const(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
95839583
{
95849584
zend_ast *class_ast;
95859585
zend_ast *const_ast;
@@ -9617,6 +9617,10 @@ static void zend_compile_class_const(znode *result, zend_ast *ast) /* {{{ */
96179617
if (opline->op1_type == IS_CONST || opline->op2_type == IS_CONST) {
96189618
opline->extended_value = zend_alloc_cache_slots(2);
96199619
}
9620+
9621+
if (type == BP_VAR_IS) {
9622+
opline->extended_value |= ZEND_FETCH_CLASS_CONSTANT_IS;
9623+
}
96209624
}
96219625
/* }}} */
96229626

@@ -10324,7 +10328,7 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */
1032410328
zend_compile_const(result, ast);
1032510329
return;
1032610330
case ZEND_AST_CLASS_CONST:
10327-
zend_compile_class_const(result, ast);
10331+
zend_compile_class_const(result, ast, BP_VAR_R);
1032810332
return;
1032910333
case ZEND_AST_CLASS_NAME:
1033010334
zend_compile_class_name(result, ast);
@@ -10385,6 +10389,12 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty
1038510389
case ZEND_AST_ZNODE:
1038610390
*result = *zend_ast_get_znode(ast);
1038710391
return NULL;
10392+
case ZEND_AST_CLASS_CONST:
10393+
if (type == BP_VAR_IS) {
10394+
zend_compile_class_const(result, ast, type);
10395+
return NULL;
10396+
}
10397+
ZEND_FALLTHROUGH;
1038810398
default:
1038910399
if (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) {
1039010400
zend_error_noreturn(E_COMPILE_ERROR,

Zend/zend_compile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,9 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
983983
#define ZEND_FETCH_DIM_WRITE 2
984984
#define ZEND_FETCH_OBJ_FLAGS 3
985985

986+
/* Shared with cache slot */
987+
#define ZEND_FETCH_CLASS_CONSTANT_IS 1
988+
986989
/* Used to mark what kind of operation a writing FETCH_DIM is used in,
987990
* to produce a more precise error on incorrect string offset use. */
988991
#define ZEND_FETCH_DIM_REF 1

Zend/zend_vm_def.h

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5866,22 +5866,24 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58665866

58675867
do {
58685868
if (OP1_TYPE == IS_CONST && OP2_TYPE == IS_CONST) {
5869-
if (EXPECTED(CACHED_PTR(opline->extended_value + sizeof(void*)))) {
5870-
value = CACHED_PTR(opline->extended_value + sizeof(void*));
5869+
void *value_cache = CACHED_PTR((opline->extended_value & ~ZEND_FETCH_CLASS_CONSTANT_IS) + sizeof(void*));
5870+
if (EXPECTED(value_cache)) {
5871+
value = value_cache;
58715872
break;
58725873
}
58735874
}
58745875
if (OP1_TYPE == IS_CONST) {
5875-
if (EXPECTED(CACHED_PTR(opline->extended_value))) {
5876-
ce = CACHED_PTR(opline->extended_value);
5876+
void *class_cache = CACHED_PTR(opline->extended_value & ~ZEND_FETCH_CLASS_CONSTANT_IS);
5877+
if (EXPECTED(class_cache)) {
5878+
ce = class_cache;
58775879
} else {
58785880
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);
58795881
if (UNEXPECTED(ce == NULL)) {
58805882
ZVAL_UNDEF(EX_VAR(opline->result.var));
58815883
FREE_OP2();
58825884
HANDLE_EXCEPTION();
58835885
}
5884-
CACHE_PTR(opline->extended_value, ce);
5886+
CACHE_PTR(opline->extended_value & ~ZEND_FETCH_CLASS_CONSTANT_IS, ce);
58855887
}
58865888
} else if (OP1_TYPE == IS_UNUSED) {
58875889
ce = zend_fetch_class(NULL, opline->op1.num);
@@ -5893,11 +5895,13 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
58935895
} else {
58945896
ce = Z_CE_P(EX_VAR(opline->op1.var));
58955897
}
5896-
if (EXPECTED(OP2_TYPE == IS_CONST
5897-
&& CACHED_PTR(opline->extended_value) == ce)
5898-
&& CACHED_PTR(opline->extended_value + sizeof(void*)) != NULL) {
5899-
value = CACHED_PTR(opline->extended_value + sizeof(void*));
5900-
break;
5898+
if (OP2_TYPE == IS_CONST) {
5899+
void *class_cache = CACHED_PTR(opline->extended_value & ~ZEND_FETCH_CLASS_CONSTANT_IS);
5900+
void *value_cache = CACHED_PTR((opline->extended_value & ~ZEND_FETCH_CLASS_CONSTANT_IS) + sizeof(void*));
5901+
if (EXPECTED(class_cache == ce && value_cache != NULL)) {
5902+
value = value_cache;
5903+
break;
5904+
}
59015905
}
59025906

59035907
constant_zv = GET_OP2_ZVAL_PTR_DEREF(BP_VAR_R);
@@ -5953,14 +5957,18 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
59535957
}
59545958
}
59555959
if (OP1_TYPE == IS_CONST && OP2_TYPE == IS_CONST) {
5956-
CACHE_POLYMORPHIC_PTR(opline->extended_value, ce, value);
5960+
CACHE_POLYMORPHIC_PTR(opline->extended_value & ~ZEND_FETCH_CLASS_CONSTANT_IS, ce, value);
59575961
}
59585962
} else {
5959-
zend_throw_error(NULL, "Undefined constant %s::%s",
5960-
ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
5961-
ZVAL_UNDEF(EX_VAR(opline->result.var));
5962-
FREE_OP2();
5963-
HANDLE_EXCEPTION();
5963+
if (EXPECTED(!(opline->extended_value & ZEND_FETCH_CLASS_CONSTANT_IS))) {
5964+
zend_throw_error(NULL, "Undefined constant %s::%s",
5965+
ZSTR_VAL(ce->name), ZSTR_VAL(constant_name));
5966+
ZVAL_UNDEF(EX_VAR(opline->result.var));
5967+
FREE_OP2();
5968+
HANDLE_EXCEPTION();
5969+
} else {
5970+
value = &EG(uninitialized_zval);
5971+
}
59645972
}
59655973
} while (0);
59665974

0 commit comments

Comments
 (0)