View Javadoc
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) {
94          this.cp = cp;
95          this.classDesc = classDesc;
96          this.code = code;
97          this.computeFrames = computeFrames;
98  
99          labels = new Label();
100         labels.markAsPushed();
101         updateCurrentBlockForLabelBeforeNextInstruction(labels);
102     }
103 
104     @NonNull
105     public Label getLabelForFirstBasicBlock() {
106         return labels;
107     }
108 
109     public Frame getFirstFrame() {
110         return labels.frame;
111     }
112 
113     @Nullable
114     public Label getLabelForCurrentBasicBlock() {
115         return currentBlock;
116     }
117 
118     public void updateCurrentBlockForZeroOperandInstruction(int opcode) {
119         if (currentBlock != null) {
120             if (computeFrames) {
121                 currentBlock.frame.execute(opcode);
122             } else {
123                 int sizeVariation = JVMInstruction.SIZE[opcode];
124                 updateStackSize(sizeVariation);
125             }
126 
127             // If opcode == ATHROW or xRETURN, ends current block (no successor).
128             if (opcode >= IRETURN && opcode <= RETURN || opcode == ATHROW) {
129                 noSuccessor();
130             }
131         }
132     }
133 
134     // Updates current and max stack sizes.
135     private void updateStackSize(int sizeVariation) {
136         int newSize = stackSize + sizeVariation;
137 
138         if (newSize > maxStackSize) {
139             maxStackSize = newSize;
140         }
141 
142         stackSize = newSize;
143     }
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) {
151             Label l = new Label();
152             l.frame = new Frame(cp, l);
153             l.resolve(code);
154             // noinspection ConstantConditions
155             previousBlock.successor = l;
156             previousBlock = l;
157         } else {
158             // noinspection ConstantConditions
159             currentBlock.outputStackMax = maxStackSize;
160         }
161 
162         currentBlock = null;
163     }
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);
176 
177         // ...and adds it to the successor list of the current block.
178         // noinspection ConstantConditions
179         currentBlock.setSuccessors(edge);
180     }
181 
182     public void updateCurrentBlockForSingleIntOperandInstruction(int opcode, int operand) {
183         if (currentBlock != null) {
184             if (computeFrames) {
185                 currentBlock.frame.executeINT(opcode, operand);
186             } else if (opcode != NEWARRAY) { // updates stack size only for NEWARRAY (variation = 0 for BIPUSH or
187                 // SIPUSH)
188                 updateStackSize(1);
189             }
190         }
191     }
192 
193     public void updateCurrentBlockForLocalVariableInstruction(int opcode, @NonNegative int varIndex) {
194         if (currentBlock != null) {
195             if (computeFrames) {
196                 currentBlock.frame.executeVAR(opcode, varIndex);
197             } else { // xLOAD or xSTORE
198                 int sizeVariation = JVMInstruction.SIZE[opcode];
199                 updateStackSize(sizeVariation);
200             }
201         }
202     }
203 
204     public void updateCurrentBlockForTypeInstruction(int opcode, @NonNull StringItem typeItem) {
205         if (currentBlock != null) {
206             if (computeFrames) {
207                 currentBlock.frame.executeTYPE(opcode, code.getLength(), typeItem);
208             } else if (opcode == NEW) { // updates stack size for NEW only; no change for ANEWARRAY, CHECKCAST,
209                 // INSTANCEOF
210                 updateStackSize(1);
211             }
212         }
213     }
214 
215     public void updateCurrentBlockForFieldInstruction(int opcode, @NonNull TypeOrMemberItem fieldItem,
216             @NonNull String fieldTypeDesc) {
217         if (currentBlock != null) {
218             if (computeFrames) {
219                 currentBlock.frame.execute(opcode, fieldItem);
220             } else {
221                 char typeCode = fieldTypeDesc.charAt(0);
222                 int sizeVariation = computeSizeVariationForFieldAccess(opcode, typeCode);
223                 updateStackSize(sizeVariation);
224             }
225         }
226     }
227 
228     private static int computeSizeVariationForFieldAccess(int fieldAccessOpcode, char fieldTypeCode) {
229         boolean doubleSizeType = fieldTypeCode == 'D' || fieldTypeCode == 'J';
230 
231         switch (fieldAccessOpcode) {
232             case GETSTATIC:
233                 return doubleSizeType ? 2 : 1;
234             case PUTSTATIC:
235                 return doubleSizeType ? -2 : -1;
236             case GETFIELD:
237                 return doubleSizeType ? 1 : 0;
238             case PUTFIELD:
239                 return doubleSizeType ? -3 : -2;
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) {
248             if (computeFrames) {
249                 currentBlock.frame.execute(opcode, invokeItem);
250             } else {
251                 int argSize = invokeItem.getArgSizeComputingIfNeeded(desc);
252                 int sizeVariation = -(argSize >> 2) + (argSize & 0x03);
253 
254                 if (opcode == INVOKESTATIC || opcode == INVOKEDYNAMIC) {
255                     sizeVariation++;
256                 }
257 
258                 updateStackSize(sizeVariation);
259             }
260         }
261     }
262 
263     @Nullable
264     public Label updateCurrentBlockForJumpInstruction(int opcode, @NonNull Label label) {
265         Label nextInsn = null;
266 
267         if (currentBlock != null) {
268             if (computeFrames) {
269                 currentBlock.frame.executeJUMP(opcode);
270 
271                 // 'label' is the target of a jump instruction.
272                 label.getFirst().markAsTarget();
273 
274                 // Adds 'label' as a successor of this basic block.
275                 addSuccessor(Edge.NORMAL, label);
276 
277                 if (opcode != GOTO) {
278                     // Creates a Label for the next basic block.
279                     nextInsn = new Label();
280                 }
281             } else {
282                 // Updates current stack size (max size unchanged because size variation always negative in this case).
283                 stackSize += JVMInstruction.SIZE[opcode];
284                 addSuccessor(stackSize, label);
285             }
286         }
287 
288         return nextInsn;
289     }
290 
291     public void updateCurrentBlockForJumpTarget(int opcode, @Nullable Label nextInsn) {
292         if (currentBlock != null) {
293             if (nextInsn != null) {
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);
298             }
299 
300             if (opcode == GOTO) {
301                 noSuccessor();
302             }
303         }
304     }
305 
306     public void updateCurrentBlockForLabelBeforeNextInstruction(@NonNull Label label) {
307         // Resolves previous forward references to label, if any.
308         label.resolve(code);
309 
310         if (label.isDebug()) {
311             return;
312         }
313 
314         if (computeFrames) {
315             if (currentBlock != null) {
316                 if (label.position == currentBlock.position) {
317                     // Successive labels, do not start a new basic block.
318                     currentBlock.markAsTarget(label);
319                     label.frame = currentBlock.frame;
320                     return;
321                 }
322 
323                 // Ends current block (with one new successor).
324                 addSuccessor(Edge.NORMAL, label);
325             }
326 
327             // Begins a new current block.
328             currentBlock = label;
329 
330             if (label.frame == null) {
331                 label.frame = new Frame(cp, label);
332             }
333 
334             // Updates the basic block list.
335             if (previousBlock != null) {
336                 if (label.position == previousBlock.position) {
337                     previousBlock.markAsTarget(label);
338                     label.frame = previousBlock.frame;
339                     currentBlock = previousBlock;
340                     return;
341                 }
342 
343                 previousBlock.successor = label;
344             }
345         } else {
346             if (currentBlock != null) {
347                 // Ends current block (with one new successor).
348                 currentBlock.outputStackMax = maxStackSize;
349                 addSuccessor(stackSize, label);
350             }
351 
352             // Begins a new current block
353             currentBlock = label;
354 
355             // Resets the relative current and max stack sizes.
356             stackSize = 0;
357             maxStackSize = 0;
358 
359             // Updates the basic block list.
360             if (previousBlock != null) {
361                 previousBlock.successor = label;
362             }
363         }
364 
365         previousBlock = label;
366     }
367 
368     public void updateCurrentBlockForLDCInstruction(@NonNull Item constItem) {
369         if (currentBlock != null) {
370             if (computeFrames) {
371                 currentBlock.frame.executeLDC(constItem);
372             } else {
373                 int sizeVariation = constItem instanceof LongValueItem ? 2 : 1;
374                 updateStackSize(sizeVariation);
375             }
376         }
377     }
378 
379     public void updateCurrentBlockForIINCInstruction(@NonNegative int varIndex) {
380         if (currentBlock != null && computeFrames) {
381             currentBlock.frame.executeIINC(varIndex);
382         }
383     }
384 
385     public void updateCurrentBlockForSwitchInstruction(@NonNull Label dflt, @NonNull Label[] caseLabels) {
386         if (currentBlock != null) {
387             if (computeFrames) {
388                 currentBlock.frame.executeSWITCH();
389 
390                 // Adds current block successors.
391                 addSuccessor(Edge.NORMAL, dflt);
392                 dflt.getFirst().markAsTarget();
393 
394                 for (Label label : caseLabels) {
395                     addSuccessor(Edge.NORMAL, label);
396                     label.getFirst().markAsTarget();
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();
409         }
410     }
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) {
421             if (computeFrames) {
422                 currentBlock.frame.executeMULTIANEWARRAY(dims, arrayTypeItem);
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     }
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;
439         Label changed = labels;
440 
441         while (changed != null) {
442             // Removes a basic block from the list of changed basic blocks.
443             Label l = changed;
444             changed = changed.next;
445             l.next = null;
446             Frame frame = l.frame;
447 
448             // A reachable jump target must be stored in the stack map.
449             if (l.isTarget()) {
450                 l.markAsStoringFrame();
451             }
452 
453             // All visited labels are reachable, by definition.
454             l.markAsReachable();
455 
456             // Updates the (absolute) maximum stack size.
457             int blockMax = frame.inputStack.length + l.outputStackMax;
458 
459             if (blockMax > max) {
460                 max = blockMax;
461             }
462 
463             changed = updateSuccessorsOfCurrentBasicBlock(changed, frame, l);
464         }
465 
466         return max;
467     }
468 
469     @Nullable
470     private Label updateSuccessorsOfCurrentBasicBlock(@Nullable Label changed, @NonNull Frame frame,
471             @NonNull Label label) {
472         Edge edge = label.successors;
473 
474         while (edge != null) {
475             Label n = edge.successor.getFirst();
476             boolean change = frame.merge(classDesc, n.frame, edge.info);
477 
478             if (change && n.next == null) {
479                 // If n has changed and is not already in the 'changed' list, adds it to this list.
480                 n.next = changed;
481 
482                 // noinspection AssignmentToMethodParameter
483                 changed = n;
484             }
485 
486             edge = edge.next;
487         }
488 
489         return changed;
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;
502         Label stack = labels;
503 
504         while (stack != null) {
505             // Pops a block from the stack.
506             Label label = stack;
507             stack = stack.next;
508 
509             // Computes the true (non relative) max stack size of this block.
510             int start = label.inputStackTop;
511             int blockMax = start + label.outputStackMax;
512 
513             // Updates the global max stack size.
514             if (blockMax > max) {
515                 max = blockMax;
516             }
517 
518             stack = analyzeBlockSuccessors(stack, label, start);
519         }
520 
521         return max;
522     }
523 
524     @Nullable
525     private static Label analyzeBlockSuccessors(@Nullable Label stack, @NonNull Label label, @NonNegative int start) {
526         Edge block = label.successors;
527 
528         while (block != null) {
529             Label successor = block.successor;
530 
531             // If this successor has not already been pushed...
532             if (!successor.isPushed()) {
533                 // computes its true beginning stack size...
534                 successor.inputStackTop = block.info == Edge.EXCEPTION ? 1 : start + block.info;
535 
536                 // ...and pushes it onto the stack.
537                 successor.markAsPushed();
538                 successor.next = stack;
539 
540                 // noinspection AssignmentToMethodParameter
541                 stack = successor;
542             }
543 
544             block = block.next;
545         }
546 
547         return stack;
548     }
549 }