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/ssa_integrity.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine, SSA validation                                          |
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: Nikita Popov <nikic@php.net>                                |
16
   +----------------------------------------------------------------------+
17
*/
18
19
#include "Optimizer/zend_optimizer_internal.h"
20
21
/* The ssa_verify_integrity() function ensures that that certain invariants of the SSA form and
22
 * CFG are upheld and prints messages to stderr if this is not the case. */
23
24
5.51M
static inline bool is_in_use_chain(zend_ssa *ssa, int var, int check) {
25
5.51M
  int use;
26
19.3M
  FOREACH_USE(&ssa->vars[var], use) {
27
19.3M
    if (use == check) {
28
5.51M
      return true;
29
5.51M
    }
30
19.3M
  } FOREACH_USE_END();
31
0
  return false;
32
5.51M
}
33
34
1.29M
static inline bool is_in_phi_use_chain(zend_ssa *ssa, int var, zend_ssa_phi *check) {
35
1.29M
  zend_ssa_phi *phi;
36
2.80M
  FOREACH_PHI_USE(&ssa->vars[var], phi) {
37
2.80M
    if (phi == check) {
38
1.29M
      return true;
39
1.29M
    }
40
2.80M
  } FOREACH_PHI_USE_END();
41
0
  return false;
42
1.29M
}
43
44
5.50M
static inline bool is_used_by_op(zend_ssa *ssa, int op, int check) {
45
5.50M
  zend_ssa_op *ssa_op = &ssa->ops[op];
46
5.50M
  return (ssa_op->op1_use == check)
47
1.31M
    || (ssa_op->op2_use == check)
48
89.6k
    || (ssa_op->result_use == check);
49
5.50M
}
50
51
4.34M
static inline bool is_defined_by_op(zend_ssa *ssa, int op, int check) {
52
4.34M
  zend_ssa_op *ssa_op = &ssa->ops[op];
53
4.34M
  return (ssa_op->op1_def == check)
54
3.82M
    || (ssa_op->op2_def == check)
55
3.79M
    || (ssa_op->result_def == check);
56
4.34M
}
57
58
1.28M
static inline bool is_in_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi, int check) {
59
1.28M
  int source;
60
4.98M
  FOREACH_PHI_SOURCE(phi, source) {
61
4.98M
    if (source == check) {
62
1.28M
      return true;
63
1.28M
    }
64
4.98M
  } FOREACH_PHI_SOURCE_END();
65
0
  return false;
66
1.28M
}
67
68
970k
static inline bool is_in_predecessors(zend_cfg *cfg, zend_basic_block *block, int check) {
69
970k
  int *predecessors = &cfg->predecessors[block->predecessor_offset];
70
1.30M
  for (uint32_t i = 0; i < block->predecessors_count; i++) {
71
1.30M
    if (predecessors[i] == check) {
72
970k
      return true;
73
970k
    }
74
1.30M
  }
75
0
  return false;
76
970k
}
77
78
969k
static inline bool is_in_successors(zend_basic_block *block, int check) {
79
1.30M
  for (uint32_t s = 0; s < block->successors_count; s++) {
80
1.30M
    if (block->successors[s] == check) {
81
969k
      return true;
82
969k
    }
83
1.30M
  }
84
0
  return false;
85
969k
}
86
87
21.6M
static inline bool is_var_type(uint8_t type) {
88
21.6M
  return (type & (IS_CV|IS_VAR|IS_TMP_VAR)) != 0;
89
21.6M
}
90
91
5.51M
static inline bool is_defined(const zend_ssa *ssa, const zend_op_array *op_array, int var) {
92
5.51M
  const zend_ssa_var *ssa_var = &ssa->vars[var];
93
5.51M
  return ssa_var->definition >= 0 || ssa_var->definition_phi || var < op_array->last_var;
94
5.51M
}
95
96
0
#define FAIL(...) do { \
97
0
  if (status == SUCCESS) { \
98
0
    fprintf(stderr, "\nIn function %s::%s (%s):\n", \
99
0
      op_array->scope ? ZSTR_VAL(op_array->scope->name) : "", \
100
0
      op_array->function_name ? ZSTR_VAL(op_array->function_name) : "{main}", extra); \
101
0
  } \
102
0
  fprintf(stderr, __VA_ARGS__); \
103
0
  status = FAILURE; \
104
0
} while (0)
105
106
#define VARFMT "%d (%s%s)"
107
#define VAR(i) \
108
0
  (i), (ssa->vars[i].var < op_array->last_var ? "CV $" : "TMP"), \
109
0
  (ssa->vars[i].var < op_array->last_var ? ZSTR_VAL(op_array->vars[ssa->vars[i].var]) : "")
110
111
#define INSTRFMT "%d (%s)"
112
#define INSTR(i) \
113
  (i), (zend_get_opcode_name(op_array->opcodes[i].opcode))
114
115
339k
void ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *extra) {
116
339k
  zend_cfg *cfg = &ssa->cfg;
117
339k
  zend_ssa_phi *phi;
118
339k
  int i;
119
339k
  zend_result status = SUCCESS;
120
121
  /* Vars */
122
6.14M
  for (i = 0; i < ssa->vars_count; i++) {
123
5.80M
    zend_ssa_var *var = &ssa->vars[i];
124
5.80M
    int use;
125
5.80M
    uint32_t type = ssa->var_info[i].type;
126
127
5.80M
    if (var->definition < 0 && !var->definition_phi && i > op_array->last_var) {
128
129k
      if (var->use_chain >= 0) {
129
0
        FAIL("var " VARFMT " without def has op uses\n", VAR(i));
130
0
      }
131
129k
      if (var->phi_use_chain) {
132
0
        FAIL("var " VARFMT " without def has phi uses\n", VAR(i));
133
0
      }
134
129k
    }
135
5.80M
    if (var->definition >= 0 && var->definition_phi) {
136
0
      FAIL("var " VARFMT " has both def and def_phi\n", VAR(i));
137
0
    }
138
5.80M
    if (var->definition >= 0) {
139
4.34M
      if (!is_defined_by_op(ssa, var->definition, i)) {
140
0
        FAIL("var " VARFMT " not defined by op " INSTRFMT "\n",
141
0
            VAR(i), INSTR(var->definition));
142
0
      }
143
4.34M
    }
144
5.80M
    if (var->definition_phi) {
145
748k
      if (var->definition_phi->ssa_var != i) {
146
0
        FAIL("var " VARFMT " not defined by given phi\n", VAR(i));
147
0
      }
148
748k
    }
149
150
    /* Floyd's cycle detection algorithm, applied for use chain. */
151
5.80M
    use = var->use_chain;
152
5.80M
    int second_use = use;
153
6.34M
    while (use >= 0 && second_use >= 0) {
154
4.95M
      use = zend_ssa_next_use(ssa->ops, var - ssa->vars, use);
155
4.95M
      second_use = zend_ssa_next_use(ssa->ops, var - ssa->vars, second_use);
156
4.95M
      if (second_use < 0) {
157
4.40M
        break;
158
4.40M
      }
159
546k
      second_use = zend_ssa_next_use(ssa->ops, var - ssa->vars, second_use);
160
546k
      if (use == second_use) {
161
0
        FAIL("cycle in uses of " VARFMT "\n", VAR(i));
162
0
        goto finish;
163
0
      }
164
546k
    }
165
166
11.3M
    FOREACH_USE(var, use) {
167
11.3M
      if (!is_used_by_op(ssa, use, i)) {
168
0
        fprintf(stderr, "var " VARFMT " not in uses of op %d\n", VAR(i), use);
169
0
      }
170
11.3M
    } FOREACH_USE_END();
171
172
    /* Floyd's cycle detection algorithm, applied for phi nodes. */
173
5.80M
    phi = var->phi_use_chain;
174
5.80M
    zend_ssa_phi *second_phi = phi;
175
6.00M
    while (phi && second_phi) {
176
1.08M
      phi = zend_ssa_next_use_phi(ssa, var - ssa->vars, phi);
177
1.08M
      second_phi = zend_ssa_next_use_phi(ssa, var - ssa->vars, second_phi);
178
1.08M
      if (!second_phi) {
179
887k
        break;
180
887k
      }
181
198k
      second_phi = zend_ssa_next_use_phi(ssa, var - ssa->vars, second_phi);
182
198k
      if (phi == second_phi) {
183
0
        FAIL("cycle in phi uses of " VARFMT "\n", VAR(i));
184
0
        goto finish;
185
0
      }
186
198k
    }
187
188
7.08M
    FOREACH_PHI_USE(var, phi) {
189
7.08M
      if (!is_in_phi_sources(ssa, phi, i)) {
190
0
        FAIL("var " VARFMT " not in phi sources of %d\n", VAR(i), phi->ssa_var);
191
0
      }
192
7.08M
    } FOREACH_PHI_USE_END();
193
194
5.80M
    if ((type & (MAY_BE_ARRAY_KEY_ANY-MAY_BE_ARRAY_EMPTY)) && !(type & MAY_BE_ARRAY_OF_ANY)) {
195
0
      FAIL("var " VARFMT " has array key type but not value type\n", VAR(i));
196
0
    }
197
5.80M
    if ((type & MAY_BE_ARRAY_OF_ANY) && !(type & (MAY_BE_ARRAY_KEY_ANY-MAY_BE_ARRAY_EMPTY))) {
198
0
      FAIL("var " VARFMT " has array value type but not key type\n", VAR(i));
199
0
    }
200
5.80M
    if ((type & MAY_BE_REF) && ssa->var_info[i].ce) {
201
0
      FAIL("var " VARFMT " may be ref but has ce\n", VAR(i));
202
0
    }
203
5.80M
  }
204
205
  /* Instructions */
206
9.93M
  FOREACH_INSTR_NUM(i) {
207
9.93M
    zend_ssa_op *ssa_op = &ssa->ops[i];
208
9.93M
    zend_op *opline = &op_array->opcodes[i];
209
9.93M
    if (is_var_type(opline->op1_type)) {
210
4.18M
      if (ssa_op->op1_use < 0 && ssa_op->op1_def < 0) {
211
0
        FAIL("var op1 of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
212
0
      }
213
4.18M
    } else {
214
3.04M
      if (ssa_op->op1_use >= 0 || ssa_op->op1_def >= 0) {
215
0
        FAIL("non-var op1 of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
216
0
      }
217
3.04M
    }
218
9.93M
    if (is_var_type(opline->op2_type)) {
219
1.23M
      if (ssa_op->op2_use < 0 && ssa_op->op2_def < 0) {
220
0
        FAIL("var op2 of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
221
0
      }
222
5.99M
    } else {
223
5.99M
      if (ssa_op->op2_use >= 0 || ssa_op->op2_def >= 0) {
224
0
        FAIL("non-var op2 of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
225
0
      }
226
5.99M
    }
227
9.93M
    if (is_var_type(opline->result_type)) {
228
3.79M
      if (ssa_op->result_use < 0 && ssa_op->result_def < 0) {
229
0
        FAIL("var result of " INSTRFMT " does not use/def an ssa var\n", INSTR(i));
230
0
      }
231
3.79M
    } else {
232
3.43M
      if (ssa_op->result_use >= 0 || ssa_op->result_def >= 0) {
233
0
        FAIL("non-var result of " INSTRFMT " uses or defs an ssa var\n", INSTR(i));
234
0
      }
235
3.43M
    }
236
237
9.93M
    if (ssa_op->op1_use >= 0) {
238
4.18M
      if (ssa_op->op1_use >= ssa->vars_count) {
239
0
        FAIL("op1 use %d out of range\n", ssa_op->op1_use);
240
0
      }
241
4.18M
      if (!is_defined(ssa, op_array, ssa_op->op1_use)) {
242
0
        FAIL("op1 use of " VARFMT " in " INSTRFMT " is not defined\n",
243
0
            VAR(ssa_op->op1_use), INSTR(i));
244
0
      }
245
4.18M
      if (!is_in_use_chain(ssa, ssa_op->op1_use, i)) {
246
0
        FAIL("op1 use of " VARFMT " in " INSTRFMT " not in use chain\n",
247
0
            VAR(ssa_op->op1_use), INSTR(i));
248
0
      }
249
4.18M
      if (VAR_NUM(opline->op1.var) != ssa->vars[ssa_op->op1_use].var) {
250
0
        FAIL("op1 use of " VARFMT " does not match op %d of " INSTRFMT "\n",
251
0
            VAR(ssa_op->op1_use), VAR_NUM(opline->op1.var), INSTR(i));
252
0
      }
253
4.18M
    }
254
9.93M
    if (ssa_op->op2_use >= 0) {
255
1.23M
      if (ssa_op->op2_use >= ssa->vars_count) {
256
0
        FAIL("op2 use %d out of range\n", ssa_op->op2_use);
257
0
      }
258
1.23M
      if (!is_defined(ssa, op_array, ssa_op->op2_use)) {
259
0
        FAIL("op2 use of " VARFMT " in " INSTRFMT " is not defined\n",
260
0
            VAR(ssa_op->op2_use), INSTR(i));
261
0
      }
262
1.23M
      if (!is_in_use_chain(ssa, ssa_op->op2_use, i)) {
263
0
        FAIL("op2 use of " VARFMT " in " INSTRFMT " not in use chain\n",
264
0
            VAR(ssa_op->op2_use), INSTR(i));
265
0
      }
266
1.23M
      if (VAR_NUM(opline->op2.var) != ssa->vars[ssa_op->op2_use].var) {
267
0
        FAIL("op2 use of " VARFMT " does not match op %d of " INSTRFMT "\n",
268
0
            VAR(ssa_op->op2_use), VAR_NUM(opline->op2.var), INSTR(i));
269
0
      }
270
1.23M
    }
271
9.93M
    if (ssa_op->result_use >= 0) {
272
89.6k
      if (ssa_op->result_use >= ssa->vars_count) {
273
0
        FAIL("result use %d out of range\n", ssa_op->result_use);
274
0
      }
275
89.6k
      if (!is_defined(ssa, op_array, ssa_op->result_use)) {
276
0
        FAIL("result use of " VARFMT " in " INSTRFMT " is not defined\n",
277
0
            VAR(ssa_op->result_use), INSTR(i));
278
0
      }
279
89.6k
      if (!is_in_use_chain(ssa, ssa_op->result_use, i)) {
280
0
        FAIL("result use of " VARFMT " in " INSTRFMT " not in use chain\n",
281
0
          VAR(ssa_op->result_use), INSTR(i));
282
0
      }
283
89.6k
      if (VAR_NUM(opline->result.var) != ssa->vars[ssa_op->result_use].var) {
284
0
        FAIL("result use of " VARFMT " does not match op %d of " INSTRFMT "\n",
285
0
            VAR(ssa_op->result_use), VAR_NUM(opline->result.var), INSTR(i));
286
0
      }
287
89.6k
    }
288
9.93M
    if (ssa_op->op1_def >= 0) {
289
525k
      if (ssa_op->op1_def >= ssa->vars_count) {
290
0
        FAIL("op1 def %d out of range\n", ssa_op->op1_def);
291
0
      }
292
525k
      if (ssa->vars[ssa_op->op1_def].definition != i) {
293
0
        FAIL("op1 def of " VARFMT " in " INSTRFMT " invalid\n",
294
0
            VAR(ssa_op->op1_def), INSTR(i));
295
0
      }
296
525k
      if (VAR_NUM(opline->op1.var) != ssa->vars[ssa_op->op1_def].var) {
297
0
        FAIL("op1 def of " VARFMT " does not match op %d of " INSTRFMT "\n",
298
0
            VAR(ssa_op->op1_def), VAR_NUM(opline->op1.var), INSTR(i));
299
0
      }
300
525k
    }
301
9.93M
    if (ssa_op->op2_def >= 0) {
302
26.2k
      if (ssa_op->op2_def >= ssa->vars_count) {
303
0
        FAIL("op2 def %d out of range\n", ssa_op->op2_def);
304
0
      }
305
26.2k
      if (ssa->vars[ssa_op->op2_def].definition != i) {
306
0
        FAIL("op2 def of " VARFMT " in " INSTRFMT " invalid\n",
307
0
            VAR(ssa_op->op2_def), INSTR(i));
308
0
      }
309
26.2k
      if (VAR_NUM(opline->op2.var) != ssa->vars[ssa_op->op2_def].var) {
310
0
        FAIL("op2 def of " VARFMT " does not match op %d of " INSTRFMT "\n",
311
0
            VAR(ssa_op->op2_def), VAR_NUM(opline->op2.var), INSTR(i));
312
0
      }
313
26.2k
    }
314
9.93M
    if (ssa_op->result_def >= 0) {
315
3.79M
      if (ssa_op->result_def >= ssa->vars_count) {
316
0
        FAIL("result def %d out of range\n", ssa_op->result_def);
317
0
      }
318
3.79M
      if (ssa->vars[ssa_op->result_def].definition != i) {
319
0
        FAIL("result def of " VARFMT " in " INSTRFMT " invalid\n",
320
0
            VAR(ssa_op->result_def), INSTR(i));
321
0
      }
322
3.79M
      if (VAR_NUM(opline->result.var) != ssa->vars[ssa_op->result_def].var) {
323
0
        FAIL("result def of " VARFMT " does not match op %d of " INSTRFMT "\n",
324
0
            VAR(ssa_op->result_def), VAR_NUM(opline->result.var), INSTR(i));
325
0
      }
326
3.79M
    }
327
9.93M
  } FOREACH_INSTR_NUM_END();
328
329
  /* Phis */
330
2.11M
  FOREACH_PHI(phi) {
331
2.11M
    uint32_t num_sources = NUM_PHI_SOURCES(phi);
332
2.11M
    for (i = 0; i < num_sources; i++) {
333
1.29M
      int source = phi->sources[i];
334
1.29M
      if (source < 0) {
335
0
        FAIL(VARFMT " negative source\n", VAR(phi->ssa_var));
336
0
      }
337
1.29M
      if (!is_in_phi_use_chain(ssa, source, phi)) {
338
0
        FAIL(VARFMT " not in phi use chain of %d\n", VAR(phi->ssa_var), source);
339
0
      }
340
1.29M
      if (ssa->vars[source].var != ssa->vars[phi->ssa_var].var) {
341
0
        FAIL(VARFMT " source of phi for " VARFMT "\n", VAR(source), VAR(phi->ssa_var));
342
0
      }
343
1.29M
      if (phi->use_chains[i]) {
344
202k
        int j;
345
387k
        for (j = i + 1; j < num_sources; j++) {
346
184k
          if (phi->sources[j] == source && phi->use_chains[j]) {
347
0
            FAIL("use chain for source " VARFMT " of phi " VARFMT
348
0
              " at %d despite earlier use\n", VAR(source), VAR(phi->ssa_var), j);
349
0
          }
350
184k
        }
351
202k
      }
352
1.29M
    }
353
2.11M
    if (ssa->vars[phi->ssa_var].definition_phi != phi) {
354
0
      FAIL(VARFMT " does not define this phi\n", VAR(phi->ssa_var));
355
0
    }
356
2.11M
  } FOREACH_PHI_END();
357
358
  /* Blocks */
359
1.36M
  for (i = 0; i < cfg->blocks_count; i++) {
360
1.02M
    zend_basic_block *block = &cfg->blocks[i];
361
1.02M
    int *predecessors = &cfg->predecessors[block->predecessor_offset];
362
1.02M
    uint32_t j;
363
364
1.02M
    if (i != 0 && block->start < (block-1)->start + (block-1)->len) {
365
0
      FAIL("Block %d start %d smaller previous end %d\n",
366
0
        i, block->start, (block-1)->start + (block-1)->len);
367
0
    }
368
1.02M
    if (i != cfg->blocks_count-1 && block->start + block->len > (block+1)->start) {
369
0
      FAIL("Block %d end %d greater next start %d\n",
370
0
        i, block->start + block->len, (block+1)->start);
371
0
    }
372
373
8.28M
    for (j = block->start; j < block->start + block->len; j++) {
374
7.26M
      if (cfg->map[j] != i) {
375
0
        FAIL("Instr " INSTRFMT " not associated with block %d\n", INSTR(j), i);
376
0
      }
377
7.26M
    }
378
379
1.02M
    if (!(block->flags & ZEND_BB_REACHABLE)) {
380
26.4k
      if (ssa->blocks[i].phis) {
381
0
        FAIL("Unreachable block %d has phis\n", i);
382
0
      }
383
26.4k
      continue;
384
26.4k
    }
385
386
1.96M
    for (uint32_t s = 0; s < block->successors_count; s++) {
387
970k
      zend_basic_block *next_block;
388
970k
      if (block->successors[s] < 0) {
389
0
        FAIL("Successor number %d of %d negative", s, i);
390
0
      }
391
970k
      next_block = &cfg->blocks[block->successors[s]];
392
970k
      if (!(next_block->flags & ZEND_BB_REACHABLE)) {
393
0
        FAIL("Successor %d of %d not reachable\n", block->successors[s], i);
394
0
      }
395
970k
      if (!is_in_predecessors(cfg, next_block, i)) {
396
0
        FAIL("Block %d predecessors missing %d\n", block->successors[s], i);
397
0
      }
398
970k
    }
399
400
1.96M
    for (j = 0; j < block->predecessors_count; j++) {
401
969k
      if (predecessors[j] >= 0) {
402
969k
        zend_basic_block *prev_block = &cfg->blocks[predecessors[j]];
403
969k
        if (!(prev_block->flags & ZEND_BB_REACHABLE)) {
404
0
          FAIL("Predecessor %d of %d not reachable\n", predecessors[j], i);
405
0
        }
406
969k
        if (!is_in_successors(prev_block, i)) {
407
0
          FAIL("Block %d successors missing %d\n", predecessors[j], i);
408
0
        }
409
2.61M
        for (uint32_t k = 0; k < block->predecessors_count; k++) {
410
1.64M
          if (k != j && predecessors[k] == predecessors[j]) {
411
0
            FAIL("Block %d has duplicate predecessor %d\n", i, predecessors[j]);
412
0
          }
413
1.64M
        }
414
969k
      }
415
969k
    }
416
999k
  }
417
418
339k
finish:
419
339k
  if (status == FAILURE) {
420
0
    zend_dump_op_array(op_array, ZEND_DUMP_SSA, "at SSA integrity verification", ssa);
421
    ZEND_ASSERT(0 && "SSA integrity verification failed");
422
0
  }
423
339k
}