Skip to content

Commit 2d0e273

Browse files
committed
Support prototypes in call graph
Even if we don't know the exact method being called, include it in the call graph with the is_prototype flag. In particular, we can still make use of return types from prototype methods, as PHP 8 makes LSP violations a hard error. Most other places are adjusted to skip calls with !is_prototype. Maybe some of them would be fine, but ignoring them is conservative.
1 parent 6689bed commit 2d0e273

File tree

11 files changed

+109
-42
lines changed

11 files changed

+109
-42
lines changed

Zend/Optimizer/dfa_pass.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
381381
zend_op *send_array;
382382
zend_op *send_needly;
383383
bool strict = 0;
384+
ZEND_ASSERT(!call_info->is_prototype);
384385

385386
if (call_info->caller_init_opline->extended_value == 2) {
386387
send_array = call_info->caller_call_opline - 1;

Zend/Optimizer/sccp.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1771,7 +1771,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
17711771
}
17721772

17731773
/* We're only interested in functions with up to three arguments right now */
1774-
if (call->num_args > 3 || call->send_unpack) {
1774+
if (call->num_args > 3 || call->send_unpack || call->is_prototype) {
17751775
SET_RESULT_BOT(result);
17761776
break;
17771777
}

Zend/Optimizer/zend_call_graph.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_
6565
call_stack[call] = call_info;
6666
func = zend_optimizer_get_called_func(
6767
script, op_array, opline, &is_prototype);
68-
/* TODO: Support prototypes? */
69-
if (func && !is_prototype) {
68+
if (func) {
7069
call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1)));
7170
call_info->caller_op_array = op_array;
7271
call_info->caller_init_opline = opline;
7372
call_info->caller_call_opline = NULL;
7473
call_info->callee_func = func;
7574
call_info->num_args = opline->extended_value;
7675
call_info->next_callee = func_info->callee_info;
76+
call_info->is_prototype = is_prototype;
7777
func_info->callee_info = call_info;
7878

7979
if (build_flags & ZEND_CALL_TREE) {
@@ -194,7 +194,11 @@ static void zend_analyze_recursion(zend_call_graph *call_graph)
194194
op_array = call_graph->op_arrays[i];
195195
func_info = call_graph->func_infos + i;
196196
call_info = func_info->caller_info;
197-
while (call_info) {
197+
for (; call_info; call_info = call_info->next_caller) {
198+
if (call_info->is_prototype) {
199+
/* Might be calling an overridden child method and not actually recursive. */
200+
continue;
201+
}
198202
if (call_info->caller_op_array == op_array) {
199203
call_info->recursive = 1;
200204
func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_DIRECTLY;
@@ -205,7 +209,6 @@ static void zend_analyze_recursion(zend_call_graph *call_graph)
205209
func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_INDIRECTLY;
206210
}
207211
}
208-
call_info = call_info->next_caller;
209212
}
210213
}
211214

Zend/Optimizer/zend_call_graph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ struct _zend_call_info {
3737
bool recursive;
3838
bool send_unpack; /* Parameters passed by SEND_UNPACK or SEND_ARRAY */
3939
bool named_args; /* Function has named arguments */
40+
bool is_prototype; /* An overridden child method may be called */
4041
int num_args;
4142
zend_send_arg_info arg_info[1];
4243
};

Zend/Optimizer/zend_func_info.c

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -878,19 +878,8 @@ ZEND_API uint32_t zend_get_func_info(
878878
}
879879
#endif
880880

881-
if (callee_func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
882-
ret = zend_fetch_arg_info_type(NULL, callee_func->common.arg_info - 1, ce);
883-
*ce_is_instanceof = 1;
884-
} else {
885-
#if 0
886-
fprintf(stderr, "Unknown internal function '%s'\n", func->common.function_name);
887-
#endif
888-
ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
889-
| MAY_BE_RC1 | MAY_BE_RCN;
890-
}
891-
if (callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) {
892-
ret |= MAY_BE_REF;
893-
}
881+
ret = zend_get_return_info_from_signature_only(
882+
callee_func, /* script */ NULL, ce, ce_is_instanceof);
894883

895884
#if ZEND_DEBUG
896885
if (internal_ret) {
@@ -919,21 +908,18 @@ ZEND_API uint32_t zend_get_func_info(
919908
}
920909
#endif
921910
} else {
922-
// FIXME: the order of functions matters!!!
923-
zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
924-
if (info) {
925-
ret = info->return_info.type;
926-
*ce = info->return_info.ce;
927-
*ce_is_instanceof = info->return_info.is_instanceof;
911+
if (!call_info->is_prototype) {
912+
// FIXME: the order of functions matters!!!
913+
zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
914+
if (info) {
915+
ret = info->return_info.type;
916+
*ce = info->return_info.ce;
917+
*ce_is_instanceof = info->return_info.is_instanceof;
918+
}
928919
}
929920
if (!ret) {
930-
ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
931-
| MAY_BE_RC1 | MAY_BE_RCN;
932-
/* For generators RETURN_REFERENCE refers to the yielded values. */
933-
if ((callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
934-
&& !(callee_func->common.fn_flags & ZEND_ACC_GENERATOR)) {
935-
ret |= MAY_BE_REF;
936-
}
921+
ret = zend_get_return_info_from_signature_only(
922+
callee_func, /* TODO: script */ NULL, ce, ce_is_instanceof);
937923
}
938924
}
939925
return ret;

Zend/Optimizer/zend_inference.c

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3979,18 +3979,38 @@ static int is_recursive_tail_call(const zend_op_array *op_array,
39793979
return 0;
39803980
}
39813981

3982+
uint32_t zend_get_return_info_from_signature_only(
3983+
const zend_function *func, const zend_script *script,
3984+
zend_class_entry **ce, bool *ce_is_instanceof) {
3985+
uint32_t type;
3986+
if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
3987+
zend_arg_info *ret_info = func->common.arg_info - 1;
3988+
type = zend_fetch_arg_info_type(script, ret_info, ce);
3989+
*ce_is_instanceof = ce != NULL;
3990+
} else {
3991+
type = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
3992+
| MAY_BE_RC1 | MAY_BE_RCN;
3993+
*ce = NULL;
3994+
*ce_is_instanceof = false;
3995+
}
3996+
3997+
/* For generators RETURN_REFERENCE refers to the yielded values. */
3998+
if ((func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
3999+
&& !(func->common.fn_flags & ZEND_ACC_GENERATOR)) {
4000+
type |= MAY_BE_REF;
4001+
}
4002+
return type;
4003+
}
4004+
39824005
ZEND_API void zend_init_func_return_info(
39834006
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret)
39844007
{
39854008
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
3986-
zend_arg_info *ret_info = op_array->arg_info - 1;
39874009
zend_ssa_range tmp_range = {0, 0, 0, 0};
3988-
3989-
ret->type = zend_fetch_arg_info_type(script, ret_info, &ret->ce);
3990-
if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
3991-
ret->type |= MAY_BE_REF;
3992-
}
3993-
ret->is_instanceof = (ret->ce) ? 1 : 0;
4010+
bool is_instanceof = false;
4011+
ret->type = zend_get_return_info_from_signature_only(
4012+
(zend_function *) op_array, script, &ret->ce, &is_instanceof);
4013+
ret->is_instanceof = is_instanceof;
39944014
ret->range = tmp_range;
39954015
ret->has_range = 0;
39964016
}

Zend/Optimizer/zend_inference.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ ZEND_API uint32_t zend_fetch_arg_info_type(
269269
const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce);
270270
ZEND_API void zend_init_func_return_info(
271271
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret);
272+
uint32_t zend_get_return_info_from_signature_only(
273+
const zend_function *func, const zend_script *script,
274+
zend_class_entry **ce, bool *ce_is_instanceof);
272275
void zend_func_return_info(const zend_op_array *op_array,
273276
const zend_script *script,
274277
int recursive,

Zend/Optimizer/zend_optimizer.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1340,6 +1340,7 @@ static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array)
13401340
zend_op *opline = call_info->caller_init_opline;
13411341

13421342
if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) {
1343+
ZEND_ASSERT(!call_info->is_prototype);
13431344
opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func);
13441345
}
13451346
call_info = call_info->next_callee;

ext/opcache/jit/zend_jit_trace.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6207,7 +6207,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
62076207
zend_call_info *call_info = jit_extension->func_info.callee_info;
62086208

62096209
while (call_info) {
6210-
if (call_info->caller_init_opline == init_opline) {
6210+
if (call_info->caller_init_opline == init_opline
6211+
&& !call_info->is_prototype) {
62116212
if (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) {
62126213
if (init_opline->opcode == ZEND_INIT_STATIC_METHOD_CALL
62136214
&& init_opline->op1_type != IS_CONST) {

ext/opcache/jit/zend_jit_x86.dasc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9186,7 +9186,7 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t
91869186
while (call_info && call_info->caller_init_opline != opline) {
91879187
call_info = call_info->next_callee;
91889188
}
9189-
if (call_info && call_info->callee_func) {
9189+
if (call_info && call_info->callee_func && !call_info->is_prototype) {
91909190
func = call_info->callee_func;
91919191
}
91929192
}
@@ -9353,7 +9353,7 @@ static int zend_jit_init_method_call(dasm_State **Dst,
93539353
while (call_info && call_info->caller_init_opline != opline) {
93549354
call_info = call_info->next_callee;
93559355
}
9356-
if (call_info && call_info->callee_func) {
9356+
if (call_info && call_info->callee_func && !call_info->is_prototype) {
93579357
func = call_info->callee_func;
93589358
}
93599359
}
@@ -9678,6 +9678,8 @@ static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ss
96789678
uint32_t num_args = 0;
96799679
zend_function *func = call_info->callee_func;
96809680

9681+
/* It's okay to handle prototypes here, because they can only increase the accepted arguments.
9682+
* Anything legal for the parent method is also legal for the parent method. */
96819683
while (num_args < call_info->num_args) {
96829684
zend_arg_info *arg_info = func->op_array.arg_info + num_args;
96839685

@@ -9731,7 +9733,7 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
97319733
while (call_info && call_info->caller_call_opline != opline) {
97329734
call_info = call_info->next_callee;
97339735
}
9734-
if (call_info && call_info->callee_func) {
9736+
if (call_info && call_info->callee_func && !call_info->is_prototype) {
97359737
func = call_info->callee_func;
97369738
}
97379739
}

0 commit comments

Comments
 (0)