/** Marks the code starting at the given offset. */
  private void markCode(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset) {
    boolean oldNext = next;

    byte[] code = codeAttribute.code;

    // Continue with the current instruction as long as we haven't marked it
    // yet.
    while (!isReachable[offset]) {
      // Get the current instruction.
      Instruction instruction = InstructionFactory.create(code, offset);

      // Mark it as reachable.
      isReachable[offset] = true;

      // By default, we'll assume we can continue with the next
      // instruction in a moment.
      next = true;

      // Mark the branch targets, if any.
      instruction.accept(clazz, method, codeAttribute, offset, this);

      // Can we really continue with the next instruction?
      if (!next) {
        break;
      }

      // Go to the next instruction.
      offset += instruction.length(offset);
    }

    next = oldNext;
  }
  public void visitExceptionInfo(
      Clazz clazz, Method method, CodeAttribute codeAttribute, ExceptionInfo exceptionInfo) {
    int startPC = Math.max(exceptionInfo.u2startPC, clipStart);
    int endPC = Math.min(exceptionInfo.u2endPC, clipEnd);
    int handlerPC = exceptionInfo.u2handlerPC;
    int catchType = exceptionInfo.u2catchType;

    // Exclude any subroutine invocations that jump out of the try block,
    // by adding a try block before (and later on, after) each invocation.
    for (int offset = startPC; offset < endPC; offset++) {
      if (branchTargetFinder.isSubroutineInvocation(offset)) {
        Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
        int instructionLength = instruction.length(offset);

        // Is it a subroutine invocation?
        if (!exceptionInfo.isApplicable(offset + ((BranchInstruction) instruction).branchOffset)) {
          if (DEBUG) {
            System.out.println(
                "  Appending extra exception [" + startPC + " -> " + offset + "] -> " + handlerPC);
          }

          // Append a try block that ends before the subroutine invocation.
          codeAttributeComposer.appendException(
              new ExceptionInfo(startPC, offset, handlerPC, catchType));

          // The next try block will start after the subroutine invocation.
          startPC = offset + instructionLength;
        }
      }
    }

    if (DEBUG) {
      System.out.println(
          "  Appending exception [" + startPC + " -> " + endPC + "] -> " + handlerPC);
    }

    // Append the exception. Note that exceptions with empty try blocks
    // are automatically ignored.
    codeAttributeComposer.appendException(new ExceptionInfo(startPC, endPC, handlerPC, catchType));
  }
  /**
   * Evaluates a block of instructions that hasn't been handled before, starting at the given offset
   * and ending at a branch instruction, a return instruction, or a throw instruction. Branch
   * instructions are handled recursively.
   */
  private void evaluateInstructionBlock(
      Clazz clazz, Method method, CodeAttribute codeAttribute, int instructionOffset) {
    if (DEBUG) {
      if (evaluated[instructionOffset]) {
        System.out.println("-- (instruction block at " + instructionOffset + " already evaluated)");
      } else {
        System.out.println("-- instruction block:");
      }
    }

    // Remember the initial stack size.
    int initialStackSize = stackSize;

    // Remember the maximum stack size.
    if (maxStackSize < stackSize) {
      maxStackSize = stackSize;
    }

    // Evaluate any instructions that haven't been evaluated before.
    while (!evaluated[instructionOffset]) {
      // Mark the instruction as evaluated.
      evaluated[instructionOffset] = true;

      Instruction instruction = InstructionFactory.create(codeAttribute.code, instructionOffset);

      if (DEBUG) {
        int stackPushCount = instruction.stackPushCount(clazz);
        int stackPopCount = instruction.stackPopCount(clazz);
        System.out.println(
            "["
                + instructionOffset
                + "]: "
                + stackSize
                + " - "
                + stackPopCount
                + " + "
                + stackPushCount
                + " = "
                + (stackSize + stackPushCount - stackPopCount)
                + ": "
                + instruction.toString(instructionOffset));
      }

      // Compute the instruction's effect on the stack size.
      stackSize -= instruction.stackPopCount(clazz);

      if (stackSize < 0) {
        throw new IllegalArgumentException(
            "Stack size becomes negative after instruction "
                + instruction.toString(instructionOffset)
                + " in ["
                + clazz.getName()
                + "."
                + method.getName(clazz)
                + method.getDescriptor(clazz)
                + "]");
      }

      stackSizes[instructionOffset] = stackSize += instruction.stackPushCount(clazz);

      // Remember the maximum stack size.
      if (maxStackSize < stackSize) {
        maxStackSize = stackSize;
      }

      // Remember the next instruction offset.
      int nextInstructionOffset = instructionOffset + instruction.length(instructionOffset);

      // Visit the instruction, in order to handle branches.
      instruction.accept(clazz, method, codeAttribute, instructionOffset, this);

      // Stop evaluating after a branch.
      if (exitInstructionBlock) {
        break;
      }

      // Continue with the next instruction.
      instructionOffset = nextInstructionOffset;

      if (DEBUG) {
        if (evaluated[instructionOffset]) {
          System.out.println("-- (instruction at " + instructionOffset + " already evaluated)");
        }
      }
    }

    // Restore the stack size for possible subsequent instruction blocks.
    this.stackSize = initialStackSize;
  }
  public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) {
    //        DEBUG =
    //            clazz.getName().equals("abc/Def") &&
    //            method.getName(clazz).equals("abc");

    if (DEBUG) {
      method.accept(clazz, new ClassPrinter());
    }

    branchTargetFinder.visitCodeAttribute(clazz, method, codeAttribute);

    // Don't bother if there aren't any subroutines anyway.
    if (!containsSubroutines(codeAttribute)) {
      return;
    }

    if (DEBUG) {
      System.out.println(
          "SubroutineInliner: processing ["
              + clazz.getName()
              + "."
              + method.getName(clazz)
              + method.getDescriptor(clazz)
              + "]");
    }

    // Append the body of the code.
    codeAttributeComposer.reset();
    codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength);

    // Copy the non-subroutine instructions.
    int offset = 0;
    while (offset < codeAttribute.u4codeLength) {
      Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
      int instructionLength = instruction.length(offset);

      // Is this returning subroutine?
      if (branchTargetFinder.isSubroutine(offset)
          && branchTargetFinder.isSubroutineReturning(offset)) {
        // Skip the subroutine.
        if (DEBUG) {
          System.out.println(
              "  Skipping original subroutine instruction " + instruction.toString(offset));
        }

        // Append a label at this offset instead.
        codeAttributeComposer.appendLabel(offset);
      } else {
        // Copy the instruction, inlining any subroutine call recursively.
        instruction.accept(clazz, method, codeAttribute, offset, this);
      }

      offset += instructionLength;
    }

    // Copy the exceptions. Note that exceptions with empty try blocks
    // are automatically removed.
    codeAttribute.exceptionsAccept(clazz, method, subroutineExceptionInliner);

    if (DEBUG) {
      System.out.println("  Appending label after code at [" + offset + "]");
    }

    // Append a label just after the code.
    codeAttributeComposer.appendLabel(codeAttribute.u4codeLength);

    // End and update the code attribute.
    codeAttributeComposer.endCodeFragment();
    codeAttributeComposer.visitCodeAttribute(clazz, method, codeAttribute);

    if (DEBUG) {
      method.accept(clazz, new ClassPrinter());
    }
  }