Skip to content

Commit 7368d0c

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 9333a22 commit 7368d0c

File tree

3 files changed

+218
-11
lines changed

3 files changed

+218
-11
lines changed

Zend/Optimizer/zend_inference.c

Lines changed: 168 additions & 11 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
}
@@ -1867,6 +1877,10 @@ static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
18671877
} \
18681878
} \
18691879
if (ssa_var_info[__var].type != __type) { \
1880+
ZEND_ASSERT(ssa_opcodes != NULL || \
1881+
__ssa_var->var >= op_array->last_var || \
1882+
(ssa_var_info[__var].type & MAY_BE_REF) \
1883+
== (__type & MAY_BE_REF)); \
18701884
if (ssa_var_info[__var].type & ~__type) { \
18711885
emit_type_narrowing_warning(op_array, ssa, __var); \
18721886
return FAILURE; \
@@ -3432,7 +3446,7 @@ static zend_always_inline int _zend_update_type_info(
34323446
zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
34333447

34343448
tmp = zend_fetch_prop_type(script, prop_info, &ce);
3435-
if (opline->result_type != IS_TMP_VAR) {
3449+
if (opline->result_type == IS_VAR) {
34363450
tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
34373451
} else if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR)) || !(t1 & MAY_BE_RC1)) {
34383452
zend_class_entry *ce = NULL;
@@ -3468,7 +3482,7 @@ static zend_always_inline int _zend_update_type_info(
34683482
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
34693483
tmp = zend_fetch_prop_type(script,
34703484
zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
3471-
if (opline->result_type != IS_TMP_VAR) {
3485+
if (opline->result_type == IS_VAR) {
34723486
tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
34733487
} else {
34743488
tmp &= ~MAY_BE_RC1;
@@ -3587,6 +3601,8 @@ static zend_always_inline int _zend_update_type_info(
35873601
} else {
35883602
tmp |= MAY_BE_RC1 | MAY_BE_RCN;
35893603
}
3604+
} else if (opline->result_type == IS_CV) {
3605+
tmp |= MAY_BE_RC1 | MAY_BE_RCN;
35903606
} else {
35913607
tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
35923608
switch (opline->opcode) {
@@ -3685,6 +3701,7 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
36853701
int i, j;
36863702
uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
36873703
bool update_worklist = 1;
3704+
const zend_op **ssa_opcodes = NULL;
36883705

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

42434260
static int zend_infer_types(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
42444261
{
4245-
zend_ssa_var_info *ssa_var_info = ssa->var_info;
42464262
int ssa_vars_count = ssa->vars_count;
42474263
int j;
42484264
zend_bitset worklist;
@@ -4254,7 +4270,6 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc
42544270
/* Type Inference */
42554271
for (j = op_array->last_var; j < ssa_vars_count; j++) {
42564272
zend_bitset_incl(worklist, j);
4257-
ssa_var_info[j].type = 0;
42584273
}
42594274

42604275
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
42734288
return SUCCESS;
42744289
}
42754290

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

4458+
if (zend_mark_cv_references(op_array, script, ssa) != SUCCESS) {
4459+
return FAILURE;
4460+
}
4461+
43054462
if (zend_infer_ranges(op_array, ssa) != SUCCESS) {
43064463
return FAILURE;
43074464
}

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)