Skip to content

[RFC] Allow arbitrary expressions in static variable initializer #9301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
break;
case ZEND_CATCH:
Expand Down
13 changes: 4 additions & 9 deletions Zend/Optimizer/dce.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ static inline bool may_have_side_effects(
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
/* For our purposes a jumps and branches are side effects. */
return 1;
case ZEND_BEGIN_SILENCE:
Expand Down Expand Up @@ -245,15 +246,9 @@ static inline bool may_have_side_effects(
if ((opline->extended_value & (ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT))) {
return 1;
}

if ((opline->extended_value & ZEND_BIND_REF) != 0) {
zval *value =
(zval*)((char*)op_array->static_variables->arData +
(opline->extended_value & ~ZEND_BIND_REF));
if (Z_TYPE_P(value) == IS_CONSTANT_AST) {
/* AST may contain undefined constants */
return 1;
}
/* Modifies static variables which are observable through reflection */
if ((opline->extended_value & ZEND_BIND_REF) && opline->op2_type != IS_UNUSED) {
return 1;
}
}
return 0;
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,7 @@ static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
if (ZEND_OP2_JMP_ADDR(opline) == op_array->opcodes + old->start) {
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, op_array->opcodes + dst->start);
}
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/pass1.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_VERIFY_NEVER_TYPE:
case ZEND_BIND_INIT_STATIC_OR_JMP:
collect_constants = 0;
break;
}
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ static bool can_replace_op1(
case ZEND_ROPE_ADD:
case ZEND_ROPE_END:
case ZEND_BIND_STATIC:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_BIND_GLOBAL:
case ZEND_MAKE_REF:
case ZEND_UNSET_CV:
Expand Down Expand Up @@ -1773,6 +1774,7 @@ static void sccp_mark_feasible_successors(
case ZEND_CATCH:
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
case ZEND_BIND_INIT_STATIC_OR_JMP:
scdf_mark_edge_feasible(scdf, block_num, block->successors[0]);
scdf_mark_edge_feasible(scdf, block_num, block->successors[1]);
return;
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/zend_cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
BB_START(i + 1);
break;
Expand Down Expand Up @@ -522,6 +523,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
block->successors_count = 2;
block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
block->successors[1] = j + 1;
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_dfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
case ZEND_POST_DEC:
case ZEND_BIND_GLOBAL:
case ZEND_BIND_STATIC:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_VAR_EX:
Expand Down
12 changes: 8 additions & 4 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2944,6 +2944,10 @@ static zend_always_inline zend_result _zend_update_type_info(
}
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
break;
case ZEND_BIND_INIT_STATIC_OR_JMP:
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;
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
break;
case ZEND_SEND_VAR:
if (ssa_op->op1_def >= 0) {
tmp = t1;
Expand Down Expand Up @@ -4363,6 +4367,7 @@ static void zend_mark_cv_references(const zend_op_array *op_array, const zend_sc
case ZEND_SEND_REF:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_BIND_INIT_STATIC_OR_JMP:
break;
case ZEND_INIT_ARRAY:
case ZEND_ADD_ARRAY_ELEMENT:
Expand Down Expand Up @@ -4518,6 +4523,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_ASSIGN_REF:
case ZEND_BIND_GLOBAL:
case ZEND_BIND_STATIC:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_FETCH_DIM_IS:
case ZEND_FETCH_OBJ_IS:
case ZEND_SEND_REF:
Expand Down Expand Up @@ -4755,14 +4761,12 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_UNSET_VAR:
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
case ZEND_BIND_STATIC:
case ZEND_BIND_INIT_STATIC_OR_JMP:
if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
/* Destructor may throw. */
return 1;
} else {
zval *value = (zval*)((char*)op_array->static_variables->arData + (opline->extended_value & ~(ZEND_BIND_REF|ZEND_BIND_IMPLICIT|ZEND_BIND_EXPLICIT)));
/* May throw if initializer is CONSTANT_AST. */
return Z_TYPE_P(value) == IS_CONSTANT_AST;
}
return 0;
case ZEND_ASSIGN_DIM:
if ((opline+1)->op1_type == IS_CV) {
if (_ssa_op1_info(op_array, ssa, opline+1, ssa_op+1) & MAY_BE_UNDEF) {
Expand Down
4 changes: 4 additions & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@ void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, z
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
break;
case ZEND_FE_FETCH_R:
Expand Down Expand Up @@ -763,6 +764,7 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_
case ZEND_COALESCE:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
break;
case ZEND_CATCH:
Expand Down Expand Up @@ -1157,6 +1159,7 @@ static void zend_redo_pass_two(zend_op_array *op_array)
case ZEND_FE_RESET_RW:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
break;
case ZEND_CATCH:
Expand Down Expand Up @@ -1277,6 +1280,7 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa)
case ZEND_FE_RESET_RW:
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
break;
case ZEND_CATCH:
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_ssa.c
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
case ZEND_POST_DEC:
case ZEND_BIND_GLOBAL:
case ZEND_BIND_STATIC:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_VAR_EX:
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/035.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Using 'static' and 'global' in global scope
--FILE--
<?php

static $var, $var, $var = -1;
static $var = -1;
var_dump($var);

global $var, $var, $var;
Expand Down
51 changes: 49 additions & 2 deletions Zend/tests/bug79778.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,69 @@ Bug #79778: Assertion failure if dumping closure with unresolved static variable
$closure1 = function() {
static $var = CONST_REF;
};

var_dump($closure1);
print_r($closure1);

try {
$closure1();
} catch (\Error $e) {
echo $e->getMessage(), "\n";
}

var_dump($closure1);
print_r($closure1);

const CONST_REF = 'foo';
$closure1();
var_dump($closure1);
print_r($closure1);

?>
--EXPECT--
object(Closure)#1 (1) {
["static"]=>
array(1) {
["var"]=>
string(14) "<constant ast>"
NULL
}
}
Closure Object
(
[static] => Array
(
[var] =>
)

)
Undefined constant "CONST_REF"
object(Closure)#1 (1) {
["static"]=>
array(1) {
["var"]=>
NULL
}
}
Closure Object
(
[static] => Array
(
[var] =>
)

)
object(Closure)#1 (1) {
["static"]=>
array(1) {
["var"]=>
string(3) "foo"
}
}
Closure Object
(
[static] => Array
(
[var] => <constant ast>
[var] => foo
)

)
9 changes: 6 additions & 3 deletions Zend/tests/constexpr/new_anon_class.phpt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
--TEST--
New with anonymous class is not supported in constant expressions
New with anonymous class works
--FILE--
<?php

static $x = new class {};

var_dump($x);

?>
--EXPECTF--
Fatal error: Cannot use anonymous class in constant expression in %s on line %d
--EXPECT--
object(class@anonymous)#1 (0) {
}
9 changes: 6 additions & 3 deletions Zend/tests/constexpr/new_arg_unpack.phpt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
--TEST--
Argument unpacking in new arguments in const expr (not yet supported)
Argument unpacking in new arguments in static variable
--FILE--
<?php

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

var_dump($x);

?>
--EXPECTF--
Fatal error: Argument unpacking in constant expressions is not supported in %s on line %d
--EXPECT--
object(stdClass)#1 (0) {
}
9 changes: 7 additions & 2 deletions Zend/tests/constexpr/new_dynamic_class_name.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ Dynamic class name in new is not supported
--FILE--
<?php

class Foo {}
const FOO = 'Foo';
static $x = new (FOO);

var_dump($x);

?>
--EXPECTF--
Fatal error: Cannot use dynamic class name in constant expression in %s on line %d
--EXPECT--
object(Foo)#1 (0) {
}
13 changes: 11 additions & 2 deletions Zend/tests/constexpr/new_invalid_operation_in_arg.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ Invalid operation in new arg in const expr
--FILE--
<?php

$foo = [1, 2, 3];
static $x = new stdClass($foo);
var_dump($foo);

?>
--EXPECTF--
Fatal error: Constant expression contains invalid operations in %s on line %d
--EXPECT--
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
18 changes: 15 additions & 3 deletions Zend/tests/constexpr/new_static.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,20 @@ Static in new is not supported
--FILE--
<?php

static $x = new static;
class Foo {
public static function singleton() {
static $x = new static;
return $x;
}
}

$x = Foo::singleton();
$y = Foo::singleton();
var_dump($x, $y);

?>
--EXPECTF--
Fatal error: "static" is not allowed in compile-time constants in %s on line %d
--EXPECT--
object(Foo)#1 (0) {
}
object(Foo)#1 (0) {
}
23 changes: 23 additions & 0 deletions Zend/tests/static_variable_func_call.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Static variable initializer with function call
--FILE--
<?php

function bar() {
echo "bar() called\n";
return 'bar';
}

function foo() {
static $bar = bar();
echo $bar, "\n";
}

foo();
foo();

?>
--EXPECT--
bar() called
bar
bar
14 changes: 14 additions & 0 deletions Zend/tests/static_variables_closure_bind.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Static variable can't override bound closure variables
--FILE--
<?php

$a = null;

function () use (&$a) {
static $a;
};

?>
--EXPECTF--
Fatal error: Duplicate declaration of static variable $a in %s on line %d
Loading