Skip to content

Implement frameless internal function calls #12461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ static void assemble_code_blocks(zend_cfg *cfg, zend_op_array *op_array, zend_op
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, new_opcodes + blocks[b->successors[0]].start);
break;
case ZEND_CATCH:
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
break;
case ZEND_DECLARE_ANON_CLASS:
case ZEND_DECLARE_CLASS_DELAYED:
case ZEND_JMP_FRAMELESS:
opline->extended_value = cache_size;
cache_size += sizeof(void *);
break;
Expand Down
5 changes: 5 additions & 0 deletions Zend/Optimizer/dce.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ static inline bool may_have_side_effects(
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
/* For our purposes a jumps and branches are side effects. */
return 1;
case ZEND_BEGIN_SILENCE:
Expand All @@ -167,6 +168,10 @@ static inline bool may_have_side_effects(
case ZEND_DO_FCALL_BY_NAME:
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
case ZEND_FRAMELESS_ICALL_0:
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3:
/* For now assume all calls have side effects */
return 1;
case ZEND_RECV:
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/dfa_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
if (ZEND_OP2_JMP_ADDR(opline) == op_array->opcodes + old->start) {
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, op_array->opcodes + dst->start);
}
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/pass1.c
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ void zend_optimizer_pass1(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_JMP_NULL:
case ZEND_VERIFY_NEVER_TYPE:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
collect_constants = 0;
break;
}
Expand Down
75 changes: 68 additions & 7 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -789,14 +789,10 @@ static bool can_ct_eval_func_call(zend_function *func, zend_string *name, uint32
/* 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 zend_result ct_eval_func_call(
zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
static inline zend_result ct_eval_func_call_ex(
zend_op_array *op_array, zval *result, zend_function *func, uint32_t num_args, zval **args) {
uint32_t i;
zend_function *func = zend_hash_find_ptr(CG(function_table), name);
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
return FAILURE;
}

zend_string *name = func->common.function_name;
if (num_args == 1 && Z_TYPE_P(args[0]) == IS_STRING &&
zend_optimizer_eval_special_func_call(result, name, Z_STR_P(args[0])) == SUCCESS) {
return SUCCESS;
Expand Down Expand Up @@ -855,6 +851,15 @@ static inline zend_result ct_eval_func_call(
return retval;
}

static inline zend_result ct_eval_func_call(
zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
zend_function *func = zend_hash_find_ptr(CG(function_table), name);
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
return FAILURE;
}
return ct_eval_func_call_ex(op_array, result, func, num_args, args);
}

#define SET_RESULT(op, zv) do { \
if (ssa_op->op##_def >= 0) { \
set_value(scdf, ctx, ssa_op->op##_def, zv); \
Expand Down Expand Up @@ -1708,6 +1713,51 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
SET_RESULT_BOT(result);
break;
}
case ZEND_FRAMELESS_ICALL_0:
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3: {
/* 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;
}

zval *args[3] = {NULL};
zend_function *func = ZEND_FLF_FUNC(opline);
uint32_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);

switch (num_args) {
case 3: {
zend_op *op_data = opline + 1;
args[2] = get_op1_value(ctx, op_data, &ctx->scdf.ssa->ops[op_data - ctx->scdf.op_array->opcodes]);
ZEND_FALLTHROUGH;
}
case 2:
args[1] = get_op2_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
ZEND_FALLTHROUGH;
case 1:
args[0] = get_op1_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
break;
}
for (uint32_t i = 0; i < num_args; i++) {
if (!args[i]) {
SET_RESULT_BOT(result);
return;
} else if (IS_BOT(args[i]) || IS_PARTIAL_ARRAY(args[i])) {
SET_RESULT_BOT(result);
return;
} else if (IS_TOP(args[i])) {
return;
}
}
if (ct_eval_func_call_ex(scdf->op_array, &zv, func, num_args, args) == SUCCESS) {
SET_RESULT(result, &zv);
zval_ptr_dtor_nogc(&zv);
break;
}
SET_RESULT_BOT(result);
break;
}
default:
{
/* If we have no explicit implementation return BOT */
Expand Down Expand Up @@ -2155,7 +2205,13 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
if (opline->opcode == ZEND_DO_ICALL) {
removed_ops = remove_call(ctx, opline, ssa_op) - 1;
} else {
bool has_op_data = opline->opcode == ZEND_FRAMELESS_ICALL_3;
zend_ssa_remove_instr(ssa, opline, ssa_op);
removed_ops++;
if (has_op_data) {
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
removed_ops++;
}
}
ssa_op->result_def = var_num;
opline->opcode = ZEND_QM_ASSIGN;
Expand Down Expand Up @@ -2191,8 +2247,13 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
if (opline->opcode == ZEND_DO_ICALL) {
removed_ops = remove_call(ctx, opline, ssa_op);
} else {
bool has_op_data = opline->opcode == ZEND_FRAMELESS_ICALL_3;
zend_ssa_remove_instr(ssa, opline, ssa_op);
removed_ops++;
if (has_op_data) {
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
removed_ops++;
}
}
}
} else if (ssa_op->op1_def == var_num) {
Expand Down
27 changes: 24 additions & 3 deletions Zend/Optimizer/zend_call_graph.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
call_info->num_args = opline->extended_value;
call_info->next_callee = func_info->callee_info;
call_info->is_prototype = is_prototype;
call_info->is_frameless = false;
func_info->callee_info = call_info;

if (build_flags & ZEND_CALL_TREE) {
Expand Down Expand Up @@ -102,6 +103,24 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
call_info = NULL;
call++;
break;
case ZEND_FRAMELESS_ICALL_0:
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3: {
func = ZEND_FLF_FUNC(opline);
zend_call_info *call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info));
call_info->caller_op_array = op_array;
call_info->caller_init_opline = opline;
call_info->caller_call_opline = NULL;
call_info->callee_func = func;
call_info->num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
call_info->next_callee = func_info->callee_info;
call_info->is_prototype = false;
call_info->is_frameless = true;
call_info->next_caller = NULL;
func_info->callee_info = call_info;
break;
}
case ZEND_DO_FCALL:
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
Expand Down Expand Up @@ -260,9 +279,11 @@ ZEND_API zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info
if (call->caller_call_opline) {
map[call->caller_call_opline - op_array->opcodes] = call;
}
for (i = 0; i < call->num_args; i++) {
if (call->arg_info[i].opline) {
map[call->arg_info[i].opline - op_array->opcodes] = call;
if (!call->is_frameless) {
for (i = 0; i < call->num_args; i++) {
if (call->arg_info[i].opline) {
map[call->arg_info[i].opline - op_array->opcodes] = call;
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_call_graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ struct _zend_call_info {
bool send_unpack; /* Parameters passed by SEND_UNPACK or SEND_ARRAY */
bool named_args; /* Function has named arguments */
bool is_prototype; /* An overridden child method may be called */
bool is_frameless; /* A frameless function sends arguments through operands */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really mean operands here and not opcodes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we do mean operands.

int num_args; /* Number of arguments, excluding named and variadic arguments */
zend_send_arg_info arg_info[1];
};
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/zend_cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
BB_START(i + 1);
break;
Expand Down Expand Up @@ -524,6 +525,7 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
block->successors_count = 2;
block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
block->successors[1] = j + 1;
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/zend_dfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
}
break;
case ZEND_ASSIGN_STATIC_PROP_OP:
case ZEND_FRAMELESS_ICALL_3:
next = opline + 1;
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
var_num = EX_VAR_TO_NUM(next->op1.var);
Expand Down
5 changes: 5 additions & 0 deletions Zend/Optimizer/zend_dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
} else {
fprintf(stderr, "OP_%d", (int)opline->opcode);
}

if (ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) {
zend_function *func = ZEND_FLF_FUNC(opline);
fprintf(stderr, "(%s)", ZSTR_VAL(func->common.function_name));
}

if (ZEND_VM_EXT_NUM == (flags & ZEND_VM_EXT_MASK)) {
fprintf(stderr, " %u", opline->extended_value);
Expand Down
2 changes: 2 additions & 0 deletions Zend/Optimizer/zend_func_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ typedef struct _func_info_t {

static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
{
ZEND_ASSERT(!call_info->is_frameless);

if (!call_info->send_unpack
&& (call_info->num_args == 2 || call_info->num_args == 3)
&& ssa
Expand Down
33 changes: 33 additions & 0 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -3849,6 +3849,38 @@ static zend_always_inline zend_result _zend_update_type_info(
UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
}
break;
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3:
if (ssa_op->op1_def >= 0) {
ZEND_ASSERT(ssa_op->op1_use >= 0);
tmp = ssa->var_info[ssa_op->op1_use].type;
if (tmp & MAY_BE_RC1) {
tmp |= MAY_BE_RCN;
}
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
}
if (ssa_op->op2_def >= 0) {
ZEND_ASSERT(ssa_op->op2_use >= 0);
tmp = ssa->var_info[ssa_op->op2_use].type;
if (tmp & MAY_BE_RC1) {
tmp |= MAY_BE_RCN;
}
UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
}
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
zend_ssa_op *next_ssa_op = ssa_op + 1;
if (next_ssa_op->op1_def >= 0) {
ZEND_ASSERT(next_ssa_op->op1_use >= 0);
tmp = ssa->var_info[next_ssa_op->op1_use].type;
if (tmp & MAY_BE_RC1) {
tmp |= MAY_BE_RCN;
}
UPDATE_SSA_TYPE(tmp, next_ssa_op->op1_def);
}
}
ZEND_FALLTHROUGH;
case ZEND_FRAMELESS_ICALL_0:
case ZEND_DO_FCALL:
case ZEND_DO_ICALL:
case ZEND_DO_UCALL:
Expand Down Expand Up @@ -4989,6 +5021,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_COPY_TMP:
case ZEND_CASE_STRICT:
case ZEND_JMP_NULL:
case ZEND_JMP_FRAMELESS:
return 0;
case ZEND_SEND_VAR:
case ZEND_SEND_VAL:
Expand Down
4 changes: 4 additions & 0 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ void zend_optimizer_migrate_jump(zend_op_array *op_array, zend_op *new_opline, z
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
ZEND_SET_OP_JMP_ADDR(new_opline, new_opline->op2, ZEND_OP2_JMP_ADDR(opline));
break;
case ZEND_FE_FETCH_R:
Expand Down Expand Up @@ -765,6 +766,7 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
ZEND_SET_OP_JMP_ADDR(opline, opline->op2, ZEND_OP2_JMP_ADDR(opline) - shiftlist[ZEND_OP2_JMP_ADDR(opline) - op_array->opcodes]);
break;
case ZEND_CATCH:
Expand Down Expand Up @@ -1164,6 +1166,7 @@ static void zend_redo_pass_two(zend_op_array *op_array)
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
break;
case ZEND_CATCH:
Expand Down Expand Up @@ -1285,6 +1288,7 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa)
case ZEND_ASSERT_CHECK:
case ZEND_JMP_NULL:
case ZEND_BIND_INIT_STATIC_OR_JMP:
case ZEND_JMP_FRAMELESS:
opline->op2.jmp_addr = &op_array->opcodes[opline->op2.jmp_addr - old_opcodes];
break;
case ZEND_CATCH:
Expand Down
29 changes: 29 additions & 0 deletions Zend/Optimizer/zend_ssa.c
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,35 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
//NEW_SSA_VAR(opline->op1.var)
}
break;
case ZEND_FRAMELESS_ICALL_1:
case ZEND_FRAMELESS_ICALL_2:
case ZEND_FRAMELESS_ICALL_3: {
if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
ssa_ops[k].op1_def = ssa_vars_count;
var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count;
ssa_vars_count++;
//NEW_SSA_VAR(opline->op1.var)
}
if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
ssa_ops[k].op2_def = ssa_vars_count;
var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
ssa_vars_count++;
//NEW_SSA_VAR(opline->op2.var)
}
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
next = opline + 1;
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
//USE_SSA_VAR(op_array->last_var + next->op1.var);
if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
ssa_ops[k + 1].op1_def = ssa_vars_count;
var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
ssa_vars_count++;
//NEW_SSA_VAR(next->op1.var)
}
}
}
}
default:
break;
}
Expand Down
12 changes: 12 additions & 0 deletions Zend/tests/frameless_jmp_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Frameless jmp
--FILE--
<?php
namespace Foo;
function test($needle, $haystack) {
return in_array($needle, $haystack, true);
}
var_dump(test('foo', ['foo', 'bar']));
?>
--EXPECT--
bool(true)
Loading