@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<ElementAnnotation> annotations = new HashSet<>();
    final Set<ParamAnnotation> paramAnnotations = new HashSet<>();
    processSignature(signature);

    boolean isInlined = isInlined(n, desc);
    Textifier printer = new Textifier();

    MethodVisitor visitor = new MethodVisitor(ASM_API_VERSION) {

      @Override
      public void visitEnd() {
        if ((access & Opcodes.ACC_SYNTHETIC) == 0 || (access & Opcodes.ACC_BRIDGE) > 0) {
          if (isInlined) {
            // use 'defaultValue' attribute to store the hash of the function body to track changes in inline method implementation
            ContentHashBuilder hashBuilder = ContentHashBuilder.create();
            for (Object o : printer.getText()) {
              hashBuilder.update(o);
            }
            defaultValue.set(hashBuilder.getResult());
          }
          myMethods.add(new JvmMethod(new JVMFlags(access), signature, n, desc, annotations, paramAnnotations, Iterators.asIterable(exceptions), defaultValue.get()));
        }
      }

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

      @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) {
        return new AnnotationCrawler(
          (TypeRepr.ClassType)TypeRepr.getType(desc),
          ElemType.PARAMETER,
          res -> paramAnnotations.add(new ParamAnnotation(parameter, res.getAnnotationClass(), res.getContentHash()))
        );
      }

      @Override
      public void visitLdcInsn(Object cst) {
        if (cst instanceof Type) {
          addUsage(new ClassUsage(((Type)cst).getInternalName()));
        }

        super.visitLdcInsn(cst);
      }

      @Override
      public void visitMultiANewArrayInsn(String desc, int dims) {
        final TypeRepr.ArrayType typ = (TypeRepr.ArrayType)TypeRepr.getType(desc);
        Iterators.collect(typ.getUsages(), myUsages);

        final TypeRepr element = typ.getDeepElementType();
        if (element instanceof TypeRepr.ClassType) {
          addUsage(new ClassNewUsage(((TypeRepr.ClassType)element).getJvmName()));
        }

        super.visitMultiANewArrayInsn(desc, dims);
      }

      @Override
      public void visitLocalVariable(String n, String desc, String signature, Label start, Label end, int index) {
        if (!"this".equals(n)) {
          processSignature(signature);
          Iterators.collect(TypeRepr.getType(desc).getUsages(), myUsages);
        }
        super.visitLocalVariable(n, desc, signature, start, end, index);
      }

      @Override
      public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
        if (type != null) {
          Iterators.collect(new TypeRepr.ClassType(type).getUsages(), myUsages);
        }
        super.visitTryCatchBlock(start, end, handler, type);
      }

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

        if (opcode == Opcodes.NEW) {
          addUsage(new ClassUsage(((TypeRepr.ClassType)typ).getJvmName()));
          addUsage(new ClassNewUsage(((TypeRepr.ClassType)typ).getJvmName()));
          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) {
              addUsage(new ClassNewUsage(type.substring(ifNameStart, ifNameEnd).replace('_', '/')));
            }
          }
        }
        else if (opcode == Opcodes.ANEWARRAY) {
          if (typ instanceof TypeRepr.ClassType) {
            addUsage(new ClassUsage(((TypeRepr.ClassType)typ).getJvmName()));
            addUsage(new ClassNewUsage(((TypeRepr.ClassType)typ).getJvmName()));
          }
        }

        Iterators.collect(typ.getUsages(), 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);
        Iterators.collect(TypeRepr.getType(returnType).getUsages(), myUsages);

        // 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()) {
                Iterators.collect(TypeRepr.getType(argType).getUsages(), myUsages);
              }
              Iterators.collect(TypeRepr.getType(type.getReturnType()).getUsages(), myUsages);
            }
            else {
              Iterators.collect(TypeRepr.getType(type).getUsages(), myUsages);
            }
          }
          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
                addUsage(new ClassNewUsage(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) {
        if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
          addUsage(new FieldAssignUsage(owner, fName, desc));
        }
        if (opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC) {
          Iterators.collect(TypeRepr.getType(desc).getUsages(), myUsages);
        }
        addUsage(new FieldUsage(owner, fName, desc));
      }

      private void registerMethodUsage(String owner, String name, @Nullable String desc) {
        //myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, methodOwner));
        if (desc != null) {
          addUsage(new MethodUsage(owner, name, desc));
          Iterators.collect(TypeRepr.getType(Type.getReturnType(desc)).getUsages(), myUsages);
        }
        else {
          // todo: verify for which methods null descriptor is passed
          addUsage(new MethodUsage(owner, name, ""));
        }
      }

    };

    return isInlined? new TraceMethodVisitor(visitor, printer) : visitor;
  }
@Override
  public MethodVisitor visitMethod(final int access, final String n, final String desc, final String signature, final String[] exceptions)