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

Detect references before range inference and exclude them from range
inference.
This commit is contained in:
Dmitry Stogov 2021-06-10 11:43:15 +03:00
parent 9333a22fa4
commit 7368d0c418
3 changed files with 218 additions and 11 deletions

View file

@ -93,7 +93,8 @@
#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)
@ -101,6 +102,7 @@
#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); \
} \
@ -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];
@ -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);
@ -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];
@ -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
@ -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);
}
@ -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);
}
@ -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; \
@ -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;
@ -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;
@ -3587,6 +3601,8 @@ unknown_opcode:
} 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) {
@ -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);
@ -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;
@ -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) {
@ -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;
@ -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;
}

View file

@ -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)

View file

@ -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)