diff options
Diffstat (limited to 'ext/opcache/Optimizer/zend_inference.c')
| -rw-r--r-- | ext/opcache/Optimizer/zend_inference.c | 1493 |
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; } } |
