Coverage Report

Created: 2026-10-28 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
/* }}} */