Coverage Report

Created: 2026-04-01 06:49

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