/src/php-src/Zend/Optimizer/zend_call_graph.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Zend Engine, Call Graph | |
4 | | +----------------------------------------------------------------------+ |
5 | | | Copyright © The PHP Group and Contributors. | |
6 | | +----------------------------------------------------------------------+ |
7 | | | This source file is subject to the Modified BSD License that is | |
8 | | | bundled with this package in the file LICENSE, and is available | |
9 | | | through the World Wide Web at <https://www.php.net/license/>. | |
10 | | | | |
11 | | | SPDX-License-Identifier: BSD-3-Clause | |
12 | | +----------------------------------------------------------------------+ |
13 | | | Authors: Dmitry Stogov <dmitry@php.net> | |
14 | | +----------------------------------------------------------------------+ |
15 | | */ |
16 | | |
17 | | #include "zend_compile.h" |
18 | | #include "zend_extensions.h" |
19 | | #include "Optimizer/zend_optimizer.h" |
20 | | #include "zend_optimizer_internal.h" |
21 | | #include "zend_inference.h" |
22 | | #include "zend_call_graph.h" |
23 | | #include "zend_func_info.h" |
24 | | |
25 | | static void zend_op_array_calc(zend_op_array *op_array, void *context) |
26 | 33.3k | { |
27 | 33.3k | zend_call_graph *call_graph = context; |
28 | 33.3k | call_graph->op_arrays_count++; |
29 | 33.3k | } |
30 | | |
31 | | static void zend_op_array_collect(zend_op_array *op_array, void *context) |
32 | 33.3k | { |
33 | 33.3k | zend_call_graph *call_graph = context; |
34 | 33.3k | zend_func_info *func_info = call_graph->func_infos + call_graph->op_arrays_count; |
35 | | |
36 | 33.3k | ZEND_SET_FUNC_INFO(op_array, func_info); |
37 | 33.3k | call_graph->op_arrays[call_graph->op_arrays_count] = op_array; |
38 | 33.3k | func_info->num = call_graph->op_arrays_count; |
39 | 33.3k | call_graph->op_arrays_count++; |
40 | 33.3k | } |
41 | | |
42 | | ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_flags, zend_op_array *op_array, zend_func_info *func_info) |
43 | 35.8k | { |
44 | 35.8k | zend_op *opline = op_array->opcodes; |
45 | 35.8k | zend_op *end = opline + op_array->last; |
46 | 35.8k | zend_function *func; |
47 | 35.8k | zend_call_info *call_info; |
48 | 35.8k | int call = 0; |
49 | 35.8k | zend_call_info **call_stack; |
50 | 35.8k | ALLOCA_FLAG(use_heap); |
51 | 35.8k | bool is_prototype; |
52 | | |
53 | | // Note: Reserve one call stack slot per operation. Each opcode pushes at |
54 | | // most one entry to the call stack, and (with dead code elimination) it's |
55 | | // possible to never pop from the stack. |
56 | 35.8k | call_stack = do_alloca(op_array->last * sizeof(zend_call_info*), use_heap); |
57 | 35.8k | call_info = NULL; |
58 | 246k | while (opline != end) { |
59 | 210k | switch (opline->opcode) { |
60 | 2.53k | case ZEND_INIT_FCALL: |
61 | 2.60k | case ZEND_INIT_METHOD_CALL: |
62 | 2.73k | case ZEND_INIT_STATIC_METHOD_CALL: |
63 | 2.73k | case ZEND_INIT_PARENT_PROPERTY_HOOK_CALL: |
64 | 2.73k | call_stack[call] = call_info; |
65 | 2.73k | func = zend_optimizer_get_called_func( |
66 | 2.73k | script, op_array, opline, &is_prototype); |
67 | 2.73k | if (func) { |
68 | 2.54k | call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info) + (sizeof(zend_send_arg_info) * ((int)opline->extended_value - 1))); |
69 | 2.54k | call_info->caller_op_array = op_array; |
70 | 2.54k | call_info->caller_init_opline = opline; |
71 | 2.54k | call_info->caller_call_opline = NULL; |
72 | 2.54k | call_info->callee_func = func; |
73 | 2.54k | call_info->num_args = opline->extended_value; |
74 | 2.54k | call_info->next_callee = func_info->callee_info; |
75 | 2.54k | call_info->is_prototype = is_prototype; |
76 | 2.54k | call_info->is_frameless = false; |
77 | 2.54k | func_info->callee_info = call_info; |
78 | | |
79 | 2.54k | if (build_flags & ZEND_CALL_TREE) { |
80 | 208 | call_info->next_caller = NULL; |
81 | 2.33k | } else if (func->type == ZEND_INTERNAL_FUNCTION |
82 | 2.18k | || func->op_array.filename != script->filename) { |
83 | 2.18k | call_info->next_caller = NULL; |
84 | 2.18k | } else { |
85 | 148 | zend_func_info *callee_func_info = ZEND_FUNC_INFO(&func->op_array); |
86 | 148 | if (callee_func_info) { |
87 | 148 | call_info->next_caller = callee_func_info->caller_info; |
88 | 148 | callee_func_info->caller_info = call_info; |
89 | 148 | } else { |
90 | 0 | call_info->next_caller = NULL; |
91 | 0 | } |
92 | 148 | } |
93 | 2.54k | } else { |
94 | 190 | call_info = NULL; |
95 | 190 | } |
96 | 2.73k | call++; |
97 | 2.73k | break; |
98 | 1.65k | case ZEND_INIT_FCALL_BY_NAME: |
99 | 1.68k | case ZEND_INIT_NS_FCALL_BY_NAME: |
100 | 1.93k | case ZEND_INIT_DYNAMIC_CALL: |
101 | 9.99k | case ZEND_NEW: |
102 | 9.99k | case ZEND_INIT_USER_CALL: |
103 | 9.99k | call_stack[call] = call_info; |
104 | 9.99k | call_info = NULL; |
105 | 9.99k | call++; |
106 | 9.99k | break; |
107 | 0 | case ZEND_FRAMELESS_ICALL_0: |
108 | 0 | case ZEND_FRAMELESS_ICALL_1: |
109 | 0 | case ZEND_FRAMELESS_ICALL_2: |
110 | 0 | case ZEND_FRAMELESS_ICALL_3: { |
111 | 0 | func = ZEND_FLF_FUNC(opline); |
112 | 0 | zend_call_info *call_info = zend_arena_calloc(arena, 1, sizeof(zend_call_info)); |
113 | 0 | call_info->caller_op_array = op_array; |
114 | 0 | call_info->caller_init_opline = opline; |
115 | 0 | call_info->caller_call_opline = NULL; |
116 | 0 | call_info->callee_func = func; |
117 | 0 | call_info->num_args = ZEND_FLF_NUM_ARGS(opline->opcode); |
118 | 0 | call_info->next_callee = func_info->callee_info; |
119 | 0 | call_info->is_prototype = false; |
120 | 0 | call_info->is_frameless = true; |
121 | 0 | call_info->next_caller = NULL; |
122 | 0 | func_info->callee_info = call_info; |
123 | 0 | break; |
124 | 0 | } |
125 | 12.4k | case ZEND_DO_FCALL: |
126 | 12.4k | case ZEND_DO_ICALL: |
127 | 12.5k | case ZEND_DO_UCALL: |
128 | 12.5k | case ZEND_DO_FCALL_BY_NAME: |
129 | 12.6k | case ZEND_CALLABLE_CONVERT: |
130 | 12.7k | case ZEND_CALLABLE_CONVERT_PARTIAL: |
131 | 12.7k | func_info->flags |= ZEND_FUNC_HAS_CALLS; |
132 | 12.7k | if (call_info) { |
133 | 2.54k | call_info->caller_call_opline = opline; |
134 | 2.54k | } |
135 | 12.7k | call--; |
136 | 12.7k | call_info = call_stack[call]; |
137 | 12.7k | break; |
138 | 10.6k | case ZEND_SEND_VAL: |
139 | 10.7k | case ZEND_SEND_VAR: |
140 | 12.3k | case ZEND_SEND_VAL_EX: |
141 | 12.4k | case ZEND_SEND_VAR_EX: |
142 | 12.6k | case ZEND_SEND_FUNC_ARG: |
143 | 12.6k | case ZEND_SEND_REF: |
144 | 12.6k | case ZEND_SEND_VAR_NO_REF: |
145 | 12.7k | case ZEND_SEND_VAR_NO_REF_EX: |
146 | 12.7k | case ZEND_SEND_USER: |
147 | 12.8k | case ZEND_SEND_PLACEHOLDER: |
148 | 12.8k | if (call_info) { |
149 | 3.25k | if (opline->op2_type == IS_CONST) { |
150 | 84 | call_info->named_args = true; |
151 | 84 | break; |
152 | 84 | } |
153 | | |
154 | 3.17k | uint32_t num = opline->op2.num; |
155 | 3.17k | if (num > 0) { |
156 | 3.17k | num--; |
157 | 3.17k | } |
158 | 3.17k | call_info->arg_info[num].opline = opline; |
159 | 3.17k | } |
160 | 12.7k | break; |
161 | 12.7k | case ZEND_SEND_ARRAY: |
162 | 14 | case ZEND_SEND_UNPACK: |
163 | 14 | if (call_info) { |
164 | 6 | call_info->send_unpack = true; |
165 | 6 | } |
166 | 14 | break; |
167 | 210k | } |
168 | 210k | opline++; |
169 | 210k | } |
170 | 35.8k | free_alloca(call_stack, use_heap); |
171 | 35.8k | } |
172 | | |
173 | | static bool zend_is_indirectly_recursive(const zend_op_array *root, const zend_op_array *op_array, zend_bitset visited) |
174 | 142 | { |
175 | 142 | const zend_func_info *func_info; |
176 | 142 | zend_call_info *call_info; |
177 | 142 | bool ret = false; |
178 | | |
179 | 142 | if (op_array == root) { |
180 | 0 | return true; |
181 | 0 | } |
182 | | |
183 | 142 | func_info = ZEND_FUNC_INFO(op_array); |
184 | 142 | if (zend_bitset_in(visited, func_info->num)) { |
185 | 0 | return false; |
186 | 0 | } |
187 | 142 | zend_bitset_incl(visited, func_info->num); |
188 | 142 | call_info = func_info->caller_info; |
189 | 142 | while (call_info) { |
190 | 0 | if (zend_is_indirectly_recursive(root, call_info->caller_op_array, visited)) { |
191 | 0 | call_info->recursive = true; |
192 | 0 | ret = true; |
193 | 0 | } |
194 | 0 | call_info = call_info->next_caller; |
195 | 0 | } |
196 | 142 | return ret; |
197 | 142 | } |
198 | | |
199 | | static void zend_analyze_recursion(zend_call_graph *call_graph) |
200 | 32.5k | { |
201 | 32.5k | const zend_op_array *op_array; |
202 | 32.5k | zend_func_info *func_info; |
203 | 32.5k | zend_call_info *call_info; |
204 | 32.5k | uint32_t set_len = zend_bitset_len(call_graph->op_arrays_count); |
205 | 32.5k | zend_bitset visited; |
206 | 32.5k | ALLOCA_FLAG(use_heap); |
207 | | |
208 | 32.5k | visited = ZEND_BITSET_ALLOCA(set_len, use_heap); |
209 | 65.8k | for (uint32_t i = 0; i < call_graph->op_arrays_count; i++) { |
210 | 33.3k | op_array = call_graph->op_arrays[i]; |
211 | 33.3k | func_info = call_graph->func_infos + i; |
212 | 33.3k | call_info = func_info->caller_info; |
213 | 33.4k | for (; call_info; call_info = call_info->next_caller) { |
214 | 148 | if (call_info->is_prototype) { |
215 | | /* Might be calling an overridden child method and not actually recursive. */ |
216 | 0 | continue; |
217 | 0 | } |
218 | 148 | if (call_info->caller_op_array == op_array) { |
219 | 6 | call_info->recursive = true; |
220 | 6 | func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_DIRECTLY; |
221 | 142 | } else { |
222 | 142 | memset(visited, 0, sizeof(zend_ulong) * set_len); |
223 | 142 | if (zend_is_indirectly_recursive(op_array, call_info->caller_op_array, visited)) { |
224 | 0 | call_info->recursive = true; |
225 | 0 | func_info->flags |= ZEND_FUNC_RECURSIVE | ZEND_FUNC_RECURSIVE_INDIRECTLY; |
226 | 0 | } |
227 | 142 | } |
228 | 148 | } |
229 | 33.3k | } |
230 | | |
231 | 32.5k | free_alloca(visited, use_heap); |
232 | 32.5k | } |
233 | | |
234 | | static void zend_sort_op_arrays(zend_call_graph *call_graph) |
235 | 32.5k | { |
236 | 32.5k | (void) call_graph; |
237 | | |
238 | | // TODO: perform topological sort of cyclic call graph |
239 | 32.5k | } |
240 | | |
241 | | ZEND_API void zend_build_call_graph(zend_arena **arena, zend_script *script, zend_call_graph *call_graph) /* {{{ */ |
242 | 32.5k | { |
243 | 32.5k | call_graph->op_arrays_count = 0; |
244 | 32.5k | zend_foreach_op_array(script, zend_op_array_calc, call_graph); |
245 | | |
246 | 32.5k | call_graph->op_arrays = (zend_op_array**)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_op_array*)); |
247 | 32.5k | call_graph->func_infos = (zend_func_info*)zend_arena_calloc(arena, call_graph->op_arrays_count, sizeof(zend_func_info)); |
248 | 32.5k | call_graph->op_arrays_count = 0; |
249 | 32.5k | zend_foreach_op_array(script, zend_op_array_collect, call_graph); |
250 | 32.5k | } |
251 | | /* }}} */ |
252 | | |
253 | | ZEND_API void zend_analyze_call_graph(zend_arena **arena, zend_script *script, zend_call_graph *call_graph) /* {{{ */ |
254 | 32.5k | { |
255 | 65.8k | for (uint32_t i = 0; i < call_graph->op_arrays_count; i++) { |
256 | 33.3k | zend_analyze_calls(arena, script, 0, call_graph->op_arrays[i], call_graph->func_infos + i); |
257 | 33.3k | } |
258 | 32.5k | zend_analyze_recursion(call_graph); |
259 | 32.5k | zend_sort_op_arrays(call_graph); |
260 | 32.5k | } |
261 | | /* }}} */ |
262 | | |
263 | | ZEND_API zend_call_info **zend_build_call_map(zend_arena **arena, const zend_func_info *info, const zend_op_array *op_array) /* {{{ */ |
264 | 35.8k | { |
265 | 35.8k | zend_call_info **map, *call; |
266 | 35.8k | if (!info->callee_info) { |
267 | | /* Don't build call map if function contains no calls */ |
268 | 33.4k | return NULL; |
269 | 33.4k | } |
270 | | |
271 | 2.34k | map = zend_arena_calloc(arena, sizeof(zend_call_info *), op_array->last); |
272 | 4.89k | for (call = info->callee_info; call; call = call->next_callee) { |
273 | 2.54k | map[call->caller_init_opline - op_array->opcodes] = call; |
274 | 2.54k | if (call->caller_call_opline) { |
275 | 2.54k | map[call->caller_call_opline - op_array->opcodes] = call; |
276 | 2.54k | } |
277 | 2.54k | if (!call->is_frameless) { |
278 | 5.71k | for (uint32_t i = 0; i < call->num_args; i++) { |
279 | 3.17k | if (call->arg_info[i].opline) { |
280 | 3.17k | map[call->arg_info[i].opline - op_array->opcodes] = call; |
281 | 3.17k | } |
282 | 3.17k | } |
283 | 2.54k | } |
284 | 2.54k | } |
285 | 2.34k | return map; |
286 | 35.8k | } |
287 | | /* }}} */ |