All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.asm.CFGAnalysis Maven / Gradle / Ivy

Go to download

JMockit is a Java toolkit for automated developer testing. It contains mocking/faking APIs and a code coverage tool, supporting both JUnit and TestNG. The mocking APIs allow all kinds of Java code, without testability restrictions, to be tested in isolation from selected dependencies.

There is a newer version: 1.49
Show newest version
package mockit.asm;

import javax.annotation.*;

import static mockit.asm.Opcodes.*;

final class CFGAnalysis
{
   @Nonnull private final ClassWriter cw;
   @Nonnull private final ConstantPoolGeneration cp;
   @Nonnull private final ByteVector code;

   // Fields for the control flow graph analysis algorithm (used to compute the maximum stack size). A control flow graph contains one node
   // per "basic block", and one edge per "jump" from one basic block to another. Each node (i.e., each basic block) is represented by the
   // Label object that corresponds to the first instruction of this basic block. Each node also stores the list of its successors in the
   // graph, as a linked list of Edge objects.
   //

   /**
    * Indicates whether frames AND max stack/locals must be automatically computed, or if only max stack/locals must be.
    */
   private final boolean computeFrames;

   /**
    * A list of labels. This list is the list of basic blocks in the method, i.e. a list of Label objects linked to each other by their
    * {@link Label#successor} field, in the order they are visited by {@link MethodVisitor#visitLabel}, and starting with the first basic
    * block.
    */
   @Nonnull private final Label labels;

   /**
    * The previous basic block.
    */
   @Nullable private Label previousBlock;

   /**
    * The current basic block.
    */
   @Nullable private Label currentBlock;

   /**
    * The (relative) stack size after the last visited instruction. This size is relative to the beginning of the current basic block, i.e.,
    * the true stack size after the last visited instruction is equal to the {@link Label#inputStackTop beginStackSize} of the current basic
    * block plus stackSize.
    */
   @Nonnegative private int stackSize;

   /**
    * The (relative) maximum stack size after the last visited instruction. This size is relative to the beginning of the current basic
    * block, i.e., the true maximum stack size after the last visited instruction is equal to the {@link Label#inputStackTop beginStackSize}
    * of the current basic block plus stackSize.
    */
   @Nonnegative private int maxStackSize;

   CFGAnalysis(@Nonnull ClassWriter cw, @Nonnull ByteVector code, boolean computeFrames) {
      this.cw = cw;
      cp = cw.cp;
      this.code = code;
      this.computeFrames = computeFrames;

      labels = new Label();
      labels.markAsPushed();
      updateCurrentBlockForLabelBeforeNextInstruction(labels);
   }

   @Nonnull Label getLabelForFirstBasicBlock() { return labels; }
   Frame getFirstFrame() { return labels.frame; }
   @Nullable Label getLabelForCurrentBasicBlock() { return currentBlock; }

   void updateCurrentBlockForZeroOperandInstruction(int opcode) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.execute(opcode);
         }
         else {
            int sizeVariation = Frame.SIZE[opcode];
            updateStackSize(sizeVariation);
         }

         // If opcode == ATHROW or xRETURN, ends current block (no successor).
         if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
            noSuccessor();
         }
      }
   }

   // Updates current and max stack sizes.
   private void updateStackSize(int sizeVariation) {
      int newSize = stackSize + sizeVariation;

      if (newSize > maxStackSize) {
         maxStackSize = newSize;
      }

      stackSize = newSize;
   }

   /**
    * Ends the current basic block. This method must be used in the case where the current basic block does not have any successor.
    */
   private void noSuccessor() {
      if (computeFrames) {
         Label l = new Label();
         l.frame = new Frame(cp, l);
         l.resolve(code);
         //noinspection ConstantConditions
         previousBlock.successor = l;
         previousBlock = l;
      }
      else {
         //noinspection ConstantConditions
         currentBlock.outputStackMax = maxStackSize;
      }

      currentBlock = null;
   }

   /**
    * Adds a successor to the {@link #currentBlock currentBlock} block.
    *
    * @param info      information about the control flow edge to be added.
    * @param successor the successor block to be added to the current block.
    */
   private void addSuccessor(int info, @Nonnull Label successor) {
      // Creates and initializes an Edge object...
      Edge b = new Edge(info, successor);

      // ...and adds it to the successor list of the current block.
      //noinspection ConstantConditions
      b.next = currentBlock.successors;
      currentBlock.successors = b;
   }

   void updateCurrentBlockForSingleIntOperandInstruction(int opcode, int operand) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeINT(opcode, operand);
         }
         else if (opcode != NEWARRAY) { // updates stack size only for NEWARRAY (variation = 0 for BIPUSH or SIPUSH)
            updateStackSize(1);
         }
      }
   }

   void updateCurrentBlockForLocalVariableInstruction(int opcode, @Nonnegative int var) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeVAR(opcode, var);
         }
         else { // xLOAD or xSTORE
            int sizeVariation = Frame.SIZE[opcode];
            updateStackSize(sizeVariation);
         }
      }
   }

   void updateCurrentBlockForTypeInstruction(int opcode, @Nonnull StringItem typeItem) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeTYPE(opcode, code.length, typeItem);
         }
         else if (opcode == NEW) { // updates stack size for NEW only; no change for ANEWARRAY, CHECKCAST, INSTANCEOF
            updateStackSize(1);
         }
      }
   }

   void updateCurrentBlockForFieldInstruction(
      int opcode, @Nonnull TypeOrMemberItem fieldItem, @Nonnull String fieldTypeDesc
   ) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.execute(opcode, fieldItem);
         }
         else {
            char typeCode = fieldTypeDesc.charAt(0);
            int sizeVariation = computeSizeVariationForFieldAccess(opcode, typeCode);
            updateStackSize(sizeVariation);
         }
      }
   }

   private static int computeSizeVariationForFieldAccess(int fieldAccessOpcode, char fieldTypeCode) {
      boolean doubleSizeType = fieldTypeCode == 'D' || fieldTypeCode == 'J';

      switch (fieldAccessOpcode) {
         case GETSTATIC: return doubleSizeType ? 2 : 1;
         case PUTSTATIC: return doubleSizeType ? -2 : -1;
         case GETFIELD:  return doubleSizeType ? 1 : 0;
         case PUTFIELD: default: return doubleSizeType ? -3 : -2;
      }
   }

   void updateCurrentBlockForInvokeInstruction(@Nonnull TypeOrMemberItem invokeItem, int opcode, @Nonnull String desc) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.execute(opcode, invokeItem);
         }
         else {
            int argSize = invokeItem.getArgSizeComputingIfNeeded(desc);
            int sizeVariation = -(argSize >> 2) + (argSize & 0x03);

            if (opcode == INVOKESTATIC || opcode == INVOKEDYNAMIC) {
               sizeVariation++;
            }

            updateStackSize(sizeVariation);
         }
      }
   }

   @Nullable
   Label updateCurrentBlockForJumpInstruction(int opcode, @Nonnull Label label) {
      Label nextInsn = null;

      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeJUMP(opcode);

            // 'label' is the target of a jump instruction.
            label.getFirst().markAsTarget();

            // Adds 'label' as a successor of this basic block.
            addSuccessor(Edge.NORMAL, label);

            if (opcode != GOTO) {
               // Creates a Label for the next basic block.
               nextInsn = new Label();
            }
         }
         else {
            // Updates current stack size (max size unchanged because size variation always negative in this case).
            stackSize += Frame.SIZE[opcode];
            addSuccessor(stackSize, label);
         }
      }

      return nextInsn;
   }

   void updateCurrentBlockForJumpTarget(int opcode, @Nullable Label nextInsn) {
      if (currentBlock != null) {
         if (nextInsn != null) {
            // If the jump instruction is not a GOTO, the next instruction is also a successor of this instruction.
            // Calling visitLabel adds the label of this next instruction as a successor of the current block, and starts a new basic block.
            updateCurrentBlockForLabelBeforeNextInstruction(nextInsn);
         }

         if (opcode == GOTO) {
            noSuccessor();
         }
      }
   }

   void updateCurrentBlockForLabelBeforeNextInstruction(@Nonnull Label label) {
      // Resolves previous forward references to label, if any.
      label.resolve(code);

      if (label.isDebug()) {
         return;
      }

      if (computeFrames) {
         if (currentBlock != null) {
            if (label.position == currentBlock.position) {
               // Successive labels, do not start a new basic block.
               currentBlock.markAsTarget(label);
               label.frame = currentBlock.frame;
               return;
            }

            // Ends current block (with one new successor).
            addSuccessor(Edge.NORMAL, label);
         }

         // Begins a new current block.
         currentBlock = label;

         if (label.frame == null) {
            label.frame = new Frame(cp, label);
         }

         // Updates the basic block list.
         if (previousBlock != null) {
            if (label.position == previousBlock.position) {
               previousBlock.markAsTarget(label);
               label.frame = previousBlock.frame;
               currentBlock = previousBlock;
               return;
            }

            previousBlock.successor = label;
         }

         previousBlock = label;
      }
      else {
         if (currentBlock != null) {
            // Ends current block (with one new successor).
            currentBlock.outputStackMax = maxStackSize;
            addSuccessor(stackSize, label);
         }

         // Begins a new current block
         currentBlock = label;

         // Resets the relative current and max stack sizes.
         stackSize = 0;
         maxStackSize = 0;

         // Updates the basic block list.
         if (previousBlock != null) {
            previousBlock.successor = label;
         }

         previousBlock = label;
      }
   }

   void updateCurrentBlockForLDCInstruction(@Nonnull Item constItem) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeLDC(constItem);
         }
         else {
            int sizeVariation = constItem instanceof LongValueItem ? 2 : 1;
            updateStackSize(sizeVariation);
         }
      }
   }

   void updateCurrentBlockForIINCInstruction(int var) {
      if (currentBlock != null && computeFrames) {
         currentBlock.frame.executeIINC(var);
      }
   }

   void updateCurrentBlockForSwitchInstruction(@Nonnull Label dflt, @Nonnull Label[] caseLabels) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeSWITCH();

            // Adds current block successors.
            addSuccessor(Edge.NORMAL, dflt);
            dflt.getFirst().markAsTarget();

            for (Label label : caseLabels) {
               addSuccessor(Edge.NORMAL, label);
               label.getFirst().markAsTarget();
            }
         }
         else {
            // Updates current stack size (max stack size unchanged).
            stackSize--;

            // Adds current block successors.
            addSuccessor(stackSize, dflt);
            addSuccessorForEachCase(caseLabels);
         }

         // Ends current block.
         noSuccessor();
      }
   }

   private void addSuccessorForEachCase(@Nonnull Label[] caseLabels) {
      for (Label label : caseLabels) {
         addSuccessor(stackSize, label);
      }
   }

   void updateCurrentBlockForMULTIANEWARRAYInstruction(@Nonnull StringItem arrayTypeItem, @Nonnegative int dims) {
      if (currentBlock != null) {
         if (computeFrames) {
            currentBlock.frame.executeMULTIANEWARRAY(dims, arrayTypeItem);
         }
         else {
            // Updates current stack size (max stack size unchanged because stack size variation always negative or 0).
            stackSize += 1 - dims;
         }
      }
   }

   /**
    * Fix point algorithm: mark the first basic block as 'changed' (i.e. put it in the 'changed' list) and, while there are changed basic
    * blocks, choose one, mark it as unchanged, and update its successors (which can be changed in the process).
    */
   int computeMaxStackSizeFromComputedFrames() {
      int max = 0;
      Label changed = labels;
      Frame frame;

      while (changed != null) {
         // Removes a basic block from the list of changed basic blocks.
         Label l = changed;
         changed = changed.next;
         l.next = null;
         frame = l.frame;

         // A reachable jump target must be stored in the stack map.
         if (l.isTarget()) {
            l.markAsStoringFrame();
         }

         // All visited labels are reachable, by definition.
         l.markAsReachable();

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

         if (blockMax > max) {
            max = blockMax;
         }

         changed = updateSuccessorsOfCurrentBasicBlock(changed, frame, l);
      }

      return max;
   }

   @Nullable
   private Label updateSuccessorsOfCurrentBasicBlock(@Nullable Label changed, @Nonnull Frame frame, @Nonnull Label label) {
      Edge e = label.successors;

      while (e != null) {
         Label n = e.successor.getFirst();
         boolean change = frame.merge(cw.thisName, n.frame, e.info);

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

         e = e.next;
      }

      return changed;
   }

   /**
    * Control flow analysis algorithm: while the block stack is not empty, pop a block from this stack, update the max stack size, compute
    * the true (non relative) begin stack size of the successors of this block, and push these successors onto the stack (unless they have
    * already been pushed onto the stack). Note: by hypothesis, the {@link Label#inputStackTop} of the blocks in the block stack are the
    * true (non relative) beginning stack sizes of these blocks.
    */
   int computeMaxStackSize() {
      int max = 0;
      Label stack = labels;

      while (stack != null) {
         // Pops a block from the stack.
         Label label = stack;
         stack = stack.next;

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

         // Updates the global max stack size.
         if (blockMax > max) {
            max = blockMax;
         }

         stack = analyzeBlockSuccessors(stack, label, start);
      }

      return max;
   }

   @Nullable
   private static Label analyzeBlockSuccessors(@Nullable Label stack, @Nonnull Label label, @Nonnegative int start) {
      Edge block = label.successors;

      while (block != null) {
         label = block.successor;

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

            // ...and pushes it onto the stack.
            label.markAsPushed();
            label.next = stack;
            stack = label;
         }

         block = block.next;
      }

      return stack;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy