Coverage Report

Created: 2026-06-02 06:39

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