mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
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:
parent
9333a22fa4
commit
7368d0c418
3 changed files with 218 additions and 11 deletions
|
@ -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 {
|
||||
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;
|
||||
}
|
||||
|
|
25
ext/opcache/tests/ref_range_1.phpt
Normal file
25
ext/opcache/tests/ref_range_1.phpt
Normal 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)
|
25
ext/opcache/tests/ref_range_2.phpt
Normal file
25
ext/opcache/tests/ref_range_2.phpt
Normal 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)
|
Loading…
Add table
Add a link
Reference in a new issue