/* +----------------------------------------------------------------------+ | Zend Engine, SCCP - Sparse Conditional Constant Propagation | +----------------------------------------------------------------------+ | Copyright (c) 1998-2018 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Nikita Popov | +----------------------------------------------------------------------+ */ #include "php.h" #include "zend_type_info.h" #include "ZendAccelerator.h" #include "Optimizer/zend_optimizer_internal.h" #include "Optimizer/zend_call_graph.h" #include "Optimizer/scdf.h" #include "Optimizer/zend_dump.h" #include "ext/standard/php_string.h" /* This implements sparse conditional constant propagation (SCCP) based on the SCDF framework. The * used value lattice is defined as follows: * * BOT < {constant values} < TOP * * TOP indicates an underdefined value, i.e. that we do not yet know the value of variable. * BOT indicates an overdefined value, i.e. that we know the variable to be non-constant. * * All variables are optimistically initialized to TOP, apart from the implicit variables defined * at the start of the first block. Note that variables that MAY_BE_REF are *not* initialized to * BOT. We rely on the fact that any operation resulting in a reference will produce a BOT anyway. * This is better because such operations might never be reached due to the conditional nature of * the algorithm. * * The meet operation for phi functions is defined as follows: * BOT + any = BOT * TOP + any = any * C_i + C_i = C_i (i.e. two equal constants) * C_i + C_j = BOT (i.e. two different constants) * * When evaluating instructions TOP and BOT are handled as follows: * a) If any operand is BOT, the result is BOT. The main exception to this is op1 of ASSIGN, which * is ignored. However, if the op1 MAY_BE_REF we do have to propagate the BOT. * b) Otherwise, if the instruction can never be evaluated (either in general, or with the * specific modifiers) the result is BOT. * c) Otherwise, if any operand is TOP, the result is TOP. * d) Otherwise (at this point all operands are known and constant), if we can compute the result * for these specific constants (without throwing notices or similar) then that is the result. * e) Otherwise the result is BOT. * * It is sometimes possible to determine a result even if one argument is TOP / BOT, e.g. for things * like BOT*0. Right now we don't bother with this -- the only thing that is done is evaluating * TYPE_CHECKS based on the type information. * * Feasible successors for conditional branches are determined as follows: * a) If we don't support the branch type or branch on BOT, all successors are feasible. * b) Otherwise, if we branch on TOP none of the successors are feasible. * c) Otherwise (we branch on a constant), the feasible successors are marked based on the constant * (usually only one successor will be feasible). */ #if 0 #define SCP_DEBUG(...) php_printf(__VA_ARGS__) #else #define SCP_DEBUG(...) #endif typedef struct _sccp_ctx { scdf_ctx scdf; zend_call_info **call_map; zval *values; zval top; zval bot; } sccp_ctx; #define TOP ((zend_uchar)-1) #define BOT ((zend_uchar)-2) #define IS_TOP(zv) (Z_TYPE_P(zv) == TOP) #define IS_BOT(zv) (Z_TYPE_P(zv) == BOT) #define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP) #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT) static inline zend_bool value_known(zval *zv) { return !IS_TOP(zv) && !IS_BOT(zv); } /* Sets new value for variable and ensures that it is lower or equal * the previous one in the constant propagation lattice. */ static void set_value(scdf_ctx *scdf, sccp_ctx *ctx, int var, zval *new) { zval *value = &ctx->values[var]; if (IS_BOT(value) || IS_TOP(new)) { return; } if (IS_BOT(new)) { SCP_DEBUG("Lowering var %d to BOT\n", var); } else { SCP_DEBUG("Lowering var %d to %Z\n", var, new); } if (IS_TOP(value) || IS_BOT(new)) { zval_ptr_dtor_nogc(value); ZVAL_COPY(value, new); scdf_add_to_worklist(scdf, var); return; } #if ZEND_DEBUG ZEND_ASSERT(zend_is_identical(value, new)); #endif } static zval *get_op1_value(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) { if (opline->op1_type == IS_CONST) { return CT_CONSTANT_EX(ctx->scdf.op_array, opline->op1.constant); } else if (ssa_op->op1_use != -1) { return &ctx->values[ssa_op->op1_use]; } else { return NULL; } } static zval *get_op2_value(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) { if (opline->op2_type == IS_CONST) { return CT_CONSTANT_EX(ctx->scdf.op_array, opline->op2.constant); } else if (ssa_op->op2_use != -1) { return &ctx->values[ssa_op->op2_use]; } else { return NULL; } } static zend_bool can_replace_op1( const zend_op_array *op_array, zend_op *opline, zend_ssa_op *ssa_op) { switch (opline->opcode) { case ZEND_PRE_INC: case ZEND_PRE_DEC: case ZEND_PRE_INC_OBJ: case ZEND_PRE_DEC_OBJ: case ZEND_POST_INC: case ZEND_POST_DEC: case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: case ZEND_ASSIGN: case ZEND_ASSIGN_REF: case ZEND_ASSIGN_DIM: case ZEND_ASSIGN_OBJ: case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_MOD: case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: case ZEND_ASSIGN_CONCAT: case ZEND_ASSIGN_BW_OR: case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: case ZEND_ASSIGN_POW: case ZEND_FETCH_DIM_W: case ZEND_FETCH_DIM_RW: case ZEND_FETCH_DIM_UNSET: case ZEND_FETCH_DIM_FUNC_ARG: case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_UNSET: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_UNSET_DIM: case ZEND_UNSET_OBJ: case ZEND_SEND_REF: case ZEND_SEND_VAR_EX: case ZEND_SEND_UNPACK: case ZEND_SEND_ARRAY: case ZEND_SEND_USER: case ZEND_FE_RESET_RW: return 0; /* Do not accept CONST */ case ZEND_VERIFY_ABSTRACT_CLASS: case ZEND_ADD_INTERFACE: case ZEND_ADD_TRAIT: case ZEND_BIND_TRAITS: case ZEND_ROPE_ADD: case ZEND_ROPE_END: case ZEND_BIND_STATIC: case ZEND_BIND_GLOBAL: case ZEND_MAKE_REF: case ZEND_UNSET_CV: case ZEND_ISSET_ISEMPTY_CV: case ZEND_INSTANCEOF: return 0; case ZEND_INIT_ARRAY: case ZEND_ADD_ARRAY_ELEMENT: return !(opline->extended_value & ZEND_ARRAY_ELEMENT_REF); case ZEND_YIELD: return !(op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE); case ZEND_VERIFY_RETURN_TYPE: // TODO: This would require a non-local change ??? return 0; default: if (ssa_op->op1_def != -1) { ZEND_ASSERT(0); return 0; } } return 1; } static zend_bool can_replace_op2( const zend_op_array *op_array, zend_op *opline, zend_ssa_op *ssa_op) { switch (opline->opcode) { /* Do not accept CONST */ case ZEND_DECLARE_INHERITED_CLASS: case ZEND_DECLARE_INHERITED_CLASS_DELAYED: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_BIND_LEXICAL: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: return 0; } return 1; } static zend_bool try_replace_op1( sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op, int var, zval *value) { if (ssa_op->op1_use == var && can_replace_op1(ctx->scdf.op_array, opline, ssa_op)) { zval zv; ZVAL_COPY(&zv, value); if (zend_optimizer_update_op1_const(ctx->scdf.op_array, opline, &zv)) { /* Reconstruct SSA */ int num; zend_basic_block *block; switch (opline->opcode) { case ZEND_JMPZ: if (zend_is_true(&zv)) { MAKE_NOP(opline); num = ctx->scdf.ssa->cfg.map[opline - ctx->scdf.op_array->opcodes]; block = &ctx->scdf.ssa->cfg.blocks[num]; if (block->successors_count == 2) { if (block->successors[1] != block->successors[0]) { zend_ssa_remove_predecessor(ctx->scdf.ssa, num, block->successors[0]); } block->successors_count = 1; block->successors[0] = block->successors[1]; } } else { opline->opcode = ZEND_JMP; COPY_NODE(opline->op1, opline->op2); num = ctx->scdf.ssa->cfg.map[opline - ctx->scdf.op_array->opcodes]; block = &ctx->scdf.ssa->cfg.blocks[num]; if (block->successors_count == 2) { if (block->successors[1] != block->successors[0]) { zend_ssa_remove_predecessor(ctx->scdf.ssa, num, block->successors[1]); } block->successors_count = 1; } } break; case ZEND_JMPNZ: if (zend_is_true(&zv)) { opline->opcode = ZEND_JMP; COPY_NODE(opline->op1, opline->op2); num = ctx->scdf.ssa->cfg.map[opline - ctx->scdf.op_array->opcodes]; block = &ctx->scdf.ssa->cfg.blocks[num]; if (block->successors_count == 2) { if (block->successors[1] != block->successors[0]) { zend_ssa_remove_predecessor(ctx->scdf.ssa, num, block->successors[1]); } block->successors_count = 1; } } else { MAKE_NOP(opline); num = ctx->scdf.ssa->cfg.map[opline - ctx->scdf.op_array->opcodes]; block = &ctx->scdf.ssa->cfg.blocks[num]; if (block->successors_count == 2) { if (block->successors[1] != block->successors[0]) { zend_ssa_remove_predecessor(ctx->scdf.ssa, num, block->successors[0]); } block->successors_count = 1; block->successors[0] = block->successors[1]; } } break; case ZEND_JMPZNZ: if (zend_is_true(&zv)) { zend_op *target_opline = ZEND_OFFSET_TO_OPLINE(opline, opline->extended_value); ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline); num = ctx->scdf.ssa->cfg.map[opline - ctx->scdf.op_array->opcodes]; block = &ctx->scdf.ssa->cfg.blocks[num]; if (block->successors_count == 2) { if (block->successors[1] != block->successors[0]) { zend_ssa_remove_predecessor(ctx->scdf.ssa, num, block->successors[0]); } block->successors_count = 1; block->successors[0] = block->successors[1]; } } else { zend_op *target_opline = ZEND_OP2_JMP_ADDR(opline); ZEND_SET_OP_JMP_ADDR(opline, opline->op1, target_opline); num = ctx->scdf.ssa->cfg.map[opline - ctx->scdf.op_array->opcodes]; block = &ctx->scdf.ssa->cfg.blocks[num]; if (block->successors_count == 2) { if (block->successors[1] != block->successors[0]) { zend_ssa_remove_predecessor(ctx->scdf.ssa, num, block->successors[1]); } block->successors_count = 1; } } opline->op1_type = IS_UNUSED; opline->extended_value = 0; opline->opcode = ZEND_JMP; break; default: break; } return 1; } else { // TODO: check the following special cases ??? switch (opline->opcode) { case ZEND_FETCH_LIST: case ZEND_CASE: case ZEND_SWITCH_STRING: case ZEND_SWITCH_LONG: if (Z_TYPE(zv) == IS_STRING) { zend_string_hash_val(Z_STR(zv)); } opline->op1.constant = zend_optimizer_add_literal(ctx->scdf.op_array, &zv); opline->op1_type = IS_CONST; return 1; } zval_ptr_dtor_nogc(&zv); } } return 0; } static zend_bool try_replace_op2( sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op, int var, zval *value) { if (ssa_op->op2_use == var && can_replace_op2(ctx->scdf.op_array, opline, ssa_op)) { zval zv; ZVAL_COPY(&zv, value); if (zend_optimizer_update_op2_const(ctx->scdf.op_array, opline, &zv)) { return 1; } else { zval_ptr_dtor_nogc(&zv); } } return 0; } static inline int zval_to_string_offset(zend_long *result, zval *op) { switch (Z_TYPE_P(op)) { case IS_LONG: *result = Z_LVAL_P(op); return SUCCESS; case IS_STRING: if (IS_LONG == is_numeric_string( Z_STRVAL_P(op), Z_STRLEN_P(op), result, NULL, 0)) { return SUCCESS; } return FAILURE; default: return FAILURE; } } static inline int fetch_array_elem(zval **result, zval *op1, zval *op2) { switch (Z_TYPE_P(op2)) { case IS_NULL: *result = zend_hash_find(Z_ARR_P(op1), ZSTR_EMPTY_ALLOC()); return SUCCESS; case IS_FALSE: *result = zend_hash_index_find(Z_ARR_P(op1), 0); return SUCCESS; case IS_TRUE: *result = zend_hash_index_find(Z_ARR_P(op1), 1); return SUCCESS; case IS_LONG: *result = zend_hash_index_find(Z_ARR_P(op1), Z_LVAL_P(op2)); return SUCCESS; case IS_DOUBLE: *result = zend_hash_index_find(Z_ARR_P(op1), zend_dval_to_lval(Z_DVAL_P(op2))); return SUCCESS; case IS_STRING: *result = zend_symtable_find(Z_ARR_P(op1), Z_STR_P(op2)); return SUCCESS; default: return FAILURE; } } static inline int ct_eval_fetch_dim(zval *result, zval *op1, zval *op2, int support_strings) { if (Z_TYPE_P(op1) == IS_ARRAY) { zval *value; if (fetch_array_elem(&value, op1, op2) == SUCCESS && value) { ZVAL_COPY(result, value); return SUCCESS; } } else if (support_strings && Z_TYPE_P(op1) == IS_STRING) { zend_long index; if (zval_to_string_offset(&index, op2) == FAILURE) { return FAILURE; } if (index >= 0 && index < Z_STRLEN_P(op1)) { ZVAL_STR(result, zend_string_init(&Z_STRVAL_P(op1)[index], 1, 0)); return SUCCESS; } } return FAILURE; } static inline int ct_eval_isset_dim(zval *result, uint32_t extended_value, zval *op1, zval *op2) { if (Z_TYPE_P(op1) == IS_ARRAY) { zval *value; if (fetch_array_elem(&value, op1, op2) == FAILURE) { return FAILURE; } if (extended_value & ZEND_ISSET) { ZVAL_BOOL(result, value && Z_TYPE_P(value) != IS_NULL); } else { ZEND_ASSERT(extended_value & ZEND_ISEMPTY); ZVAL_BOOL(result, !value || !zend_is_true(value)); } return SUCCESS; } else if (Z_TYPE_P(op1) == IS_STRING) { // TODO return FAILURE; } else { ZVAL_BOOL(result, extended_value != ZEND_ISSET); return SUCCESS; } } static inline int ct_eval_add_array_elem(zval *result, zval *value, zval *key) { if (!key) { if ((value = zend_hash_next_index_insert(Z_ARR_P(result), value))) { Z_TRY_ADDREF_P(value); return SUCCESS; } return FAILURE; } switch (Z_TYPE_P(key)) { case IS_NULL: value = zend_hash_update(Z_ARR_P(result), ZSTR_EMPTY_ALLOC(), value); break; case IS_FALSE: value = zend_hash_index_update(Z_ARR_P(result), 0, value); break; case IS_TRUE: value = zend_hash_index_update(Z_ARR_P(result), 1, value); break; case IS_LONG: value = zend_hash_index_update(Z_ARR_P(result), Z_LVAL_P(key), value); break; case IS_DOUBLE: value = zend_hash_index_update( Z_ARR_P(result), zend_dval_to_lval(Z_DVAL_P(key)), value); break; case IS_STRING: value = zend_symtable_update(Z_ARR_P(result), Z_STR_P(key), value); break; default: return FAILURE; } Z_TRY_ADDREF_P(value); return SUCCESS; } static inline int ct_eval_assign_dim(zval *result, zval *value, zval *key) { switch (Z_TYPE_P(result)) { case IS_NULL: case IS_FALSE: array_init(result); /* break missing intentionally */ case IS_ARRAY: return ct_eval_add_array_elem(result, value, key); case IS_STRING: // TODO Before enabling this case, make sure ARRAY_DIM result op is correct #if 0 zend_long index; zend_string *new_str, *value_str; if (!key || Z_TYPE_P(value) == IS_ARRAY || zval_to_string_offset(&index, key) == FAILURE || index < 0) { return FAILURE; } if (index >= Z_STRLEN_P(result)) { new_str = zend_string_alloc(index + 1, 0); memcpy(ZSTR_VAL(new_str), Z_STRVAL_P(result), Z_STRLEN_P(result)); memset(ZSTR_VAL(new_str) + Z_STRLEN_P(result), ' ', index - Z_STRLEN_P(result)); ZSTR_VAL(new_str)[index + 1] = 0; } else { new_str = zend_string_init(Z_STRVAL_P(result), Z_STRLEN_P(result), 0); } value_str = zval_get_string(value); ZVAL_STR(result, new_str); Z_STRVAL_P(result)[index] = ZSTR_VAL(value_str)[0]; zend_string_release(value_str); #endif return FAILURE; default: return FAILURE; } } static inline int ct_eval_incdec(zval *result, zend_uchar opcode, zval *op1) { ZVAL_COPY(result, op1); if (opcode == ZEND_PRE_INC || opcode == ZEND_POST_INC) { increment_function(result); } else { decrement_function(result); } return SUCCESS; } static inline int ct_eval_isset_isempty(zval *result, uint32_t extended_value, zval *op1) { if (extended_value & ZEND_ISSET) { ZVAL_BOOL(result, Z_TYPE_P(op1) != IS_NULL); } else { ZEND_ASSERT(extended_value & ZEND_ISEMPTY); ZVAL_BOOL(result, !zend_is_true(op1)); } return SUCCESS; } static inline void ct_eval_type_check(zval *result, uint32_t type, zval *op1) { if (type == _IS_BOOL) { ZVAL_BOOL(result, Z_TYPE_P(op1) == IS_TRUE || Z_TYPE_P(op1) == IS_FALSE); } else { ZVAL_BOOL(result, Z_TYPE_P(op1) == type); } } static inline int ct_eval_in_array(zval *result, uint32_t extended_value, zval *op1, zval *op2) { HashTable *ht; zend_bool res; if (Z_TYPE_P(op2) != IS_ARRAY) { return FAILURE; } ht = Z_ARRVAL_P(op2); if (EXPECTED(Z_TYPE_P(op1) == IS_STRING)) { res = zend_hash_exists(ht, Z_STR_P(op1)); } else if (extended_value) { if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) { res = zend_hash_index_exists(ht, Z_LVAL_P(op1)); } else { res = 0; } } else if (Z_TYPE_P(op1) <= IS_FALSE) { res = zend_hash_exists(ht, ZSTR_EMPTY_ALLOC()); } else { zend_string *key; zval key_tmp, result_tmp; res = 0; ZEND_HASH_FOREACH_STR_KEY(ht, key) { ZVAL_STR(&key_tmp, key); compare_function(&result_tmp, op1, &key_tmp); if (Z_LVAL(result_tmp) == 0) { res = 1; break; } } ZEND_HASH_FOREACH_END(); } ZVAL_BOOL(result, res); return SUCCESS; } /* The functions chosen here are simple to implement and either likely to affect a branch, * or just happened to be commonly used with constant operands in WP (need to test other * applications as well, of course). */ static inline int ct_eval_func_call( zval *result, zend_string *name, uint32_t num_args, zval **args) { uint32_t i; zend_execute_data *execute_data, *prev_execute_data; zend_function *func; int overflow; if (num_args == 0) { if (zend_string_equals_literal(name, "get_magic_quotes_gpc") || zend_string_equals_literal(name, "get_magic_quotes_gpc_runtime") || zend_string_equals_literal(name, "php_sapi_name") || zend_string_equals_literal(name, "imagetypes") || zend_string_equals_literal(name, "phpversion")) { /* pass */ } else { return FAILURE; } } else if (num_args == 1) { if (zend_string_equals_literal(name, "chr")) { zend_long c; if (Z_TYPE_P(args[0]) != IS_LONG) { return FAILURE; } c = Z_LVAL_P(args[0]) & 0xff; ZVAL_INTERNED_STR(result, ZSTR_CHAR(c)); return SUCCESS; } else if (zend_string_equals_literal(name, "count")) { if (Z_TYPE_P(args[0]) != IS_ARRAY) { return FAILURE; } ZVAL_LONG(result, zend_hash_num_elements(Z_ARRVAL_P(args[0]))); return SUCCESS; } else if (zend_string_equals_literal(name, "ini_get")) { zend_ini_entry *ini_entry; if (Z_TYPE_P(args[0]) != IS_STRING) { return FAILURE; } ini_entry = zend_hash_find_ptr(EG(ini_directives), Z_STR_P(args[0])); if (!ini_entry) { ZVAL_FALSE(result); } else if (ini_entry->modifiable != ZEND_INI_SYSTEM) { return FAILURE; } else if (ini_entry->value) { ZVAL_STR_COPY(result, ini_entry->value); } else { ZVAL_EMPTY_STRING(result); } return SUCCESS; } else if (zend_string_equals_literal(name, "trim") || zend_string_equals_literal(name, "rtrim") || zend_string_equals_literal(name, "ltrim") || zend_string_equals_literal(name, "str_split") || zend_string_equals_literal(name, "preg_quote") || zend_string_equals_literal(name, "base64_encode") || zend_string_equals_literal(name, "base64_decode") || zend_string_equals_literal(name, "urlencode") || zend_string_equals_literal(name, "urldecode") || zend_string_equals_literal(name, "rawurlencode") || zend_string_equals_literal(name, "rawurldecode") || zend_string_equals_literal(name, "php_uname")) { if (Z_TYPE_P(args[0]) != IS_STRING) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "array_keys") || zend_string_equals_literal(name, "array_values")) { if (Z_TYPE_P(args[0]) != IS_ARRAY) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "array_flip")) { zval *entry; if (Z_TYPE_P(args[0]) != IS_ARRAY) { return FAILURE; } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) { if (Z_TYPE_P(entry) != IS_LONG && Z_TYPE_P(entry) != IS_STRING) { return FAILURE; } } ZEND_HASH_FOREACH_END(); /* pass */ } else if (zend_string_equals_literal(name, "implode")) { zval *entry; if (Z_TYPE_P(args[0]) != IS_ARRAY) { return FAILURE; } ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) { if (Z_TYPE_P(entry) > IS_STRING) { return FAILURE; } } ZEND_HASH_FOREACH_END(); /* pass */ } else if (zend_string_equals_literal(name, "serialize")) { /* pass */ } else { return FAILURE; } } else if (num_args == 2) { if (zend_string_equals_literal(name, "in_array")) { if (Z_TYPE_P(args[1]) != IS_ARRAY) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "strpos")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_STRING || !Z_STRLEN_P(args[1]) || (CG(compiler_options) & ZEND_COMPILE_NO_BUILTIN_STRLEN)) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "str_split")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_LONG || Z_LVAL_P(args[1]) <= 0) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "array_key_exists")) { if (Z_TYPE_P(args[1]) != IS_ARRAY || (Z_TYPE_P(args[0]) != IS_LONG && Z_TYPE_P(args[0]) != IS_STRING && Z_TYPE_P(args[0]) != IS_NULL)) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "trim") || zend_string_equals_literal(name, "rtrim") || zend_string_equals_literal(name, "ltrim") || zend_string_equals_literal(name, "preg_quote")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_STRING) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "str_repeat")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_LONG || zend_safe_address(Z_STRLEN_P(args[0]), Z_LVAL_P(args[1]), 0, &overflow) > 64 * 1024 || overflow) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "array_merge") || zend_string_equals_literal(name, "array_replace") || zend_string_equals_literal(name, "array_merge_recursive") || zend_string_equals_literal(name, "array_replace_recursive") || zend_string_equals_literal(name, "array_diff") || zend_string_equals_literal(name, "array_diff_assoc") || zend_string_equals_literal(name, "array_diff_key")) { for (i = 0; i < num_args; i++) { if (Z_TYPE_P(args[i]) != IS_ARRAY) { return FAILURE; } } /* pass */ } else if (zend_string_equals_literal(name, "implode")) { zval *entry; if ((Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_ARRAY) && (Z_TYPE_P(args[0]) != IS_ARRAY || Z_TYPE_P(args[1]) != IS_STRING)) { return FAILURE; } if (Z_TYPE_P(args[0]) == IS_ARRAY) { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[0]), entry) { if (Z_TYPE_P(entry) > IS_STRING) { return FAILURE; } } ZEND_HASH_FOREACH_END(); } else { ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(args[1]), entry) { if (Z_TYPE_P(entry) > IS_STRING) { return FAILURE; } } ZEND_HASH_FOREACH_END(); } /* pass */ } else if (zend_string_equals_literal(name, "version_compare")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_STRING) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "substr")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_LONG || (CG(compiler_options) & ZEND_COMPILE_NO_BUILTIN_STRLEN)) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "pow")) { if ((Z_TYPE_P(args[0]) != IS_LONG && Z_TYPE_P(args[0]) != IS_DOUBLE) || (Z_TYPE_P(args[1]) != IS_LONG && Z_TYPE_P(args[1]) != IS_DOUBLE)) { return FAILURE; } /* pass */ } else { return FAILURE; } } else if (num_args == 3) { if (zend_string_equals_literal(name, "in_array")) { if (Z_TYPE_P(args[1]) != IS_ARRAY || (Z_TYPE_P(args[2]) != IS_FALSE && Z_TYPE_P(args[2]) != IS_TRUE)) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "array_merge") || zend_string_equals_literal(name, "array_replace") || zend_string_equals_literal(name, "array_merge_recursive") || zend_string_equals_literal(name, "array_replace_recursive") || zend_string_equals_literal(name, "array_diff") || zend_string_equals_literal(name, "array_diff_assoc") || zend_string_equals_literal(name, "array_diff_key")) { for (i = 0; i < num_args; i++) { if (Z_TYPE_P(args[i]) != IS_ARRAY) { return FAILURE; } } /* pass */ } else if (zend_string_equals_literal(name, "version_compare")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_STRING || Z_TYPE_P(args[2]) != IS_STRING) { return FAILURE; } /* pass */ } else if (zend_string_equals_literal(name, "substr")) { if (Z_TYPE_P(args[0]) != IS_STRING || Z_TYPE_P(args[1]) != IS_LONG || Z_TYPE_P(args[2]) != IS_LONG || (CG(compiler_options) & ZEND_COMPILE_NO_BUILTIN_STRLEN)) { return FAILURE; } /* pass */ } else { return FAILURE; } } else { return FAILURE; } func = zend_hash_find_ptr(CG(function_table), name); if (!func || func->type != ZEND_INTERNAL_FUNCTION || func->internal_function.handler == ZEND_FN(display_disabled_function)) { return FAILURE; } execute_data = safe_emalloc(num_args, sizeof(zval), ZEND_CALL_FRAME_SLOT * sizeof(zval)); memset(execute_data, 0, sizeof(zend_execute_data)); prev_execute_data = EG(current_execute_data); EG(current_execute_data) = execute_data; EX(func) = func; EX_NUM_ARGS() = num_args; for (i = 0; i < num_args; i++) { ZVAL_COPY(EX_VAR_NUM(i), args[i]); } func->internal_function.handler(execute_data, result); for (i = 0; i < num_args; i++) { zval_ptr_dtor_nogc(EX_VAR_NUM(i)); } efree(execute_data); EG(current_execute_data) = prev_execute_data; return SUCCESS; } #define SET_RESULT(op, zv) do { \ if (ssa_op->op##_def >= 0) { \ set_value(scdf, ctx, ssa_op->op##_def, zv); \ } \ } while (0) #define SET_RESULT_BOT(op) SET_RESULT(op, &ctx->bot) #define SET_RESULT_TOP(op) SET_RESULT(op, &ctx->top) #define SKIP_IF_TOP(op) if (IS_TOP(op)) break; static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_op) { sccp_ctx *ctx = (sccp_ctx *) scdf; zval *op1, *op2, zv; /* zv is a temporary to hold result values */ op1 = get_op1_value(ctx, opline, ssa_op); op2 = get_op2_value(ctx, opline, ssa_op); switch (opline->opcode) { case ZEND_ASSIGN: /* The value of op1 is irrelevant here, because we are overwriting it * -- unless it can be a reference, in which case we propagate a BOT. */ if (IS_BOT(op1) && (ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_REF)) { SET_RESULT_BOT(op1); } else { SET_RESULT(op1, op2); } SET_RESULT(result, op2); return; case ZEND_TYPE_CHECK: /* We may be able to evaluate TYPE_CHECK based on type inference info, * even if we don't know the precise value. */ if (!value_known(op1)) { uint32_t type = ctx->scdf.ssa->var_info[ssa_op->op1_use].type; uint32_t expected_type = opline->extended_value == _IS_BOOL ? (MAY_BE_TRUE|MAY_BE_FALSE) : (1 << opline->extended_value); if (!(type & expected_type) && !(type & MAY_BE_UNDEF)) { ZVAL_FALSE(&zv); SET_RESULT(result, &zv); return; } else if (!(type & ((MAY_BE_ANY|MAY_BE_UNDEF) - expected_type)) && opline->extended_value != IS_RESOURCE) { ZVAL_TRUE(&zv); SET_RESULT(result, &zv); return; } } break; case ZEND_ASSIGN_DIM: /* If $a in $a[$b]=$c is UNDEF, treat it like NULL. There is no warning. */ if ((ctx->scdf.ssa->var_info[ssa_op->op1_use].type & MAY_BE_ANY) == 0) { op1 = &EG(uninitialized_zval); } break; case ZEND_SEND_VAL: case ZEND_SEND_VAR: { /* If the value of a SEND for an ICALL changes, we need to reconsider the * ICALL result value. Otherwise we can ignore the opcode. */ zend_call_info *call; if (!ctx->call_map) { return; } call = ctx->call_map[opline - ctx->scdf.op_array->opcodes]; if (IS_TOP(op1) || !call || call->caller_call_opline->opcode != ZEND_DO_ICALL) { return; } opline = call->caller_call_opline; ssa_op = &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]; break; } } if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) { /* If any operand is BOT, mark the result as BOT right away. * Exceptions to this rule are handled above. */ SET_RESULT_BOT(result); SET_RESULT_BOT(op1); SET_RESULT_BOT(op2); return; } switch (opline->opcode) { case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: case ZEND_DIV: case ZEND_MOD: case ZEND_POW: case ZEND_SL: case ZEND_SR: case ZEND_CONCAT: case ZEND_FAST_CONCAT: case ZEND_IS_EQUAL: case ZEND_IS_NOT_EQUAL: case ZEND_IS_SMALLER: case ZEND_IS_SMALLER_OR_EQUAL: case ZEND_IS_IDENTICAL: case ZEND_IS_NOT_IDENTICAL: case ZEND_BW_OR: case ZEND_BW_AND: case ZEND_BW_XOR: case ZEND_BOOL_XOR: case ZEND_CASE: SKIP_IF_TOP(op1); SKIP_IF_TOP(op2); if (zend_optimizer_eval_binary_op(&zv, opline->opcode, op1, op2) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_MOD: case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: case ZEND_ASSIGN_CONCAT: case ZEND_ASSIGN_BW_OR: case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: case ZEND_ASSIGN_POW: /* Obj/dim compound assign */ if (opline->extended_value) { SET_RESULT_BOT(op1); SET_RESULT_BOT(result); break; } SKIP_IF_TOP(op1); SKIP_IF_TOP(op2); if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) { SET_RESULT(op1, &zv); SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(op1); SET_RESULT_BOT(result); break; case ZEND_PRE_INC: case ZEND_PRE_DEC: SKIP_IF_TOP(op1); if (ct_eval_incdec(&zv, opline->opcode, op1) == SUCCESS) { SET_RESULT(op1, &zv); SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(op1); SET_RESULT_BOT(result); break; case ZEND_POST_INC: case ZEND_POST_DEC: SKIP_IF_TOP(op1); SET_RESULT(result, op1); if (ct_eval_incdec(&zv, opline->opcode, op1) == SUCCESS) { SET_RESULT(op1, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(op1); break; case ZEND_BW_NOT: case ZEND_BOOL_NOT: SKIP_IF_TOP(op1); if (zend_optimizer_eval_unary_op(&zv, opline->opcode, op1) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_CAST: SKIP_IF_TOP(op1); if (zend_optimizer_eval_cast(&zv, opline->extended_value, op1) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_BOOL: case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: SKIP_IF_TOP(op1); ZVAL_BOOL(&zv, zend_is_true(op1)); SET_RESULT(result, &zv); break; case ZEND_STRLEN: SKIP_IF_TOP(op1); if (zend_optimizer_eval_strlen(&zv, op1) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_COUNT: SKIP_IF_TOP(op1); if (Z_TYPE_P(op1) == IS_ARRAY) { ZVAL_LONG(&zv, zend_hash_num_elements(Z_ARRVAL_P(op1))); SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_IN_ARRAY: SKIP_IF_TOP(op1); SKIP_IF_TOP(op2); if (ct_eval_in_array(&zv, opline->extended_value, op1, op2) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_FETCH_DIM_R: case ZEND_FETCH_DIM_IS: case ZEND_FETCH_LIST: SKIP_IF_TOP(op1); SKIP_IF_TOP(op2); if (ct_eval_fetch_dim(&zv, op1, op2, (opline->opcode != ZEND_FETCH_LIST)) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_ISSET_ISEMPTY_DIM_OBJ: SKIP_IF_TOP(op1); SKIP_IF_TOP(op2); if (ct_eval_isset_dim(&zv, opline->extended_value, op1, op2) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_QM_ASSIGN: case ZEND_JMP_SET: case ZEND_COALESCE: SET_RESULT(result, op1); break; case ZEND_FETCH_CLASS: if (!op1) { SET_RESULT_BOT(result); break; } SET_RESULT(result, op1); break; case ZEND_ISSET_ISEMPTY_CV: SKIP_IF_TOP(op1); if (ct_eval_isset_isempty(&zv, opline->extended_value, op1) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_TYPE_CHECK: SKIP_IF_TOP(op1); ct_eval_type_check(&zv, opline->extended_value, op1); SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; case ZEND_INSTANCEOF: SKIP_IF_TOP(op1); ZVAL_FALSE(&zv); SET_RESULT(result, &zv); break; case ZEND_ROPE_INIT: SKIP_IF_TOP(op2); if (zend_optimizer_eval_cast(&zv, IS_STRING, op2) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_ROPE_ADD: case ZEND_ROPE_END: // TODO The way this is currently implemented will result in quadratic runtime // This is not necessary, the way the algorithm works it's okay to reuse the same // string for all SSA vars with some extra checks SKIP_IF_TOP(op1); SKIP_IF_TOP(op2); if (zend_optimizer_eval_binary_op(&zv, ZEND_CONCAT, op1, op2) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); break; case ZEND_INIT_ARRAY: case ZEND_ADD_ARRAY_ELEMENT: { zval *result = NULL; if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) { SET_RESULT_BOT(result); SET_RESULT_BOT(op1); break; } if (opline->opcode == ZEND_ADD_ARRAY_ELEMENT) { result = &ctx->values[ssa_op->result_use]; if (IS_BOT(result)) { SET_RESULT_BOT(result); break; } SKIP_IF_TOP(result); } SKIP_IF_TOP(op1); if (op2) { SKIP_IF_TOP(op2); } /* We want to avoid keeping around intermediate arrays for each SSA variable in the * ADD_ARRAY_ELEMENT chain. We do this by only keeping the array on the last opcode * and use a NULL value everywhere else. */ if (Z_TYPE(ctx->values[ssa_op->result_def]) == IS_NULL) { break; } if (result) { ZVAL_COPY_VALUE(&zv, result); ZVAL_NULL(result); } else { array_init(&zv); } if (ct_eval_add_array_elem(&zv, op1, op2) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); zval_ptr_dtor_nogc(&zv); break; } case ZEND_ASSIGN_DIM: { zval *data = get_op1_value(ctx, opline+1, ssa_op+1); if (IS_BOT(data)) { SET_RESULT_BOT(op1); SET_RESULT_BOT(result); break; } SKIP_IF_TOP(data); SKIP_IF_TOP(op1); if (op2) { SKIP_IF_TOP(op2); } ZVAL_DUP(&zv, op1); if (ct_eval_assign_dim(&zv, data, op2) == SUCCESS) { SET_RESULT(result, data); SET_RESULT(op1, &zv); zval_ptr_dtor_nogc(&zv); break; } SET_RESULT_BOT(result); SET_RESULT_BOT(op1); zval_ptr_dtor_nogc(&zv); break; } case ZEND_DO_ICALL: { zend_call_info *call; zval *name, *args[3] = {NULL}; int i; if (!ctx->call_map) { SET_RESULT_BOT(result); break; } call = ctx->call_map[opline - ctx->scdf.op_array->opcodes]; name = CT_CONSTANT_EX(ctx->scdf.op_array, call->caller_init_opline->op2.constant); /* We already know it can't be evaluated, don't bother checking again */ if (ssa_op->result_def < 0 || IS_BOT(&ctx->values[ssa_op->result_def])) { break; } /* We're only interested in functions with up to three arguments right now */ if (call->num_args > 3) { SET_RESULT_BOT(result); break; } for (i = 0; i < call->num_args; i++) { zend_op *opline = call->arg_info[i].opline; if (opline->opcode != ZEND_SEND_VAL && opline->opcode != ZEND_SEND_VAR) { SET_RESULT_BOT(result); return; } args[i] = get_op1_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]); if (args[i]) { if (IS_BOT(args[i])) { SET_RESULT_BOT(result); return; } else if (IS_TOP(args[i])) { return; } } } /* We didn't get a BOT argument, so value stays the same */ if (!IS_TOP(&ctx->values[ssa_op->result_def])) { break; } if (ct_eval_func_call(&zv, Z_STR_P(name), call->num_args, args) == SUCCESS) { SET_RESULT(result, &zv); zval_ptr_dtor_nogc(&zv); break; } #if 0 /* sort out | uniq -c | sort -n */ fprintf(stderr, "%s\n", Z_STRVAL_P(name)); /*if (args[1]) { php_printf("%s %Z %Z\n", Z_STRVAL_P(name), args[0], args[1]); } else { php_printf("%s %Z\n", Z_STRVAL_P(name), args[0]); }*/ #endif SET_RESULT_BOT(result); break; } default: { /* If we have no explicit implementation return BOT */ SET_RESULT_BOT(result); SET_RESULT_BOT(op1); SET_RESULT_BOT(op2); break; } } } /* Returns whether there is a successor */ static void sccp_mark_feasible_successors( scdf_ctx *scdf, int block_num, zend_basic_block *block, zend_op *opline, zend_ssa_op *ssa_op) { sccp_ctx *ctx = (sccp_ctx *) scdf; zval *op1; int s; /* We can't determine the branch target at compile-time for these */ switch (opline->opcode) { case ZEND_ASSERT_CHECK: case ZEND_CATCH: case ZEND_DECLARE_ANON_CLASS: case ZEND_DECLARE_ANON_INHERITED_CLASS: case ZEND_FE_FETCH_R: case ZEND_FE_FETCH_RW: scdf_mark_edge_feasible(scdf, block_num, block->successors[0]); scdf_mark_edge_feasible(scdf, block_num, block->successors[1]); return; } op1 = get_op1_value(ctx, opline, ssa_op); /* Branch target can be either one */ if (!op1 || IS_BOT(op1)) { for (s = 0; s < block->successors_count; s++) { scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } return; } /* Branch target not yet known */ if (IS_TOP(op1)) { return; } switch (opline->opcode) { case ZEND_JMPZ: case ZEND_JMPZNZ: case ZEND_JMPZ_EX: s = zend_is_true(op1); break; case ZEND_JMPNZ: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: s = !zend_is_true(op1); break; case ZEND_COALESCE: s = (Z_TYPE_P(op1) == IS_NULL); break; case ZEND_FE_RESET_R: case ZEND_FE_RESET_RW: if (Z_TYPE_P(op1) != IS_ARRAY) { scdf_mark_edge_feasible(scdf, block_num, block->successors[0]); scdf_mark_edge_feasible(scdf, block_num, block->successors[1]); return; } s = zend_hash_num_elements(Z_ARR_P(op1)) != 0; break; default: for (s = 0; s < block->successors_count; s++) { scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } return; } scdf_mark_edge_feasible(scdf, block_num, block->successors[s]); } static void join_phi_values(zval *a, zval *b) { if (IS_BOT(a) || IS_TOP(b)) { return; } if (IS_TOP(a)) { zval_ptr_dtor_nogc(a); ZVAL_COPY(a, b); return; } if (IS_BOT(b) || !zend_is_identical(a, b)) { zval_ptr_dtor_nogc(a); MAKE_BOT(a); } } static void sccp_visit_phi(scdf_ctx *scdf, zend_ssa_phi *phi) { sccp_ctx *ctx = (sccp_ctx *) scdf; zend_ssa *ssa = scdf->ssa; ZEND_ASSERT(phi->ssa_var >= 0); if (!IS_BOT(&ctx->values[phi->ssa_var])) { zend_basic_block *block = &ssa->cfg.blocks[phi->block]; int *predecessors = &ssa->cfg.predecessors[block->predecessor_offset]; int i; zval result; MAKE_TOP(&result); SCP_DEBUG("Handling PHI("); if (phi->pi >= 0) { ZEND_ASSERT(phi->sources[0] >= 0); if (scdf_is_edge_feasible(scdf, phi->pi, phi->block)) { join_phi_values(&result, &ctx->values[phi->sources[0]]); } } else { for (i = 0; i < block->predecessors_count; i++) { ZEND_ASSERT(phi->sources[i] >= 0); if (scdf_is_edge_feasible(scdf, predecessors[i], phi->block)) { SCP_DEBUG("val, "); join_phi_values(&result, &ctx->values[phi->sources[i]]); } else { SCP_DEBUG("--, "); } } } SCP_DEBUG(")\n"); set_value(scdf, ctx, phi->ssa_var, &result); zval_ptr_dtor_nogc(&result); } } static zval *value_from_type_and_range(sccp_ctx *ctx, int var_num, zval *tmp) { zend_ssa *ssa = ctx->scdf.ssa; zend_ssa_var_info *info = &ssa->var_info[var_num]; if (ssa->vars[var_num].var >= ctx->scdf.op_array->last_var) { // TODO Non-CVs may cause issues with FREEs return NULL; } if (info->type & MAY_BE_UNDEF) { return NULL; } if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_NULL))) { ZVAL_NULL(tmp); return tmp; } if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_FALSE))) { ZVAL_FALSE(tmp); return tmp; } if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_TRUE))) { ZVAL_TRUE(tmp); return tmp; } if (!(info->type & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_LONG)) && info->has_range && !info->range.overflow && !info->range.underflow && info->range.min == info->range.max) { ZVAL_LONG(tmp, info->range.min); return tmp; } return NULL; } /* This will try to replace uses of SSA variables we have determined to be constant. Not all uses * can be replaced, because some instructions don't accept constant operands or only accept them * if they have a certain type. */ static int replace_constant_operands(sccp_ctx *ctx) { zend_ssa *ssa = ctx->scdf.ssa; zend_op_array *op_array = ctx->scdf.op_array; int i; zval tmp; int removed_ops = 0; /* We iterate the variables backwards, so we can eliminate sequences like INIT_ROPE * and INIT_ARRAY. */ for (i = ssa->vars_count - 1; i >= op_array->last_var; i--) { zend_ssa_var *var = &ssa->vars[i]; zval *value; int use; if (value_known(&ctx->values[i])) { value = &ctx->values[i]; } else { value = value_from_type_and_range(ctx, i, &tmp); if (!value) { continue; } } FOREACH_USE(var, use) { zend_op *opline = &op_array->opcodes[use]; zend_ssa_op *ssa_op = &ssa->ops[use]; if (try_replace_op1(ctx, opline, ssa_op, i, value)) { if (opline->opcode == ZEND_NOP) { removed_ops++; } ZEND_ASSERT(ssa_op->op1_def == -1); if (ssa_op->op1_use != ssa_op->op2_use) { zend_ssa_unlink_use_chain(ssa, use, ssa_op->op1_use); } else { ssa_op->op2_use_chain = ssa_op->op1_use_chain; } ssa_op->op1_use = -1; ssa_op->op1_use_chain = -1; } if (try_replace_op2(ctx, opline, ssa_op, i, value)) { ZEND_ASSERT(ssa_op->op2_def == -1); if (ssa_op->op2_use != ssa_op->op1_use) { zend_ssa_unlink_use_chain(ssa, use, ssa_op->op2_use); } ssa_op->op2_use = -1; ssa_op->op2_use_chain = -1; } } FOREACH_USE_END(); /* This is a basic DCE pass we run after SCCP. It only works on those instructions those result * value(s) were determined by SCCP. It removes dead computational instructions and converts * CV-affecting instructions into CONST ASSIGNs. This basic DCE is performed for multiple reasons: * a) During operand replacement we eliminate FREEs. The corresponding computational instructions * must be removed to avoid leaks. This way SCCP can run independently of the full DCE pass. * b) The main DCE pass relies on type analysis to determine whether instructions have side-effects * and can't be DCEd. This means that it will not be able collect all instructions rendered dead * by SCCP, because they may have potentially side-effecting types, but the actual values are * not. As such doing DCE here will allow us to eliminate more dead code in combination. * c) The ordinary DCE pass cannot collect dead calls. However SCCP can result in dead calls, which * we need to collect. */ if (var->definition >= 0 && value_known(&ctx->values[i])) { zend_op *opline = &op_array->opcodes[var->definition]; zend_ssa_op *ssa_op = &ssa->ops[var->definition]; if (opline->opcode == ZEND_ASSIGN) { /* Leave assigns to DCE (due to dtor effects) */ continue; } if (ssa_op->result_def == i && ssa_op->op1_def < 0 && ssa_op->op2_def < 0 && var->use_chain < 0 && var->phi_use_chain == NULL) { if (opline->opcode == ZEND_DO_ICALL) { /* Call instruction -> remove opcodes that are part of the call */ zend_call_info *call; int i; ZEND_ASSERT(ctx->call_map); call = ctx->call_map[var->definition]; ZEND_ASSERT(call); ZEND_ASSERT(call->caller_call_opline == opline); if (opline->result_type & (IS_TMP_VAR|IS_VAR)) { zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition); } zend_ssa_remove_result_def(ssa, ssa_op); zend_ssa_remove_instr(ssa, opline, ssa_op); zend_ssa_remove_instr(ssa, call->caller_init_opline, &ssa->ops[call->caller_init_opline - op_array->opcodes]); for (i = 0; i < call->num_args; i++) { zend_ssa_remove_instr(ssa, call->arg_info[i].opline, &ssa->ops[call->arg_info[i].opline - op_array->opcodes]); } removed_ops = call->num_args + 2; // TODO: remove call_info completely??? call->callee_func = NULL; } else { /* Ordinary computational instruction -> remove it */ if (opline->result_type & (IS_TMP_VAR|IS_VAR)) { zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition); } zend_ssa_remove_result_def(ssa, ssa_op); zend_ssa_remove_instr(ssa, opline, ssa_op); removed_ops++; } } else if (ssa_op->op1_def == i && (ssa_op->result_def < 0 || (ssa->vars[ssa_op->result_def].use_chain < 0 && ssa->vars[ssa_op->result_def].phi_use_chain == NULL))) { /* Compound assign or incdec -> convert to direct ASSIGN */ /* Destroy previous op2 */ if (opline->op2_type == IS_CONST) { literal_dtor(&ZEND_OP2_LITERAL(opline)); } else if (ssa_op->op2_use >= 0) { if (ssa_op->op2_use != ssa_op->op1_use) { zend_ssa_unlink_use_chain(ssa, var->definition, ssa_op->op2_use); } ssa_op->op2_use = -1; ssa_op->op2_use_chain = -1; } /* We checked that result has no uses, mark unused */ if (ssa_op->result_def >= 0) { if (opline->result_type & (IS_TMP_VAR|IS_VAR)) { zend_optimizer_remove_live_range_ex(op_array, opline->result.var, var->definition); } zend_ssa_remove_result_def(ssa, ssa_op); opline->result_type = IS_UNUSED; } /* Remove OP_DATA opcode */ if (opline->opcode == ZEND_ASSIGN_DIM) { removed_ops++; zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1); } /* Convert to ASSIGN */ opline->opcode = ZEND_ASSIGN; opline->op2_type = IS_CONST; opline->op2.constant = zend_optimizer_add_literal(op_array, value); Z_TRY_ADDREF_P(value); } } if (var->definition_phi && value_known(&ctx->values[i]) && var->use_chain < 0 && var->phi_use_chain == NULL) { zend_ssa_remove_phi(ssa, var->definition_phi); } } return removed_ops; } static void sccp_context_init(zend_optimizer_ctx *ctx, sccp_ctx *sccp, zend_ssa *ssa, zend_op_array *op_array, zend_call_info **call_map) { int i; sccp->call_map = call_map; sccp->values = zend_arena_alloc(&ctx->arena, sizeof(zval) * ssa->vars_count); MAKE_TOP(&sccp->top); MAKE_BOT(&sccp->bot); i = 0; for (; i < op_array->last_var; ++i) { /* These are all undefined variables, which we have to mark BOT. * Otherwise the undefined variable warning might not be preserved. */ MAKE_BOT(&sccp->values[i]); } for (; i < ssa->vars_count; ++i) { if (ssa->vars[i].alias) { MAKE_BOT(&sccp->values[i]); } else { MAKE_TOP(&sccp->values[i]); } } } static void sccp_context_free(sccp_ctx *sccp) { int i; for (i = sccp->scdf.op_array->last_var; i < sccp->scdf.ssa->vars_count; ++i) { zval_ptr_dtor_nogc(&sccp->values[i]); } } int sccp_optimize_op_array(zend_optimizer_ctx *ctx, zend_op_array *op_array, zend_ssa *ssa, zend_call_info **call_map) { sccp_ctx sccp; int removed_ops = 0; void *checkpoint = zend_arena_checkpoint(ctx->arena); sccp_context_init(ctx, &sccp, ssa, op_array, call_map); sccp.scdf.handlers.visit_instr = sccp_visit_instr; sccp.scdf.handlers.visit_phi = sccp_visit_phi; sccp.scdf.handlers.mark_feasible_successors = sccp_mark_feasible_successors; scdf_init(ctx, &sccp.scdf, op_array, ssa); scdf_solve(&sccp.scdf, "SCCP"); removed_ops += scdf_remove_unreachable_blocks(&sccp.scdf); removed_ops += replace_constant_operands(&sccp); sccp_context_free(&sccp); zend_arena_release(&ctx->arena, checkpoint); return removed_ops; }