Skip to content

Commit 8cb302a

Browse files
committed
Allow arbitrary expressions in static variable initializer
1 parent 1fb40b5 commit 8cb302a

23 files changed

+873
-598
lines changed

Zend/tests/035.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Using 'static' and 'global' in global scope
33
--FILE--
44
<?php
55

6-
static $var, $var, $var = -1;
6+
static $var = -1;
77
var_dump($var);
88

99
global $var, $var, $var;

Zend/tests/bug79778.phpt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,49 @@ Bug #79778: Assertion failure if dumping closure with unresolved static variable
55
$closure1 = function() {
66
static $var = CONST_REF;
77
};
8+
9+
var_dump($closure1);
10+
print_r($closure1);
11+
12+
try {
13+
$closure1();
14+
} catch (\Error $e) {
15+
echo $e->getMessage(), "\n";
16+
}
17+
18+
var_dump($closure1);
19+
print_r($closure1);
20+
21+
const CONST_REF = 'foo';
22+
$closure1();
823
var_dump($closure1);
924
print_r($closure1);
25+
1026
?>
1127
--EXPECT--
28+
object(Closure)#1 (0) {
29+
}
30+
Closure Object
31+
(
32+
)
33+
Undefined constant "CONST_REF"
34+
object(Closure)#1 (0) {
35+
}
36+
Closure Object
37+
(
38+
)
1239
object(Closure)#1 (1) {
1340
["static"]=>
1441
array(1) {
1542
["var"]=>
16-
string(14) "<constant ast>"
43+
string(3) "foo"
1744
}
1845
}
1946
Closure Object
2047
(
2148
[static] => Array
2249
(
23-
[var] => <constant ast>
50+
[var] => foo
2451
)
2552

2653
)
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
--TEST--
2-
New with anonymous class is not supported in constant expressions
2+
New with anonymous class works
33
--FILE--
44
<?php
55

66
static $x = new class {};
77

8+
var_dump($x);
9+
810
?>
9-
--EXPECTF--
10-
Fatal error: Cannot use anonymous class in constant expression in %s on line %d
11+
--EXPECT--
12+
object(class@anonymous)#1 (0) {
13+
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
--TEST--
2-
Argument unpacking in new arguments in const expr (not yet supported)
2+
Argument unpacking in new arguments in static variable
33
--FILE--
44
<?php
55

66
static $x = new stdClass(...[0]);
77

8+
var_dump($x);
9+
810
?>
9-
--EXPECTF--
10-
Fatal error: Argument unpacking in constant expressions is not supported in %s on line %d
11+
--EXPECT--
12+
object(stdClass)#1 (0) {
13+
}

Zend/tests/constexpr/new_dynamic_class_name.phpt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ Dynamic class name in new is not supported
33
--FILE--
44
<?php
55

6+
class Foo {}
7+
const FOO = 'Foo';
68
static $x = new (FOO);
79

10+
var_dump($x);
11+
812
?>
9-
--EXPECTF--
10-
Fatal error: Cannot use dynamic class name in constant expression in %s on line %d
13+
--EXPECT--
14+
object(Foo)#1 (0) {
15+
}

Zend/tests/constexpr/new_invalid_operation_in_arg.phpt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@ Invalid operation in new arg in const expr
33
--FILE--
44
<?php
55

6+
$foo = [1, 2, 3];
67
static $x = new stdClass($foo);
8+
var_dump($foo);
79

810
?>
9-
--EXPECTF--
10-
Fatal error: Constant expression contains invalid operations in %s on line %d
11+
--EXPECT--
12+
array(3) {
13+
[0]=>
14+
int(1)
15+
[1]=>
16+
int(2)
17+
[2]=>
18+
int(3)
19+
}

Zend/tests/constexpr/new_static.phpt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@ Static in new is not supported
33
--FILE--
44
<?php
55

6-
static $x = new static;
6+
class Foo {
7+
public static function singleton() {
8+
static $x = new static;
9+
return $x;
10+
}
11+
}
12+
13+
$x = Foo::singleton();
14+
$y = Foo::singleton();
15+
var_dump($x, $y);
716

817
?>
9-
--EXPECTF--
10-
Fatal error: "static" is not allowed in compile-time constants in %s on line %d
18+
--EXPECT--
19+
object(Foo)#1 (0) {
20+
}
21+
object(Foo)#1 (0) {
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Static variable initializer with function call
3+
--FILE--
4+
<?php
5+
6+
function bar() {
7+
echo "bar() called\n";
8+
return 'bar';
9+
}
10+
11+
function foo() {
12+
static $bar = bar();
13+
echo $bar, "\n";
14+
}
15+
16+
foo();
17+
foo();
18+
19+
?>
20+
--EXPECT--
21+
bar() called
22+
bar
23+
bar

Zend/zend_compile.c

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,6 +2174,7 @@ static inline void zend_update_jump_target(uint32_t opnum_jump, uint32_t opnum_t
21742174
zend_op *opline = &CG(active_op_array)->opcodes[opnum_jump];
21752175
switch (opline->opcode) {
21762176
case ZEND_JMP:
2177+
case ZEND_JMP_STATIC_DEF:
21772178
opline->op1.opline_num = opnum_target;
21782179
break;
21792180
case ZEND_JMPZ:
@@ -4778,16 +4779,53 @@ static void zend_compile_static_var_common(zend_string *var_name, zval *value, u
47784779
static void zend_compile_static_var(zend_ast *ast) /* {{{ */
47794780
{
47804781
zend_ast *var_ast = ast->child[0];
4781-
zend_ast **value_ast_ptr = &ast->child[1];
4782-
zval value_zv;
4782+
zend_ast *value_ast = ast->child[1];
4783+
zend_string *var_name = zend_ast_get_str(var_ast);
47834784

4784-
if (*value_ast_ptr) {
4785-
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ true);
4786-
} else {
4787-
ZVAL_NULL(&value_zv);
4785+
if (zend_string_equals_literal(var_name, "this")) {
4786+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use $this as static variable");
47884787
}
47894788

4790-
zend_compile_static_var_common(zend_ast_get_str(var_ast), &value_zv, ZEND_BIND_REF);
4789+
if (!value_ast) {
4790+
zval value_zv;
4791+
ZVAL_NULL(&value_zv);
4792+
zend_compile_static_var_common(zend_ast_get_str(var_ast), &value_zv, ZEND_BIND_REF);
4793+
} else {
4794+
zend_op *opline;
4795+
4796+
if (!CG(active_op_array)->static_variables) {
4797+
if (CG(active_op_array)->scope) {
4798+
CG(active_op_array)->scope->ce_flags |= ZEND_HAS_STATIC_IN_METHODS;
4799+
}
4800+
CG(active_op_array)->static_variables = zend_new_array(8);
4801+
}
4802+
4803+
if (zend_hash_exists(CG(active_op_array)->static_variables, var_name)) {
4804+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate declaration of static variable $%s", ZSTR_VAL(var_name));
4805+
}
4806+
4807+
zval placeholder;
4808+
ZVAL_UNDEF(&placeholder);
4809+
zval *placeholder_ptr = zend_hash_update(CG(active_op_array)->static_variables, var_name, &placeholder);
4810+
uint32_t placeholder_offset = (uint32_t)((char*)placeholder_ptr - (char*)CG(active_op_array)->static_variables->arData);
4811+
4812+
uint32_t jmp_opnum = get_next_op_number();
4813+
opline = zend_emit_op(NULL, ZEND_JMP_STATIC_DEF, NULL, NULL);
4814+
opline->extended_value = placeholder_offset;
4815+
4816+
znode expr;
4817+
zend_compile_expr(&expr, value_ast);
4818+
4819+
opline = zend_emit_op(NULL, ZEND_ASSIGN_STATIC, &expr, NULL);
4820+
opline->extended_value = placeholder_offset;
4821+
4822+
zend_update_jump_target_to_next(jmp_opnum);
4823+
4824+
opline = zend_emit_op(NULL, ZEND_BIND_STATIC, NULL, NULL);
4825+
opline->op1_type = IS_CV;
4826+
opline->op1.var = lookup_cv(var_name);
4827+
opline->extended_value = placeholder_offset | ZEND_BIND_REF;
4828+
}
47914829
}
47924830
/* }}} */
47934831

Zend/zend_opcode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,7 @@ ZEND_API void pass_two(zend_op_array *op_array)
11031103
}
11041104
ZEND_FALLTHROUGH;
11051105
case ZEND_JMP:
1106+
case ZEND_JMP_STATIC_DEF:
11061107
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
11071108
break;
11081109
case ZEND_JMPZ:

Zend/zend_vm_def.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8890,6 +8890,55 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF)
88908890
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
88918891
}
88928892

8893+
ZEND_VM_HANDLER(203, ZEND_JMP_STATIC_DEF, JMP_ADDR, UNUSED)
8894+
{
8895+
USE_OPLINE
8896+
HashTable *ht;
8897+
zval *value;
8898+
8899+
ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
8900+
if (!ht) {
8901+
ZEND_VM_NEXT_OPCODE();
8902+
}
8903+
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
8904+
8905+
value = (zval*)((char*)ht->arData + (opline->extended_value));
8906+
if (Z_TYPE_P(value) == IS_UNDEF) {
8907+
ZEND_VM_NEXT_OPCODE();
8908+
} else {
8909+
ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op1), 0);
8910+
}
8911+
}
8912+
8913+
ZEND_VM_HANDLER(204, ZEND_ASSIGN_STATIC, ANY, UNUSED)
8914+
{
8915+
USE_OPLINE
8916+
HashTable *ht;
8917+
zval *static_var;
8918+
zval *value;
8919+
8920+
value = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8921+
8922+
ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
8923+
if (!ht) {
8924+
ht = zend_array_dup(EX(func)->op_array.static_variables);
8925+
ZEND_MAP_PTR_SET(EX(func)->op_array.static_variables_ptr, ht);
8926+
}
8927+
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
8928+
8929+
static_var = (zval*)((char*)ht->arData + (opline->extended_value));
8930+
8931+
SAVE_OPLINE();
8932+
i_zval_ptr_dtor(static_var);
8933+
if (EG(exception)) {
8934+
HANDLE_EXCEPTION();
8935+
}
8936+
ZVAL_COPY(static_var, value);
8937+
8938+
FREE_OP1();
8939+
ZEND_VM_NEXT_OPCODE();
8940+
}
8941+
88938942
ZEND_VM_HOT_HANDLER(184, ZEND_FETCH_THIS, UNUSED, UNUSED)
88948943
{
88958944
USE_OPLINE

0 commit comments

Comments
 (0)