Skip to content

Commit 43eb04c

Browse files
committed
Implement stackless internal function calls
1 parent c00b0c7 commit 43eb04c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3761
-845
lines changed

Zend/Optimizer/dce.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ static inline bool may_have_side_effects(
167167
case ZEND_DO_FCALL_BY_NAME:
168168
case ZEND_DO_ICALL:
169169
case ZEND_DO_UCALL:
170+
case ZEND_FRAMELESS_ICALL_0:
171+
case ZEND_FRAMELESS_ICALL_1:
172+
case ZEND_FRAMELESS_ICALL_2:
173+
case ZEND_FRAMELESS_ICALL_3:
170174
/* For now assume all calls have side effects */
171175
return 1;
172176
case ZEND_RECV:

Zend/Optimizer/sccp.c

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -789,14 +789,10 @@ static bool can_ct_eval_func_call(zend_function *func, zend_string *name, uint32
789789
/* The functions chosen here are simple to implement and either likely to affect a branch,
790790
* or just happened to be commonly used with constant operands in WP (need to test other
791791
* applications as well, of course). */
792-
static inline zend_result ct_eval_func_call(
793-
zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
792+
static inline zend_result ct_eval_func_call_ex(
793+
zend_op_array *op_array, zval *result, zend_function *func, uint32_t num_args, zval **args) {
794794
uint32_t i;
795-
zend_function *func = zend_hash_find_ptr(CG(function_table), name);
796-
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
797-
return FAILURE;
798-
}
799-
795+
zend_string *name = func->common.function_name;
800796
if (num_args == 1 && Z_TYPE_P(args[0]) == IS_STRING &&
801797
zend_optimizer_eval_special_func_call(result, name, Z_STR_P(args[0])) == SUCCESS) {
802798
return SUCCESS;
@@ -855,6 +851,15 @@ static inline zend_result ct_eval_func_call(
855851
return retval;
856852
}
857853

854+
static inline zend_result ct_eval_func_call(
855+
zend_op_array *op_array, zval *result, zend_string *name, uint32_t num_args, zval **args) {
856+
zend_function *func = zend_hash_find_ptr(CG(function_table), name);
857+
if (!func || func->type != ZEND_INTERNAL_FUNCTION) {
858+
return FAILURE;
859+
}
860+
return ct_eval_func_call_ex(op_array, result, func, num_args, args);
861+
}
862+
858863
#define SET_RESULT(op, zv) do { \
859864
if (ssa_op->op##_def >= 0) { \
860865
set_value(scdf, ctx, ssa_op->op##_def, zv); \
@@ -1708,6 +1713,51 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
17081713
SET_RESULT_BOT(result);
17091714
break;
17101715
}
1716+
case ZEND_FRAMELESS_ICALL_0:
1717+
case ZEND_FRAMELESS_ICALL_1:
1718+
case ZEND_FRAMELESS_ICALL_2:
1719+
case ZEND_FRAMELESS_ICALL_3: {
1720+
/* We already know it can't be evaluated, don't bother checking again */
1721+
if (ssa_op->result_def < 0 || IS_BOT(&ctx->values[ssa_op->result_def])) {
1722+
break;
1723+
}
1724+
1725+
zval *args[3] = {NULL};
1726+
zend_function *func = ZEND_FLF_FUNC(opline);
1727+
uint32_t num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
1728+
1729+
switch (num_args) {
1730+
case 3: {
1731+
zend_op *op_data = opline + 1;
1732+
args[2] = get_op1_value(ctx, op_data, &ctx->scdf.ssa->ops[op_data - ctx->scdf.op_array->opcodes]);
1733+
ZEND_FALLTHROUGH;
1734+
}
1735+
case 2:
1736+
args[1] = get_op2_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
1737+
ZEND_FALLTHROUGH;
1738+
case 1:
1739+
args[0] = get_op1_value(ctx, opline, &ctx->scdf.ssa->ops[opline - ctx->scdf.op_array->opcodes]);
1740+
break;
1741+
}
1742+
for (uint32_t i = 0; i < num_args; i++) {
1743+
if (!args[i]) {
1744+
SET_RESULT_BOT(result);
1745+
return;
1746+
} else if (IS_BOT(args[i]) || IS_PARTIAL_ARRAY(args[i])) {
1747+
SET_RESULT_BOT(result);
1748+
return;
1749+
} else if (IS_TOP(args[i])) {
1750+
return;
1751+
}
1752+
}
1753+
if (ct_eval_func_call_ex(scdf->op_array, &zv, func, num_args, args) == SUCCESS) {
1754+
SET_RESULT(result, &zv);
1755+
zval_ptr_dtor_nogc(&zv);
1756+
break;
1757+
}
1758+
SET_RESULT_BOT(result);
1759+
break;
1760+
}
17111761
default:
17121762
{
17131763
/* If we have no explicit implementation return BOT */
@@ -2155,7 +2205,13 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
21552205
if (opline->opcode == ZEND_DO_ICALL) {
21562206
removed_ops = remove_call(ctx, opline, ssa_op) - 1;
21572207
} else {
2208+
bool has_op_data = opline->opcode == ZEND_FRAMELESS_ICALL_3;
21582209
zend_ssa_remove_instr(ssa, opline, ssa_op);
2210+
removed_ops++;
2211+
if (has_op_data) {
2212+
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
2213+
removed_ops++;
2214+
}
21592215
}
21602216
ssa_op->result_def = var_num;
21612217
opline->opcode = ZEND_QM_ASSIGN;
@@ -2191,8 +2247,13 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var,
21912247
if (opline->opcode == ZEND_DO_ICALL) {
21922248
removed_ops = remove_call(ctx, opline, ssa_op);
21932249
} else {
2250+
bool has_op_data = opline->opcode == ZEND_FRAMELESS_ICALL_3;
21942251
zend_ssa_remove_instr(ssa, opline, ssa_op);
21952252
removed_ops++;
2253+
if (has_op_data) {
2254+
zend_ssa_remove_instr(ssa, opline + 1, ssa_op + 1);
2255+
removed_ops++;
2256+
}
21962257
}
21972258
}
21982259
} else if (ssa_op->op1_def == var_num) {

Zend/Optimizer/zend_call_graph.c

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
7373
call_info->num_args = opline->extended_value;
7474
call_info->next_callee = func_info->callee_info;
7575
call_info->is_prototype = is_prototype;
76+
call_info->is_frameless = false;
7677
func_info->callee_info = call_info;
7778

7879
if (build_flags & ZEND_CALL_TREE) {
@@ -102,6 +103,24 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
102103
call_info = NULL;
103104
call++;
104105
break;
106+
case ZEND_FRAMELESS_ICALL_0:
107+
case ZEND_FRAMELESS_ICALL_1:
108+
case ZEND_FRAMELESS_ICALL_2:
109+
case ZEND_FRAMELESS_ICALL_3: {
110+
func = ZEND_FLF_FUNC(opline);
111+
zend_call_info *call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info));
112+
call_info->caller_op_array = op_array;
113+
call_info->caller_init_opline = opline;
114+
call_info->caller_call_opline = NULL;
115+
call_info->callee_func = func;
116+
call_info->num_args = ZEND_FLF_NUM_ARGS(opline->opcode);
117+
call_info->next_callee = func_info->callee_info;
118+
call_info->is_prototype = false;
119+
call_info->is_frameless = true;
120+
call_info->next_caller = NULL;
121+
func_info->callee_info = call_info;
122+
break;
123+
}
105124
case ZEND_DO_FCALL:
106125
case ZEND_DO_ICALL:
107126
case ZEND_DO_UCALL:
@@ -260,9 +279,11 @@ ZEND_API zend_call_info **zend_build_call_map(zend_arena **arena, zend_func_info
260279
if (call->caller_call_opline) {
261280
map[call->caller_call_opline - op_array->opcodes] = call;
262281
}
263-
for (i = 0; i < call->num_args; i++) {
264-
if (call->arg_info[i].opline) {
265-
map[call->arg_info[i].opline - op_array->opcodes] = call;
282+
if (!call->is_frameless) {
283+
for (i = 0; i < call->num_args; i++) {
284+
if (call->arg_info[i].opline) {
285+
map[call->arg_info[i].opline - op_array->opcodes] = call;
286+
}
266287
}
267288
}
268289
}

Zend/Optimizer/zend_call_graph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ struct _zend_call_info {
3838
bool send_unpack; /* Parameters passed by SEND_UNPACK or SEND_ARRAY */
3939
bool named_args; /* Function has named arguments */
4040
bool is_prototype; /* An overridden child method may be called */
41+
bool is_frameless; /* A frameless function sends arguments through operands */
4142
int num_args; /* Number of arguments, excluding named and variadic arguments */
4243
zend_send_arg_info arg_info[1] ZEND_ELEMENT_COUNT(num_args);
4344
};

Zend/Optimizer/zend_dfg.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
122122
}
123123
break;
124124
case ZEND_ASSIGN_STATIC_PROP_OP:
125+
case ZEND_FRAMELESS_ICALL_3:
125126
next = opline + 1;
126127
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
127128
var_num = EX_VAR_TO_NUM(next->op1.var);

Zend/Optimizer/zend_dump.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,11 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
462462
} else {
463463
fprintf(stderr, "OP_%d", (int)opline->opcode);
464464
}
465+
466+
if (ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) {
467+
zend_function *func = ZEND_FLF_FUNC(opline);
468+
fprintf(stderr, "(%s)", ZSTR_VAL(func->common.function_name));
469+
}
465470

466471
if (ZEND_VM_EXT_NUM == (flags & ZEND_VM_EXT_MASK)) {
467472
fprintf(stderr, " %u", opline->extended_value);

Zend/Optimizer/zend_func_info.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ typedef struct _func_info_t {
5151

5252
static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
5353
{
54+
ZEND_ASSERT(!call_info->is_frameless);
55+
5456
if (!call_info->send_unpack
5557
&& (call_info->num_args == 2 || call_info->num_args == 3)
5658
&& ssa

Zend/Optimizer/zend_inference.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3806,6 +3806,38 @@ static zend_always_inline zend_result _zend_update_type_info(
38063806
UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
38073807
}
38083808
break;
3809+
case ZEND_FRAMELESS_ICALL_1:
3810+
case ZEND_FRAMELESS_ICALL_2:
3811+
case ZEND_FRAMELESS_ICALL_3:
3812+
if (ssa_op->op1_def >= 0) {
3813+
ZEND_ASSERT(ssa_op->op1_use >= 0);
3814+
tmp = ssa->var_info[ssa_op->op1_use].type;
3815+
if (tmp & MAY_BE_RC1) {
3816+
tmp |= MAY_BE_RCN;
3817+
}
3818+
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
3819+
}
3820+
if (ssa_op->op2_def >= 0) {
3821+
ZEND_ASSERT(ssa_op->op2_use >= 0);
3822+
tmp = ssa->var_info[ssa_op->op2_use].type;
3823+
if (tmp & MAY_BE_RC1) {
3824+
tmp |= MAY_BE_RCN;
3825+
}
3826+
UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
3827+
}
3828+
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
3829+
zend_ssa_op *next_ssa_op = ssa_op + 1;
3830+
if (next_ssa_op->op1_def >= 0) {
3831+
ZEND_ASSERT(next_ssa_op->op1_use >= 0);
3832+
tmp = ssa->var_info[next_ssa_op->op1_use].type;
3833+
if (tmp & MAY_BE_RC1) {
3834+
tmp |= MAY_BE_RCN;
3835+
}
3836+
UPDATE_SSA_TYPE(tmp, next_ssa_op->op1_def);
3837+
}
3838+
}
3839+
ZEND_FALLTHROUGH;
3840+
case ZEND_FRAMELESS_ICALL_0:
38093841
case ZEND_DO_FCALL:
38103842
case ZEND_DO_ICALL:
38113843
case ZEND_DO_UCALL:

Zend/Optimizer/zend_ssa.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,35 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
766766
//NEW_SSA_VAR(opline->op1.var)
767767
}
768768
break;
769+
case ZEND_FRAMELESS_ICALL_1:
770+
case ZEND_FRAMELESS_ICALL_2:
771+
case ZEND_FRAMELESS_ICALL_3: {
772+
if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op1_type == IS_CV) {
773+
ssa_ops[k].op1_def = ssa_vars_count;
774+
var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count;
775+
ssa_vars_count++;
776+
//NEW_SSA_VAR(opline->op1.var)
777+
}
778+
if ((build_flags & ZEND_SSA_RC_INFERENCE) && opline->op2_type == IS_CV) {
779+
ssa_ops[k].op2_def = ssa_vars_count;
780+
var[EX_VAR_TO_NUM(opline->op2.var)] = ssa_vars_count;
781+
ssa_vars_count++;
782+
//NEW_SSA_VAR(opline->op2.var)
783+
}
784+
if (opline->opcode == ZEND_FRAMELESS_ICALL_3) {
785+
next = opline + 1;
786+
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
787+
ssa_ops[k + 1].op1_use = var[EX_VAR_TO_NUM(next->op1.var)];
788+
//USE_SSA_VAR(op_array->last_var + next->op1.var);
789+
if ((build_flags & ZEND_SSA_RC_INFERENCE) && next->op1_type == IS_CV) {
790+
ssa_ops[k + 1].op1_def = ssa_vars_count;
791+
var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
792+
ssa_vars_count++;
793+
//NEW_SSA_VAR(next->op1.var)
794+
}
795+
}
796+
}
797+
}
769798
default:
770799
break;
771800
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Frameless call with throwing destructor
3+
--FILE--
4+
<?php
5+
class Foo {}
6+
class Bar {
7+
public function __destruct() {
8+
throw new Exception();
9+
}
10+
}
11+
in_array(new Foo(), [new Bar()], true);
12+
?>
13+
--EXPECTF--
14+
Fatal error: Uncaught Exception in %s:%d
15+
Stack trace:
16+
#0 %s(%d): Bar->__destruct()
17+
#1 {main}
18+
thrown in %s on line %d
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Undefined var in frameless call
3+
--FILE--
4+
<?php
5+
set_error_handler(function ($errno, $errstr) {
6+
throw new Exception($errstr);
7+
});
8+
function test() {
9+
strpos($foo, 'o');
10+
}
11+
try {
12+
test();
13+
} catch (Exception $e) {
14+
echo $e->getMessage(), "\n";
15+
}
16+
?>
17+
--EXPECT--
18+
Undefined variable $foo

0 commit comments

Comments
 (0)