Skip to content

Fixed bug #81096: Inconsistent range inferece for variables passed by reference #7121

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 3 commits 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
179 changes: 168 additions & 11 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,16 @@

#define ADD_SCC_VAR(_var) \
do { \
if (ssa->vars[_var].scc == scc) { \
if (ssa->vars[_var].scc == scc && \
!(ssa->var_info[_var].type & MAY_BE_REF)) { \
zend_bitset_incl(worklist, _var); \
} \
} while (0)

#define ADD_SCC_VAR_1(_var) \
do { \
if (ssa->vars[_var].scc == scc && \
!(ssa->var_info[_var].type & MAY_BE_REF) && \
!zend_bitset_in(visited, _var)) { \
zend_bitset_incl(worklist, _var); \
} \
Expand Down Expand Up @@ -1657,7 +1659,8 @@ static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ss
for (n = 0; n < RANGE_WARMUP_PASSES; n++) {
j= scc_var[scc];
while (j >= 0) {
if (ssa->vars[j].scc_entry) {
if (ssa->vars[j].scc_entry
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
zend_bitset_incl(worklist, j);
}
j = next_scc_var[j];
Expand Down Expand Up @@ -1758,7 +1761,9 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
j = scc_var[scc];
if (next_scc_var[j] < 0) {
/* SCC with a single element */
if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
if (ssa->var_info[j].type & MAY_BE_REF) {
/* pass */
} else if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
zend_inference_init_range(op_array, ssa, j, tmp.underflow, tmp.min, tmp.max, tmp.overflow);
} else {
zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
Expand All @@ -1767,7 +1772,8 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
/* Find SCC entry points */
memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
do {
if (ssa->vars[j].scc_entry) {
if (ssa->vars[j].scc_entry
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
zend_bitset_incl(worklist, j);
}
j = next_scc_var[j];
Expand All @@ -1777,7 +1783,9 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
zend_infer_ranges_warmup(op_array, ssa, scc_var, next_scc_var, scc);
j = scc_var[scc];
do {
zend_bitset_incl(worklist, j);
if (!(ssa->var_info[j].type & MAY_BE_REF)) {
zend_bitset_incl(worklist, j);
}
j = next_scc_var[j];
} while (j >= 0);
#endif
Expand All @@ -1791,7 +1799,8 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{

/* initialize missing ranges */
for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
if (!ssa->var_info[j].has_range) {
if (!ssa->var_info[j].has_range
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
}
Expand All @@ -1807,7 +1816,8 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
/* Add all SCC entry variables into worklist for narrowing */
for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
if (ssa->vars[j].definition_phi
&& ssa->vars[j].definition_phi->pi < 0) {
&& ssa->vars[j].definition_phi->pi < 0
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
/* narrowing Phi functions first */
zend_ssa_range_narrowing(op_array, ssa, j, scc);
}
Expand Down Expand Up @@ -1867,6 +1877,10 @@ static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
} \
} \
if (ssa_var_info[__var].type != __type) { \
ZEND_ASSERT(ssa_opcodes != NULL || \
__ssa_var->var >= op_array->last_var || \
(ssa_var_info[__var].type & MAY_BE_REF) \
== (__type & MAY_BE_REF)); \
if (ssa_var_info[__var].type & ~__type) { \
emit_type_narrowing_warning(op_array, ssa, __var); \
return FAILURE; \
Expand Down Expand Up @@ -3432,7 +3446,7 @@ static zend_always_inline int _zend_update_type_info(
zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);

tmp = zend_fetch_prop_type(script, prop_info, &ce);
if (opline->result_type != IS_TMP_VAR) {
if (opline->result_type == IS_VAR) {
tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
} else if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR)) || !(t1 & MAY_BE_RC1)) {
zend_class_entry *ce = NULL;
Expand Down Expand Up @@ -3468,7 +3482,7 @@ static zend_always_inline int _zend_update_type_info(
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
tmp = zend_fetch_prop_type(script,
zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
if (opline->result_type != IS_TMP_VAR) {
if (opline->result_type == IS_VAR) {
tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
} else {
tmp &= ~MAY_BE_RC1;
Expand Down Expand Up @@ -3587,6 +3601,8 @@ static zend_always_inline int _zend_update_type_info(
} else {
tmp |= MAY_BE_RC1 | MAY_BE_RCN;
}
} else if (opline->result_type == IS_CV) {
tmp |= MAY_BE_RC1 | MAY_BE_RCN;
} else {
tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
switch (opline->opcode) {
Expand Down Expand Up @@ -3685,6 +3701,7 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
int i, j;
uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
bool update_worklist = 1;
const zend_op **ssa_opcodes = NULL;

while (!zend_bitset_empty(worklist, worklist_len)) {
j = zend_bitset_first(worklist, worklist_len);
Expand Down Expand Up @@ -4242,7 +4259,6 @@ void zend_func_return_info(const zend_op_array *op_array,

static int zend_infer_types(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
{
zend_ssa_var_info *ssa_var_info = ssa->var_info;
int ssa_vars_count = ssa->vars_count;
int j;
zend_bitset worklist;
Expand All @@ -4254,7 +4270,6 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc
/* Type Inference */
for (j = op_array->last_var; j < ssa_vars_count; j++) {
zend_bitset_incl(worklist, j);
ssa_var_info[j].type = 0;
}

if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) != SUCCESS) {
Expand All @@ -4273,6 +4288,144 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc
return SUCCESS;
}

static int zend_mark_cv_references(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa)
{
int var, def;
const zend_op *opline;
zend_arg_info *arg_info;
uint32_t worklist_len = zend_bitset_len(ssa->vars_count);
zend_bitset worklist;
ALLOCA_FLAG(use_heap);

worklist = do_alloca(sizeof(zend_ulong) * worklist_len, use_heap);
memset(worklist, 0, sizeof(zend_ulong) * worklist_len);

/* Collect SSA variables which definitions creates PHP reference */
for (var = 0; var < ssa->vars_count; var++) {
def = ssa->vars[var].definition;
if (def >= 0 && ssa->vars[var].var < op_array->last_var) {
opline = op_array->opcodes + def;
if (ssa->ops[def].result_def == var) {
switch (opline->opcode) {
case ZEND_RECV:
case ZEND_RECV_INIT:
arg_info = &op_array->arg_info[opline->op1.num-1];
if (!ZEND_ARG_SEND_MODE(arg_info)) {
continue;
}
break;
default:
continue;
}
} else if (ssa->ops[def].op1_def == var) {
switch (opline->opcode) {
case ZEND_ASSIGN_REF:
case ZEND_MAKE_REF:
case ZEND_FE_RESET_RW:
case ZEND_BIND_GLOBAL:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
break;
case ZEND_INIT_ARRAY:
case ZEND_ADD_ARRAY_ELEMENT:
if (!(opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) {
continue;
}
break;
case ZEND_BIND_STATIC:
if (!(opline->extended_value & ZEND_BIND_REF)) {
continue;
}
break;
case ZEND_YIELD:
if (!(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
continue;
}
break;
case ZEND_OP_DATA:
switch ((opline-1)->opcode) {
case ZEND_ASSIGN_OBJ_REF:
case ZEND_ASSIGN_STATIC_PROP_REF:
break;
default:
continue;
}
break;
default:
continue;
}
} else if (ssa->ops[def].op2_def == var) {
switch (opline->opcode) {
case ZEND_ASSIGN_REF:
case ZEND_FE_FETCH_RW:
break;
case ZEND_BIND_LEXICAL:
if (!(opline->extended_value & ZEND_BIND_REF)) {
continue;
}
break;
default:
continue;
}
} else {
ZEND_UNREACHABLE();
}
zend_bitset_incl(worklist, var);
} else if (ssa->var_info[var].type & MAY_BE_REF) {
zend_bitset_incl(worklist, var);
} else if (ssa->vars[var].alias == SYMTABLE_ALIAS) {
zend_bitset_incl(worklist, var);
}
}

/* Set and propagate MAY_BE_REF */
WHILE_WORKLIST(worklist, worklist_len, var) {

ssa->var_info[var].type |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;

if (ssa->vars[var].phi_use_chain) {
zend_ssa_phi *p = ssa->vars[var].phi_use_chain;
do {
if (!(ssa->var_info[p->ssa_var].type & MAY_BE_REF)) {
zend_bitset_incl(worklist, p->ssa_var);
}
p = zend_ssa_next_use_phi(ssa, var, p);
} while (p);
}

if (ssa->vars[var].use_chain >= 0) {
int use = ssa->vars[var].use_chain;
FOREACH_USE(&ssa->vars[var], use) {
zend_ssa_op *op = ssa->ops + use;
if (op->op1_use == var && op->op1_def >= 0) {
if (!(ssa->var_info[op->op1_def].type & MAY_BE_REF)) {
/* Unset breaks references (outside global scope). */
if (op_array->opcodes[use].opcode == ZEND_UNSET_CV
&& op_array->function_name) {
continue;
}
zend_bitset_incl(worklist, op->op1_def);
}
}
if (op->op2_use == var && op->op2_def >= 0) {
if (!(ssa->var_info[op->op2_def].type & MAY_BE_REF)) {
zend_bitset_incl(worklist, op->op2_def);
}
}
if (op->result_use == var && op->result_def >= 0) {
if (!(ssa->var_info[op->result_def].type & MAY_BE_REF)) {
zend_bitset_incl(worklist, op->result_def);
}
}
} FOREACH_USE_END();
}
} WHILE_WORKLIST_END();

free_alloca(worklist, use_heap);
return SUCCESS;
}

ZEND_API int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level) /* {{{ */
{
zend_ssa_var_info *ssa_var_info;
Expand Down Expand Up @@ -4302,6 +4455,10 @@ ZEND_API int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_arra
ssa_var_info[i].has_range = 0;
}

if (zend_mark_cv_references(op_array, script, ssa) != SUCCESS) {
return FAILURE;
}

if (zend_infer_ranges(op_array, ssa) != SUCCESS) {
return FAILURE;
}
Expand Down
25 changes: 25 additions & 0 deletions ext/opcache/tests/ref_range_1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Range info for references (1)
--FILE--
<?php

function test() {
escape_x($x);
$x = 0;
modify_x();
return (int) $x;
}

function escape_x(&$x) {
$GLOBALS['x'] =& $x;
}

function modify_x() {
$GLOBALS['x']++;
}

var_dump(test());

?>
--EXPECT--
int(1)
25 changes: 25 additions & 0 deletions ext/opcache/tests/ref_range_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Range info for references (2)
--FILE--
<?php

function test() {
escape_x($x);
$x = 0;
modify_x();
return PHP_INT_MAX + (int) $x;
}

function escape_x(&$x) {
$GLOBALS['x'] =& $x;
}

function modify_x() {
$GLOBALS['x']++;
}

var_dump(test());

?>
--EXPECTF--
float(%s)