summaryrefslogtreecommitdiff
path: root/Zend
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2021-03-18 15:40:48 +0100
committerNikita Popov <nikita.ppv@gmail.com>2021-03-19 10:49:15 +0100
commit2d0e2733c8a0010df9f45693d536531a7a725bdf (patch)
tree715b953768f04d3240c57b14bd8d9c5218af4ada /Zend
parent6689bedd1796380f882fdecc6dcf8da1ff885c2b (diff)
downloadphp-git-2d0e2733c8a0010df9f45693d536531a7a725bdf.tar.gz
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.
Diffstat (limited to 'Zend')
-rw-r--r--Zend/Optimizer/dfa_pass.c1
-rw-r--r--Zend/Optimizer/sccp.c2
-rw-r--r--Zend/Optimizer/zend_call_graph.c11
-rw-r--r--Zend/Optimizer/zend_call_graph.h1
-rw-r--r--Zend/Optimizer/zend_func_info.c38
-rw-r--r--Zend/Optimizer/zend_inference.c34
-rw-r--r--Zend/Optimizer/zend_inference.h3
-rw-r--r--Zend/Optimizer/zend_optimizer.c1
8 files changed, 53 insertions, 38 deletions
diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c
index fe06de276b..3ce28f63e3 100644
--- a/Zend/Optimizer/dfa_pass.c
+++ b/Zend/Optimizer/dfa_pass.c
@@ -381,6 +381,7 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
zend_op *send_array;
zend_op *send_needly;
bool strict = 0;
+ ZEND_ASSERT(!call_info->is_prototype);
if (call_info->caller_init_opline->extended_value == 2) {
send_array = call_info->caller_call_opline - 1;
diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c
index e4b7531f46..5bcf94d144 100644
--- a/Zend/Optimizer/sccp.c
+++ b/Zend/Optimizer/sccp.c
@@ -1771,7 +1771,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
}
/* We're only interested in functions with up to three arguments right now */
- if (call->num_args > 3 || call->send_unpack) {
+ if (call->num_args > 3 || call->send_unpack || call->is_prototype) {
SET_RESULT_BOT(result);
break;
}
diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c
index b0f3247cd7..fb24a8d40f 100644
--- a/Zend/Optimizer/zend_call_graph.c
+++ b/Zend/Optimizer/zend_call_graph.c
@@ -65,8 +65,7 @@ ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_
call_stack[call] = call_info;
func = zend_optimizer_get_called_func(
script, op_array, opline, &is_prototype);
- /* TODO: Support prototypes? */
- if (func && !is_prototype) {
+ if (func) {
call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1)));
call_info->caller_op_array = op_array;
call_info->caller_init_opline = opline;
@@ -74,6 +73,7 @@ ZEND_API int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_
call_info->callee_func = func;
call_info->num_args = opline->extended_value;
call_info->next_callee = func_info->callee_info;
+ call_info->is_prototype = is_prototype;
func_info->callee_info = call_info;
if (build_flags & ZEND_CALL_TREE) {
@@ -194,7 +194,11 @@ static void zend_analyze_recursion(zend_call_graph *call_graph)
op_array = call_graph->op_arrays[i];
func_info = call_graph->func_infos + i;
call_info = func_info->caller_info;
- while (call_info) {
+ for (; call_info; call_info = call_info->next_caller) {
+ if (call_info->is_prototype) {
+ /* Might be calling an overridden child method and not actually recursive. */
+ continue;
+ }
if (call_info->caller_op_array == op_array) {
call_info->recursive = 1;
func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_DIRECTLY;
@@ -205,7 +209,6 @@ static void zend_analyze_recursion(zend_call_graph *call_graph)
func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_INDIRECTLY;
}
}
- call_info = call_info->next_caller;
}
}
diff --git a/Zend/Optimizer/zend_call_graph.h b/Zend/Optimizer/zend_call_graph.h
index a456dcfbb8..c0699dc031 100644
--- a/Zend/Optimizer/zend_call_graph.h
+++ b/Zend/Optimizer/zend_call_graph.h
@@ -37,6 +37,7 @@ struct _zend_call_info {
bool recursive;
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 */
int num_args;
zend_send_arg_info arg_info[1];
};
diff --git a/Zend/Optimizer/zend_func_info.c b/Zend/Optimizer/zend_func_info.c
index 533f9f8594..22f22f77a7 100644
--- a/Zend/Optimizer/zend_func_info.c
+++ b/Zend/Optimizer/zend_func_info.c
@@ -878,19 +878,8 @@ ZEND_API uint32_t zend_get_func_info(
}
#endif
- if (callee_func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
- ret = zend_fetch_arg_info_type(NULL, callee_func->common.arg_info - 1, ce);
- *ce_is_instanceof = 1;
- } else {
-#if 0
- fprintf(stderr, "Unknown internal function '%s'\n", func->common.function_name);
-#endif
- ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
- | MAY_BE_RC1 | MAY_BE_RCN;
- }
- if (callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE) {
- ret |= MAY_BE_REF;
- }
+ ret = zend_get_return_info_from_signature_only(
+ callee_func, /* script */ NULL, ce, ce_is_instanceof);
#if ZEND_DEBUG
if (internal_ret) {
@@ -919,21 +908,18 @@ ZEND_API uint32_t zend_get_func_info(
}
#endif
} else {
- // FIXME: the order of functions matters!!!
- zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
- if (info) {
- ret = info->return_info.type;
- *ce = info->return_info.ce;
- *ce_is_instanceof = info->return_info.is_instanceof;
+ if (!call_info->is_prototype) {
+ // FIXME: the order of functions matters!!!
+ zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
+ if (info) {
+ ret = info->return_info.type;
+ *ce = info->return_info.ce;
+ *ce_is_instanceof = info->return_info.is_instanceof;
+ }
}
if (!ret) {
- ret = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
- | MAY_BE_RC1 | MAY_BE_RCN;
- /* For generators RETURN_REFERENCE refers to the yielded values. */
- if ((callee_func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
- && !(callee_func->common.fn_flags & ZEND_ACC_GENERATOR)) {
- ret |= MAY_BE_REF;
- }
+ ret = zend_get_return_info_from_signature_only(
+ callee_func, /* TODO: script */ NULL, ce, ce_is_instanceof);
}
}
return ret;
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index 8602e7cb03..8ba67a4dab 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -3979,18 +3979,38 @@ static int is_recursive_tail_call(const zend_op_array *op_array,
return 0;
}
+uint32_t zend_get_return_info_from_signature_only(
+ const zend_function *func, const zend_script *script,
+ zend_class_entry **ce, bool *ce_is_instanceof) {
+ uint32_t type;
+ if (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_arg_info *ret_info = func->common.arg_info - 1;
+ type = zend_fetch_arg_info_type(script, ret_info, ce);
+ *ce_is_instanceof = ce != NULL;
+ } else {
+ type = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | MAY_BE_RC1 | MAY_BE_RCN;
+ *ce = NULL;
+ *ce_is_instanceof = false;
+ }
+
+ /* For generators RETURN_REFERENCE refers to the yielded values. */
+ if ((func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
+ && !(func->common.fn_flags & ZEND_ACC_GENERATOR)) {
+ type |= MAY_BE_REF;
+ }
+ return type;
+}
+
ZEND_API void zend_init_func_return_info(
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret)
{
if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
- zend_arg_info *ret_info = op_array->arg_info - 1;
zend_ssa_range tmp_range = {0, 0, 0, 0};
-
- ret->type = zend_fetch_arg_info_type(script, ret_info, &ret->ce);
- if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
- ret->type |= MAY_BE_REF;
- }
- ret->is_instanceof = (ret->ce) ? 1 : 0;
+ bool is_instanceof = false;
+ ret->type = zend_get_return_info_from_signature_only(
+ (zend_function *) op_array, script, &ret->ce, &is_instanceof);
+ ret->is_instanceof = is_instanceof;
ret->range = tmp_range;
ret->has_range = 0;
}
diff --git a/Zend/Optimizer/zend_inference.h b/Zend/Optimizer/zend_inference.h
index 8390946012..35af4d8823 100644
--- a/Zend/Optimizer/zend_inference.h
+++ b/Zend/Optimizer/zend_inference.h
@@ -269,6 +269,9 @@ ZEND_API uint32_t zend_fetch_arg_info_type(
const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce);
ZEND_API void zend_init_func_return_info(
const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret);
+uint32_t zend_get_return_info_from_signature_only(
+ const zend_function *func, const zend_script *script,
+ zend_class_entry **ce, bool *ce_is_instanceof);
void zend_func_return_info(const zend_op_array *op_array,
const zend_script *script,
int recursive,
diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c
index cc9971a9f8..d6f27b19e6 100644
--- a/Zend/Optimizer/zend_optimizer.c
+++ b/Zend/Optimizer/zend_optimizer.c
@@ -1340,6 +1340,7 @@ static void zend_adjust_fcall_stack_size_graph(zend_op_array *op_array)
zend_op *opline = call_info->caller_init_opline;
if (opline && call_info->callee_func && opline->opcode == ZEND_INIT_FCALL) {
+ ZEND_ASSERT(!call_info->is_prototype);
opline->op1.num = zend_vm_calc_used_stack(opline->extended_value, call_info->callee_func);
}
call_info = call_info->next_callee;