Coverage Report

Created: 2025-12-31 07:28

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