Skip to content

Commit f670e02

Browse files
committed
Fixed bug #81096: Inconsistent range inferece for variables passed by reference
Detect references before range inference and exclude them from range inference.
1 parent 87068ae commit f670e02

File tree

3 files changed

+199
-7
lines changed

3 files changed

+199
-7
lines changed

Zend/Optimizer/zend_inference.c

Lines changed: 149 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,16 @@
9393

9494
#define ADD_SCC_VAR(_var) \
9595
do { \
96-
if (ssa->vars[_var].scc == scc) { \
96+
if (ssa->vars[_var].scc == scc && \
97+
!(ssa->var_info[_var].type & MAY_BE_REF)) { \
9798
zend_bitset_incl(worklist, _var); \
9899
} \
99100
} while (0)
100101

101102
#define ADD_SCC_VAR_1(_var) \
102103
do { \
103104
if (ssa->vars[_var].scc == scc && \
105+
!(ssa->var_info[_var].type & MAY_BE_REF) && \
104106
!zend_bitset_in(visited, _var)) { \
105107
zend_bitset_incl(worklist, _var); \
106108
} \
@@ -1657,7 +1659,8 @@ static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ss
16571659
for (n = 0; n < RANGE_WARMUP_PASSES; n++) {
16581660
j= scc_var[scc];
16591661
while (j >= 0) {
1660-
if (ssa->vars[j].scc_entry) {
1662+
if (ssa->vars[j].scc_entry
1663+
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
16611664
zend_bitset_incl(worklist, j);
16621665
}
16631666
j = next_scc_var[j];
@@ -1758,7 +1761,9 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
17581761
j = scc_var[scc];
17591762
if (next_scc_var[j] < 0) {
17601763
/* SCC with a single element */
1761-
if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
1764+
if (ssa->var_info[j].type & MAY_BE_REF) {
1765+
/* pass */
1766+
} else if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
17621767
zend_inference_init_range(op_array, ssa, j, tmp.underflow, tmp.min, tmp.max, tmp.overflow);
17631768
} else {
17641769
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) /* {{
17671772
/* Find SCC entry points */
17681773
memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
17691774
do {
1770-
if (ssa->vars[j].scc_entry) {
1775+
if (ssa->vars[j].scc_entry
1776+
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
17711777
zend_bitset_incl(worklist, j);
17721778
}
17731779
j = next_scc_var[j];
@@ -1777,7 +1783,9 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
17771783
zend_infer_ranges_warmup(op_array, ssa, scc_var, next_scc_var, scc);
17781784
j = scc_var[scc];
17791785
do {
1780-
zend_bitset_incl(worklist, j);
1786+
if (!(ssa->var_info[j].type & MAY_BE_REF)) {
1787+
zend_bitset_incl(worklist, j);
1788+
}
17811789
j = next_scc_var[j];
17821790
} while (j >= 0);
17831791
#endif
@@ -1791,7 +1799,8 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
17911799

17921800
/* initialize missing ranges */
17931801
for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
1794-
if (!ssa->var_info[j].has_range) {
1802+
if (!ssa->var_info[j].has_range
1803+
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
17951804
zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
17961805
FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
17971806
}
@@ -1807,7 +1816,8 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
18071816
/* Add all SCC entry variables into worklist for narrowing */
18081817
for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
18091818
if (ssa->vars[j].definition_phi
1810-
&& ssa->vars[j].definition_phi->pi < 0) {
1819+
&& ssa->vars[j].definition_phi->pi < 0
1820+
&& !(ssa->var_info[j].type & MAY_BE_REF)) {
18111821
/* narrowing Phi functions first */
18121822
zend_ssa_range_narrowing(op_array, ssa, j, scc);
18131823
}
@@ -4273,6 +4283,134 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc
42734283
return SUCCESS;
42744284
}
42754285

4286+
static int zend_mark_cv_references(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa)
4287+
{
4288+
int var, def;
4289+
const zend_op *opline;
4290+
zend_arg_info *arg_info;
4291+
uint32_t worklist_len = zend_bitset_len(ssa->vars_count);
4292+
zend_bitset worklist;
4293+
ALLOCA_FLAG(use_heap);
4294+
4295+
worklist = do_alloca(sizeof(zend_ulong) * worklist_len, use_heap);
4296+
memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
4297+
4298+
/* Collect SSA variables which definitions creates PHP reference */
4299+
for (var = 0; var < ssa->vars_count; var++) {
4300+
def = ssa->vars[var].definition;
4301+
if (def >= 0 && ssa->vars[var].var < op_array->last_var) {
4302+
opline = op_array->opcodes + def;
4303+
if (ssa->ops[def].result_def == var) {
4304+
switch (opline->opcode) {
4305+
case ZEND_RECV:
4306+
case ZEND_RECV_INIT:
4307+
arg_info = &op_array->arg_info[opline->op1.num-1];
4308+
if (!ZEND_ARG_SEND_MODE(arg_info)) {
4309+
continue;
4310+
}
4311+
break;
4312+
default:
4313+
continue;
4314+
}
4315+
} else if (ssa->ops[def].op1_def == var) {
4316+
switch (opline->opcode) {
4317+
case ZEND_ASSIGN_REF:
4318+
case ZEND_MAKE_REF:
4319+
case ZEND_FE_RESET_RW:
4320+
case ZEND_BIND_GLOBAL:
4321+
case ZEND_SEND_REF:
4322+
case ZEND_SEND_VAR_EX:
4323+
case ZEND_SEND_FUNC_ARG:
4324+
break;
4325+
case ZEND_ADD_ARRAY_ELEMENT:
4326+
if (!(opline->extended_value & ZEND_ARRAY_ELEMENT_REF)) {
4327+
continue;
4328+
}
4329+
break;
4330+
case ZEND_BIND_LEXICAL:
4331+
case ZEND_BIND_STATIC:
4332+
if (!(opline->extended_value & ZEND_BIND_REF)) {
4333+
continue;
4334+
}
4335+
break;
4336+
case ZEND_YIELD:
4337+
if (!(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) {
4338+
continue;
4339+
}
4340+
break;
4341+
case ZEND_OP_DATA:
4342+
switch ((opline-1)->opcode) {
4343+
case ZEND_ASSIGN_OBJ_REF:
4344+
case ZEND_ASSIGN_STATIC_PROP_REF:
4345+
break;
4346+
default:
4347+
continue;
4348+
}
4349+
break;
4350+
default:
4351+
continue;
4352+
}
4353+
} else if (ssa->ops[def].op2_def == var) {
4354+
if (opline->opcode != ZEND_ASSIGN_REF) {
4355+
continue;
4356+
}
4357+
} else {
4358+
ZEND_UNREACHABLE();
4359+
}
4360+
zend_bitset_incl(worklist, var);
4361+
} else if (ssa->var_info[var].type & MAY_BE_REF) {
4362+
zend_bitset_incl(worklist, var);
4363+
} else if (ssa->vars[var].alias == SYMTABLE_ALIAS) {
4364+
zend_bitset_incl(worklist, var);
4365+
}
4366+
}
4367+
4368+
/* Set and propagate MAY_BE_REF */
4369+
WHILE_WORKLIST(worklist, worklist_len, var) {
4370+
4371+
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;
4372+
4373+
if (ssa->vars[var].phi_use_chain) {
4374+
zend_ssa_phi *p = ssa->vars[var].phi_use_chain;
4375+
do {
4376+
if (!(ssa->var_info[p->ssa_var].type & MAY_BE_REF)) {
4377+
zend_bitset_incl(worklist, p->ssa_var);
4378+
}
4379+
p = zend_ssa_next_use_phi(ssa, var, p);
4380+
} while (p);
4381+
}
4382+
4383+
if (ssa->vars[var].use_chain >= 0) {
4384+
int use = ssa->vars[var].use_chain;
4385+
zend_ssa_op *op;
4386+
4387+
do {
4388+
op = ssa->ops + use;
4389+
if (op->op1_use == var && op->op1_def >= 0) {
4390+
if (!(ssa->var_info[op->op1_def].type & MAY_BE_REF)) {
4391+
zend_bitset_incl(worklist, op->op1_def);
4392+
}
4393+
}
4394+
if (op->op2_use == var && op->op2_def >= 0) {
4395+
if (!(ssa->var_info[op->op2_def].type & MAY_BE_REF)) {
4396+
zend_bitset_incl(worklist, op->op2_def);
4397+
}
4398+
}
4399+
if (op->result_use == var && op->result_def >= 0) {
4400+
if (!(ssa->var_info[op->result_def].type & MAY_BE_REF)) {
4401+
zend_bitset_incl(worklist, op->result_def);
4402+
}
4403+
}
4404+
4405+
use = zend_ssa_next_use(ssa->ops, var, use);
4406+
} while (use >= 0);
4407+
}
4408+
} WHILE_WORKLIST_END();
4409+
4410+
free_alloca(worklist, use_heap);
4411+
return SUCCESS;
4412+
}
4413+
42764414
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) /* {{{ */
42774415
{
42784416
zend_ssa_var_info *ssa_var_info;
@@ -4302,6 +4440,10 @@ ZEND_API int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_arra
43024440
ssa_var_info[i].has_range = 0;
43034441
}
43044442

4443+
if (zend_mark_cv_references(op_array, script, ssa) != SUCCESS) {
4444+
return FAILURE;
4445+
}
4446+
43054447
if (zend_infer_ranges(op_array, ssa) != SUCCESS) {
43064448
return FAILURE;
43074449
}

ext/opcache/tests/ref_range_1.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Range info for references (1)
3+
--FILE--
4+
<?php
5+
6+
function test() {
7+
escape_x($x);
8+
$x = 0;
9+
modify_x();
10+
return (int) $x;
11+
}
12+
13+
function escape_x(&$x) {
14+
$GLOBALS['x'] =& $x;
15+
}
16+
17+
function modify_x() {
18+
$GLOBALS['x']++;
19+
}
20+
21+
var_dump(test());
22+
23+
?>
24+
--EXPECT--
25+
int(1)

ext/opcache/tests/ref_range_2.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Range info for references (2)
3+
--FILE--
4+
<?php
5+
6+
function test() {
7+
escape_x($x);
8+
$x = 0;
9+
modify_x();
10+
return PHP_INT_MAX + (int) $x;
11+
}
12+
13+
function escape_x(&$x) {
14+
$GLOBALS['x'] =& $x;
15+
}
16+
17+
function modify_x() {
18+
$GLOBALS['x']++;
19+
}
20+
21+
var_dump(test());
22+
23+
?>
24+
--EXPECTF--
25+
float(%s)

0 commit comments

Comments
 (0)