Coverage Report

Created: 2026-06-02 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/Optimizer/zend_cfg.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine, CFG - Control Flow 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_cfg.h"
19
#include "zend_func_info.h"
20
#include "zend_worklist.h"
21
#include "zend_optimizer.h"
22
#include "zend_optimizer_internal.h"
23
#include "zend_sort.h"
24
25
static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_block *b) /* {{{ */
26
173k
{
27
173k
  zend_basic_block *blocks = cfg->blocks;
28
29
173k
  zend_worklist work;
30
173k
  ALLOCA_FLAG(list_use_heap)
31
173k
  ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap);
32
33
173k
  zend_worklist_push(&work, b - cfg->blocks);
34
35
693k
  while (zend_worklist_len(&work)) {
36
519k
    b = cfg->blocks + zend_worklist_pop(&work);
37
38
519k
    b->flags |= ZEND_BB_REACHABLE;
39
519k
    if (b->successors_count == 0) {
40
146k
      b->flags |= ZEND_BB_EXIT;
41
146k
      continue;
42
146k
    }
43
44
894k
    for (uint32_t i = 0; i < b->successors_count; i++) {
45
521k
      zend_basic_block *succ = blocks + b->successors[i];
46
47
521k
      if (b->len != 0) {
48
521k
        uint8_t opcode = opcodes[b->start + b->len - 1].opcode;
49
521k
        if (opcode == ZEND_MATCH) {
50
1.19k
          succ->flags |= ZEND_BB_TARGET;
51
520k
        } else if (opcode == ZEND_SWITCH_LONG || opcode == ZEND_SWITCH_STRING) {
52
624
          if (i == b->successors_count - 1) {
53
104
            succ->flags |= ZEND_BB_FOLLOW | ZEND_BB_TARGET;
54
520
          } else {
55
520
            succ->flags |= ZEND_BB_TARGET;
56
520
          }
57
519k
        } else if (b->successors_count == 1) {
58
224k
          if (opcode == ZEND_JMP) {
59
80.9k
            succ->flags |= ZEND_BB_TARGET;
60
143k
          } else {
61
143k
            succ->flags |= ZEND_BB_FOLLOW;
62
63
143k
            if ((cfg->flags & ZEND_CFG_STACKLESS)) {
64
0
              if (opcode == ZEND_INCLUDE_OR_EVAL ||
65
0
                opcode == ZEND_GENERATOR_CREATE ||
66
0
                opcode == ZEND_YIELD ||
67
0
                opcode == ZEND_YIELD_FROM ||
68
0
                opcode == ZEND_DO_FCALL ||
69
0
                opcode == ZEND_DO_UCALL ||
70
0
                opcode == ZEND_DO_FCALL_BY_NAME) {
71
0
                succ->flags |= ZEND_BB_ENTRY;
72
0
              }
73
0
            }
74
143k
            if ((cfg->flags & ZEND_CFG_RECV_ENTRY)) {
75
0
              if (opcode == ZEND_RECV ||
76
0
                opcode == ZEND_RECV_INIT) {
77
0
                succ->flags |= ZEND_BB_RECV_ENTRY;
78
0
              }
79
0
            }
80
143k
          }
81
295k
        } else {
82
295k
          ZEND_ASSERT(b->successors_count == 2);
83
295k
          if (i == 0) {
84
147k
            succ->flags |= ZEND_BB_TARGET;
85
147k
          } else {
86
147k
            succ->flags |= ZEND_BB_FOLLOW;
87
147k
          }
88
295k
        }
89
521k
      } else {
90
272
        succ->flags |= ZEND_BB_FOLLOW;
91
272
      }
92
93
      /* Check reachability of successor */
94
521k
      if (!(succ->flags & ZEND_BB_REACHABLE)) {
95
424k
        zend_worklist_push(&work, succ - cfg->blocks);
96
424k
      }
97
521k
    }
98
372k
  }
99
100
173k
  ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap);
101
173k
}
102
/* }}} */
103
104
static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg, uint32_t start) /* {{{ */
105
136k
{
106
136k
  zend_basic_block *blocks = cfg->blocks;
107
108
136k
  blocks[start].flags = ZEND_BB_START;
109
136k
  zend_mark_reachable(op_array->opcodes, cfg, blocks + start);
110
111
136k
  if (op_array->last_try_catch) {
112
28.6k
    zend_basic_block *b;
113
28.6k
    int changed;
114
28.6k
    uint32_t *block_map = cfg->map;
115
116
57.0k
    do {
117
57.0k
      changed = 0;
118
119
      /* Add exception paths */
120
132k
      for (uint32_t j = 0; j < op_array->last_try_catch; j++) {
121
122
        /* check for jumps into the middle of try block */
123
75.0k
        b = blocks + block_map[op_array->try_catch_array[j].try_op];
124
75.0k
        if (!(b->flags & ZEND_BB_REACHABLE)) {
125
32
          zend_basic_block *end;
126
127
32
          if (op_array->try_catch_array[j].catch_op) {
128
32
            end = blocks + block_map[op_array->try_catch_array[j].catch_op];
129
98
            while (b != end) {
130
68
              if (b->flags & ZEND_BB_REACHABLE) {
131
2
                op_array->try_catch_array[j].try_op = b->start;
132
2
                break;
133
2
              }
134
66
              b++;
135
66
            }
136
32
          }
137
32
          b = blocks + block_map[op_array->try_catch_array[j].try_op];
138
32
          if (!(b->flags & ZEND_BB_REACHABLE)) {
139
30
            if (op_array->try_catch_array[j].finally_op) {
140
6
              end = blocks + block_map[op_array->try_catch_array[j].finally_op];
141
22
              while (b != end) {
142
22
                if (b->flags & ZEND_BB_REACHABLE) {
143
                  /* In case we get here, there is no live try block but there is a live finally block.
144
                   * If we do have catch_op set, we need to set it to the first catch block to satisfy
145
                   * the constraint try_op <= catch_op <= finally_op */
146
6
                  op_array->try_catch_array[j].try_op =
147
6
                    op_array->try_catch_array[j].catch_op ? op_array->try_catch_array[j].catch_op : b->start;
148
6
                  changed = 1;
149
6
                  zend_mark_reachable(op_array->opcodes, cfg, blocks + block_map[op_array->try_catch_array[j].try_op]);
150
6
                  break;
151
6
                }
152
16
                b++;
153
16
              }
154
6
            }
155
30
          }
156
32
        }
157
158
75.0k
        b = blocks + block_map[op_array->try_catch_array[j].try_op];
159
75.0k
        if (b->flags & ZEND_BB_REACHABLE) {
160
75.0k
          b->flags |= ZEND_BB_TRY;
161
75.0k
          if (op_array->try_catch_array[j].catch_op) {
162
73.9k
            b = blocks + block_map[op_array->try_catch_array[j].catch_op];
163
73.9k
            b->flags |= ZEND_BB_CATCH;
164
73.9k
            if (!(b->flags & ZEND_BB_REACHABLE)) {
165
36.9k
              changed = 1;
166
36.9k
              zend_mark_reachable(op_array->opcodes, cfg, b);
167
36.9k
            }
168
73.9k
          }
169
75.0k
          if (op_array->try_catch_array[j].finally_op) {
170
1.50k
            b = blocks + block_map[op_array->try_catch_array[j].finally_op];
171
1.50k
            b->flags |= ZEND_BB_FINALLY;
172
1.50k
            if (!(b->flags & ZEND_BB_REACHABLE)) {
173
176
              changed = 1;
174
176
              zend_mark_reachable(op_array->opcodes, cfg, b);
175
176
            }
176
1.50k
          }
177
75.0k
          if (op_array->try_catch_array[j].finally_end) {
178
1.50k
            b = blocks + block_map[op_array->try_catch_array[j].finally_end];
179
1.50k
            b->flags |= ZEND_BB_FINALLY_END;
180
1.50k
            if (!(b->flags & ZEND_BB_REACHABLE)) {
181
296
              changed = 1;
182
296
              zend_mark_reachable(op_array->opcodes, cfg, b);
183
296
            }
184
1.50k
          }
185
75.0k
        } else {
186
24
          if (op_array->try_catch_array[j].catch_op) {
187
24
            ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].catch_op]].flags & ZEND_BB_REACHABLE));
188
24
          }
189
24
          if (op_array->try_catch_array[j].finally_op) {
190
0
            ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_op]].flags & ZEND_BB_REACHABLE));
191
0
          }
192
24
          if (op_array->try_catch_array[j].finally_end) {
193
0
            ZEND_ASSERT(!(blocks[block_map[op_array->try_catch_array[j].finally_end]].flags & ZEND_BB_REACHABLE));
194
0
          }
195
24
        }
196
75.0k
      }
197
57.0k
    } while (changed);
198
28.6k
  }
199
200
136k
  if (cfg->flags & ZEND_FUNC_FREE_LOOP_VAR) {
201
19.7k
    zend_basic_block *b;
202
19.7k
    uint32_t *block_map = cfg->map;
203
204
    /* Mark blocks that are unreachable, but free a loop var created in a reachable block. */
205
199k
    for (b = blocks; b < blocks + cfg->blocks_count; b++) {
206
179k
      if (b->flags & ZEND_BB_REACHABLE) {
207
172k
        continue;
208
172k
      }
209
210
8.13k
      for (uint32_t j = b->start; j < b->start + b->len; j++) {
211
1.53k
        zend_op *opline = &op_array->opcodes[j];
212
1.53k
        if (zend_optimizer_is_loop_var_free(opline)) {
213
38
          zend_op *def_opline = zend_optimizer_get_loop_var_def(op_array, opline);
214
38
          if (def_opline) {
215
38
            uint32_t def_block = block_map[def_opline - op_array->opcodes];
216
38
            if (blocks[def_block].flags & ZEND_BB_REACHABLE) {
217
32
              b->flags |= ZEND_BB_UNREACHABLE_FREE;
218
32
              break;
219
32
            }
220
38
          }
221
38
        }
222
1.53k
      }
223
6.63k
    }
224
19.7k
  }
225
136k
}
226
/* }}} */
227
228
void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
229
55.1k
{
230
55.1k
  zend_basic_block *blocks = cfg->blocks;
231
55.1k
  uint32_t i;
232
55.1k
  uint32_t start = 0;
233
234
55.1k
  for (i = 0; i < cfg->blocks_count; i++) {
235
55.1k
    if (blocks[i].flags & ZEND_BB_REACHABLE) {
236
55.1k
      start = i;
237
55.1k
      i++;
238
55.1k
      break;
239
55.1k
    }
240
55.1k
  }
241
242
  /* clear all flags */
243
325k
  for (i = 0; i < cfg->blocks_count; i++) {
244
270k
    blocks[i].flags = 0;
245
270k
  }
246
247
55.1k
  zend_mark_reachable_blocks(op_array, cfg, start);
248
55.1k
}
249
/* }}} */
250
251
283k
static void initialize_block(zend_basic_block *block) {
252
283k
  block->flags = 0;
253
283k
  block->successors = block->successors_storage;
254
283k
  block->successors_count = 0;
255
283k
  block->predecessors_count = 0;
256
283k
  block->predecessor_offset = -1;
257
283k
  block->idom = -1;
258
283k
  block->loop_header = -1;
259
283k
  block->level = -1;
260
283k
  block->children = -1;
261
283k
  block->next_child = -1;
262
283k
}
263
264
373k
#define BB_START(i) do { \
265
373k
    if (!block_map[i]) { blocks_count++;} \
266
373k
    block_map[i]++; \
267
373k
  } while (0)
268
269
ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, uint32_t build_flags, zend_cfg *cfg) /* {{{ */
270
81.0k
{
271
81.0k
  uint32_t flags = 0;
272
81.0k
  uint32_t i;
273
81.0k
  uint32_t *block_map;
274
81.0k
  zend_function *fn;
275
81.0k
  int blocks_count = 0;
276
81.0k
  zend_basic_block *blocks;
277
81.0k
  zval *zv;
278
81.0k
  bool extra_entry_block = false;
279
280
81.0k
  cfg->flags = build_flags & (ZEND_CFG_STACKLESS|ZEND_CFG_RECV_ENTRY);
281
282
81.0k
  cfg->map = block_map = zend_arena_calloc(arena, op_array->last, sizeof(uint32_t));
283
284
  /* Build CFG, Step 1: Find basic blocks starts, calculate number of blocks */
285
81.0k
  BB_START(0);
286
2.02M
  for (i = 0; i < op_array->last; i++) {
287
1.94M
    zend_op *opline = op_array->opcodes + i;
288
1.94M
    switch (opline->opcode) {
289
23.0k
      case ZEND_RECV:
290
25.8k
      case ZEND_RECV_INIT:
291
25.8k
        if (build_flags & ZEND_CFG_RECV_ENTRY) {
292
0
          BB_START(i + 1);
293
0
        }
294
25.8k
        break;
295
87.8k
      case ZEND_RETURN:
296
89.4k
      case ZEND_RETURN_BY_REF:
297
91.2k
      case ZEND_GENERATOR_RETURN:
298
91.2k
      case ZEND_VERIFY_NEVER_TYPE:
299
91.2k
        if (i + 1 < op_array->last) {
300
11.1k
          BB_START(i + 1);
301
11.1k
        }
302
91.2k
        break;
303
318
      case ZEND_MATCH_ERROR:
304
2.15k
      case ZEND_THROW:
305
        /* Don't treat THROW as terminator if it's used in expression context,
306
         * as we may lose live ranges when eliminating unreachable code. */
307
2.15k
        if (opline->extended_value != ZEND_THROW_IS_EXPR && i + 1 < op_array->last) {
308
1.47k
          BB_START(i + 1);
309
1.47k
        }
310
2.15k
        break;
311
5.09k
      case ZEND_INCLUDE_OR_EVAL:
312
5.09k
        flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
313
5.09k
        ZEND_FALLTHROUGH;
314
6.79k
      case ZEND_GENERATOR_CREATE:
315
8.81k
      case ZEND_YIELD:
316
9.38k
      case ZEND_YIELD_FROM:
317
9.38k
        if (build_flags & ZEND_CFG_STACKLESS) {
318
0
          BB_START(i + 1);
319
0
        }
320
9.38k
        break;
321
167k
      case ZEND_DO_FCALL:
322
178k
      case ZEND_DO_UCALL:
323
178k
      case ZEND_DO_FCALL_BY_NAME:
324
178k
        flags |= ZEND_FUNC_HAS_CALLS;
325
178k
        if (build_flags & ZEND_CFG_STACKLESS) {
326
0
          BB_START(i + 1);
327
0
        }
328
178k
        break;
329
0
      case ZEND_DO_ICALL:
330
0
        flags |= ZEND_FUNC_HAS_CALLS;
331
0
        break;
332
95.3k
      case ZEND_INIT_FCALL:
333
97.2k
      case ZEND_INIT_NS_FCALL_BY_NAME:
334
97.2k
        zv = CRT_CONSTANT(opline->op2);
335
97.2k
        if (opline->opcode == ZEND_INIT_NS_FCALL_BY_NAME) {
336
          /* The third literal is the lowercased unqualified name */
337
1.95k
          zv += 2;
338
1.95k
        }
339
97.2k
        if ((fn = zend_hash_find_ptr(EG(function_table), Z_STR_P(zv))) != NULL) {
340
76.7k
          if (fn->type == ZEND_INTERNAL_FUNCTION) {
341
76.7k
            flags |= zend_optimizer_classify_function(
342
76.7k
              Z_STR_P(zv), opline->extended_value);
343
76.7k
          }
344
76.7k
        }
345
97.2k
        break;
346
503
      case ZEND_FAST_CALL:
347
503
        BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes);
348
503
        BB_START(i + 1);
349
503
        break;
350
363
      case ZEND_FAST_RET:
351
363
        if (i + 1 < op_array->last) {
352
363
          BB_START(i + 1);
353
363
        }
354
363
        break;
355
36.6k
      case ZEND_JMP:
356
36.6k
        BB_START(OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes);
357
36.6k
        if (i + 1 < op_array->last) {
358
36.3k
          BB_START(i + 1);
359
36.3k
        }
360
36.6k
        break;
361
13.2k
      case ZEND_JMPZ:
362
24.0k
      case ZEND_JMPNZ:
363
25.8k
      case ZEND_JMPZ_EX:
364
28.0k
      case ZEND_JMPNZ_EX:
365
29.7k
      case ZEND_JMP_SET:
366
34.1k
      case ZEND_COALESCE:
367
35.3k
      case ZEND_ASSERT_CHECK:
368
57.7k
      case ZEND_JMP_NULL:
369
57.9k
      case ZEND_BIND_INIT_STATIC_OR_JMP:
370
57.9k
      case ZEND_JMP_FRAMELESS:
371
57.9k
        BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
372
57.9k
        BB_START(i + 1);
373
57.9k
        break;
374
14.6k
      case ZEND_CATCH:
375
14.6k
        if (!(opline->extended_value & ZEND_LAST_CATCH)) {
376
2.22k
          BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
377
2.22k
        }
378
14.6k
        BB_START(i + 1);
379
14.6k
        break;
380
8.38k
      case ZEND_FE_FETCH_R:
381
9.12k
      case ZEND_FE_FETCH_RW:
382
9.12k
        BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
383
9.12k
        BB_START(i + 1);
384
9.12k
        break;
385
8.38k
      case ZEND_FE_RESET_R:
386
9.12k
      case ZEND_FE_RESET_RW:
387
9.12k
        BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes);
388
9.12k
        BB_START(i + 1);
389
9.12k
        break;
390
12
      case ZEND_SWITCH_LONG:
391
68
      case ZEND_SWITCH_STRING:
392
218
      case ZEND_MATCH:
393
218
      {
394
218
        HashTable *jumptable = Z_ARRVAL_P(CRT_CONSTANT(opline->op2));
395
218
        zval *zv;
396
2.21k
        ZEND_HASH_FOREACH_VAL(jumptable, zv) {
397
2.21k
          BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv)));
398
2.21k
        } ZEND_HASH_FOREACH_END();
399
218
        BB_START(ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value));
400
218
        BB_START(i + 1);
401
218
        break;
402
68
      }
403
5.34k
      case ZEND_FETCH_R:
404
7.29k
      case ZEND_FETCH_W:
405
8.93k
      case ZEND_FETCH_RW:
406
8.98k
      case ZEND_FETCH_FUNC_ARG:
407
9.18k
      case ZEND_FETCH_IS:
408
9.20k
      case ZEND_FETCH_UNSET:
409
9.33k
      case ZEND_UNSET_VAR:
410
9.47k
      case ZEND_ISSET_ISEMPTY_VAR:
411
9.47k
        if (opline->extended_value & ZEND_FETCH_LOCAL) {
412
8.05k
          flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
413
8.05k
        } else if ((opline->extended_value & (ZEND_FETCH_GLOBAL | ZEND_FETCH_GLOBAL_LOCK)) &&
414
1.41k
                   !op_array->function_name) {
415
522
          flags |= ZEND_FUNC_INDIRECT_VAR_ACCESS;
416
522
        }
417
9.47k
        break;
418
88
      case ZEND_FUNC_GET_ARGS:
419
88
        flags |= ZEND_FUNC_VARARG;
420
88
        break;
421
0
      case ZEND_EXT_STMT:
422
0
        flags |= ZEND_FUNC_HAS_EXTENDED_STMT;
423
0
        break;
424
0
      case ZEND_EXT_FCALL_BEGIN:
425
0
      case ZEND_EXT_FCALL_END:
426
0
        flags |= ZEND_FUNC_HAS_EXTENDED_FCALL;
427
0
        break;
428
19.4k
      case ZEND_FREE:
429
28.8k
      case ZEND_FE_FREE:
430
28.8k
        if (zend_optimizer_is_loop_var_free(opline)
431
9.32k
         && ((opline-1)->opcode != ZEND_MATCH_ERROR
432
9.31k
          || (opline-1)->extended_value != ZEND_THROW_IS_EXPR)) {
433
9.31k
          BB_START(i);
434
9.31k
          flags |= ZEND_FUNC_FREE_LOOP_VAR;
435
9.31k
        }
436
28.8k
        break;
437
1.94M
    }
438
1.94M
  }
439
440
  /* If the entry block has predecessors, we may need to split it */
441
81.0k
  if ((build_flags & ZEND_CFG_NO_ENTRY_PREDECESSORS)
442
35.7k
      && op_array->last > 0 && block_map[0] > 1) {
443
27
    extra_entry_block = true;
444
27
  }
445
446
81.0k
  if (op_array->last_try_catch) {
447
22.3k
    for (uint32_t j = 0; j < op_array->last_try_catch; j++) {
448
12.6k
      BB_START(op_array->try_catch_array[j].try_op);
449
12.6k
      if (op_array->try_catch_array[j].catch_op) {
450
12.3k
        BB_START(op_array->try_catch_array[j].catch_op);
451
12.3k
      }
452
12.6k
      if (op_array->try_catch_array[j].finally_op) {
453
363
        BB_START(op_array->try_catch_array[j].finally_op);
454
363
      }
455
12.6k
      if (op_array->try_catch_array[j].finally_end) {
456
363
        BB_START(op_array->try_catch_array[j].finally_end);
457
363
      }
458
12.6k
    }
459
9.62k
  }
460
461
81.0k
  blocks_count += extra_entry_block;
462
81.0k
  cfg->blocks_count = blocks_count;
463
464
  /* Build CFG, Step 2: Build Array of Basic Blocks */
465
81.0k
  cfg->blocks = blocks = zend_arena_calloc(arena, sizeof(zend_basic_block), blocks_count);
466
467
81.0k
  blocks_count = -1;
468
469
81.0k
  if (extra_entry_block) {
470
27
    initialize_block(&blocks[0]);
471
27
    blocks[0].start = 0;
472
27
    blocks[0].len = 0;
473
27
    blocks_count++;
474
27
  }
475
476
2.02M
  for (i = 0; i < op_array->last; i++) {
477
1.94M
    if (block_map[i]) {
478
283k
      if (blocks_count >= 0) {
479
202k
        blocks[blocks_count].len = i - blocks[blocks_count].start;
480
202k
      }
481
283k
      blocks_count++;
482
283k
      initialize_block(&blocks[blocks_count]);
483
283k
      blocks[blocks_count].start = i;
484
283k
    }
485
1.94M
    block_map[i] = blocks_count;
486
1.94M
  }
487
488
81.0k
  blocks[blocks_count].len = i - blocks[blocks_count].start;
489
81.0k
  blocks_count++;
490
491
  /* Build CFG, Step 3: Calculate successors */
492
364k
  for (int j = 0; j < blocks_count; j++) {
493
283k
    zend_basic_block *block = &blocks[j];
494
283k
    zend_op *opline;
495
283k
    if (block->len == 0) {
496
27
      block->successors_count = 1;
497
27
      block->successors[0] = j + 1;
498
27
      continue;
499
27
    }
500
501
283k
    opline = op_array->opcodes + block->start + block->len - 1;
502
283k
    switch (opline->opcode) {
503
363
      case ZEND_FAST_RET:
504
88.1k
      case ZEND_RETURN:
505
89.7k
      case ZEND_RETURN_BY_REF:
506
91.6k
      case ZEND_GENERATOR_RETURN:
507
93.3k
      case ZEND_THROW:
508
93.6k
      case ZEND_MATCH_ERROR:
509
93.7k
      case ZEND_VERIFY_NEVER_TYPE:
510
93.7k
        break;
511
36.6k
      case ZEND_JMP:
512
36.6k
        block->successors_count = 1;
513
36.6k
        block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes];
514
36.6k
        break;
515
13.2k
      case ZEND_JMPZ:
516
24.0k
      case ZEND_JMPNZ:
517
25.8k
      case ZEND_JMPZ_EX:
518
28.0k
      case ZEND_JMPNZ_EX:
519
29.7k
      case ZEND_JMP_SET:
520
34.1k
      case ZEND_COALESCE:
521
35.3k
      case ZEND_ASSERT_CHECK:
522
57.7k
      case ZEND_JMP_NULL:
523
57.9k
      case ZEND_BIND_INIT_STATIC_OR_JMP:
524
57.9k
      case ZEND_JMP_FRAMELESS:
525
57.9k
        block->successors_count = 2;
526
57.9k
        block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
527
57.9k
        block->successors[1] = j + 1;
528
57.9k
        break;
529
14.6k
      case ZEND_CATCH:
530
14.6k
        if (!(opline->extended_value & ZEND_LAST_CATCH)) {
531
2.22k
          block->successors_count = 2;
532
2.22k
          block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
533
2.22k
          block->successors[1] = j + 1;
534
12.3k
        } else {
535
12.3k
          block->successors_count = 1;
536
12.3k
          block->successors[0] = j + 1;
537
12.3k
        }
538
14.6k
        break;
539
8.38k
      case ZEND_FE_FETCH_R:
540
9.12k
      case ZEND_FE_FETCH_RW:
541
9.12k
        block->successors_count = 2;
542
9.12k
        block->successors[0] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
543
9.12k
        block->successors[1] = j + 1;
544
9.12k
        break;
545
8.38k
      case ZEND_FE_RESET_R:
546
9.12k
      case ZEND_FE_RESET_RW:
547
9.12k
        block->successors_count = 2;
548
9.12k
        block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes];
549
9.12k
        block->successors[1] = j + 1;
550
9.12k
        break;
551
503
      case ZEND_FAST_CALL:
552
503
        block->successors_count = 2;
553
503
        block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op1) - op_array->opcodes];
554
503
        block->successors[1] = j + 1;
555
503
        break;
556
12
      case ZEND_SWITCH_LONG:
557
68
      case ZEND_SWITCH_STRING:
558
218
      case ZEND_MATCH:
559
218
      {
560
218
        HashTable *jumptable = Z_ARRVAL_P(CRT_CONSTANT(opline->op2));
561
218
        zval *zv;
562
218
        uint32_t s = 0;
563
564
218
        block->successors_count = (opline->opcode == ZEND_MATCH ? 1 : 2) + zend_hash_num_elements(jumptable);
565
218
        block->successors = zend_arena_calloc(arena, block->successors_count, sizeof(int));
566
567
2.21k
        ZEND_HASH_FOREACH_VAL(jumptable, zv) {
568
2.21k
          block->successors[s++] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(zv))];
569
2.21k
        } ZEND_HASH_FOREACH_END();
570
571
218
        block->successors[s++] = block_map[ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value)];
572
218
        if (opline->opcode != ZEND_MATCH) {
573
68
          block->successors[s++] = j + 1;
574
68
        }
575
218
        break;
576
68
      }
577
61.9k
      default:
578
61.9k
        block->successors_count = 1;
579
61.9k
        block->successors[0] = j + 1;
580
61.9k
        break;
581
283k
    }
582
283k
  }
583
584
  /* Build CFG, Step 4, Mark Reachable Basic Blocks */
585
81.0k
  cfg->flags |= flags;
586
81.0k
  zend_mark_reachable_blocks(op_array, cfg, 0);
587
81.0k
}
588
/* }}} */
589
590
ZEND_API void zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */
591
34.0k
{
592
34.0k
  zend_basic_block *b;
593
34.0k
  zend_basic_block *blocks = cfg->blocks;
594
34.0k
  zend_basic_block *end = blocks + cfg->blocks_count;
595
34.0k
  uint32_t edges = 0;
596
34.0k
  int *predecessors;
597
598
127k
  for (b = blocks; b < end; b++) {
599
92.9k
    b->predecessors_count = 0;
600
92.9k
  }
601
127k
  for (b = blocks; b < end; b++) {
602
92.9k
    if (!(b->flags & ZEND_BB_REACHABLE)) {
603
10
      b->successors_count = 0;
604
10
      b->predecessors_count = 0;
605
92.9k
    } else {
606
179k
      for (uint32_t s = 0; s < b->successors_count; s++) {
607
86.5k
        edges++;
608
86.5k
        blocks[b->successors[s]].predecessors_count++;
609
86.5k
      }
610
92.9k
    }
611
92.9k
  }
612
613
34.0k
  cfg->edges_count = edges;
614
34.0k
  cfg->predecessors = predecessors = (int*)zend_arena_calloc(arena, sizeof(int), edges);
615
616
34.0k
  edges = 0;
617
127k
  for (b = blocks; b < end; b++) {
618
92.9k
    if (b->flags & ZEND_BB_REACHABLE) {
619
92.9k
      b->predecessor_offset = edges;
620
92.9k
      edges += b->predecessors_count;
621
92.9k
      b->predecessors_count = 0;
622
92.9k
    }
623
92.9k
  }
624
625
127k
  for (uint32_t j = 0; j < cfg->blocks_count; j++) {
626
92.9k
    if (blocks[j].flags & ZEND_BB_REACHABLE) {
627
      /* SWITCH_STRING/LONG may have few identical successors */
628
179k
      for (uint32_t s = 0; s < blocks[j].successors_count; s++) {
629
86.5k
        int duplicate = 0;
630
631
116k
        for (uint32_t p = 0; p < s; p++) {
632
30.3k
          if (blocks[j].successors[p] == blocks[j].successors[s]) {
633
86
            duplicate = 1;
634
86
            break;
635
86
          }
636
30.3k
        }
637
86.5k
        if (!duplicate) {
638
86.4k
          zend_basic_block *b = blocks + blocks[j].successors[s];
639
640
86.4k
          predecessors[b->predecessor_offset + b->predecessors_count] = j;
641
86.4k
          b->predecessors_count++;
642
86.4k
        }
643
86.5k
      }
644
92.9k
    }
645
92.9k
  }
646
34.0k
}
647
/* }}} */
648
649
/* Computes a postorder numbering of the CFG */
650
static void compute_postnum_recursive(
651
    int *postnum, uint32_t *cur, const zend_cfg *cfg, int block_num) /* {{{ */
652
93.6k
{
653
93.6k
  zend_basic_block *block = &cfg->blocks[block_num];
654
93.6k
  if (postnum[block_num] != -1) {
655
27.6k
    return;
656
27.6k
  }
657
658
66.0k
  postnum[block_num] = -2; /* Marker for "currently visiting" */
659
152k
  for (uint32_t s = 0; s < block->successors_count; s++) {
660
86.5k
    compute_postnum_recursive(postnum, cur, cfg, block->successors[s]);
661
86.5k
  }
662
66.0k
  postnum[block_num] = (*cur)++;
663
66.0k
}
664
/* }}} */
665
666
/* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by
667
 * Cooper, Harvey and Kennedy. */
668
ZEND_API void zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
669
34.0k
{
670
34.0k
  zend_basic_block *blocks = cfg->blocks;
671
34.0k
  uint32_t blocks_count = cfg->blocks_count;
672
34.0k
  uint32_t j;
673
34.0k
  int changed;
674
675
34.0k
  if (cfg->blocks_count == 1) {
676
26.9k
    blocks[0].level = 0;
677
26.9k
    return;
678
26.9k
  }
679
680
7.16k
  ALLOCA_FLAG(use_heap)
681
7.16k
  int *postnum = do_alloca(sizeof(int) * cfg->blocks_count, use_heap);
682
7.16k
  memset(postnum, -1, sizeof(int) * cfg->blocks_count);
683
7.16k
  j = 0;
684
7.16k
  compute_postnum_recursive(postnum, &j, cfg, 0);
685
686
  /* FIXME: move declarations */
687
7.16k
  blocks[0].idom = 0;
688
17.6k
  do {
689
17.6k
    changed = 0;
690
    /* Iterating in RPO here would converge faster */
691
167k
    for (j = 1; j < blocks_count; j++) {
692
150k
      int idom = -1;
693
694
150k
      if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
695
20
        continue;
696
20
      }
697
368k
      for (uint32_t k = 0; k < blocks[j].predecessors_count; k++) {
698
218k
        int pred = cfg->predecessors[blocks[j].predecessor_offset + k];
699
700
218k
        if (blocks[pred].idom >= 0) {
701
184k
          if (idom < 0) {
702
130k
            idom = pred;
703
130k
          } else {
704
109k
            while (idom != pred) {
705
150k
              while (postnum[pred] < postnum[idom]) pred = blocks[pred].idom;
706
59.1k
              while (postnum[idom] < postnum[pred]) idom = blocks[idom].idom;
707
54.7k
            }
708
54.4k
          }
709
184k
        }
710
218k
      }
711
712
150k
      if (idom >= 0 && blocks[j].idom != idom) {
713
58.8k
        blocks[j].idom = idom;
714
58.8k
        changed = 1;
715
58.8k
      }
716
150k
    }
717
17.6k
  } while (changed);
718
7.16k
  blocks[0].idom = -1;
719
720
66.0k
  for (j = 1; j < blocks_count; j++) {
721
58.8k
    if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
722
10
      continue;
723
10
    }
724
58.8k
    if (blocks[j].idom >= 0) {
725
      /* Sort by block number to traverse children in pre-order */
726
58.8k
      if (blocks[blocks[j].idom].children < 0 ||
727
32.3k
          j < blocks[blocks[j].idom].children) {
728
32.3k
        blocks[j].next_child = blocks[blocks[j].idom].children;
729
32.3k
        blocks[blocks[j].idom].children = j;
730
32.3k
      } else {
731
26.5k
        int k = blocks[blocks[j].idom].children;
732
30.1k
        while (blocks[k].next_child >=0 && j > blocks[k].next_child) {
733
3.60k
          k = blocks[k].next_child;
734
3.60k
        }
735
26.5k
        blocks[j].next_child = blocks[k].next_child;
736
26.5k
        blocks[k].next_child = j;
737
26.5k
      }
738
58.8k
    }
739
58.8k
  }
740
741
73.1k
  for (j = 0; j < blocks_count; j++) {
742
66.0k
    int idom = blocks[j].idom, level = 0;
743
66.0k
    if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
744
10
      continue;
745
10
    }
746
69.7k
    while (idom >= 0) {
747
62.6k
      level++;
748
62.6k
      if (blocks[idom].level >= 0) {
749
58.8k
        level += blocks[idom].level;
750
58.8k
        break;
751
58.8k
      } else {
752
3.77k
        idom = blocks[idom].idom;
753
3.77k
      }
754
62.6k
    }
755
66.0k
    blocks[j].level = level;
756
66.0k
  }
757
758
7.16k
  free_alloca(postnum, use_heap);
759
7.16k
}
760
/* }}} */
761
762
static bool dominates(zend_basic_block *blocks, int a, int b) /* {{{ */
763
29.4k
{
764
51.1k
  while (blocks[b].level > blocks[a].level) {
765
21.6k
    b = blocks[b].idom;
766
21.6k
  }
767
29.4k
  return a == b;
768
29.4k
}
769
/* }}} */
770
771
ZEND_API void zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
772
34.0k
{
773
34.0k
  int i, j, n;
774
34.0k
  int time;
775
34.0k
  zend_basic_block *blocks = cfg->blocks;
776
34.0k
  int *entry_times, *exit_times;
777
34.0k
  zend_worklist work;
778
34.0k
  int flag = ZEND_FUNC_NO_LOOPS;
779
34.0k
  int *sorted_blocks;
780
34.0k
  ALLOCA_FLAG(list_use_heap)
781
34.0k
  ALLOCA_FLAG(tree_use_heap)
782
783
34.0k
  if (cfg->blocks_count == 1) {
784
26.9k
    cfg->flags |= flag;
785
26.9k
    return;
786
26.9k
  }
787
788
7.16k
  ZEND_WORKLIST_ALLOCA(&work, cfg->blocks_count, list_use_heap);
789
790
  /* We don't materialize the DJ spanning tree explicitly, as we are only interested in ancestor
791
   * queries. These are implemented by checking entry/exit times of the DFS search. */
792
7.16k
  entry_times = do_alloca(3 * sizeof(int) * cfg->blocks_count, tree_use_heap);
793
7.16k
  exit_times = entry_times + cfg->blocks_count;
794
7.16k
  sorted_blocks = exit_times + cfg->blocks_count;
795
7.16k
  memset(entry_times, -1, 2 * sizeof(int) * cfg->blocks_count);
796
797
7.16k
  zend_worklist_push(&work, 0);
798
7.16k
  time = 0;
799
73.1k
  while (zend_worklist_len(&work)) {
800
124k
  next:
801
124k
    i = zend_worklist_peek(&work);
802
124k
    if (entry_times[i] == -1) {
803
66.0k
      entry_times[i] = time++;
804
66.0k
    }
805
    /* Visit blocks immediately dominated by i. */
806
193k
    for (j = blocks[i].children; j >= 0; j = blocks[j].next_child) {
807
107k
      if (zend_worklist_push(&work, j)) {
808
38.9k
        goto next;
809
38.9k
      }
810
107k
    }
811
    /* Visit join edges.  */
812
172k
    for (j = 0; j < blocks[i].successors_count; j++) {
813
106k
      int succ = blocks[i].successors[j];
814
106k
      if (blocks[succ].idom == i) {
815
57.0k
        continue;
816
57.0k
      } else if (zend_worklist_push(&work, succ)) {
817
19.9k
        goto next;
818
19.9k
      }
819
106k
    }
820
66.0k
    exit_times[i] = time++;
821
66.0k
    zend_worklist_pop(&work);
822
66.0k
  }
823
824
  /* Sort blocks by level, which is the opposite order in which we want to process them */
825
7.16k
  sorted_blocks[0] = 0;
826
7.16k
  j = 0;
827
7.16k
  n = 1;
828
44.1k
  while (j != n) {
829
37.0k
    i = j;
830
37.0k
    j = n;
831
103k
    for (; i < j; i++) {
832
66.0k
      int child;
833
124k
      for (child = blocks[sorted_blocks[i]].children; child >= 0; child = blocks[child].next_child) {
834
58.8k
        sorted_blocks[n++] = child;
835
58.8k
      }
836
66.0k
    }
837
37.0k
  }
838
839
  /* Identify loops. See Sreedhar et al, "Identifying Loops Using DJ Graphs". */
840
73.1k
  while (n > 0) {
841
66.0k
    i = sorted_blocks[--n];
842
843
66.0k
    if (blocks[i].predecessors_count < 2) {
844
        /* loop header has at least two input edges */
845
39.9k
      continue;
846
39.9k
    }
847
848
79.6k
    for (j = 0; j < blocks[i].predecessors_count; j++) {
849
53.6k
      int pred = cfg->predecessors[blocks[i].predecessor_offset + j];
850
851
      /* A join edge is one for which the predecessor does not
852
         immediately dominate the successor. */
853
53.6k
      if (blocks[i].idom == pred) {
854
24.1k
        continue;
855
24.1k
      }
856
857
      /* In a loop back-edge (back-join edge), the successor dominates
858
         the predecessor.  */
859
29.4k
      if (dominates(blocks, i, pred)) {
860
6.59k
        blocks[i].flags |= ZEND_BB_LOOP_HEADER;
861
6.59k
        flag &= ~ZEND_FUNC_NO_LOOPS;
862
6.59k
        if (!zend_worklist_len(&work)) {
863
6.09k
          zend_bitset_clear(work.visited, zend_bitset_len(cfg->blocks_count));
864
6.09k
        }
865
6.59k
        zend_worklist_push(&work, pred);
866
22.8k
      } else {
867
        /* Otherwise it's a cross-join edge.  See if it's a branch
868
           to an ancestor on the DJ spanning tree.  */
869
22.8k
        if (entry_times[pred] > entry_times[i] && exit_times[pred] < exit_times[i]) {
870
0
          blocks[i].flags |= ZEND_BB_IRREDUCIBLE_LOOP;
871
0
          flag |= ZEND_FUNC_IRREDUCIBLE;
872
0
          flag &= ~ZEND_FUNC_NO_LOOPS;
873
0
        }
874
22.8k
      }
875
29.4k
    }
876
56.6k
    while (zend_worklist_len(&work)) {
877
30.6k
      j = zend_worklist_pop(&work);
878
32.2k
      while (blocks[j].loop_header >= 0) {
879
1.61k
        j = blocks[j].loop_header;
880
1.61k
      }
881
30.6k
      if (j != i) {
882
23.7k
        if (blocks[j].idom < 0 && j != 0) {
883
          /* Ignore blocks that are unreachable or only abnormally reachable. */
884
0
          continue;
885
0
        }
886
23.7k
        blocks[j].loop_header = i;
887
56.3k
        for (uint32_t k = 0; k < blocks[j].predecessors_count; k++) {
888
32.5k
          zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]);
889
32.5k
        }
890
23.7k
      }
891
30.6k
    }
892
26.0k
  }
893
894
7.16k
  free_alloca(entry_times, tree_use_heap);
895
7.16k
  ZEND_WORKLIST_FREE_ALLOCA(&work, list_use_heap);
896
897
7.16k
  cfg->flags |= flag;
898
7.16k
}
899
/* }}} */