Skip to content

Commit 0b1d750

Browse files
committed
Allow arbitrary expressions in static variable initializer
Closes GH-9301
1 parent 3189a9f commit 0b1d750

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1118
-690
lines changed

UPGRADING

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ PHP 8.3 UPGRADE NOTES
4141
property to the class directly without traits.
4242
. Assigning a negative index n to an empty array will now make sure that the
4343
next index is n+1 instead of 0.
44+
. Static variable initializers can now contain arbitrary expressions.
45+
RFC: https://wiki.php.net/rfc/arbitrary_static_variable_initializers
4446

4547
- FFI:
4648
. C functions that have a return type of void now return null instead of

Zend/Optimizer/block_pass.c

+1
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
10101010
case ZEND_COALESCE:
10111011
case ZEND_ASSERT_CHECK:
10121012
case ZEND_JMP_NULL:
1013+
case ZEND_BIND_INIT_STATIC_OR_JMP:
10131014
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
10141015
break;
10151016
case ZEND_CATCH:

Zend/Optimizer/dce.c

+4-9
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ static inline bool may_have_side_effects(
145145
case ZEND_COALESCE:
146146
case ZEND_ASSERT_CHECK:
147147
case ZEND_JMP_NULL:
148+
case ZEND_BIND_INIT_STATIC_OR_JMP:
148149
/* For our purposes a jumps and branches are side effects. */
149150
return 1;
150151
case ZEND_BEGIN_SILENCE:
@@ -245,15 +246,9 @@ static inline bool may_have_side_effects(
245246
if ((opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) {
246247
return 1;
247248
}
248-
249-
if ((opline->extended_value & ZEND_BIND_REF) != 0) {
250-
zval *value =
251-
(zval*)((char*)op_array->static_variables->arData +
252-
(opline->extended_value & ~ZEND_BIND_REF));
253-
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
254-
/* AST may contain undefined constants */
255-
return 1;
256-
}
249+
/* Modifies static variables which are observable through reflection */
250+
if ((opline->extended_value & ZEND_BIND_REF) && opline->op2_type != IS_UNUSED) {
251+
return 1;
257252
}
258253
}
259254
return 0;

Zend/Optimizer/dfa_pass.c

+1
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa
652652
case ZEND_COALESCE:
653653
case ZEND_ASSERT_CHECK:
654654
case ZEND_JMP_NULL:
655+
case ZEND_BIND_INIT_STATIC_OR_JMP:
655656
if (ZEND_OP2_JMP_ADDR(opline) == op_array->opcodes + old->start) {
656657
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, op_array->opcodes + dst->start);
657658
}

Zend/Optimizer/pass1.c

+1
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
354354
case ZEND_ASSERT_CHECK:
355355
case ZEND_JMP_NULL:
356356
case ZEND_VERIFY_NEVER_TYPE:
357+
case ZEND_BIND_INIT_STATIC_OR_JMP:
357358
collect_constants = 0;
358359
break;
359360
}

Zend/Optimizer/sccp.c

+2
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ static bool can_replace_op1(
249249
case ZEND_ROPE_ADD:
250250
case ZEND_ROPE_END:
251251
case ZEND_BIND_STATIC:
252+
case ZEND_BIND_INIT_STATIC_OR_JMP:
252253
case ZEND_BIND_GLOBAL:
253254
case ZEND_MAKE_REF:
254255
case ZEND_UNSET_CV:
@@ -1773,6 +1774,7 @@ static void sccp_mark_feasible_successors(
17731774
case ZEND_CATCH:
17741775
case ZEND_FE_FETCH_R:
17751776
case ZEND_FE_FETCH_RW:
1777+
case ZEND_BIND_INIT_STATIC_OR_JMP:
17761778
scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
17771779
scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
17781780
return;

Zend/Optimizer/zend_cfg.c

+2
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
369369
case ZEND_COALESCE:
370370
case ZEND_ASSERT_CHECK:
371371
case ZEND_JMP_NULL:
372+
case ZEND_BIND_INIT_STATIC_OR_JMP:
372373
BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
373374
BB_START(i + 1);
374375
break;
@@ -522,6 +523,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
522523
case ZEND_COALESCE:
523524
case ZEND_ASSERT_CHECK:
524525
case ZEND_JMP_NULL:
526+
case ZEND_BIND_INIT_STATIC_OR_JMP:
525527
block->successors_count = 2;
526528
block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
527529
block->successors[1] = j + 1;

Zend/Optimizer/zend_dfg.c

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
150150
case ZEND_POST_DEC:
151151
case ZEND_BIND_GLOBAL:
152152
case ZEND_BIND_STATIC:
153+
case ZEND_BIND_INIT_STATIC_OR_JMP:
153154
case ZEND_SEND_VAR_NO_REF:
154155
case ZEND_SEND_VAR_NO_REF_EX:
155156
case ZEND_SEND_VAR_EX:

Zend/Optimizer/zend_inference.c

+8-4
Original file line numberDiff line numberDiff line change
@@ -2944,6 +2944,10 @@ static zend_always_inline zend_result _zend_update_type_info(
29442944
}
29452945
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
29462946
break;
2947+
case ZEND_BIND_INIT_STATIC_OR_JMP:
2948+
tmp = MAY_BE_UNDEF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_REF;
2949+
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
2950+
break;
29472951
case ZEND_SEND_VAR:
29482952
if (ssa_op->op1_def >= 0) {
29492953
tmp = t1;
@@ -4363,6 +4367,7 @@ static void zend_mark_cv_references(const zend_op_array *op_array, const zend_sc
43634367
case ZEND_SEND_REF:
43644368
case ZEND_SEND_VAR_EX:
43654369
case ZEND_SEND_FUNC_ARG:
4370+
case ZEND_BIND_INIT_STATIC_OR_JMP:
43664371
break;
43674372
case ZEND_INIT_ARRAY:
43684373
case ZEND_ADD_ARRAY_ELEMENT:
@@ -4518,6 +4523,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
45184523
case ZEND_ASSIGN_REF:
45194524
case ZEND_BIND_GLOBAL:
45204525
case ZEND_BIND_STATIC:
4526+
case ZEND_BIND_INIT_STATIC_OR_JMP:
45214527
case ZEND_FETCH_DIM_IS:
45224528
case ZEND_FETCH_OBJ_IS:
45234529
case ZEND_SEND_REF:
@@ -4755,14 +4761,12 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
47554761
case ZEND_UNSET_VAR:
47564762
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
47574763
case ZEND_BIND_STATIC:
4764+
case ZEND_BIND_INIT_STATIC_OR_JMP:
47584765
if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
47594766
/* Destructor may throw. */
47604767
return 1;
4761-
} else {
4762-
zval *value = (zval*)((char*)op_array->static_variables->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));
4763-
/* May throw if initializer is CONSTANT_AST. */
4764-
return Z_TYPE_P(value) == IS_CONSTANT_AST;
47654768
}
4769+
return 0;
47664770
case ZEND_ASSIGN_DIM:
47674771
if ((opline+1)->op1_type == IS_CV) {
47684772
if (_ssa_op1_info(op_array, ssa, opline+1, ssa_op+1) & MAY_BE_UNDEF) {

Zend/Optimizer/zend_optimizer.c

+4
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, z
720720
case ZEND_COALESCE:
721721
case ZEND_ASSERT_CHECK:
722722
case ZEND_JMP_NULL:
723+
case ZEND_BIND_INIT_STATIC_OR_JMP:
723724
ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
724725
break;
725726
case ZEND_FE_FETCH_R:
@@ -763,6 +764,7 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_
763764
case ZEND_COALESCE:
764765
case ZEND_ASSERT_CHECK:
765766
case ZEND_JMP_NULL:
767+
case ZEND_BIND_INIT_STATIC_OR_JMP:
766768
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
767769
break;
768770
case ZEND_CATCH:
@@ -1157,6 +1159,7 @@ static void zend_redo_pass_two(zend_op_array *op_array)
11571159
case ZEND_FE_RESET_RW:
11581160
case ZEND_ASSERT_CHECK:
11591161
case ZEND_JMP_NULL:
1162+
case ZEND_BIND_INIT_STATIC_OR_JMP:
11601163
opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
11611164
break;
11621165
case ZEND_CATCH:
@@ -1277,6 +1280,7 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa)
12771280
case ZEND_FE_RESET_RW:
12781281
case ZEND_ASSERT_CHECK:
12791282
case ZEND_JMP_NULL:
1283+
case ZEND_BIND_INIT_STATIC_OR_JMP:
12801284
opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
12811285
break;
12821286
case ZEND_CATCH:

Zend/Optimizer/zend_ssa.c

+1
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
679679
case ZEND_POST_DEC:
680680
case ZEND_BIND_GLOBAL:
681681
case ZEND_BIND_STATIC:
682+
case ZEND_BIND_INIT_STATIC_OR_JMP:
682683
case ZEND_SEND_VAR_NO_REF:
683684
case ZEND_SEND_VAR_NO_REF_EX:
684685
case ZEND_SEND_VAR_EX:

Zend/tests/035.phpt

+1-1
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

+49-2
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,69 @@ 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--
1228
object(Closure)#1 (1) {
1329
["static"]=>
1430
array(1) {
1531
["var"]=>
16-
string(14) "<constant ast>"
32+
NULL
33+
}
34+
}
35+
Closure Object
36+
(
37+
[static] => Array
38+
(
39+
[var] =>
40+
)
41+
42+
)
43+
Undefined constant "CONST_REF"
44+
object(Closure)#1 (1) {
45+
["static"]=>
46+
array(1) {
47+
["var"]=>
48+
NULL
49+
}
50+
}
51+
Closure Object
52+
(
53+
[static] => Array
54+
(
55+
[var] =>
56+
)
57+
58+
)
59+
object(Closure)#1 (1) {
60+
["static"]=>
61+
array(1) {
62+
["var"]=>
63+
string(3) "foo"
1764
}
1865
}
1966
Closure Object
2067
(
2168
[static] => Array
2269
(
23-
[var] => <constant ast>
70+
[var] => foo
2471
)
2572

2673
)
+6-3
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+
}
+6-3
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

+7-2
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

+11-2
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

+15-3
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+
}
+23
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
Static variable can't override bound closure variables
3+
--FILE--
4+
<?php
5+
6+
$a = null;
7+
8+
function () use (&$a) {
9+
static $a;
10+
};
11+
12+
?>
13+
--EXPECTF--
14+
Fatal error: Duplicate declaration of static variable $a in %s on line %d

0 commit comments

Comments
 (0)