@Override
    public MethodVisitor visitMethod(final int access, final String n, final String desc, final String signature, final String[] exceptions) {
      final Ref<Object> defaultValue = Ref.create();
      final Set<TypeRepr.ClassType> annotations = new HashSet<>();
      final Set<ParamAnnotation> paramAnnotations = new HashSet<>();
      processSignature(signature);

      return new MethodVisitor(ASM_API_VERSION) {
        @Override
        public void visitEnd() {
          if ((access & Opcodes.ACC_SYNTHETIC) == 0 || (access & Opcodes.ACC_BRIDGE) > 0) {
            myMethods.add(new MethodRepr(
              myContext, access, myContext.get(n), myContext.get(signature), desc, annotations, paramAnnotations, exceptions, defaultValue.get()
            ));
          }
        }

        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
          final TypeRepr.ClassType annoType = (TypeRepr.ClassType)TypeRepr.getType(myContext, desc);
          annotations.add(annoType);
          return new AnnotationCrawler(annoType, "<init>".equals(n) ? ElemType.CONSTRUCTOR : ElemType.METHOD);
        }

        @Override
        public AnnotationVisitor visitAnnotationDefault() {
          return new AnnotationVisitor(ASM_API_VERSION) {
            private @Nullable List<Object> myAcc;

            @Override
            public void visit(String name, Object value) {
              collectValue(value);
            }

            @Override
            public void visitEnum(String name, String desc, String value) {
              collectValue(value);
            }

            @Override
            public AnnotationVisitor visitArray(String name) {
              myAcc = new SmartList<>();
              return this;
            }

            @Override
            public void visitEnd() {
              if (myAcc != null) {
                Object[] template = null;
                if (!myAcc.isEmpty()) {
                  final Object elem = myAcc.get(0);
                  if (elem != null) {
                    template = ArrayUtil.newArray(elem.getClass(), 0);
                  }
                }
                defaultValue.set(template != null? myAcc.toArray(template) : myAcc.toArray());
              }
            }

            private void collectValue(Object value) {
              if (myAcc != null) {
                myAcc.add(value);
              }
              else {
                defaultValue.set(value);
              }
            }
          };
        }

        @Override
        public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
          final TypeRepr.ClassType annoType = (TypeRepr.ClassType)TypeRepr.getType(myContext, desc);
          paramAnnotations.add(new ParamAnnotation(parameter, annoType));
          return new AnnotationCrawler(annoType, ElemType.PARAMETER);
        }

        @Override
        public void visitLdcInsn(Object cst) {
          if (cst instanceof Type) {
            myUsages.add(UsageRepr.createClassUsage(myContext, myContext.get(((Type)cst).getInternalName())));
          }

          super.visitLdcInsn(cst);
        }

        @Override
        public void visitMultiANewArrayInsn(String desc, int dims) {
          final TypeRepr.ArrayType typ = (TypeRepr.ArrayType)TypeRepr.getType(myContext, desc);
          final TypeRepr.AbstractType element = typ.getDeepElementType();

          if (element instanceof TypeRepr.ClassType) {
            final int className = ((TypeRepr.ClassType)element).className;
            myUsages.add(UsageRepr.createClassUsage(myContext, className));
            myUsages.add(UsageRepr.createClassNewUsage(myContext, className));
          }

          typ.updateClassUsages(myContext, myName, myUsages);

          super.visitMultiANewArrayInsn(desc, dims);
        }

        @Override
        public void visitLocalVariable(String n, String desc, String signature, Label start, Label end, int index) {
          processSignature(signature);
          TypeRepr.getType(myContext, desc).updateClassUsages(myContext, myName, myUsages);
          super.visitLocalVariable(n, desc, signature, start, end, index);
        }

        @Override
        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
          if (type != null) {
            TypeRepr.createClassType(myContext, myContext.get(type)).updateClassUsages(myContext, myName, myUsages);
          }

          super.visitTryCatchBlock(start, end, handler, type);
        }

        @Override
        public void visitTypeInsn(int opcode, String type) {
          final TypeRepr.AbstractType typ = type.startsWith("[")? TypeRepr.getType(myContext, type) : TypeRepr.createClassType(myContext, myContext.get(type));

          if (opcode == Opcodes.NEW) {
            myUsages.add(UsageRepr.createClassUsage(myContext, ((TypeRepr.ClassType)typ).className));
            myUsages.add(UsageRepr.createClassNewUsage(myContext, ((TypeRepr.ClassType)typ).className));
            final int ktLambdaMarker = type.indexOf(KOTLIN_LAMBDA_USAGE_CLASS_MARKER);
            if (ktLambdaMarker > 0) {
              final int ifNameStart = ktLambdaMarker + KOTLIN_LAMBDA_USAGE_CLASS_MARKER.length();
              final int ifNameEnd = type.indexOf("$", ifNameStart);
              if (ifNameEnd > ifNameStart) {
                myUsages.add(UsageRepr.createClassNewUsage(myContext, myContext.get(type.substring(ifNameStart, ifNameEnd).replace('_', '/'))));
              }
            }
          }
          else if (opcode == Opcodes.ANEWARRAY) {
            if (typ instanceof TypeRepr.ClassType) {
              myUsages.add(UsageRepr.createClassUsage(myContext, ((TypeRepr.ClassType)typ).className));
              myUsages.add(UsageRepr.createClassNewUsage(myContext, ((TypeRepr.ClassType)typ).className));
            }
          }

          typ.updateClassUsages(myContext, myName, myUsages);

          super.visitTypeInsn(opcode, type);
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
          registerFieldUsage(opcode, owner, name, desc);
          super.visitFieldInsn(opcode, owner, name, desc);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
          registerMethodUsage(owner, name, desc);
          super.visitMethodInsn(opcode, owner, name, desc, itf);
        }

        @Override
        public void visitInvokeDynamicInsn(String methodName, String desc, Handle bsm, Object... bsmArgs) {
          final Type returnType = Type.getReturnType(desc);
          addClassUsage(TypeRepr.getType(myContext, returnType));

          // common args processing
          for (Object arg : bsmArgs) {
            if (arg instanceof Type) {
              final Type type = (Type)arg;
              if (type.getSort() == Type.METHOD) {
                for (Type argType : type.getArgumentTypes()) {
                  addClassUsage(TypeRepr.getType(myContext, argType));
                }
                addClassUsage(TypeRepr.getType(myContext, type.getReturnType()));
              }
              else {
                addClassUsage(TypeRepr.getType(myContext, type));
              }
            }
            else if (arg instanceof Handle) {
              processMethodHandle((Handle)arg);
            }
          }

          if (LAMBDA_FACTORY_CLASS.equals(bsm.getOwner())) {
            // This invokeDynamic implements a lambda or method reference usage.
            // Need to register method usage for the corresponding SAM-type.
            // First three arguments to the bootstrap methods are provided automatically by VM.
            // Arguments in args array are expected to be as following:
            // [0]: Type: Signature and return type of method to be implemented by the function object.
            // [1]: Handle: implementation method handle
            // [2]: Type: The signature and return type that should be enforced dynamically at invocation time. May be the same as samMethodType, or may be a specialization of it
            // [...]: optional additional arguments

            if (returnType.getSort() == Type.OBJECT && bsmArgs.length >= 3) {
              if (bsmArgs[0] instanceof Type) {
                final Type samMethodType = (Type)bsmArgs[0];
                if (samMethodType.getSort() == Type.METHOD) {
                  registerMethodUsage(returnType.getInternalName(), methodName, samMethodType.getDescriptor());
                  // reflect dynamic proxy instantiation with NewClassUsage
                  myUsages.add(UsageRepr.createClassNewUsage(myContext, myContext.get(returnType.getInternalName())));
                }
              }
            }
          }

          super.visitInvokeDynamicInsn(methodName, desc, bsm, bsmArgs);
        }

        private void processMethodHandle(Handle handle) {
          final String memberOwner = handle.getOwner();
          if (memberOwner != null &&  !memberOwner.equals(myClassNameHolder.get())) {
            // do not register access to own class members
            final String memberName = handle.getName();
            final String memberDescriptor = handle.getDesc();
            final int opCode = getFieldAccessOpcode(handle);
            if (opCode > 0) {
              registerFieldUsage(opCode, memberOwner, memberName, memberDescriptor);
            }
            else {
              registerMethodUsage(memberOwner, memberName, memberDescriptor);
            }
          }
        }

        private void registerFieldUsage(int opcode, String owner, String fName, String desc) {
          final int fieldName = myContext.get(fName);
          final int fieldOwner = myContext.get(owner);
          final int descr = myContext.get(desc);
          if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
            myUsages.add(UsageRepr.createFieldAssignUsage(myContext, fieldName, fieldOwner, descr));
          }
          if (opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC) {
            addClassUsage(TypeRepr.getType(myContext, descr));
          }
          myUsages.add(UsageRepr.createFieldUsage(myContext, fieldName, fieldOwner, descr));
        }

        private void registerMethodUsage(String owner, String name, @Nullable String desc) {
          final int methodOwner = myContext.get(owner);
          final int methodName = myContext.get(name);
          myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, methodOwner));
          if (desc != null) {
            myUsages.add(UsageRepr.createMethodUsage(myContext, methodName, methodOwner, desc));
            addClassUsage(TypeRepr.getType(myContext, Type.getReturnType(desc)));
          }
        }

        private void addClassUsage(final TypeRepr.AbstractType type) {
          TypeRepr.ClassType classType = null;
          if (type instanceof TypeRepr.ClassType) {
            classType = (TypeRepr.ClassType)type;
          }
          else if (type instanceof TypeRepr.ArrayType) {
            final TypeRepr.AbstractType elemType = ((TypeRepr.ArrayType)type).getDeepElementType();
            if (elemType instanceof TypeRepr.ClassType) {
              classType = (TypeRepr.ClassType)elemType;
            }
          }
          if (classType != null) {
            myUsages.add(UsageRepr.createClassUsage(myContext, classType.className));
          }
        }

      };
    }
@Override
    public MethodVisitor visitMethod(final int access, final String n, final String desc, final String signature, final String[] exceptions)