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));
  }
  public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) {
    // Update the descriptor if it has any unused parameters.
    String descriptor = programMethod.getDescriptor(programClass);
    String newDescriptor = shrinkDescriptor(programMethod, descriptor);

    if (!descriptor.equals(newDescriptor)) {
      // Shrink the signature and parameter annotations.
      programMethod.attributesAccept(programClass, this);

      String name = programMethod.getName(programClass);
      String newName = name;

      // Append a code, if the method isn't a class instance initializer.
      if (!name.equals(ClassConstants.INTERNAL_METHOD_NAME_INIT)) {
        newName +=
            ClassConstants.SPECIAL_MEMBER_SEPARATOR
                + Long.toHexString(Math.abs((descriptor).hashCode()));
      }

      if (DEBUG) {
        System.out.println("MethodDescriptorShrinker:");
        System.out.println("  Class file        = " + programClass.getName());
        System.out.println("  Method name       = " + name);
        System.out.println("                   -> " + newName);
        System.out.println("  Method descriptor = " + descriptor);
        System.out.println("                   -> " + newDescriptor);
      }

      ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor(programClass);

      // Update the name, if necessary.
      if (!newName.equals(name)) {
        programMethod.u2nameIndex = constantPoolEditor.addUtf8Constant(newName);
      }

      // Update the referenced classes.
      programMethod.referencedClasses =
          shrinkReferencedClasses(programMethod, descriptor, programMethod.referencedClasses);

      // Finally, update the descriptor itself.
      programMethod.u2descriptorIndex = constantPoolEditor.addUtf8Constant(newDescriptor);

      // Visit the method, if required.
      if (extraMemberVisitor != null) {
        extraMemberVisitor.visitProgramMethod(programClass, programMethod);
      }
    }
  }