Skip to content

Commit

Permalink
Fixed bug #81096: Inconsistent range inferece for variables passed by…
Browse files Browse the repository at this point in the history
… reference

Detect references before range inference and exclude them from range
inference.
  • Loading branch information
dstogov committed Jun 10, 2021
1 parent 9333a22 commit 7368d0c
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 11 deletions.
179 changes: 168 additions & 11 deletions Zend/Optimizer/zend_inference.c
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
@@ -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
@@ -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)

0 comments on commit 7368d0c

Please sign in to comment.