summaryrefslogtreecommitdiff
path: root/ext/opcache/Optimizer/zend_inference.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/opcache/Optimizer/zend_inference.c')
-rw-r--r--ext/opcache/Optimizer/zend_inference.c1493
1 files changed, 706 insertions, 787 deletions
diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c
index a1e2335fa9..ee14e49b5c 100644
--- a/ext/opcache/Optimizer/zend_inference.c
+++ b/ext/opcache/Optimizer/zend_inference.c
@@ -158,9 +158,9 @@ int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
int index = 0, *dfs, *root;
zend_worklist_stack stack;
int j;
- ALLOCA_FLAG(dfs_use_heap);
- ALLOCA_FLAG(root_use_heap);
- ALLOCA_FLAG(stack_use_heap);
+ ALLOCA_FLAG(dfs_use_heap)
+ ALLOCA_FLAG(root_use_heap)
+ ALLOCA_FLAG(stack_use_heap)
dfs = do_alloca(sizeof(int) * ssa->vars_count, dfs_use_heap);
memset(dfs, -1, sizeof(int) * ssa->vars_count);
@@ -199,6 +199,18 @@ int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
}
/* }}} */
+static inline zend_bool is_no_val_use(const zend_op *opline, const zend_ssa_op *ssa_op, int var)
+{
+ if (opline->opcode == ZEND_ASSIGN ||
+ (opline->opcode == ZEND_UNSET_VAR && (opline->extended_value & ZEND_QUICK_SET))) {
+ return ssa_op->op1_use == var && ssa_op->op2_use != var;
+ }
+ if (opline->opcode == ZEND_FE_FETCH_R) {
+ return ssa_op->op2_use == var && ssa_op->op1_use != var;
+ }
+ return 0;
+}
+
int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
{
zend_ssa_var *ssa_vars = ssa->vars;
@@ -220,11 +232,10 @@ int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ss
ssa_vars[i].no_val = 1; /* mark as unused */
use = ssa->vars[i].use_chain;
while (use >= 0) {
- if (op_array->opcodes[use].opcode != ZEND_ASSIGN ||
- ssa->ops[use].op1_use != i ||
- ssa->ops[use].op2_use == i) {
+ if (!is_no_val_use(&op_array->opcodes[use], &ssa->ops[use], i)) {
ssa_vars[i].no_val = 0; /* used directly */
zend_bitset_incl(worklist, i);
+ break;
}
use = zend_ssa_next_use(ssa_ops, i, use);
}
@@ -475,8 +486,9 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
tmp->min = ZEND_LONG_MAX;
tmp->max = ZEND_LONG_MIN;
tmp->overflow = 0;
- if (p->pi >= 0 && p->constraint.type_mask == (uint32_t) -1) {
- if (p->constraint.negative) {
+ if (p->pi >= 0 && p->has_range_constraint) {
+ zend_ssa_range_constraint *constraint = &p->constraint.range;
+ if (constraint->negative) {
if (ssa->var_info[p->sources[0]].has_range) {
tmp->underflow = ssa->var_info[p->sources[0]].range.underflow;
tmp->min = ssa->var_info[p->sources[0]].range.min;
@@ -489,8 +501,8 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
tmp->overflow = 1;
}
#ifdef NEG_RANGE
- if (p->constraint.min_ssa_var < 0 &&
- p->constraint.min_ssa_var < 0 &&
+ if (constraint->min_ssa_var < 0 &&
+ constraint->min_ssa_var < 0 &&
ssa->var_info[p->ssa_var].has_range) {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "%s() #%d [%ld..%ld] -> [%ld..%ld]?\n",
@@ -501,19 +513,19 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
tmp->min,
tmp->max);
#endif
- if (p->constraint.negative == NEG_USE_LT &&
- tmp->max >= p->constraint.range.min) {
+ if (constraint->negative == NEG_USE_LT &&
+ tmp->max >= constraint->range.min) {
tmp->overflow = 0;
- tmp->max = p->constraint.range.min - 1;
+ tmp->max = constraint->range.min - 1;
#ifdef LOG_NEG_RANGE
fprintf(stderr, " => [%ld..%ld]\n",
tmp->min,
tmp->max);
#endif
- } else if (p->constraint.negative == NEG_USE_GT &&
- tmp->min <= p->constraint.range.max) {
+ } else if (constraint->negative == NEG_USE_GT &&
+ tmp->min <= constraint->range.max) {
tmp->underflow = 0;
- tmp->min = p->constraint.range.max + 1;
+ tmp->min = constraint->range.max + 1;
#ifdef LOG_NEG_RANGE
fprintf(stderr, " => [%ld..%ld]\n",
tmp->min,
@@ -528,44 +540,44 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
tmp->min = ssa->var_info[p->sources[0]].range.min;
tmp->max = ssa->var_info[p->sources[0]].range.max;
tmp->overflow = ssa->var_info[p->sources[0]].range.overflow;
- if (p->constraint.min_ssa_var < 0) {
- tmp->underflow = p->constraint.range.underflow && tmp->underflow;
- tmp->min = MAX(p->constraint.range.min, tmp->min);
+ if (constraint->min_ssa_var < 0) {
+ tmp->underflow = constraint->range.underflow && tmp->underflow;
+ tmp->min = MAX(constraint->range.min, tmp->min);
#ifdef SYM_RANGE
- } else if (narrowing && ssa->var_info[p->constraint.min_ssa_var].has_range) {
- tmp->underflow = ssa->var_info[p->constraint.min_ssa_var].range.underflow && tmp->underflow;
- tmp->min = MAX(ssa->var_info[p->constraint.min_ssa_var].range.min + p->constraint.range.min, tmp->min);
+ } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
+ tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow && tmp->underflow;
+ tmp->min = MAX(ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min, tmp->min);
#endif
}
- if (p->constraint.max_ssa_var < 0) {
- tmp->max = MIN(p->constraint.range.max, tmp->max);
- tmp->overflow = p->constraint.range.overflow && tmp->overflow;
+ if (constraint->max_ssa_var < 0) {
+ tmp->max = MIN(constraint->range.max, tmp->max);
+ tmp->overflow = constraint->range.overflow && tmp->overflow;
#ifdef SYM_RANGE
- } else if (narrowing && ssa->var_info[p->constraint.max_ssa_var].has_range) {
- tmp->max = MIN(ssa->var_info[p->constraint.max_ssa_var].range.max + p->constraint.range.max, tmp->max);
- tmp->overflow = ssa->var_info[p->constraint.max_ssa_var].range.overflow && tmp->overflow;
+ } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
+ tmp->max = MIN(ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max, tmp->max);
+ tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow && tmp->overflow;
#endif
}
} else if (narrowing) {
- if (p->constraint.min_ssa_var < 0) {
- tmp->underflow = p->constraint.range.underflow;
- tmp->min = p->constraint.range.min;
+ if (constraint->min_ssa_var < 0) {
+ tmp->underflow = constraint->range.underflow;
+ tmp->min = constraint->range.min;
#ifdef SYM_RANGE
- } else if (narrowing && ssa->var_info[p->constraint.min_ssa_var].has_range) {
- tmp->underflow = ssa->var_info[p->constraint.min_ssa_var].range.underflow;
- tmp->min = ssa->var_info[p->constraint.min_ssa_var].range.min + p->constraint.range.min;
+ } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
+ tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow;
+ tmp->min = ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min;
#endif
} else {
tmp->underflow = 1;
tmp->min = ZEND_LONG_MIN;
}
- if (p->constraint.max_ssa_var < 0) {
- tmp->max = p->constraint.range.max;
- tmp->overflow = p->constraint.range.overflow;
+ if (constraint->max_ssa_var < 0) {
+ tmp->max = constraint->range.max;
+ tmp->overflow = constraint->range.overflow;
#ifdef SYM_RANGE
- } else if (narrowing && ssa->var_info[p->constraint.max_ssa_var].has_range) {
- tmp->max = ssa->var_info[p->constraint.max_ssa_var].range.max + p->constraint.range.max;
- tmp->overflow = ssa->var_info[p->constraint.max_ssa_var].range.overflow;
+ } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
+ tmp->max = ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max;
+ tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow;
#endif
} else {
tmp->max = ZEND_LONG_MAX;
@@ -591,7 +603,6 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int
return (tmp->min <= tmp->max);
} else if (ssa->vars[var].definition < 0) {
if (var < op_array->last_var &&
- var != EX_VAR_TO_NUM(op_array->this_var) &&
op_array->function_name) {
tmp->min = 0;
@@ -1814,55 +1825,57 @@ static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ss
ssa->var_info[j].has_range &&
ssa->vars[j].definition_phi &&
ssa->vars[j].definition_phi->pi >= 0 &&
- ssa->vars[j].definition_phi->constraint.type_mask == (uint32_t) -1 &&
- ssa->vars[j].definition_phi->constraint.negative &&
- ssa->vars[j].definition_phi->constraint.min_ssa_var < 0 &&
- ssa->vars[j].definition_phi->constraint.min_ssa_var < 0) {
+ ssa->vars[j].definition_phi->has_range_constraint &&
+ ssa->vars[j].definition_phi->constraint.range.negative &&
+ ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0 &&
+ ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0) {
+ zend_ssa_range_constraint *constraint =
+ &ssa->vars[j].definition_phi->constraint.range;
if (tmp.min == ssa->var_info[j].range.min &&
tmp.max == ssa->var_info[j].range.max) {
- if (ssa->vars[j].definition_phi->constraint.negative == NEG_INIT) {
+ if (constraint->negative == NEG_INIT) {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "#%d INVARIANT\n", j);
#endif
- ssa->vars[j].definition_phi->constraint.negative = NEG_INVARIANT;
+ constraint->negative = NEG_INVARIANT;
}
} else if (tmp.min == ssa->var_info[j].range.min &&
tmp.max == ssa->var_info[j].range.max + 1 &&
- tmp.max < ssa->vars[j].definition_phi->constraint.range.min) {
- if (ssa->vars[j].definition_phi->constraint.negative == NEG_INIT ||
- ssa->vars[j].definition_phi->constraint.negative == NEG_INVARIANT) {
+ tmp.max < constraint->range.min) {
+ if (constraint->negative == NEG_INIT ||
+ constraint->negative == NEG_INVARIANT) {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "#%d LT\n", j);
#endif
- ssa->vars[j].definition_phi->constraint.negative = NEG_USE_LT;
+ constraint->negative = NEG_USE_LT;
//???NEG
- } else if (ssa->vars[j].definition_phi->constraint.negative == NEG_USE_GT) {
+ } else if (constraint->negative == NEG_USE_GT) {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "#%d UNKNOWN\n", j);
#endif
- ssa->vars[j].definition_phi->constraint.negative = NEG_UNKNOWN;
+ constraint->negative = NEG_UNKNOWN;
}
} else if (tmp.max == ssa->var_info[j].range.max &&
tmp.min == ssa->var_info[j].range.min - 1 &&
- tmp.min > ssa->vars[j].definition_phi->constraint.range.max) {
- if (ssa->vars[j].definition_phi->constraint.negative == NEG_INIT ||
- ssa->vars[j].definition_phi->constraint.negative == NEG_INVARIANT) {
+ tmp.min > constraint->range.max) {
+ if (constraint->negative == NEG_INIT ||
+ constraint->negative == NEG_INVARIANT) {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "#%d GT\n", j);
#endif
- ssa->vars[j].definition_phi->constraint.negative = NEG_USE_GT;
+ constraint->negative = NEG_USE_GT;
//???NEG
- } else if (ssa->vars[j].definition_phi->constraint.negative == NEG_USE_LT) {
+ } else if (constraint->negative == NEG_USE_LT) {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "#%d UNKNOWN\n", j);
#endif
- ssa->vars[j].definition_phi->constraint.negative = NEG_UNKNOWN;
+ constraint->negative = NEG_UNKNOWN;
}
} else {
#ifdef LOG_NEG_RANGE
fprintf(stderr, "#%d UNKNOWN\n", j);
#endif
- ssa->vars[j].definition_phi->constraint.negative = NEG_UNKNOWN;
+ constraint->negative = NEG_UNKNOWN;
}
}
#endif
@@ -2006,16 +2019,26 @@ static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{
#define UPDATE_SSA_OBJ_TYPE(_ce, _is_instanceof, var) \
do { \
if (var >= 0) { \
- if (ssa_var_info[var].ce != _ce || \
- ssa_var_info[var].is_instanceof != _is_instanceof) { \
- ssa_var_info[var].ce = _ce; \
- ssa_var_info[var].is_instanceof = _is_instanceof; \
- add_usages(op_array, ssa, worklist, var); \
+ if (ssa_var_info[var].ce != (_ce) || \
+ ssa_var_info[var].is_instanceof != (_is_instanceof)) { \
+ ssa_var_info[var].ce = (_ce); \
+ ssa_var_info[var].is_instanceof = (_is_instanceof); \
+ add_usages(op_array, ssa, worklist, var); \
} \
/*zend_bitset_excl(worklist, var);*/ \
} \
} while (0)
+#define COPY_SSA_OBJ_TYPE(from_var, to_var) do { \
+ if ((from_var) >= 0 && (ssa_var_info[(from_var)].type & MAY_BE_OBJECT) \
+ && ssa_var_info[(from_var)].ce) { \
+ UPDATE_SSA_OBJ_TYPE(ssa_var_info[(from_var)].ce, \
+ ssa_var_info[(from_var)].is_instanceof, (to_var)); \
+ } else { \
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, (to_var)); \
+ } \
+} while (0)
+
static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var)
{
if (ssa->vars[var].phi_use_chain) {
@@ -2027,25 +2050,29 @@ static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset
}
if (ssa->vars[var].use_chain >= 0) {
int use = ssa->vars[var].use_chain;
+ zend_ssa_op *op;
+
do {
- if (ssa->ops[use].result_def >= 0) {
- zend_bitset_incl(worklist, ssa->ops[use].result_def);
+ op = ssa->ops + use;
+ if (op->result_def >= 0) {
+ zend_bitset_incl(worklist, op->result_def);
}
- if (ssa->ops[use].op1_def >= 0) {
- zend_bitset_incl(worklist, ssa->ops[use].op1_def);
+ if (op->op1_def >= 0) {
+ zend_bitset_incl(worklist, op->op1_def);
}
- if (ssa->ops[use].op2_def >= 0) {
- zend_bitset_incl(worklist, ssa->ops[use].op2_def);
+ if (op->op2_def >= 0) {
+ zend_bitset_incl(worklist, op->op2_def);
}
if (op_array->opcodes[use].opcode == ZEND_OP_DATA) {
- if (ssa->ops[use-1].result_def >= 0) {
- zend_bitset_incl(worklist, ssa->ops[use-1].result_def);
+ op--;
+ if (op->result_def >= 0) {
+ zend_bitset_incl(worklist, op->result_def);
}
- if (ssa->ops[use-1].op1_def >= 0) {
- zend_bitset_incl(worklist, ssa->ops[use-1].op1_def);
+ if (op->op1_def >= 0) {
+ zend_bitset_incl(worklist, op->op1_def);
}
- if (ssa->ops[use-1].op2_def >= 0) {
- zend_bitset_incl(worklist, ssa->ops[use-1].op2_def);
+ if (op->op2_def >= 0) {
+ zend_bitset_incl(worklist, op->op2_def);
}
}
use = zend_ssa_next_use(ssa->ops, var, use);
@@ -2175,138 +2202,248 @@ uint32_t zend_array_element_type(uint32_t t1, int write, int insert)
return tmp;
}
-static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) {
- zend_class_entry *ce = zend_hash_find_ptr(&script->class_table, lcname);
- if (ce) {
- return ce;
- }
-
- ce = zend_hash_find_ptr(CG(class_table), lcname);
- if (ce && ce->type == ZEND_INTERNAL_CLASS) {
- return ce;
+static uint32_t assign_dim_result_type(
+ uint32_t arr_type, uint32_t dim_type, uint32_t value_type, zend_uchar dim_op_type) {
+ uint32_t tmp = arr_type
+ & (MAY_BE_ANY|MAY_BE_REF|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF)
+ & ~(MAY_BE_NULL|MAY_BE_FALSE);
+ if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_STRING)) {
+ tmp |= MAY_BE_ARRAY;
+ }
+ if (arr_type & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (tmp & MAY_BE_ARRAY) {
+ tmp |= (value_type & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ if (value_type & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_ARRAY_OF_NULL;
+ }
+ if (dim_op_type == IS_UNUSED) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (dim_type & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (dim_type & MAY_BE_STRING) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ if (dim_op_type != IS_CONST) {
+ // FIXME: numeric string
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (dim_type & (MAY_BE_UNDEF|MAY_BE_NULL)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
}
-
- return NULL;
+ return tmp;
}
-static void zend_update_type_info(const zend_op_array *op_array,
- zend_ssa *ssa,
- const zend_script *script,
- zend_bitset worklist,
- int i)
-{
- uint32_t t1, t2;
- uint32_t tmp, orig;
- zend_op *opline = op_array->opcodes + i;
- zend_ssa_op *ssa_ops = ssa->ops;
- zend_ssa_var *ssa_vars = ssa->vars;
- zend_ssa_var_info *ssa_var_info = ssa->var_info;
- zend_class_entry *ce;
- int j;
-
- if (opline->opcode == ZEND_OP_DATA) {
- opline--;
- i--;
- }
-
- t1 = OP1_INFO();
- t2 = OP2_INFO();
-
- switch (opline->opcode) {
+/* For binary ops that have compound assignment operators */
+static uint32_t binary_op_result_type(
+ zend_ssa *ssa, zend_uchar opcode, uint32_t t1, uint32_t t2, uint32_t result_var) {
+ uint32_t tmp;
+ uint32_t t1_type = (t1 & MAY_BE_ANY) | (t1 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
+ uint32_t t2_type = (t2 & MAY_BE_ANY) | (t2 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
+ switch (opcode) {
case ZEND_ADD:
tmp = MAY_BE_RC1;
- if ((t1 & MAY_BE_ANY) == MAY_BE_LONG &&
- (t2 & MAY_BE_ANY) == MAY_BE_LONG) {
-
- if (!ssa_var_info[ssa_ops[i].result_def].has_range ||
- ssa_var_info[ssa_ops[i].result_def].range.underflow ||
- ssa_var_info[ssa_ops[i].result_def].range.overflow) {
+ if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
+ if (!ssa->var_info[result_var].has_range ||
+ ssa->var_info[result_var].range.underflow ||
+ ssa->var_info[result_var].range.overflow) {
/* may overflow */
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
} else {
tmp |= MAY_BE_LONG;
}
- } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE ||
- (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) {
+ } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
tmp |= MAY_BE_DOUBLE;
- } else if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY &&
- (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
+ } else if (t1_type == MAY_BE_ARRAY && t2_type == MAY_BE_ARRAY) {
tmp |= MAY_BE_ARRAY;
tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
} else {
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
- if ((t1 & MAY_BE_ARRAY) && (t2 & MAY_BE_ARRAY)) {
+ if ((t1_type & MAY_BE_ARRAY) && (t2_type & MAY_BE_ARRAY)) {
tmp |= MAY_BE_ARRAY;
tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
}
}
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
case ZEND_SUB:
case ZEND_MUL:
tmp = MAY_BE_RC1;
- if ((t1 & MAY_BE_ANY) == MAY_BE_LONG &&
- (t2 & MAY_BE_ANY) == MAY_BE_LONG) {
- if (!ssa_var_info[ssa_ops[i].result_def].has_range ||
- ssa_var_info[ssa_ops[i].result_def].range.underflow ||
- ssa_var_info[ssa_ops[i].result_def].range.overflow) {
+ if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
+ if (!ssa->var_info[result_var].has_range ||
+ ssa->var_info[result_var].range.underflow ||
+ ssa->var_info[result_var].range.overflow) {
/* may overflow */
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
} else {
tmp |= MAY_BE_LONG;
}
- } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE ||
- (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) {
+ } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
tmp |= MAY_BE_DOUBLE;
} else {
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
}
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
case ZEND_DIV:
case ZEND_POW:
tmp = MAY_BE_RC1;
- if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE ||
- (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) {
+ if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
tmp |= MAY_BE_DOUBLE;
} else {
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
}
/* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special
* handling */
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
case ZEND_MOD:
tmp = MAY_BE_RC1 | MAY_BE_LONG;
/* Division by zero results in an exception, so it doesn't need any special handling */
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
- case ZEND_BW_NOT:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
tmp = MAY_BE_RC1;
- if (t1 & MAY_BE_STRING) {
+ if ((t1_type & MAY_BE_STRING) && (t2_type & MAY_BE_STRING)) {
tmp |= MAY_BE_STRING;
}
- if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) {
+ if ((t1_type & ~MAY_BE_STRING) || (t2_type & ~MAY_BE_STRING)) {
tmp |= MAY_BE_LONG;
}
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
+ case ZEND_SL:
+ case ZEND_SR:
+ tmp = MAY_BE_RC1|MAY_BE_LONG;
+ break;
+ case ZEND_CONCAT:
+ /* TODO: +MAY_BE_OBJECT ??? */
+ tmp = MAY_BE_RC1|MAY_BE_STRING;
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ return tmp;
+}
+
+/* Get the normal op corresponding to a compound assignment op */
+static inline zend_uchar get_compound_assign_op(zend_uchar opcode) {
+ switch (opcode) {
+ case ZEND_ASSIGN_ADD: return ZEND_ADD;
+ case ZEND_ASSIGN_SUB: return ZEND_SUB;
+ case ZEND_ASSIGN_MUL: return ZEND_MUL;
+ case ZEND_ASSIGN_DIV: return ZEND_DIV;
+ case ZEND_ASSIGN_MOD: return ZEND_MOD;
+ case ZEND_ASSIGN_SL: return ZEND_SL;
+ case ZEND_ASSIGN_SR: return ZEND_SR;
+ case ZEND_ASSIGN_CONCAT: return ZEND_CONCAT;
+ case ZEND_ASSIGN_BW_OR: return ZEND_BW_OR;
+ case ZEND_ASSIGN_BW_AND: return ZEND_BW_AND;
+ case ZEND_ASSIGN_BW_XOR: return ZEND_BW_XOR;
+ case ZEND_ASSIGN_POW: return ZEND_POW;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+}
+
+static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) {
+ zend_class_entry *ce = zend_hash_find_ptr(&script->class_table, lcname);
+ if (ce) {
+ return ce;
+ }
+
+ ce = zend_hash_find_ptr(CG(class_table), lcname);
+ if (ce && ce->type == ZEND_INTERNAL_CLASS) {
+ return ce;
+ }
+
+ return NULL;
+}
+
+static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce)
+{
+ uint32_t tmp = 0;
+
+ *pce = NULL;
+ if (arg_info->class_name) {
+ // class type hinting...
+ zend_string *lcname = zend_string_tolower(arg_info->class_name);
+ tmp |= MAY_BE_OBJECT;
+ *pce = get_class_entry(script, lcname);
+ zend_string_release(lcname);
+ } else if (arg_info->type_hint != IS_UNDEF) {
+ if (arg_info->type_hint == IS_VOID) {
+ tmp |= MAY_BE_NULL;
+ } else if (arg_info->type_hint == IS_CALLABLE) {
+ tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ } else if (arg_info->type_hint == IS_ARRAY) {
+ tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ } else if (arg_info->type_hint == _IS_BOOL) {
+ tmp |= MAY_BE_TRUE|MAY_BE_FALSE;
+ } else {
+ ZEND_ASSERT(arg_info->type_hint < IS_REFERENCE);
+ tmp |= 1 << arg_info->type_hint;
+ }
+ } else {
+ tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (arg_info->allow_null) {
+ tmp |= MAY_BE_NULL;
+ }
+ return tmp;
+}
+
+static void zend_update_type_info(const zend_op_array *op_array,
+ zend_ssa *ssa,
+ const zend_script *script,
+ zend_bitset worklist,
+ int i)
+{
+ uint32_t t1, t2;
+ uint32_t tmp, orig;
+ zend_op *opline = op_array->opcodes + i;
+ zend_ssa_op *ssa_ops = ssa->ops;
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ zend_class_entry *ce;
+ int j;
+
+ if (opline->opcode == ZEND_OP_DATA) {
+ opline--;
+ i--;
+ }
+
+ t1 = OP1_INFO();
+ t2 = OP2_INFO();
+
+ switch (opline->opcode) {
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_POW:
+ case ZEND_MOD:
case ZEND_BW_OR:
case ZEND_BW_AND:
case ZEND_BW_XOR:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_CONCAT:
+ tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_ops[i].result_def);
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ break;
+ case ZEND_BW_NOT:
tmp = MAY_BE_RC1;
- if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) {
+ if (t1 & MAY_BE_STRING) {
tmp |= MAY_BE_STRING;
}
- if ((t1 & (MAY_BE_ANY-MAY_BE_STRING)) || (t1 & (MAY_BE_ANY-MAY_BE_STRING))) {
+ if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) {
tmp |= MAY_BE_LONG;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
- case ZEND_SL:
- case ZEND_SR:
case ZEND_BEGIN_SILENCE:
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_LONG, ssa_ops[i].result_def);
break;
@@ -2344,7 +2481,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
if (t1 & MAY_BE_OBJECT) {
tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
} else {
- tmp |= (t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ tmp |= ((t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT) | MAY_BE_ARRAY_KEY_LONG;
}
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
@@ -2368,328 +2505,29 @@ static void zend_update_type_info(const zend_op_array *op_array,
}
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def);
break;
case ZEND_ASSIGN_ADD:
- orig = 0;
- if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- tmp = MAY_BE_RC1;
- orig = t1;
- t1 = MAY_BE_ANY;
- t2 = OP1_DATA_INFO();
- } else if (opline->extended_value == ZEND_ASSIGN_DIM) {
- tmp = MAY_BE_RC1;
- orig = t1;
- t1 = zend_array_element_type(t1, 1, 0);
- t2 = OP1_DATA_INFO();
- } else {
- tmp = 0;
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- if (ssa_ops[i].result_def >= 0) {
- tmp |= MAY_BE_RCN;
- }
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- }
- if ((t1 & MAY_BE_ANY) == MAY_BE_LONG &&
- (t2 & MAY_BE_ANY) == MAY_BE_LONG) {
-
- if (!ssa_var_info[ssa_ops[i].op1_def].has_range ||
- ssa_var_info[ssa_ops[i].op1_def].range.underflow ||
- ssa_var_info[ssa_ops[i].op1_def].range.overflow) {
- /* may overflow */
- tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
- } else {
- tmp |= MAY_BE_LONG;
- }
- } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE ||
- (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) {
- tmp |= MAY_BE_DOUBLE;
- } else if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY &&
- (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
- tmp |= MAY_BE_ARRAY;
- tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
- tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
- } else {
- tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
- if ((t1 & MAY_BE_ARRAY) && (t2 & MAY_BE_ARRAY)) {
- tmp |= MAY_BE_ARRAY;
- tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
- tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
- }
- }
- if (opline->extended_value == ZEND_ASSIGN_DIM) {
- if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
- }
- UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
- if ((orig & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
- }
- } else if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- if (opline->op1_type == IS_CV) {
- if (orig & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING)) {
- orig |= MAY_BE_OBJECT;
- }
- if (orig & MAY_BE_RCN) {
- orig |= MAY_BE_RC1;
- }
- UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
- if ((orig & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
- }
- } else {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- }
- if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- }
- break;
case ZEND_ASSIGN_SUB:
case ZEND_ASSIGN_MUL:
- if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- goto unknown_opcode;
- } else if (opline->extended_value == ZEND_ASSIGN_DIM) {
- tmp = MAY_BE_RC1;
- orig = t1;
- t1 = zend_array_element_type(t1, 1, 0);
- t2 = OP1_DATA_INFO();
- } else {
- tmp = 0;
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- if (ssa_ops[i].result_def >= 0) {
- tmp |= MAY_BE_RCN;
- }
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- }
- if ((t1 & MAY_BE_ANY) == MAY_BE_LONG &&
- (t2 & MAY_BE_ANY) == MAY_BE_LONG) {
- if (!ssa_var_info[ssa_ops[i].op1_def].has_range ||
- ssa_var_info[ssa_ops[i].op1_def].range.underflow ||
- ssa_var_info[ssa_ops[i].op1_def].range.overflow) {
- /* may overflow */
- tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
- } else {
- tmp |= MAY_BE_LONG;
- }
- } else if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE ||
- (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) {
- tmp |= MAY_BE_DOUBLE;
- } else {
- tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
- }
- if (opline->extended_value == ZEND_ASSIGN_DIM) {
- if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
- }
- UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
- }
- } else {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- }
- if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- }
- break;
case ZEND_ASSIGN_DIV:
case ZEND_ASSIGN_POW:
- if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- goto unknown_opcode;
- } else if (opline->extended_value == ZEND_ASSIGN_DIM) {
- tmp = MAY_BE_RC1;
- orig = t1;
- t1 = zend_array_element_type(t1, 1, 0);
- t2 = OP1_DATA_INFO();
- } else {
- tmp = 0;
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- if (ssa_ops[i].result_def >= 0) {
- tmp |= MAY_BE_RCN;
- }
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- }
- if ((t1 & MAY_BE_ANY) == MAY_BE_DOUBLE ||
- (t2 & MAY_BE_ANY) == MAY_BE_DOUBLE) {
- tmp |= MAY_BE_DOUBLE;
- } else {
- tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
- }
- /* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special
- * handling */
- if (opline->extended_value == ZEND_ASSIGN_DIM) {
- if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
- }
- UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
- }
- } else {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- }
- if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- }
- break;
case ZEND_ASSIGN_MOD:
- if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- goto unknown_opcode;
- } else if (opline->extended_value == ZEND_ASSIGN_DIM) {
- tmp = MAY_BE_RC1;
- orig = t1;
- t1 = zend_array_element_type(t1, 1, 0);
- t2 = OP1_DATA_INFO();
- } else {
- tmp = 0;
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- if (ssa_ops[i].result_def >= 0) {
- tmp |= MAY_BE_RCN;
- }
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- }
- tmp |= MAY_BE_LONG;
- if (opline->extended_value == ZEND_ASSIGN_DIM) {
- if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
- }
- UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
- }
- } else {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- }
- if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- }
- break;
case ZEND_ASSIGN_SL:
case ZEND_ASSIGN_SR:
+ case ZEND_ASSIGN_BW_OR:
+ case ZEND_ASSIGN_BW_AND:
+ case ZEND_ASSIGN_BW_XOR:
+ case ZEND_ASSIGN_CONCAT:
+ orig = 0;
if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- goto unknown_opcode;
- } else if (opline->extended_value == ZEND_ASSIGN_DIM) {
tmp = MAY_BE_RC1;
- orig = t1;
- t1 = zend_array_element_type(t1, 1, 0);
+ orig = t1 & ~MAY_BE_UNDEF;
+ t1 = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
t2 = OP1_DATA_INFO();
- } else {
- tmp = 0;
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- if (ssa_ops[i].result_def >= 0) {
- tmp |= MAY_BE_RCN;
- }
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- }
- tmp |= MAY_BE_LONG;
- if (opline->extended_value == ZEND_ASSIGN_DIM) {
- if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
- }
- UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
- }
- } else {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- }
- if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- }
- break;
- case ZEND_ASSIGN_CONCAT:
- if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- goto unknown_opcode;
} else if (opline->extended_value == ZEND_ASSIGN_DIM) {
tmp = MAY_BE_RC1;
- orig = t1;
+ orig = t1 & ~MAY_BE_UNDEF;
t1 = zend_array_element_type(t1, 1, 0);
t2 = OP1_DATA_INFO();
} else {
@@ -2704,79 +2542,26 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_REF;
}
}
- tmp |= MAY_BE_STRING;
+
+ tmp |= binary_op_result_type(
+ ssa, get_compound_assign_op(opline->opcode), t1, t2, ssa_ops[i].op1_def);
+
if (opline->extended_value == ZEND_ASSIGN_DIM) {
if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
- }
+ orig = assign_dim_result_type(orig, OP2_INFO(), tmp, opline->op1_type);
UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
- } else {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- }
- if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- }
- break;
- case ZEND_ASSIGN_BW_OR:
- case ZEND_ASSIGN_BW_AND:
- case ZEND_ASSIGN_BW_XOR:
- if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- goto unknown_opcode;
- } else if (opline->extended_value == ZEND_ASSIGN_DIM) {
- tmp = MAY_BE_RC1;
- orig = t1;
- t1 = zend_array_element_type(t1, 1, 0);
- t2 = OP1_DATA_INFO();
- } else {
- tmp = 0;
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- if (ssa_ops[i].result_def >= 0) {
- tmp |= MAY_BE_RCN;
- }
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- }
- if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) {
- tmp |= MAY_BE_STRING;
- }
- if ((t1 & (MAY_BE_ANY-MAY_BE_STRING)) || (t1 & (MAY_BE_ANY-MAY_BE_STRING))) {
- tmp |= MAY_BE_LONG;
- }
- if (opline->extended_value == ZEND_ASSIGN_DIM) {
+ } else if (opline->extended_value == ZEND_ASSIGN_OBJ) {
if (opline->op1_type == IS_CV) {
- orig |= MAY_BE_ARRAY | ((tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT);
- t2 = OP2_INFO();
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
+ if (orig & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING)) {
+ orig |= MAY_BE_OBJECT;
}
- if (!(orig & (MAY_BE_OBJECT|MAY_BE_REF))) {
- orig &= ~MAY_BE_RCN;
+ if (orig & MAY_BE_RCN) {
+ orig |= MAY_BE_RC1;
}
UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
} else {
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
@@ -2785,12 +2570,6 @@ static void zend_update_type_info(const zend_op_array *op_array,
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
}
break;
-// TODO: ???
-// UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].op1_def);
-// if (ssa_ops[i].result_def >= 0) {
-// UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_ops[i].result_def);
-// }
-// break;
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
tmp = 0;
@@ -2817,8 +2596,15 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_LONG;
}
} else {
+ if (t1 & MAY_BE_ERROR) {
+ tmp |= MAY_BE_NULL;
+ }
if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_LONG;
+ if (opline->opcode == ZEND_PRE_INC) {
+ tmp |= MAY_BE_LONG;
+ } else {
+ tmp |= MAY_BE_NULL;
+ }
}
if (t1 & MAY_BE_LONG) {
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
@@ -2841,7 +2627,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
case ZEND_POST_INC:
case ZEND_POST_DEC:
if (ssa_ops[i].result_def >= 0) {
- tmp = (MAY_BE_RC1 | t1) & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RCN);
+ tmp = (MAY_BE_RC1 | t1) & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_REF|MAY_BE_RCN);
if (t1 & MAY_BE_UNDEF) {
tmp |= MAY_BE_NULL;
}
@@ -2868,8 +2654,15 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_LONG;
}
} else {
+ if (t1 & MAY_BE_ERROR) {
+ tmp |= MAY_BE_NULL;
+ }
if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_LONG;
+ if (opline->opcode == ZEND_POST_INC) {
+ tmp |= MAY_BE_LONG;
+ } else {
+ tmp |= MAY_BE_NULL;
+ }
}
if (t1 & MAY_BE_LONG) {
tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
@@ -2888,36 +2681,9 @@ static void zend_update_type_info(const zend_op_array *op_array,
break;
case ZEND_ASSIGN_DIM:
if (opline->op1_type == IS_CV) {
- tmp = (t1 & (MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF));
- tmp &= ~MAY_BE_NULL;
- if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING)) {
- tmp |= MAY_BE_ARRAY;
- }
- if (t1 & MAY_BE_REF) {
- tmp |= MAY_BE_REF;
- }
- if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
- tmp |= MAY_BE_RC1;
- }
- if (tmp & MAY_BE_ARRAY) {
- tmp |= (OP1_DATA_INFO() & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
- if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
- tmp |= MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_STRING)) {
- // FIXME: numeric string
- tmp |= MAY_BE_ARRAY_KEY_STRING | MAY_BE_ARRAY_KEY_LONG;
- }
- if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
- tmp |= MAY_BE_ARRAY_KEY_STRING;
- }
- }
+ tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type);
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
tmp = 0;
@@ -2959,11 +2725,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_RC1;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
// TODO: ???
@@ -3022,19 +2784,11 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_DOUBLE;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t2 & MAY_BE_OBJECT) && ssa_ops[i].op2_use >= 0 && ssa_var_info[ssa_ops[i].op2_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op2_use].ce, ssa_var_info[ssa_ops[i].op2_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
- UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- if ((t2 & MAY_BE_OBJECT) && ssa_ops[i].op2_use >= 0 && ssa_var_info[ssa_ops[i].op2_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op2_use].ce, ssa_var_info[ssa_ops[i].op2_use].is_instanceof, ssa_ops[i].result_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
- }
+ UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_ops[i].result_def);
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def);
}
break;
case ZEND_ASSIGN_REF:
@@ -3049,7 +2803,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
if (opline->op2_type == IS_VAR && opline->extended_value == ZEND_RETURNS_FUNCTION) {
tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
} else {
- tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN);
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_RC1|MAY_BE_RCN);
}
if (t2 & MAY_BE_UNDEF) {
tmp |= MAY_BE_NULL;
@@ -3060,20 +2814,18 @@ static void zend_update_type_info(const zend_op_array *op_array,
}
break;
case ZEND_BIND_GLOBAL:
- tmp = (MAY_BE_REF | MAY_BE_ANY);
+ tmp = MAY_BE_REF | MAY_BE_ANY
+ | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
break;
case ZEND_BIND_STATIC:
- tmp = MAY_BE_ANY | (opline->extended_value ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN));
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | (opline->extended_value ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN));
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
break;
case ZEND_SEND_VAR:
UPDATE_SSA_TYPE(t1 | MAY_BE_RC1 | MAY_BE_RCN, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
break;
case ZEND_BIND_LEXICAL:
if (ssa_ops[i].op2_def >= 0) {
@@ -3082,11 +2834,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_REF;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
- if ((t2 & MAY_BE_OBJECT) && ssa_var_info[ssa_ops[i].op2_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op2_use].ce, ssa_var_info[ssa_ops[i].op2_use].is_instanceof, ssa_ops[i].op2_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op2_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].op2_def);
}
break;
case ZEND_YIELD:
@@ -3096,11 +2844,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_REF;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
@@ -3110,6 +2854,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
break;
case ZEND_SEND_VAR_EX:
case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_REF:
// TODO: ???
if (ssa_ops[i].op1_def >= 0) {
@@ -3117,16 +2862,22 @@ static void zend_update_type_info(const zend_op_array *op_array,
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
}
break;
+ case ZEND_SEND_UNPACK:
+ if (ssa_ops[i].op1_def >= 0) {
+ tmp = t1 | MAY_BE_RC1|MAY_BE_RCN;
+ if (t1 & MAY_BE_ARRAY) {
+ /* SEND_UNPACK may acquire references into the array */
+ tmp |= MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+ }
+ break;
case ZEND_FAST_CONCAT:
case ZEND_ROPE_INIT:
case ZEND_ROPE_ADD:
case ZEND_ROPE_END:
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_STRING, ssa_ops[i].result_def);
break;
- case ZEND_CONCAT:
- /* TODO: +MAY_BE_OBJECT ??? */
- UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_STRING, ssa_ops[i].result_def);
- break;
case ZEND_RECV:
case ZEND_RECV_INIT:
{
@@ -3139,30 +2890,8 @@ static void zend_update_type_info(const zend_op_array *op_array,
ce = NULL;
if (arg_info) {
- tmp = 0;
- if (arg_info->class_name) {
- // class type hinting...
- zend_string *lcname = zend_string_tolower(arg_info->class_name);
- tmp |= MAY_BE_OBJECT;
- ce = get_class_entry(script, lcname);
- zend_string_release(lcname);
- } else if (arg_info->type_hint != IS_UNDEF) {
- if (arg_info->type_hint == IS_CALLABLE) {
- tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
- } else if (arg_info->type_hint == IS_ARRAY) {
- tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
- } else if (arg_info->type_hint == _IS_BOOL) {
- tmp |= MAY_BE_TRUE|MAY_BE_FALSE;
- } else {
- ZEND_ASSERT(arg_info->type_hint < IS_REFERENCE);
- tmp |= 1 << arg_info->type_hint;
- }
- } else {
- tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
- }
- if (arg_info->allow_null) {
- tmp |= MAY_BE_NULL;
- } else if (opline->opcode == ZEND_RECV_INIT &&
+ tmp = zend_fetch_arg_info(script, arg_info, &ce);
+ if (opline->opcode == ZEND_RECV_INIT &&
Z_CONSTANT_P(CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants))) {
/* The constant may resolve to NULL */
tmp |= MAY_BE_NULL;
@@ -3179,12 +2908,6 @@ static void zend_update_type_info(const zend_op_array *op_array,
if (func_info && (int)opline->op1.num-1 < func_info->num_args) {
tmp = (tmp & (MAY_BE_RC1|MAY_BE_RCN|MAY_BE_REF)) |
(tmp & func_info->arg_info[opline->op1.num-1].info.type);
- } else {
- if (opline->opcode == ZEND_RECV && (!arg_info || arg_info->type_hint == IS_UNDEF)) {
- /* If the argument has no default value and no typehint, it is possible
- * to pass less arguments than the function expects */
- tmp |= MAY_BE_UNDEF|MAY_BE_RC1;
- }
}
#if 0
/* We won't recieve unused arguments */
@@ -3214,6 +2937,8 @@ static void zend_update_type_info(const zend_op_array *op_array,
}
case ZEND_DECLARE_CLASS:
case ZEND_DECLARE_INHERITED_CLASS:
+ case ZEND_DECLARE_ANON_CLASS:
+ case ZEND_DECLARE_ANON_INHERITED_CLASS:
UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_ops[i].result_def);
if ((ce = zend_hash_find_ptr(&script->class_table, Z_STR_P(CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants)))) != NULL) {
UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_ops[i].result_def);
@@ -3250,12 +2975,8 @@ static void zend_update_type_info(const zend_op_array *op_array,
} else {
UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
}
- } else if (t2 & MAY_BE_OBJECT) {
- if (ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def);
- }
} else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op2_use, ssa_ops[i].result_def);
}
break;
case ZEND_NEW:
@@ -3272,11 +2993,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
break;
case ZEND_CLONE:
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def);
break;
case ZEND_INIT_ARRAY:
case ZEND_ADD_ARRAY_ELEMENT:
@@ -3345,11 +3062,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
case ZEND_UNSET_OBJ:
if (ssa_ops[i].op1_def >= 0) {
UPDATE_SSA_TYPE(t1, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
break;
// case ZEND_INCLUDE_OR_EVAL:
@@ -3368,11 +3081,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp |= MAY_BE_REF;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (opline->opcode == ZEND_FE_RESET_RW) {
//???
@@ -3383,11 +3092,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp = MAY_BE_RC1 | MAY_BE_RCN | (t1 & (MAY_BE_REF | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].result_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].result_def);
break;
case ZEND_FE_FETCH_R:
case ZEND_FE_FETCH_RW:
@@ -3418,6 +3123,9 @@ static void zend_update_type_info(const zend_op_array *op_array,
tmp = MAY_BE_RCN;
}
}
+ if (opline->opcode == ZEND_FE_FETCH_R && (t2 & MAY_BE_REF)) {
+ tmp |= MAY_BE_REF;
+ }
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op2_def);
if (ssa_ops[i].result_def >= 0) {
tmp = MAY_BE_RC1;
@@ -3502,6 +3210,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
break;
case ZEND_SEND_VAR_EX:
case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_REF:
case ZEND_ASSIGN_REF:
tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
@@ -3523,11 +3232,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
j = zend_ssa_next_use(ssa_ops, ssa_ops[i].result_def, j);
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
/* FETCH_LIST on a string behaves like FETCH_R on null */
tmp = zend_array_element_type(
@@ -3545,9 +3250,15 @@ static void zend_update_type_info(const zend_op_array *op_array,
} else if (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) {
tmp |= MAY_BE_ERROR;
}
+ } else if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) {
+ tmp |= MAY_BE_NULL;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
break;
+ case ZEND_FETCH_THIS:
+ UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_ops[i].result_def);
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_ops[i].result_def);
+ break;
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_IS:
case ZEND_FETCH_OBJ_RW:
@@ -3573,14 +3284,13 @@ static void zend_update_type_info(const zend_op_array *op_array,
}
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
- if ((t1 & MAY_BE_OBJECT) && ssa_ops[i].op1_use >= 0 && ssa_var_info[ssa_ops[i].op1_use].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_ops[i].op1_use].ce, ssa_var_info[ssa_ops[i].op1_use].is_instanceof, ssa_ops[i].op1_def);
- } else {
- UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
- }
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
- tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ERROR;
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ if (opline->opcode != ZEND_FETCH_OBJ_R && opline->opcode != ZEND_FETCH_OBJ_IS) {
+ tmp |= MAY_BE_ERROR;
+ }
if (opline->result_type == IS_TMP_VAR) {
tmp |= MAY_BE_RC1;
} else {
@@ -3626,7 +3336,7 @@ static void zend_update_type_info(const zend_op_array *op_array,
break;
case ZEND_STRLEN:
tmp = MAY_BE_RC1|MAY_BE_LONG;
- if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT))) {
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) {
tmp |= MAY_BE_NULL;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
@@ -3635,6 +3345,29 @@ static void zend_update_type_info(const zend_op_array *op_array,
case ZEND_DEFINED:
UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_FALSE|MAY_BE_TRUE, ssa_ops[i].result_def);
break;
+ case ZEND_VERIFY_RETURN_TYPE:
+ {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+
+ tmp = zend_fetch_arg_info(script, ret_info, &ce);
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].op1_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].op1_def);
+ }
+ } else {
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_ops[i].result_def);
+ }
+ }
+ break;
+ }
default:
unknown_opcode:
if (ssa_ops[i].op1_def >= 0) {
@@ -3654,6 +3387,50 @@ unknown_opcode:
}
}
+static uint32_t get_class_entry_rank(zend_class_entry *ce) {
+ uint32_t rank = 0;
+ while (ce->parent) {
+ rank++;
+ ce = ce->parent;
+ }
+ return rank;
+}
+
+/* Compute least common ancestor on class inheritance tree only */
+static zend_class_entry *join_class_entries(
+ zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) {
+ uint32_t rank1, rank2;
+ if (ce1 == ce2) {
+ return ce1;
+ }
+ if (!ce1 || !ce2) {
+ return NULL;
+ }
+
+ rank1 = get_class_entry_rank(ce1);
+ rank2 = get_class_entry_rank(ce2);
+
+ while (rank1 != rank2) {
+ if (rank1 > rank2) {
+ ce1 = ce1->parent;
+ rank1--;
+ } else {
+ ce2 = ce2->parent;
+ rank2--;
+ }
+ }
+
+ while (ce1 != ce2) {
+ ce1 = ce1->parent;
+ ce2 = ce2->parent;
+ }
+
+ if (ce1) {
+ *is_instanceof = 1;
+ }
+ return ce1;
+}
+
int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist)
{
zend_basic_block *blocks = ssa->cfg.blocks;
@@ -3670,14 +3447,28 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
if (ssa_vars[j].definition_phi) {
zend_ssa_phi *p = ssa_vars[j].definition_phi;
if (p->pi >= 0) {
+ zend_class_entry *ce = ssa_var_info[p->sources[0]].ce;
+ int is_instanceof = ssa_var_info[p->sources[0]].is_instanceof;
tmp = get_ssa_var_info(ssa, p->sources[0]);
- if (p->constraint.type_mask != (uint32_t) -1) {
- tmp &= p->constraint.type_mask;
+
+ if (!p->has_range_constraint) {
+ zend_ssa_type_constraint *constraint = &p->constraint.type;
+ tmp &= constraint->type_mask;
+ if ((tmp & MAY_BE_OBJECT) && constraint->ce && ce != constraint->ce) {
+ if (!ce) {
+ ce = constraint->ce;
+ is_instanceof = 1;
+ } else if (is_instanceof && instanceof_function(constraint->ce, ce)) {
+ ce = constraint->ce;
+ } else {
+ /* Ignore the constraint (either ce instanceof constraint->ce or
+ * they are unrelated, as far as we can statically determine) */
+ }
+ }
}
+
UPDATE_SSA_TYPE(tmp, j);
- if (ssa_var_info[p->sources[0]].ce) {
- UPDATE_SSA_OBJ_TYPE(ssa_var_info[p->sources[0]].ce, ssa_var_info[p->sources[0]].is_instanceof, j);
- }
+ UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j);
} else {
int first = 1;
int is_instanceof = 0;
@@ -3689,20 +3480,21 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
}
UPDATE_SSA_TYPE(tmp, j);
for (i = 0; i < blocks[p->block].predecessors_count; i++) {
- if (get_ssa_var_info(ssa, p->sources[i])) {
- if (first) {
- ce = ssa_var_info[p->sources[i]].ce;
- is_instanceof = ssa_var_info[p->sources[i]].is_instanceof;
- first = 0;
- } else if (ce != ssa_var_info[p->sources[i]].ce) {
- ce = NULL;
- is_instanceof = 0;
- } else if (is_instanceof != ssa_var_info[p->sources[i]].is_instanceof) {
- is_instanceof = 1;
+ if (p->sources[i] >= 0) {
+ zend_ssa_var_info *info = &ssa_var_info[p->sources[i]];
+ if (info->type & MAY_BE_OBJECT) {
+ if (first) {
+ ce = info->ce;
+ is_instanceof = info->is_instanceof;
+ first = 0;
+ } else {
+ is_instanceof |= info->is_instanceof;
+ ce = join_class_entries(ce, info->ce, &is_instanceof);
+ }
}
}
}
- UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j);
+ UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j);
}
} else if (ssa_vars[j].definition >= 0) {
i = ssa_vars[j].definition;
@@ -3712,121 +3504,219 @@ int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script
return SUCCESS;
}
-#define CHECK_MAY_BE_USED_AS_DOUBLE(var2) do { \
- if ((ssa->var_info[var2].type & MAY_BE_ANY) == (MAY_BE_LONG | MAY_BE_DOUBLE)) { \
- if (ssa->vars[var2].var < op_array->last_var) { \
- return 0; \
- } else if (ssa->vars[var2].definition >= 0 && \
- op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_ADD && \
- op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_SUB && \
- op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_MUL && \
- op_array->opcodes[ssa->vars[var2].definition].opcode != ZEND_DIV) { \
- return 0; \
- } else if (!zend_may_be_used_as_double(op_array, ssa, var2)) { \
- return 0; \
- } \
- } \
- } while (0)
+static zend_bool is_narrowable_instr(zend_op *opline) {
+ return opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB
+ || opline->opcode == ZEND_MUL || opline->opcode == ZEND_DIV;
+}
-static int zend_may_be_used_as_double(const zend_op_array *op_array, zend_ssa *ssa, int var)
-{
- FOR_EACH_VAR_USAGE(var, CHECK_MAY_BE_USED_AS_DOUBLE);
- return 1;
+static zend_bool is_effective_op1_double_cast(zend_op *opline, zval *op2) {
+ return (opline->opcode == ZEND_ADD && Z_LVAL_P(op2) == 0)
+ || (opline->opcode == ZEND_SUB && Z_LVAL_P(op2) == 0)
+ || (opline->opcode == ZEND_MUL && Z_LVAL_P(op2) == 1)
+ || (opline->opcode == ZEND_DIV && Z_LVAL_P(op2) == 1);
+}
+static zend_bool is_effective_op2_double_cast(zend_op *opline, zval *op1) {
+ /* In PHP it holds that (double)(0-$int) is bitwise identical to 0.0-(double)$int,
+ * so allowing SUB here is fine. */
+ return (opline->opcode == ZEND_ADD && Z_LVAL_P(op1) == 0)
+ || (opline->opcode == ZEND_SUB && Z_LVAL_P(op1) == 0)
+ || (opline->opcode == ZEND_MUL && Z_LVAL_P(op1) == 1);
}
-static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa)
-{
- zend_ssa_op *ssa_ops = ssa->ops;
- zend_ssa_var *ssa_vars = ssa->vars;
- zend_ssa_var_info *ssa_var_info = ssa->var_info;
- int ssa_vars_count = ssa->vars_count;
- int j;
- zend_bitset worklist, types;
- ALLOCA_FLAG(use_heap);
+/* This function recursively checks whether it's possible to convert an integer variable
+ * initialization to a double initialization. The basic idea is that if the value is used
+ * only in add/sub/mul/div ("narrowable" instructions) with a double result value, then it
+ * will be cast to double at that point anyway, so we may as well do it earlier already.
+ *
+ * The tricky case are chains of operations, where it's not necessarily a given that converting
+ * an integer to double before the chain of operations is the same as converting it after the
+ * chain. What this function does is detect two cases where it is safe:
+ * * If the operations only involve constants, then we can simply verify that performing the
+ * calculation on integers and doubles yields the same value.
+ * * Even if one operand is not known, we may be able to determine that the operations with the
+ * integer replaced by a double only acts as an effective double cast on the unknown operand.
+ * E.g. 0+$i and 0.0+$i only differ by that cast. If then the consuming instruction of this
+ * result will perform a double cast anyway, the conversion is safe.
+ *
+ * The checks happens recursively, while keeping track of which variables are already visisted to
+ * avoid infinite loops. An iterative, worklist driven approach would be possible, but the state
+ * management more cumbersome to implement, so we don't bother for now.
+ */
+static zend_bool can_convert_to_double(
+ const zend_op_array *op_array, zend_ssa *ssa, int var_num,
+ zval *value, zend_bitset visited) {
+ zend_ssa_var *var = &ssa->vars[var_num];
+ zend_ssa_phi *phi;
+ int use;
+ uint32_t type;
- types = do_alloca(sizeof(zend_ulong) * (op_array->last_var + zend_bitset_len(ssa_vars_count)), use_heap);
- memset(types, 0, sizeof(zend_ulong) * op_array->last_var);
- worklist = types + op_array->last_var;
- memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));
+ if (zend_bitset_in(visited, var_num)) {
+ return 1;
+ }
+ zend_bitset_incl(visited, var_num);
- /* Find variables that may be only LONG or DOUBLE */
- for (j = op_array->last_var; j < ssa_vars_count; j++) {
- if (ssa_vars[j].var < op_array->last_var) {
- types[ssa_vars[j].var] |= ssa_var_info[j].type & (MAY_BE_ANY - MAY_BE_NULL);
+ for (use = var->use_chain; use >= 0; use = zend_ssa_next_use(ssa->ops, var_num, use)) {
+ zend_op *opline = &op_array->opcodes[use];
+ zend_ssa_op *ssa_op = &ssa->ops[use];
+
+ if (is_no_val_use(opline, ssa_op, var_num)) {
+ continue;
}
- }
- for (j = 0; j < op_array->last_var; j++) {
- if (types[j] == (MAY_BE_LONG | MAY_BE_DOUBLE)) {
- zend_bitset_incl(worklist, j);
+
+ if (!is_narrowable_instr(opline)) {
+ return 0;
}
- }
- if (zend_bitset_empty(worklist, zend_bitset_len(ssa_vars_count))) {
- free_alloca(types, use_heap);
- return SUCCESS;
- }
- /* Exclude variables that can't be narrowed */
- for (j = op_array->last_var; j < ssa_vars_count; j++) {
- if (ssa_vars[j].var < op_array->last_var &&
- zend_bitset_in(worklist, ssa_vars[j].var)) {
- if (ssa_vars[j].definition >= 0) {
- if ((ssa_var_info[j].type & MAY_BE_ANY) == MAY_BE_LONG) {
- if (ssa_vars[j].use_chain >= 0 ||
- op_array->opcodes[ssa_vars[j].definition].opcode != ZEND_ASSIGN ||
- op_array->opcodes[ssa_vars[j].definition].op2_type != IS_CONST) {
- zend_bitset_excl(worklist, ssa_vars[j].var);
- }
- } else if ((ssa_var_info[j].type & MAY_BE_ANY) != MAY_BE_DOUBLE) {
- zend_bitset_excl(worklist, ssa_vars[j].var);
+ /* Instruction always returns double, the conversion is certainly fine */
+ type = ssa->var_info[ssa_op->result_def].type;
+ if ((type & MAY_BE_ANY) == MAY_BE_DOUBLE) {
+ continue;
+ }
+
+ /* UNDEF signals that the previous result is an effective double cast, this is only allowed
+ * if this instruction would have done the cast anyway (previous check). */
+ if (Z_ISUNDEF_P(value)) {
+ return 0;
+ }
+
+ /* Check that narrowing can actually be useful */
+ if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
+ return 0;
+ }
+
+ {
+ /* For calculation on original values */
+ zval orig_op1, orig_op2, orig_result;
+ /* For calculation with var_num cast to double */
+ zval dval_op1, dval_op2, dval_result;
+
+ ZVAL_UNDEF(&orig_op1);
+ ZVAL_UNDEF(&dval_op1);
+ if (ssa_op->op1_use == var_num) {
+ ZVAL_COPY_VALUE(&orig_op1, value);
+ ZVAL_DOUBLE(&dval_op1, (double) Z_LVAL_P(value));
+ } else if (opline->op1_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT_EX(op_array, opline->op1, ssa->rt_constants);
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
+ ZVAL_COPY_VALUE(&orig_op1, zv);
+ ZVAL_COPY_VALUE(&dval_op1, zv);
+ }
+ }
+
+ ZVAL_UNDEF(&orig_op2);
+ ZVAL_UNDEF(&dval_op2);
+ if (ssa_op->op2_use == var_num) {
+ ZVAL_COPY_VALUE(&orig_op2, value);
+ ZVAL_DOUBLE(&dval_op2, (double) Z_LVAL_P(value));
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants);
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
+ ZVAL_COPY_VALUE(&orig_op2, zv);
+ ZVAL_COPY_VALUE(&dval_op2, zv);
}
}
- }
- }
- if (zend_bitset_empty(worklist, zend_bitset_len(ssa_vars_count))) {
- free_alloca(types, use_heap);
- return SUCCESS;
- }
- for (j = op_array->last_var; j < ssa_vars_count; j++) {
- if (ssa_vars[j].var < op_array->last_var &&
- zend_bitset_in(worklist, ssa_vars[j].var) &&
- (ssa_var_info[j].type & (MAY_BE_ANY-MAY_BE_NULL)) == (MAY_BE_LONG|MAY_BE_DOUBLE) &&
- ssa_vars[j].use_chain >= 0) {
- if (!zend_may_be_used_as_double(op_array, ssa, j)) {
- zend_bitset_excl(worklist, ssa_vars[j].var);
+ ZEND_ASSERT(!Z_ISUNDEF(orig_op1) || !Z_ISUNDEF(orig_op2));
+ if (Z_ISUNDEF(orig_op1)) {
+ if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op2) == 0) {
+ ZVAL_LONG(&orig_result, 0);
+ } else if (is_effective_op1_double_cast(opline, &orig_op2)) {
+ ZVAL_UNDEF(&orig_result);
+ } else {
+ return 0;
+ }
+ } else if (Z_ISUNDEF(orig_op2)) {
+ if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op1) == 0) {
+ ZVAL_LONG(&orig_result, 0);
+ } else if (is_effective_op2_double_cast(opline, &orig_op1)) {
+ ZVAL_UNDEF(&orig_result);
+ } else {
+ return 0;
+ }
+ } else {
+ /* Avoid division by zero */
+ if (opline->opcode == ZEND_DIV && zval_get_double(&orig_op2) == 0.0) {
+ return 0;
+ }
+
+ get_binary_op(opline->opcode)(&orig_result, &orig_op1, &orig_op2);
+ get_binary_op(opline->opcode)(&dval_result, &dval_op1, &dval_op2);
+ ZEND_ASSERT(Z_TYPE(dval_result) == IS_DOUBLE);
+ if (zval_get_double(&orig_result) != Z_DVAL(dval_result)) {
+ return 0;
+ }
+ }
+
+ if (!can_convert_to_double(op_array, ssa, ssa_op->result_def, &orig_result, visited)) {
+ return 0;
}
}
}
- if (zend_bitset_empty(worklist, zend_bitset_len(ssa_vars_count))) {
- free_alloca(types, use_heap);
- return SUCCESS;
+
+ for (phi = var->phi_use_chain; phi; phi = zend_ssa_next_use_phi(ssa, var_num, phi)) {
+ /* Check that narrowing can actually be useful */
+ type = ssa->var_info[phi->ssa_var].type;
+ if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
+ return 0;
+ }
+
+ if (!can_convert_to_double(op_array, ssa, phi->ssa_var, value, visited)) {
+ return 0;
+ }
}
- for (j = op_array->last_var; j < ssa_vars_count; j++) {
- if (ssa_vars[j].var < op_array->last_var &&
- zend_bitset_in(worklist, ssa_vars[j].var)) {
- if ((ssa_var_info[j].type & MAY_BE_ANY) == MAY_BE_LONG &&
- ssa_vars[j].definition >= 0 &&
- ssa_ops[ssa_vars[j].definition].result_def < 0 &&
- op_array->opcodes[ssa_vars[j].definition].opcode == ZEND_ASSIGN &&
- op_array->opcodes[ssa_vars[j].definition].op2_type == IS_CONST &&
- Z_TYPE_P(CRT_CONSTANT_EX(op_array, op_array->opcodes[ssa_vars[j].definition].op2, ssa->rt_constants)) == IS_LONG) {
- ssa_var_info[j].use_as_double = 1;
- }
- ssa_var_info[j].type &= ~MAY_BE_ANY;
- zend_bitset_incl(worklist, j);
+ return 1;
+}
+
+static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa)
+{
+ uint32_t bitset_len = zend_bitset_len(ssa->vars_count);
+ ALLOCA_FLAG(use_heap);
+ zend_bitset visited = ZEND_BITSET_ALLOCA(2 * bitset_len, use_heap);
+ zend_bitset worklist = visited + bitset_len;
+ int i;
+ uint32_t v;
+ zend_op *opline;
+ zend_bool narrowed = 0;
+
+ zend_bitset_clear(worklist, bitset_len);
+
+ for (v = op_array->last_var; v < ssa->vars_count; v++) {
+ if ((ssa->var_info[v].type & (MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF)) != MAY_BE_LONG) continue;
+ if (ssa->vars[v].definition < 0) continue;
+ if (ssa->vars[v].no_val) continue;
+ opline = op_array->opcodes + ssa->vars[v].definition;
+ /* Go through assignments of literal integers and check if they can be converted to
+ * doubles instead, in the hope that we'll narrow long|double to double. */
+ if (opline->opcode == ZEND_ASSIGN && opline->result_type == IS_UNUSED &&
+ opline->op1_type == IS_CV && opline->op2_type == IS_CONST) {
+ zval *value = CRT_CONSTANT_EX(op_array, opline->op2, ssa->rt_constants);
+
+ zend_bitset_clear(visited, bitset_len);
+ if (can_convert_to_double(op_array, ssa, v, value, visited)) {
+ narrowed = 1;
+ ssa->var_info[v].use_as_double = 1;
+ /* The "visited" vars are exactly those which may change their type due to
+ * narrowing. Reset their types and add them to the type inference worklist */
+ ZEND_BITSET_FOREACH(visited, bitset_len, i) {
+ ssa->var_info[i].type &= ~MAY_BE_ANY;
+ } ZEND_BITSET_FOREACH_END();
+ zend_bitset_union(worklist, visited, bitset_len);
+ }
}
}
- for (j = 0; j < op_array->last_var; j++) {
- zend_bitset_excl(worklist, j);
+
+ if (!narrowed) {
+ free_alloca(visited, use_heap);
+ return SUCCESS;
}
if (zend_infer_types_ex(op_array, script, ssa, worklist) != SUCCESS) {
- free_alloca(types, use_heap);
+ free_alloca(visited, use_heap);
return FAILURE;
}
- free_alloca(types, use_heap);
+ free_alloca(visited, use_heap);
return SUCCESS;
}
@@ -3855,7 +3745,26 @@ static int is_recursive_tail_call(const zend_op_array *op_array,
return 0;
}
+void zend_init_func_return_info(const zend_op_array *op_array,
+ const zend_script *script,
+ zend_ssa_var_info *ret)
+{
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+ zend_ssa_range tmp_range = {0, 0, 0, 0};
+
+ ret->type = zend_fetch_arg_info(script, ret_info, &ret->ce);
+ if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
+ ret->type |= MAY_BE_REF;
+ }
+ ret->is_instanceof = (ret->ce) ? 1 : 0;
+ ret->range = tmp_range;
+ ret->has_range = 0;
+ }
+}
+
void zend_func_return_info(const zend_op_array *op_array,
+ const zend_script *script,
int recursive,
int widening,
zend_ssa_var_info *ret)
@@ -3884,8 +3793,8 @@ void zend_func_return_info(const zend_op_array *op_array,
}
for (j = 0; j < blocks_count; j++) {
- if (blocks[j].flags & ZEND_BB_REACHABLE) {
- zend_op *opline = op_array->opcodes + blocks[j].end;
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
+ zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
if (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_RETURN_BY_REF) {
if (!recursive &&
@@ -4033,16 +3942,19 @@ void zend_func_return_info(const zend_op_array *op_array,
}
}
}
- if (tmp_is_instanceof < 0) {
- tmp_is_instanceof = 0;
- tmp_ce = NULL;
- }
- if (tmp_has_range < 0) {
- tmp_has_range = 0;
+
+ if (!(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
+ if (tmp_is_instanceof < 0) {
+ tmp_is_instanceof = 0;
+ tmp_ce = NULL;
+ }
+ if (tmp_has_range < 0) {
+ tmp_has_range = 0;
+ }
+ ret->type = tmp;
+ ret->ce = tmp_ce;
+ ret->is_instanceof = tmp_is_instanceof;
}
- ret->type = tmp;
- ret->ce = tmp_ce;
- ret->is_instanceof = tmp_is_instanceof;
ret->range = tmp_range;
ret->has_range = tmp_has_range;
}
@@ -4072,8 +3984,21 @@ static int zend_infer_types(const zend_op_array *op_array, const zend_script *sc
/* Narrowing integer initialization to doubles */
zend_type_narrowing(op_array, script, ssa);
+ for (j = 0; j < op_array->last_var; j++) {
+ if (zend_string_equals_literal(op_array->vars[j], "php_errormsg")) {
+ /* Mark all SSA vars for $php_errormsg as references,
+ * to make sure we don't optimize it. */
+ int i;
+ for (i = 0; i < ssa_vars_count; i++) {
+ if (ssa->vars[i].var == j) {
+ ssa_var_info[i].type |= MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ }
+ }
+ }
+
if (ZEND_FUNC_INFO(op_array)) {
- zend_func_return_info(op_array, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info);
+ zend_func_return_info(op_array, script, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info);
}
free_alloca(worklist, use_heap);
@@ -4097,13 +4022,7 @@ int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const
}
} else {
for (i = 0; i < op_array->last_var; i++) {
- if (i == EX_VAR_TO_NUM(op_array->this_var)) {
- ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_OBJECT;
- ssa_var_info[i].ce = op_array->scope;
- ssa_var_info[i].is_instanceof = 1;
- } else {
- ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RCN;
- }
+ ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RCN;
ssa_var_info[i].has_range = 0;
}
}