/* We can interpret $a + 5 == 0 as $a = 0 - 5, i.e. shift the adjustment to the other operand. * This negated adjustment is what is written into the "adjustment" parameter. */ static int find_adjusted_tmp_var(const zend_op_array *op_array, uint32_t build_flags, zend_op *opline, uint32_t var_num, zend_long *adjustment) { zend_op *op = opline; while (op != op_array->opcodes) { op--; if (op->result_type != IS_TMP_VAR || op->result.var != var_num) { continue; } if (op->opcode == ZEND_POST_DEC) { if (op->op1_type == IS_CV) { *adjustment = -1; return EX_VAR_TO_NUM(op->op1.var); } } else if (op->opcode == ZEND_POST_INC) { if (op->op1_type == IS_CV) { *adjustment = 1; return EX_VAR_TO_NUM(op->op1.var); } } else if (op->opcode == ZEND_ADD) { if (op->op1_type == IS_CV && op->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(op->op2)) == IS_LONG && Z_LVAL_P(CRT_CONSTANT(op->op2)) != ZEND_LONG_MIN) { *adjustment = -Z_LVAL_P(CRT_CONSTANT(op->op2)); return EX_VAR_TO_NUM(op->op1.var); } else if (op->op2_type == IS_CV && op->op1_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(op->op1)) == IS_LONG && Z_LVAL_P(CRT_CONSTANT(op->op1)) != ZEND_LONG_MIN) { *adjustment = -Z_LVAL_P(CRT_CONSTANT(op->op1)); return EX_VAR_TO_NUM(op->op2.var); } } else if (op->opcode == ZEND_SUB) { if (op->op1_type == IS_CV && op->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT(op->op2)) == IS_LONG) { *adjustment = Z_LVAL_P(CRT_CONSTANT(op->op2)); return EX_VAR_TO_NUM(op->op1.var); } } break; } return -1; }
/* e-SSA construction: Pi placement (Pi is actually a Phi with single * source and constraint). * Order of Phis is importent, Pis must be placed before Phis */ static void place_essa_pis( zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_ssa *ssa, zend_dfg *dfg) { zend_basic_block *blocks = ssa->cfg.blocks; int j, blocks_count = ssa->cfg.blocks_count; for (j = 0; j < blocks_count; j++) { zend_ssa_phi *pi; zend_op *opline = op_array->opcodes + ssa->cfg.blocks[j].end; int bt; /* successor block number if a condition is true */ int bf; /* successor block number if a condition is false */ if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) { continue; } /* the last instruction of basic block is conditional branch, * based on comparison of CV(s) */ switch (opline->opcode) { case ZEND_JMPZ: case ZEND_JMPZNZ: bf = ssa->cfg.blocks[j].successors[0]; bt = ssa->cfg.blocks[j].successors[1]; break; case ZEND_JMPNZ: bt = ssa->cfg.blocks[j].successors[0]; bf = ssa->cfg.blocks[j].successors[1]; break; default: continue; } if (opline->op1_type == IS_TMP_VAR && ((opline-1)->opcode == ZEND_IS_EQUAL || (opline-1)->opcode == ZEND_IS_NOT_EQUAL || (opline-1)->opcode == ZEND_IS_SMALLER || (opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) && opline->op1.var == (opline-1)->result.var) { int var1 = -1; int var2 = -1; zend_long val1 = 0; zend_long val2 = 0; // long val = 0; if ((opline-1)->op1_type == IS_CV) { var1 = EX_VAR_TO_NUM((opline-1)->op1.var); } else if ((opline-1)->op1_type == IS_TMP_VAR) { var1 = find_adjusted_tmp_var( op_array, build_flags, opline, (opline-1)->op1.var, &val2); } if ((opline-1)->op2_type == IS_CV) { var2 = EX_VAR_TO_NUM((opline-1)->op2.var); } else if ((opline-1)->op2_type == IS_TMP_VAR) { var2 = find_adjusted_tmp_var( op_array, build_flags, opline, (opline-1)->op2.var, &val1); } if (var1 >= 0 && var2 >= 0) { if (!sub_will_overflow(val1, val2) && !sub_will_overflow(val2, val1)) { zend_long tmp = val1; val1 -= val2; val2 -= tmp; } else { var1 = -1; var2 = -1; } } else if (var1 >= 0 && var2 < 0) { zend_long add_val2 = 0; if ((opline-1)->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT((opline-1)->op2)) == IS_LONG) { add_val2 = Z_LVAL_P(CRT_CONSTANT((opline-1)->op2)); } else if ((opline-1)->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT((opline-1)->op2)) == IS_FALSE) { add_val2 = 0; } else if ((opline-1)->op2_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT((opline-1)->op2)) == IS_TRUE) { add_val2 = 1; } else { var1 = -1; } if (!add_will_overflow(val2, add_val2)) { val2 += add_val2; } else { var1 = -1; } } else if (var1 < 0 && var2 >= 0) { zend_long add_val1 = 0; if ((opline-1)->op1_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT((opline-1)->op1)) == IS_LONG) { add_val1 = Z_LVAL_P(CRT_CONSTANT((opline-1)->op1)); } else if ((opline-1)->op1_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT((opline-1)->op1)) == IS_FALSE) { add_val1 = 0; } else if ((opline-1)->op1_type == IS_CONST && Z_TYPE_P(CRT_CONSTANT((opline-1)->op1)) == IS_TRUE) { add_val1 = 1; } else { var2 = -1; } if (!add_will_overflow(val1, add_val1)) { val1 += add_val1; } else { var2 = -1; } } if (var1 >= 0) { if ((opline-1)->opcode == ZEND_IS_EQUAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { pi_range_equals(pi, var2, val2); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { pi_range_not_equals(pi, var2, val2); } } else if ((opline-1)->opcode == ZEND_IS_NOT_EQUAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { pi_range_equals(pi, var2, val2); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { pi_range_not_equals(pi, var2, val2); } } else if ((opline-1)->opcode == ZEND_IS_SMALLER) { if (val2 > ZEND_LONG_MIN) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { pi_range_max(pi, var2, val2-1); } } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { pi_range_min(pi, var2, val2); } } else if ((opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var1))) { pi_range_max(pi, var2, val2); } if (val2 < ZEND_LONG_MAX) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var1))) { pi_range_min(pi, var2, val2+1); } } } } if (var2 >= 0) { if((opline-1)->opcode == ZEND_IS_EQUAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { pi_range_equals(pi, var1, val1); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { pi_range_not_equals(pi, var1, val1); } } else if ((opline-1)->opcode == ZEND_IS_NOT_EQUAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { pi_range_equals(pi, var1, val1); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { pi_range_not_equals(pi, var1, val1); } } else if ((opline-1)->opcode == ZEND_IS_SMALLER) { if (val1 < ZEND_LONG_MAX) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { pi_range_min(pi, var1, val1+1); } } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { pi_range_max(pi, var1, val1); } } else if ((opline-1)->opcode == ZEND_IS_SMALLER_OR_EQUAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var2))) { pi_range_min(pi, var1, val1); } if (val1 > ZEND_LONG_MIN) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var2))) { pi_range_max(pi, var1, val1-1); } } } } } else if (opline->op1_type == IS_TMP_VAR && ((opline-1)->opcode == ZEND_POST_INC || (opline-1)->opcode == ZEND_POST_DEC) && opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV) { int var = EX_VAR_TO_NUM((opline-1)->op1.var); if ((opline-1)->opcode == ZEND_POST_DEC) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { pi_range_equals(pi, -1, -1); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { pi_range_not_equals(pi, -1, -1); } } else if ((opline-1)->opcode == ZEND_POST_INC) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { pi_range_equals(pi, -1, 1); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { pi_range_not_equals(pi, -1, 1); } } } else if (opline->op1_type == IS_VAR && ((opline-1)->opcode == ZEND_PRE_INC || (opline-1)->opcode == ZEND_PRE_DEC) && opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV) { int var = EX_VAR_TO_NUM((opline-1)->op1.var); if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { pi_range_equals(pi, -1, 0); } /* speculative */ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { pi_range_not_equals(pi, -1, 0); } } else if (opline->op1_type == IS_TMP_VAR && (opline-1)->opcode == ZEND_TYPE_CHECK && opline->op1.var == (opline-1)->result.var && (opline-1)->op1_type == IS_CV) { int var = EX_VAR_TO_NUM((opline-1)->op1.var); uint32_t type = (opline-1)->extended_value; if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { pi_type_mask(pi, mask_for_type_check(type)); } if (type != IS_OBJECT && type != IS_RESOURCE) { /* is_object() and is_resource() may return false, even though the value is * an object/resource. */ if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { pi_not_type_mask(pi, mask_for_type_check(type)); } } } else if (opline->op1_type == IS_TMP_VAR && ((opline-1)->opcode == ZEND_IS_IDENTICAL || (opline-1)->opcode == ZEND_IS_NOT_IDENTICAL) && opline->op1.var == (opline-1)->result.var) { int var; zval *val; uint32_t type_mask; if ((opline-1)->op1_type == IS_CV && (opline-1)->op2_type == IS_CONST) { var = EX_VAR_TO_NUM((opline-1)->op1.var); val = CRT_CONSTANT((opline-1)->op2); } else if ((opline-1)->op1_type == IS_CONST && (opline-1)->op2_type == IS_CV) { var = EX_VAR_TO_NUM((opline-1)->op2.var); val = CRT_CONSTANT((opline-1)->op1); } else { continue; } /* We're interested in === null/true/false comparisons here, because they eliminate * a type in the false-branch. Other === VAL comparisons are unlikely to be useful. */ if (Z_TYPE_P(val) != IS_NULL && Z_TYPE_P(val) != IS_TRUE && Z_TYPE_P(val) != IS_FALSE) { continue; } type_mask = _const_op_type(val); if ((opline-1)->opcode == ZEND_IS_IDENTICAL) { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { pi_type_mask(pi, type_mask); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { pi_not_type_mask(pi, type_mask); } } else { if ((pi = add_pi(arena, op_array, dfg, ssa, j, bf, var))) { pi_type_mask(pi, type_mask); } if ((pi = add_pi(arena, op_array, dfg, ssa, j, bt, var))) { pi_not_type_mask(pi, type_mask); } } } } }
int zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg, uint32_t *func_flags) /* {{{ */ { uint32_t flags = 0; uint32_t i; int j; uint32_t *block_map; zend_function *fn; int blocks_count = 0; zend_basic_block *blocks; zval *zv; cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t)); if (!block_map) { return FAILURE; } /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */ BB_START(0); for (i = 0; i < op_array->last; i++) { zend_op *opline = op_array->opcodes + i; switch(opline->opcode) { case ZEND_RETURN: case ZEND_RETURN_BY_REF: case ZEND_GENERATOR_RETURN: case ZEND_EXIT: case ZEND_THROW: if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_INCLUDE_OR_EVAL: flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; case ZEND_YIELD: case ZEND_YIELD_FROM: if (build_flags & ZEND_CFG_STACKLESS) { BB_START(i + 1); } break; case ZEND_DO_FCALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: flags |= ZEND_FUNC_HAS_CALLS; if (build_flags & ZEND_CFG_STACKLESS) { BB_START(i + 1); } break; case ZEND_DO_ICALL: flags |= ZEND_FUNC_HAS_CALLS; break; case ZEND_INIT_FCALL: case ZEND_INIT_NS_FCALL_BY_NAME: zv = CRT_CONSTANT(opline->op2); if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { /* The third literal is the lowercased unqualified name */ zv += 2; } if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) { if (fn->type == ZEND_INTERNAL_FUNCTION) { if (zend_string_equals_literal(Z_STR_P(zv), "extract")) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "compact")) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "parse_str") && opline->extended_value == 1) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "mb_parse_str") && opline->extended_value == 1) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "get_defined_vars")) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_num_args")) { flags |= ZEND_FUNC_VARARG; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_arg")) { flags |= ZEND_FUNC_VARARG; } else if (zend_string_equals_literal(Z_STR_P(zv), "func_get_args")) { flags |= ZEND_FUNC_VARARG; } } } break; case ZEND_FAST_CALL: BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); BB_START(i + 1); break; case ZEND_FAST_RET: if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMP: BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes); if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMPZNZ: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); if (i + 1 < op_array->last) { BB_START(i + 1); } break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; case ZEND_CATCH: if (!opline->result.num) { BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); } BB_START(i + 1); break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)); BB_START(i + 1); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_NEW: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); break; case ZEND_UNSET_VAR: case ZEND_ISSET_ISEMPTY_VAR: if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) && !(opline->extended_value & ZEND_QUICK_SET)) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && !op_array->function_name) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } break; case ZEND_FETCH_R: case ZEND_FETCH_W: case ZEND_FETCH_RW: case ZEND_FETCH_FUNC_ARG: case ZEND_FETCH_IS: case ZEND_FETCH_UNSET: if ((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_LOCAL) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } else if (((opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL || (opline->extended_value & ZEND_FETCH_TYPE_MASK) == ZEND_FETCH_GLOBAL_LOCK) && !op_array->function_name) { flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS; } break; } } for (j = 0; j < op_array->last_live_range; j++) { BB_START(op_array->live_range[j].start); BB_START(op_array->live_range[j].end); } if (op_array->last_try_catch) { for (j = 0; j < op_array->last_try_catch; j++) { BB_START(op_array->try_catch_array[j].try_op); if (op_array->try_catch_array[j].catch_op) { BB_START(op_array->try_catch_array[j].catch_op); } if (op_array->try_catch_array[j].finally_op) { BB_START(op_array->try_catch_array[j].finally_op); } if (op_array->try_catch_array[j].finally_end) { BB_START(op_array->try_catch_array[j].finally_end); } } } cfg->blocks_count = blocks_count; /* Build CFG, Step 2: Build Array of Basic Blocks */ cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count); if (!blocks) { return FAILURE; } for (i = 0, blocks_count = -1; i < op_array->last; i++) { if (block_map[i]) { if (blocks_count >= 0) { blocks[blocks_count].end = i - 1; } blocks_count++; blocks[blocks_count].flags = 0; blocks[blocks_count].start = i; blocks[blocks_count].successors[0] = -1; blocks[blocks_count].successors[1] = -1; blocks[blocks_count].predecessors_count = 0; blocks[blocks_count].predecessor_offset = -1; blocks[blocks_count].idom = -1; blocks[blocks_count].loop_header = -1; blocks[blocks_count].level = -1; blocks[blocks_count].children = -1; blocks[blocks_count].next_child = -1; block_map[i] = blocks_count; } else { block_map[i] = (uint32_t)-1; } } blocks[blocks_count].end = i - 1; blocks_count++; /* Build CFG, Step 3: Calculate successors */ for (j = 0; j < blocks_count; j++) { zend_op *opline = op_array->opcodes + blocks[j].end; switch(opline->opcode) { case ZEND_FAST_RET: case ZEND_RETURN: case ZEND_RETURN_BY_REF: case ZEND_GENERATOR_RETURN: case ZEND_EXIT: case ZEND_THROW: break; case ZEND_JMP: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); break; case ZEND_JMPZNZ: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); break; case ZEND_JMPZ: case ZEND_JMPNZ: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: case ZEND_COALESCE: case ZEND_ASSERT_CHECK: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_CATCH: if (!opline->result.num) { record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); record_successor(blocks, j, 1, j + 1); } else { record_successor(blocks, j, 0, j + 1); } break; case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: record_successor(blocks, j, 0, block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)]); record_successor(blocks, j, 1, j + 1); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: case ZEND_NEW: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; case ZEND_FAST_CALL: record_successor(blocks, j, 0, block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes]); record_successor(blocks, j, 1, j + 1); break; default: record_successor(blocks, j, 0, j + 1); break; } } /* Build CFG, Step 4, Mark Reachable Basic Blocks */ zend_mark_reachable_blocks(op_array, cfg, 0); if (func_flags) { *func_flags |= flags; } return SUCCESS; }