• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / jmockit1 / 496

15 Nov 2025 05:33PM UTC coverage: 72.192% (-0.008%) from 72.2%
496

push

github

web-flow
Merge pull request #412 from hazendaz/renovate/major-spring-core

Update spring core to v7 (major)

5677 of 8360 branches covered (67.91%)

Branch coverage included in aggregate %.

11922 of 16018 relevant lines covered (74.43%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.98
/main/src/main/java/mockit/asm/controlFlow/CFGAnalysis.java
1
/*
2
 * MIT License
3
 * Copyright (c) 2006-2025 JMockit developers
4
 * See LICENSE file for full license text.
5
 */
6
package mockit.asm.controlFlow;
7

8
import static mockit.asm.jvmConstants.Opcodes.ATHROW;
9
import static mockit.asm.jvmConstants.Opcodes.GETFIELD;
10
import static mockit.asm.jvmConstants.Opcodes.GETSTATIC;
11
import static mockit.asm.jvmConstants.Opcodes.GOTO;
12
import static mockit.asm.jvmConstants.Opcodes.INVOKEDYNAMIC;
13
import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
14
import static mockit.asm.jvmConstants.Opcodes.IRETURN;
15
import static mockit.asm.jvmConstants.Opcodes.NEW;
16
import static mockit.asm.jvmConstants.Opcodes.NEWARRAY;
17
import static mockit.asm.jvmConstants.Opcodes.PUTFIELD;
18
import static mockit.asm.jvmConstants.Opcodes.PUTSTATIC;
19
import static mockit.asm.jvmConstants.Opcodes.RETURN;
20

21
import edu.umd.cs.findbugs.annotations.NonNull;
22
import edu.umd.cs.findbugs.annotations.Nullable;
23

24
import mockit.asm.constantPool.ConstantPoolGeneration;
25
import mockit.asm.constantPool.Item;
26
import mockit.asm.constantPool.LongValueItem;
27
import mockit.asm.constantPool.StringItem;
28
import mockit.asm.constantPool.TypeOrMemberItem;
29
import mockit.asm.jvmConstants.JVMInstruction;
30
import mockit.asm.util.ByteVector;
31

32
import org.checkerframework.checker.index.qual.NonNegative;
33

34
/**
35
 * The control flow graph analysis algorithm, used to compute the maximum stack size for a method or constructor.
36
 * <p>
37
 * A control flow graph contains one node per "basic block", and one edge per "jump" from one basic block to another.
38
 * Each node (i.e., each basic block) is represented by the Label object that corresponds to the first instruction of
39
 * this basic block. Each node also stores the list of its successors in the graph, as a linked list of Edge objects.
40
 */
41
@SuppressWarnings("OverlyComplexClass")
42
public final class CFGAnalysis {
43
    @NonNull
44
    private final ConstantPoolGeneration cp;
45
    @NonNull
46
    private final String classDesc;
47
    @NonNull
48
    private final ByteVector code;
49

50
    /**
51
     * Indicates whether frames AND max stack/locals must be automatically computed, or if only max stack/locals must
52
     * be.
53
     */
54
    private final boolean computeFrames;
55

56
    /**
57
     * A list of labels. This list is the list of basic blocks in the method, i.e. a list of Label objects linked to
58
     * each other by their {@link Label#successor} field, in the order they are visited, and starting with the first
59
     * basic block.
60
     */
61
    @NonNull
62
    private final Label labels;
63

64
    /**
65
     * The previous basic block.
66
     */
67
    @Nullable
68
    private Label previousBlock;
69

70
    /**
71
     * The current basic block.
72
     */
73
    @Nullable
74
    private Label currentBlock;
75

76
    /**
77
     * The (relative) stack size after the last visited instruction. This size is relative to the beginning of the
78
     * current basic block, i.e., the true stack size after the last visited instruction is equal to the
79
     * {@link Label#inputStackTop beginStackSize} of the current basic block plus <code>stackSize</code>.
80
     */
81
    @NonNegative
82
    private int stackSize;
83

84
    /**
85
     * The (relative) maximum stack size after the last visited instruction. This size is relative to the beginning of
86
     * the current basic block, i.e., the true maximum stack size after the last visited instruction is equal to the
87
     * {@link Label#inputStackTop beginStackSize} of the current basic block plus <code>stackSize</code>.
88
     */
89
    @NonNegative
90
    private int maxStackSize;
91

92
    public CFGAnalysis(@NonNull ConstantPoolGeneration cp, @NonNull String classDesc, @NonNull ByteVector code,
93
            boolean computeFrames) {
1✔
94
        this.cp = cp;
1✔
95
        this.classDesc = classDesc;
1✔
96
        this.code = code;
1✔
97
        this.computeFrames = computeFrames;
1✔
98

99
        labels = new Label();
1✔
100
        labels.markAsPushed();
1✔
101
        updateCurrentBlockForLabelBeforeNextInstruction(labels);
1✔
102
    }
1✔
103

104
    @NonNull
105
    public Label getLabelForFirstBasicBlock() {
106
        return labels;
1✔
107
    }
108

109
    public Frame getFirstFrame() {
110
        return labels.frame;
1✔
111
    }
112

113
    @Nullable
114
    public Label getLabelForCurrentBasicBlock() {
115
        return currentBlock;
×
116
    }
117

118
    public void updateCurrentBlockForZeroOperandInstruction(int opcode) {
119
        if (currentBlock != null) {
1!
120
            if (computeFrames) {
1✔
121
                currentBlock.frame.execute(opcode);
1✔
122
            } else {
123
                int sizeVariation = JVMInstruction.SIZE[opcode];
1✔
124
                updateStackSize(sizeVariation);
1✔
125
            }
126

127
            // If opcode == ATHROW or xRETURN, ends current block (no successor).
128
            if (opcode >= IRETURN && opcode <= RETURN || opcode == ATHROW) {
1✔
129
                noSuccessor();
1✔
130
            }
131
        }
132
    }
1✔
133

134
    // Updates current and max stack sizes.
135
    private void updateStackSize(int sizeVariation) {
136
        int newSize = stackSize + sizeVariation;
1✔
137

138
        if (newSize > maxStackSize) {
1✔
139
            maxStackSize = newSize;
1✔
140
        }
141

142
        stackSize = newSize;
1✔
143
    }
1✔
144

145
    /**
146
     * Ends the current basic block. This method must be used in the case where the current basic block does not have
147
     * any successor.
148
     */
149
    private void noSuccessor() {
150
        if (computeFrames) {
1✔
151
            Label l = new Label();
1✔
152
            l.frame = new Frame(cp, l);
1✔
153
            l.resolve(code);
1✔
154
            // noinspection ConstantConditions
155
            previousBlock.successor = l;
1✔
156
            previousBlock = l;
1✔
157
        } else {
1✔
158
            // noinspection ConstantConditions
159
            currentBlock.outputStackMax = maxStackSize;
1✔
160
        }
161

162
        currentBlock = null;
1✔
163
    }
1✔
164

165
    /**
166
     * Adds a successor to the {@link #currentBlock currentBlock} block.
167
     *
168
     * @param info
169
     *            information about the control flow edge to be added.
170
     * @param successor
171
     *            the successor block to be added to the current block.
172
     */
173
    private void addSuccessor(int info, @NonNull Label successor) {
174
        // Creates and initializes an Edge object...
175
        Edge edge = new Edge(info, successor);
1✔
176

177
        // ...and adds it to the successor list of the current block.
178
        // noinspection ConstantConditions
179
        currentBlock.setSuccessors(edge);
1✔
180
    }
1✔
181

182
    public void updateCurrentBlockForSingleIntOperandInstruction(int opcode, int operand) {
183
        if (currentBlock != null) {
1!
184
            if (computeFrames) {
1✔
185
                currentBlock.frame.executeINT(opcode, operand);
1✔
186
            } else if (opcode != NEWARRAY) { // updates stack size only for NEWARRAY (variation = 0 for BIPUSH or
1!
187
                // SIPUSH)
188
                updateStackSize(1);
1✔
189
            }
190
        }
191
    }
1✔
192

193
    public void updateCurrentBlockForLocalVariableInstruction(int opcode, @NonNegative int varIndex) {
194
        if (currentBlock != null) {
1!
195
            if (computeFrames) {
1✔
196
                currentBlock.frame.executeVAR(opcode, varIndex);
1✔
197
            } else { // xLOAD or xSTORE
198
                int sizeVariation = JVMInstruction.SIZE[opcode];
1✔
199
                updateStackSize(sizeVariation);
1✔
200
            }
201
        }
202
    }
1✔
203

204
    public void updateCurrentBlockForTypeInstruction(int opcode, @NonNull StringItem typeItem) {
205
        if (currentBlock != null) {
1!
206
            if (computeFrames) {
1✔
207
                currentBlock.frame.executeTYPE(opcode, code.getLength(), typeItem);
1✔
208
            } else if (opcode == NEW) { // updates stack size for NEW only; no change for ANEWARRAY, CHECKCAST,
1✔
209
                // INSTANCEOF
210
                updateStackSize(1);
1✔
211
            }
212
        }
213
    }
1✔
214

215
    public void updateCurrentBlockForFieldInstruction(int opcode, @NonNull TypeOrMemberItem fieldItem,
216
            @NonNull String fieldTypeDesc) {
217
        if (currentBlock != null) {
1!
218
            if (computeFrames) {
1✔
219
                currentBlock.frame.execute(opcode, fieldItem);
1✔
220
            } else {
221
                char typeCode = fieldTypeDesc.charAt(0);
1✔
222
                int sizeVariation = computeSizeVariationForFieldAccess(opcode, typeCode);
1✔
223
                updateStackSize(sizeVariation);
1✔
224
            }
225
        }
226
    }
1✔
227

228
    private static int computeSizeVariationForFieldAccess(int fieldAccessOpcode, char fieldTypeCode) {
229
        boolean doubleSizeType = fieldTypeCode == 'D' || fieldTypeCode == 'J';
1!
230

231
        switch (fieldAccessOpcode) {
1!
232
            case GETSTATIC:
233
                return doubleSizeType ? 2 : 1;
×
234
            case PUTSTATIC:
235
                return doubleSizeType ? -2 : -1;
×
236
            case GETFIELD:
237
                return doubleSizeType ? 1 : 0;
1!
238
            case PUTFIELD:
239
                return doubleSizeType ? -3 : -2;
1!
240
            default:
241
                throw new IllegalArgumentException("Unknown field access opcode: " + fieldAccessOpcode);
×
242
        }
243
    }
244

245
    public void updateCurrentBlockForInvokeInstruction(@NonNull TypeOrMemberItem invokeItem, int opcode,
246
            @NonNull String desc) {
247
        if (currentBlock != null) {
1!
248
            if (computeFrames) {
1✔
249
                currentBlock.frame.execute(opcode, invokeItem);
1✔
250
            } else {
251
                int argSize = invokeItem.getArgSizeComputingIfNeeded(desc);
1✔
252
                int sizeVariation = -(argSize >> 2) + (argSize & 0x03);
1✔
253

254
                if (opcode == INVOKESTATIC || opcode == INVOKEDYNAMIC) {
1!
255
                    sizeVariation++;
1✔
256
                }
257

258
                updateStackSize(sizeVariation);
1✔
259
            }
260
        }
261
    }
1✔
262

263
    @Nullable
264
    public Label updateCurrentBlockForJumpInstruction(int opcode, @NonNull Label label) {
265
        Label nextInsn = null;
1✔
266

267
        if (currentBlock != null) {
1!
268
            if (computeFrames) {
1✔
269
                currentBlock.frame.executeJUMP(opcode);
1✔
270

271
                // 'label' is the target of a jump instruction.
272
                label.getFirst().markAsTarget();
1✔
273

274
                // Adds 'label' as a successor of this basic block.
275
                addSuccessor(Edge.NORMAL, label);
1✔
276

277
                if (opcode != GOTO) {
1✔
278
                    // Creates a Label for the next basic block.
279
                    nextInsn = new Label();
1✔
280
                }
281
            } else {
282
                // Updates current stack size (max size unchanged because size variation always negative in this case).
283
                stackSize += JVMInstruction.SIZE[opcode];
1✔
284
                addSuccessor(stackSize, label);
1✔
285
            }
286
        }
287

288
        return nextInsn;
1✔
289
    }
290

291
    public void updateCurrentBlockForJumpTarget(int opcode, @Nullable Label nextInsn) {
292
        if (currentBlock != null) {
1!
293
            if (nextInsn != null) {
1✔
294
                // If the jump instruction is not a GOTO, the next instruction is also a successor of this instruction.
295
                // Calling visitLabel adds the label of this next instruction as a successor of the current block, and
296
                // starts a new basic block.
297
                updateCurrentBlockForLabelBeforeNextInstruction(nextInsn);
1✔
298
            }
299

300
            if (opcode == GOTO) {
1✔
301
                noSuccessor();
1✔
302
            }
303
        }
304
    }
1✔
305

306
    public void updateCurrentBlockForLabelBeforeNextInstruction(@NonNull Label label) {
307
        // Resolves previous forward references to label, if any.
308
        label.resolve(code);
1✔
309

310
        if (label.isDebug()) {
1✔
311
            return;
1✔
312
        }
313

314
        if (computeFrames) {
1✔
315
            if (currentBlock != null) {
1✔
316
                if (label.position == currentBlock.position) {
1✔
317
                    // Successive labels, do not start a new basic block.
318
                    currentBlock.markAsTarget(label);
1✔
319
                    label.frame = currentBlock.frame;
1✔
320
                    return;
1✔
321
                }
322

323
                // Ends current block (with one new successor).
324
                addSuccessor(Edge.NORMAL, label);
1✔
325
            }
326

327
            // Begins a new current block.
328
            currentBlock = label;
1✔
329

330
            if (label.frame == null) {
1!
331
                label.frame = new Frame(cp, label);
1✔
332
            }
333

334
            // Updates the basic block list.
335
            if (previousBlock != null) {
1✔
336
                if (label.position == previousBlock.position) {
1✔
337
                    previousBlock.markAsTarget(label);
1✔
338
                    label.frame = previousBlock.frame;
1✔
339
                    currentBlock = previousBlock;
1✔
340
                    return;
1✔
341
                }
342

343
                previousBlock.successor = label;
1✔
344
            }
345
        } else {
346
            if (currentBlock != null) {
1✔
347
                // Ends current block (with one new successor).
348
                currentBlock.outputStackMax = maxStackSize;
1✔
349
                addSuccessor(stackSize, label);
1✔
350
            }
351

352
            // Begins a new current block
353
            currentBlock = label;
1✔
354

355
            // Resets the relative current and max stack sizes.
356
            stackSize = 0;
1✔
357
            maxStackSize = 0;
1✔
358

359
            // Updates the basic block list.
360
            if (previousBlock != null) {
1✔
361
                previousBlock.successor = label;
1✔
362
            }
363
        }
364

365
        previousBlock = label;
1✔
366
    }
1✔
367

368
    public void updateCurrentBlockForLDCInstruction(@NonNull Item constItem) {
369
        if (currentBlock != null) {
1!
370
            if (computeFrames) {
1✔
371
                currentBlock.frame.executeLDC(constItem);
1✔
372
            } else {
373
                int sizeVariation = constItem instanceof LongValueItem ? 2 : 1;
1!
374
                updateStackSize(sizeVariation);
1✔
375
            }
376
        }
377
    }
1✔
378

379
    public void updateCurrentBlockForIINCInstruction(@NonNegative int varIndex) {
380
        if (currentBlock != null && computeFrames) {
1!
381
            currentBlock.frame.executeIINC(varIndex);
1✔
382
        }
383
    }
1✔
384

385
    public void updateCurrentBlockForSwitchInstruction(@NonNull Label dflt, @NonNull Label[] caseLabels) {
386
        if (currentBlock != null) {
1!
387
            if (computeFrames) {
1!
388
                currentBlock.frame.executeSWITCH();
1✔
389

390
                // Adds current block successors.
391
                addSuccessor(Edge.NORMAL, dflt);
1✔
392
                dflt.getFirst().markAsTarget();
1✔
393

394
                for (Label label : caseLabels) {
1✔
395
                    addSuccessor(Edge.NORMAL, label);
1✔
396
                    label.getFirst().markAsTarget();
1✔
397
                }
398
            } else {
399
                // Updates current stack size (max stack size unchanged).
400
                stackSize--;
×
401

402
                // Adds current block successors.
403
                addSuccessor(stackSize, dflt);
×
404
                addSuccessorForEachCase(caseLabels);
×
405
            }
406

407
            // Ends current block.
408
            noSuccessor();
1✔
409
        }
410
    }
1✔
411

412
    private void addSuccessorForEachCase(@NonNull Label[] caseLabels) {
413
        for (Label label : caseLabels) {
×
414
            addSuccessor(stackSize, label);
×
415
        }
416
    }
×
417

418
    public void updateCurrentBlockForMULTIANEWARRAYInstruction(@NonNull StringItem arrayTypeItem,
419
            @NonNegative int dims) {
420
        if (currentBlock != null) {
1!
421
            if (computeFrames) {
1!
422
                currentBlock.frame.executeMULTIANEWARRAY(dims, arrayTypeItem);
1✔
423
            } else {
424
                // Updates current stack size (max stack size unchanged because stack size variation always negative or
425
                // 0).
426
                stackSize += 1 - dims;
×
427
            }
428
        }
429
    }
1✔
430

431
    /**
432
     * Fix point algorithm: mark the first basic block as 'changed' (i.e. put it in the 'changed' list) and, while there
433
     * are changed basic blocks, choose one, mark it as unchanged, and update its successors (which can be changed in
434
     * the process).
435
     */
436
    @NonNegative
437
    public int computeMaxStackSizeFromComputedFrames() {
438
        int max = 0;
1✔
439
        Label changed = labels;
1✔
440

441
        while (changed != null) {
1✔
442
            // Removes a basic block from the list of changed basic blocks.
443
            Label l = changed;
1✔
444
            changed = changed.next;
1✔
445
            l.next = null;
1✔
446
            Frame frame = l.frame;
1✔
447

448
            // A reachable jump target must be stored in the stack map.
449
            if (l.isTarget()) {
1✔
450
                l.markAsStoringFrame();
1✔
451
            }
452

453
            // All visited labels are reachable, by definition.
454
            l.markAsReachable();
1✔
455

456
            // Updates the (absolute) maximum stack size.
457
            int blockMax = frame.inputStack.length + l.outputStackMax;
1✔
458

459
            if (blockMax > max) {
1✔
460
                max = blockMax;
1✔
461
            }
462

463
            changed = updateSuccessorsOfCurrentBasicBlock(changed, frame, l);
1✔
464
        }
1✔
465

466
        return max;
1✔
467
    }
468

469
    @Nullable
470
    private Label updateSuccessorsOfCurrentBasicBlock(@Nullable Label changed, @NonNull Frame frame,
471
            @NonNull Label label) {
472
        Edge edge = label.successors;
1✔
473

474
        while (edge != null) {
1✔
475
            Label n = edge.successor.getFirst();
1✔
476
            boolean change = frame.merge(classDesc, n.frame, edge.info);
1✔
477

478
            if (change && n.next == null) {
1✔
479
                // If n has changed and is not already in the 'changed' list, adds it to this list.
480
                n.next = changed;
1✔
481

482
                // noinspection AssignmentToMethodParameter
483
                changed = n;
1✔
484
            }
485

486
            edge = edge.next;
1✔
487
        }
1✔
488

489
        return changed;
1✔
490
    }
491

492
    /**
493
     * Control flow analysis algorithm: while the block stack is not empty, pop a block from this stack, update the max
494
     * stack size, compute the true (non relative) begin stack size of the successors of this block, and push these
495
     * successors onto the stack (unless they have already been pushed onto the stack). Note: by hypothesis, the
496
     * {@link Label#inputStackTop} of the blocks in the block stack are the true (non relative) beginning stack sizes of
497
     * these blocks.
498
     */
499
    @NonNegative
500
    public int computeMaxStackSize() {
501
        int max = 0;
1✔
502
        Label stack = labels;
1✔
503

504
        while (stack != null) {
1✔
505
            // Pops a block from the stack.
506
            Label label = stack;
1✔
507
            stack = stack.next;
1✔
508

509
            // Computes the true (non relative) max stack size of this block.
510
            int start = label.inputStackTop;
1✔
511
            int blockMax = start + label.outputStackMax;
1✔
512

513
            // Updates the global max stack size.
514
            if (blockMax > max) {
1✔
515
                max = blockMax;
1✔
516
            }
517

518
            stack = analyzeBlockSuccessors(stack, label, start);
1✔
519
        }
1✔
520

521
        return max;
1✔
522
    }
523

524
    @Nullable
525
    private static Label analyzeBlockSuccessors(@Nullable Label stack, @NonNull Label label, @NonNegative int start) {
526
        Edge block = label.successors;
1✔
527

528
        while (block != null) {
1✔
529
            Label successor = block.successor;
1✔
530

531
            // If this successor has not already been pushed...
532
            if (!successor.isPushed()) {
1✔
533
                // computes its true beginning stack size...
534
                successor.inputStackTop = block.info == Edge.EXCEPTION ? 1 : start + block.info;
1!
535

536
                // ...and pushes it onto the stack.
537
                successor.markAsPushed();
1✔
538
                successor.next = stack;
1✔
539

540
                // noinspection AssignmentToMethodParameter
541
                stack = successor;
1✔
542
            }
543

544
            block = block.next;
1✔
545
        }
1✔
546

547
        return stack;
1✔
548
    }
549
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc