View Javadoc
1   package mockit.asm.controlFlow;
2   
3   import static mockit.asm.jvmConstants.Opcodes.ATHROW;
4   import static mockit.asm.jvmConstants.Opcodes.GETFIELD;
5   import static mockit.asm.jvmConstants.Opcodes.GETSTATIC;
6   import static mockit.asm.jvmConstants.Opcodes.GOTO;
7   import static mockit.asm.jvmConstants.Opcodes.INVOKEDYNAMIC;
8   import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
9   import static mockit.asm.jvmConstants.Opcodes.IRETURN;
10  import static mockit.asm.jvmConstants.Opcodes.NEW;
11  import static mockit.asm.jvmConstants.Opcodes.NEWARRAY;
12  import static mockit.asm.jvmConstants.Opcodes.PUTFIELD;
13  import static mockit.asm.jvmConstants.Opcodes.PUTSTATIC;
14  import static mockit.asm.jvmConstants.Opcodes.RETURN;
15  
16  import edu.umd.cs.findbugs.annotations.NonNull;
17  import edu.umd.cs.findbugs.annotations.Nullable;
18  
19  import mockit.asm.constantPool.ConstantPoolGeneration;
20  import mockit.asm.constantPool.Item;
21  import mockit.asm.constantPool.LongValueItem;
22  import mockit.asm.constantPool.StringItem;
23  import mockit.asm.constantPool.TypeOrMemberItem;
24  import mockit.asm.jvmConstants.JVMInstruction;
25  import mockit.asm.util.ByteVector;
26  
27  import org.checkerframework.checker.index.qual.NonNegative;
28  
29  /**
30   * The control flow graph analysis algorithm, used to compute the maximum stack size for a method or constructor.
31   * <p>
32   * A control flow graph contains one node per "basic block", and one edge per "jump" from one basic block to another.
33   * Each node (i.e., each basic block) is represented by the Label object that corresponds to the first instruction of
34   * this basic block. Each node also stores the list of its successors in the graph, as a linked list of Edge objects.
35   */
36  @SuppressWarnings("OverlyComplexClass")
37  public final class CFGAnalysis {
38      @NonNull
39      private final ConstantPoolGeneration cp;
40      @NonNull
41      private final String classDesc;
42      @NonNull
43      private final ByteVector code;
44  
45      /**
46       * Indicates whether frames AND max stack/locals must be automatically computed, or if only max stack/locals must
47       * be.
48       */
49      private final boolean computeFrames;
50  
51      /**
52       * A list of labels. This list is the list of basic blocks in the method, i.e. a list of Label objects linked to
53       * each other by their {@link Label#successor} field, in the order they are visited, and starting with the first
54       * basic block.
55       */
56      @NonNull
57      private final Label labels;
58  
59      /**
60       * The previous basic block.
61       */
62      @Nullable
63      private Label previousBlock;
64  
65      /**
66       * The current basic block.
67       */
68      @Nullable
69      private Label currentBlock;
70  
71      /**
72       * The (relative) stack size after the last visited instruction. This size is relative to the beginning of the
73       * current basic block, i.e., the true stack size after the last visited instruction is equal to the
74       * {@link Label#inputStackTop beginStackSize} of the current basic block plus <code>stackSize</code>.
75       */
76      @NonNegative
77      private int stackSize;
78  
79      /**
80       * The (relative) maximum stack size after the last visited instruction. This size is relative to the beginning of
81       * the current basic block, i.e., the true maximum stack size after the last visited instruction is equal to the
82       * {@link Label#inputStackTop beginStackSize} of the current basic block plus <code>stackSize</code>.
83       */
84      @NonNegative
85      private int maxStackSize;
86  
87      public CFGAnalysis(@NonNull ConstantPoolGeneration cp, @NonNull String classDesc, @NonNull ByteVector code,
88              boolean computeFrames) {
89          this.cp = cp;
90          this.classDesc = classDesc;
91          this.code = code;
92          this.computeFrames = computeFrames;
93  
94          labels = new Label();
95          labels.markAsPushed();
96          updateCurrentBlockForLabelBeforeNextInstruction(labels);
97      }
98  
99      @NonNull
100     public Label getLabelForFirstBasicBlock() {
101         return labels;
102     }
103 
104     public Frame getFirstFrame() {
105         return labels.frame;
106     }
107 
108     @Nullable
109     public Label getLabelForCurrentBasicBlock() {
110         return currentBlock;
111     }
112 
113     public void updateCurrentBlockForZeroOperandInstruction(int opcode) {
114         if (currentBlock != null) {
115             if (computeFrames) {
116                 currentBlock.frame.execute(opcode);
117             } else {
118                 int sizeVariation = JVMInstruction.SIZE[opcode];
119                 updateStackSize(sizeVariation);
120             }
121 
122             // If opcode == ATHROW or xRETURN, ends current block (no successor).
123             if (opcode >= IRETURN && opcode <= RETURN || opcode == ATHROW) {
124                 noSuccessor();
125             }
126         }
127     }
128 
129     // Updates current and max stack sizes.
130     private void updateStackSize(int sizeVariation) {
131         int newSize = stackSize + sizeVariation;
132 
133         if (newSize > maxStackSize) {
134             maxStackSize = newSize;
135         }
136 
137         stackSize = newSize;
138     }
139 
140     /**
141      * Ends the current basic block. This method must be used in the case where the current basic block does not have
142      * any successor.
143      */
144     private void noSuccessor() {
145         if (computeFrames) {
146             Label l = new Label();
147             l.frame = new Frame(cp, l);
148             l.resolve(code);
149             // noinspection ConstantConditions
150             previousBlock.successor = l;
151             previousBlock = l;
152         } else {
153             // noinspection ConstantConditions
154             currentBlock.outputStackMax = maxStackSize;
155         }
156 
157         currentBlock = null;
158     }
159 
160     /**
161      * Adds a successor to the {@link #currentBlock currentBlock} block.
162      *
163      * @param info
164      *            information about the control flow edge to be added.
165      * @param successor
166      *            the successor block to be added to the current block.
167      */
168     private void addSuccessor(int info, @NonNull Label successor) {
169         // Creates and initializes an Edge object...
170         Edge edge = new Edge(info, successor);
171 
172         // ...and adds it to the successor list of the current block.
173         // noinspection ConstantConditions
174         currentBlock.setSuccessors(edge);
175     }
176 
177     public void updateCurrentBlockForSingleIntOperandInstruction(int opcode, int operand) {
178         if (currentBlock != null) {
179             if (computeFrames) {
180                 currentBlock.frame.executeINT(opcode, operand);
181             } else if (opcode != NEWARRAY) { // updates stack size only for NEWARRAY (variation = 0 for BIPUSH or
182                 // SIPUSH)
183                 updateStackSize(1);
184             }
185         }
186     }
187 
188     public void updateCurrentBlockForLocalVariableInstruction(int opcode, @NonNegative int varIndex) {
189         if (currentBlock != null) {
190             if (computeFrames) {
191                 currentBlock.frame.executeVAR(opcode, varIndex);
192             } else { // xLOAD or xSTORE
193                 int sizeVariation = JVMInstruction.SIZE[opcode];
194                 updateStackSize(sizeVariation);
195             }
196         }
197     }
198 
199     public void updateCurrentBlockForTypeInstruction(int opcode, @NonNull StringItem typeItem) {
200         if (currentBlock != null) {
201             if (computeFrames) {
202                 currentBlock.frame.executeTYPE(opcode, code.getLength(), typeItem);
203             } else if (opcode == NEW) { // updates stack size for NEW only; no change for ANEWARRAY, CHECKCAST,
204                 // INSTANCEOF
205                 updateStackSize(1);
206             }
207         }
208     }
209 
210     public void updateCurrentBlockForFieldInstruction(int opcode, @NonNull TypeOrMemberItem fieldItem,
211             @NonNull String fieldTypeDesc) {
212         if (currentBlock != null) {
213             if (computeFrames) {
214                 currentBlock.frame.execute(opcode, fieldItem);
215             } else {
216                 char typeCode = fieldTypeDesc.charAt(0);
217                 int sizeVariation = computeSizeVariationForFieldAccess(opcode, typeCode);
218                 updateStackSize(sizeVariation);
219             }
220         }
221     }
222 
223     private static int computeSizeVariationForFieldAccess(int fieldAccessOpcode, char fieldTypeCode) {
224         boolean doubleSizeType = fieldTypeCode == 'D' || fieldTypeCode == 'J';
225 
226         switch (fieldAccessOpcode) {
227             case GETSTATIC:
228                 return doubleSizeType ? 2 : 1;
229             case PUTSTATIC:
230                 return doubleSizeType ? -2 : -1;
231             case GETFIELD:
232                 return doubleSizeType ? 1 : 0;
233             case PUTFIELD:
234                 return doubleSizeType ? -3 : -2;
235             default:
236                 throw new IllegalArgumentException("Unknown field access opcode: " + fieldAccessOpcode);
237         }
238     }
239 
240     public void updateCurrentBlockForInvokeInstruction(@NonNull TypeOrMemberItem invokeItem, int opcode,
241             @NonNull String desc) {
242         if (currentBlock != null) {
243             if (computeFrames) {
244                 currentBlock.frame.execute(opcode, invokeItem);
245             } else {
246                 int argSize = invokeItem.getArgSizeComputingIfNeeded(desc);
247                 int sizeVariation = -(argSize >> 2) + (argSize & 0x03);
248 
249                 if (opcode == INVOKESTATIC || opcode == INVOKEDYNAMIC) {
250                     sizeVariation++;
251                 }
252 
253                 updateStackSize(sizeVariation);
254             }
255         }
256     }
257 
258     @Nullable
259     public Label updateCurrentBlockForJumpInstruction(int opcode, @NonNull Label label) {
260         Label nextInsn = null;
261 
262         if (currentBlock != null) {
263             if (computeFrames) {
264                 currentBlock.frame.executeJUMP(opcode);
265 
266                 // 'label' is the target of a jump instruction.
267                 label.getFirst().markAsTarget();
268 
269                 // Adds 'label' as a successor of this basic block.
270                 addSuccessor(Edge.NORMAL, label);
271 
272                 if (opcode != GOTO) {
273                     // Creates a Label for the next basic block.
274                     nextInsn = new Label();
275                 }
276             } else {
277                 // Updates current stack size (max size unchanged because size variation always negative in this case).
278                 stackSize += JVMInstruction.SIZE[opcode];
279                 addSuccessor(stackSize, label);
280             }
281         }
282 
283         return nextInsn;
284     }
285 
286     public void updateCurrentBlockForJumpTarget(int opcode, @Nullable Label nextInsn) {
287         if (currentBlock != null) {
288             if (nextInsn != null) {
289                 // If the jump instruction is not a GOTO, the next instruction is also a successor of this instruction.
290                 // Calling visitLabel adds the label of this next instruction as a successor of the current block, and
291                 // starts a new basic block.
292                 updateCurrentBlockForLabelBeforeNextInstruction(nextInsn);
293             }
294 
295             if (opcode == GOTO) {
296                 noSuccessor();
297             }
298         }
299     }
300 
301     public void updateCurrentBlockForLabelBeforeNextInstruction(@NonNull Label label) {
302         // Resolves previous forward references to label, if any.
303         label.resolve(code);
304 
305         if (label.isDebug()) {
306             return;
307         }
308 
309         if (computeFrames) {
310             if (currentBlock != null) {
311                 if (label.position == currentBlock.position) {
312                     // Successive labels, do not start a new basic block.
313                     currentBlock.markAsTarget(label);
314                     label.frame = currentBlock.frame;
315                     return;
316                 }
317 
318                 // Ends current block (with one new successor).
319                 addSuccessor(Edge.NORMAL, label);
320             }
321 
322             // Begins a new current block.
323             currentBlock = label;
324 
325             if (label.frame == null) {
326                 label.frame = new Frame(cp, label);
327             }
328 
329             // Updates the basic block list.
330             if (previousBlock != null) {
331                 if (label.position == previousBlock.position) {
332                     previousBlock.markAsTarget(label);
333                     label.frame = previousBlock.frame;
334                     currentBlock = previousBlock;
335                     return;
336                 }
337 
338                 previousBlock.successor = label;
339             }
340         } else {
341             if (currentBlock != null) {
342                 // Ends current block (with one new successor).
343                 currentBlock.outputStackMax = maxStackSize;
344                 addSuccessor(stackSize, label);
345             }
346 
347             // Begins a new current block
348             currentBlock = label;
349 
350             // Resets the relative current and max stack sizes.
351             stackSize = 0;
352             maxStackSize = 0;
353 
354             // Updates the basic block list.
355             if (previousBlock != null) {
356                 previousBlock.successor = label;
357             }
358         }
359 
360         previousBlock = label;
361     }
362 
363     public void updateCurrentBlockForLDCInstruction(@NonNull Item constItem) {
364         if (currentBlock != null) {
365             if (computeFrames) {
366                 currentBlock.frame.executeLDC(constItem);
367             } else {
368                 int sizeVariation = constItem instanceof LongValueItem ? 2 : 1;
369                 updateStackSize(sizeVariation);
370             }
371         }
372     }
373 
374     public void updateCurrentBlockForIINCInstruction(@NonNegative int varIndex) {
375         if (currentBlock != null && computeFrames) {
376             currentBlock.frame.executeIINC(varIndex);
377         }
378     }
379 
380     public void updateCurrentBlockForSwitchInstruction(@NonNull Label dflt, @NonNull Label[] caseLabels) {
381         if (currentBlock != null) {
382             if (computeFrames) {
383                 currentBlock.frame.executeSWITCH();
384 
385                 // Adds current block successors.
386                 addSuccessor(Edge.NORMAL, dflt);
387                 dflt.getFirst().markAsTarget();
388 
389                 for (Label label : caseLabels) {
390                     addSuccessor(Edge.NORMAL, label);
391                     label.getFirst().markAsTarget();
392                 }
393             } else {
394                 // Updates current stack size (max stack size unchanged).
395                 stackSize--;
396 
397                 // Adds current block successors.
398                 addSuccessor(stackSize, dflt);
399                 addSuccessorForEachCase(caseLabels);
400             }
401 
402             // Ends current block.
403             noSuccessor();
404         }
405     }
406 
407     private void addSuccessorForEachCase(@NonNull Label[] caseLabels) {
408         for (Label label : caseLabels) {
409             addSuccessor(stackSize, label);
410         }
411     }
412 
413     public void updateCurrentBlockForMULTIANEWARRAYInstruction(@NonNull StringItem arrayTypeItem,
414             @NonNegative int dims) {
415         if (currentBlock != null) {
416             if (computeFrames) {
417                 currentBlock.frame.executeMULTIANEWARRAY(dims, arrayTypeItem);
418             } else {
419                 // Updates current stack size (max stack size unchanged because stack size variation always negative or
420                 // 0).
421                 stackSize += 1 - dims;
422             }
423         }
424     }
425 
426     /**
427      * Fix point algorithm: mark the first basic block as 'changed' (i.e. put it in the 'changed' list) and, while there
428      * are changed basic blocks, choose one, mark it as unchanged, and update its successors (which can be changed in
429      * the process).
430      */
431     @NonNegative
432     public int computeMaxStackSizeFromComputedFrames() {
433         int max = 0;
434         Label changed = labels;
435 
436         while (changed != null) {
437             // Removes a basic block from the list of changed basic blocks.
438             Label l = changed;
439             changed = changed.next;
440             l.next = null;
441             Frame frame = l.frame;
442 
443             // A reachable jump target must be stored in the stack map.
444             if (l.isTarget()) {
445                 l.markAsStoringFrame();
446             }
447 
448             // All visited labels are reachable, by definition.
449             l.markAsReachable();
450 
451             // Updates the (absolute) maximum stack size.
452             int blockMax = frame.inputStack.length + l.outputStackMax;
453 
454             if (blockMax > max) {
455                 max = blockMax;
456             }
457 
458             changed = updateSuccessorsOfCurrentBasicBlock(changed, frame, l);
459         }
460 
461         return max;
462     }
463 
464     @Nullable
465     private Label updateSuccessorsOfCurrentBasicBlock(@Nullable Label changed, @NonNull Frame frame,
466             @NonNull Label label) {
467         Edge edge = label.successors;
468 
469         while (edge != null) {
470             Label n = edge.successor.getFirst();
471             boolean change = frame.merge(classDesc, n.frame, edge.info);
472 
473             if (change && n.next == null) {
474                 // If n has changed and is not already in the 'changed' list, adds it to this list.
475                 n.next = changed;
476 
477                 // noinspection AssignmentToMethodParameter
478                 changed = n;
479             }
480 
481             edge = edge.next;
482         }
483 
484         return changed;
485     }
486 
487     /**
488      * Control flow analysis algorithm: while the block stack is not empty, pop a block from this stack, update the max
489      * stack size, compute the true (non relative) begin stack size of the successors of this block, and push these
490      * successors onto the stack (unless they have already been pushed onto the stack). Note: by hypothesis, the
491      * {@link Label#inputStackTop} of the blocks in the block stack are the true (non relative) beginning stack sizes of
492      * these blocks.
493      */
494     @NonNegative
495     public int computeMaxStackSize() {
496         int max = 0;
497         Label stack = labels;
498 
499         while (stack != null) {
500             // Pops a block from the stack.
501             Label label = stack;
502             stack = stack.next;
503 
504             // Computes the true (non relative) max stack size of this block.
505             int start = label.inputStackTop;
506             int blockMax = start + label.outputStackMax;
507 
508             // Updates the global max stack size.
509             if (blockMax > max) {
510                 max = blockMax;
511             }
512 
513             stack = analyzeBlockSuccessors(stack, label, start);
514         }
515 
516         return max;
517     }
518 
519     @Nullable
520     private static Label analyzeBlockSuccessors(@Nullable Label stack, @NonNull Label label, @NonNegative int start) {
521         Edge block = label.successors;
522 
523         while (block != null) {
524             Label successor = block.successor;
525 
526             // If this successor has not already been pushed...
527             if (!successor.isPushed()) {
528                 // computes its true beginning stack size...
529                 successor.inputStackTop = block.info == Edge.EXCEPTION ? 1 : start + block.info;
530 
531                 // ...and pushes it onto the stack.
532                 successor.markAsPushed();
533                 successor.next = stack;
534 
535                 // noinspection AssignmentToMethodParameter
536                 stack = successor;
537             }
538 
539             block = block.next;
540         }
541 
542         return stack;
543     }
544 }