Skip to content

Commit 8fc58cd

Browse files
committed
Allow arbitrary expressions in static variable initializer
1 parent 04d5fae commit 8fc58cd

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
@@ -2169,6 +2169,7 @@ static inline void zend_update_jump_target(uint32_t opnum_jump, uint32_t opnum_t
21692169
zend_op *opline = &CG(active_op_array)->opcodes[opnum_jump];
21702170
switch (opline->opcode) {
21712171
case ZEND_JMP:
2172+
case ZEND_JMP_STATIC_DEF:
21722173
opline->op1.opline_num = opnum_target;
21732174
break;
21742175
case ZEND_JMPZ:
@@ -4754,16 +4755,53 @@ static void zend_compile_static_var_common(zend_string *var_name, zval *value, u
47544755
static void zend_compile_static_var(zend_ast *ast) /* {{{ */
47554756
{
47564757
zend_ast *var_ast = ast->child[0];
4757-
zend_ast **value_ast_ptr = &ast->child[1];
4758-
zval value_zv;
4758+
zend_ast *value_ast = ast->child[1];
4759+
zend_string *var_name = zend_ast_get_str(var_ast);
47594760

4760-
if (*value_ast_ptr) {
4761-
zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ true);
4762-
} else {
4763-
ZVAL_NULL(&value_zv);
4761+
if (zend_string_equals_literal(var_name, "this")) {
4762+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot use $this as static variable");
47644763
}
47654764

4766-
zend_compile_static_var_common(zend_ast_get_str(var_ast), &value_zv, ZEND_BIND_REF);
4765+
if (!value_ast) {
4766+
zval value_zv;
4767+
ZVAL_NULL(&value_zv);
4768+
zend_compile_static_var_common(zend_ast_get_str(var_ast), &value_zv, ZEND_BIND_REF);
4769+
} else {
4770+
zend_op *opline;
4771+
4772+
if (!CG(active_op_array)->static_variables) {
4773+
if (CG(active_op_array)->scope) {
4774+
CG(active_op_array)->scope->ce_flags |= ZEND_HAS_STATIC_IN_METHODS;
4775+
}
4776+
CG(active_op_array)->static_variables = zend_new_array(8);
4777+
}
4778+
4779+
if (zend_hash_exists(CG(active_op_array)->static_variables, var_name)) {
4780+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate declaration of static variable $%s", ZSTR_VAL(var_name));
4781+
}
4782+
4783+
zval placeholder;
4784+
ZVAL_UNDEF(&placeholder);
4785+
zval *placeholder_ptr = zend_hash_update(CG(active_op_array)->static_variables, var_name, &placeholder);
4786+
uint32_t placeholder_offset = (uint32_t)((char*)placeholder_ptr - (char*)CG(active_op_array)->static_variables->arData);
4787+
4788+
uint32_t jmp_opnum = get_next_op_number();
4789+
opline = zend_emit_op(NULL, ZEND_JMP_STATIC_DEF, NULL, NULL);
4790+
opline->extended_value = placeholder_offset;
4791+
4792+
znode expr;
4793+
zend_compile_expr(&expr, value_ast);
4794+
4795+
opline = zend_emit_op(NULL, ZEND_ASSIGN_STATIC, &expr, NULL);
4796+
opline->extended_value = placeholder_offset;
4797+
4798+
zend_update_jump_target_to_next(jmp_opnum);
4799+
4800+
opline = zend_emit_op(NULL, ZEND_BIND_STATIC, NULL, NULL);
4801+
opline->op1_type = IS_CV;
4802+
opline->op1.var = lookup_cv(var_name);
4803+
opline->extended_value = placeholder_offset | ZEND_BIND_REF;
4804+
}
47674805
}
47684806
/* }}} */
47694807

Zend/zend_opcode.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,7 @@ ZEND_API void pass_two(zend_op_array *op_array)
10951095
}
10961096
ZEND_FALLTHROUGH;
10971097
case ZEND_JMP:
1098+
case ZEND_JMP_STATIC_DEF:
10981099
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op1);
10991100
break;
11001101
case ZEND_JMPZ:

Zend/zend_vm_def.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8855,6 +8855,55 @@ ZEND_VM_HANDLER(183, ZEND_BIND_STATIC, CV, UNUSED, REF)
88558855
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
88568856
}
88578857

8858+
ZEND_VM_HANDLER(203, ZEND_JMP_STATIC_DEF, JMP_ADDR, UNUSED)
8859+
{
8860+
USE_OPLINE
8861+
HashTable *ht;
8862+
zval *value;
8863+
8864+
ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
8865+
if (!ht) {
8866+
ZEND_VM_NEXT_OPCODE();
8867+
}
8868+
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
8869+
8870+
value = (zval*)((char*)ht->arData + (opline->extended_value));
8871+
if (Z_TYPE_P(value) == IS_UNDEF) {
8872+
ZEND_VM_NEXT_OPCODE();
8873+
} else {
8874+
ZEND_VM_JMP_EX(OP_JMP_ADDR(opline, opline->op1), 0);
8875+
}
8876+
}
8877+
8878+
ZEND_VM_HANDLER(204, ZEND_ASSIGN_STATIC, ANY, UNUSED)
8879+
{
8880+
USE_OPLINE
8881+
HashTable *ht;
8882+
zval *static_var;
8883+
zval *value;
8884+
8885+
value = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
8886+
8887+
ht = ZEND_MAP_PTR_GET(EX(func)->op_array.static_variables_ptr);
8888+
if (!ht) {
8889+
ht = zend_array_dup(EX(func)->op_array.static_variables);
8890+
ZEND_MAP_PTR_SET(EX(func)->op_array.static_variables_ptr, ht);
8891+
}
8892+
ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
8893+
8894+
static_var = (zval*)((char*)ht->arData + (opline->extended_value));
8895+
8896+
SAVE_OPLINE();
8897+
i_zval_ptr_dtor(static_var);
8898+
if (EG(exception)) {
8899+
HANDLE_EXCEPTION();
8900+
}
8901+
ZVAL_COPY(static_var, value);
8902+
8903+
FREE_OP1();
8904+
ZEND_VM_NEXT_OPCODE();
8905+
}
8906+
88588907
ZEND_VM_HOT_HANDLER(184, ZEND_FETCH_THIS, UNUSED, UNUSED)
88598908
{
88608909
USE_OPLINE

0 commit comments

Comments
 (0)