MethodData (ClassReader reader, final Set<String> notNullAnnotations) { final MethodData result = new MethodData(); reader.accept(new ClassVisitor(Opcodes.API_VERSION) { private boolean myEnum, myInner; @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); result.myClassName = name; myEnum = (access & ACC_ENUM) != 0; } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { super.visitInnerClass(name, outerName, innerName, access); if (result.myClassName.equals(name)) { myInner = (access & ACC_STATIC) == 0; } } @Override public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) { final Type[] args = Type.getArgumentTypes(desc); final boolean methodCanHaveNullability = isReferenceType(Type.getReturnType(desc)); final Map<Integer, Integer> paramSlots = new LinkedHashMap<>(); // map: localVariableSlot -> methodParameterIndex int slotIndex = isStatic(access) ? 0 : 1; for (int paramIndex = 0; paramIndex < args.length; paramIndex++) { Type arg = args[paramIndex]; paramSlots.put(slotIndex, paramIndex); slotIndex += arg.getSize(); } final MethodInfo methodInfo = new MethodInfo(); methodInfo.isStable = (access & (Opcodes.ACC_FINAL | Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE)) != 0; methodInfo.paramAnnotationOffset = !"<init>".equals(name) ? 0 : myEnum ? 2 : myInner ? 1 : 0; result.myMethodInfos.put(MethodData.key(name, desc), methodInfo); return new MethodVisitor(api) { private int myParamAnnotationOffset = methodInfo.paramAnnotationOffset; @Override public void visitAnnotableParameterCount(int parameterCount, boolean visible) { if (myParamAnnotationOffset != 0 && parameterCount == args.length) { myParamAnnotationOffset = 0; } super.visitAnnotableParameterCount(parameterCount, visible); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String anno, boolean visible) { AnnotationVisitor base = super.visitParameterAnnotation(parameter, anno, visible); return checkParameterNullability(parameter + myParamAnnotationOffset, anno, base, false); } @Override public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) { AnnotationVisitor base = super.visitAnnotation(anno, isRuntime); if (methodCanHaveNullability && notNullAnnotations.contains(anno)) { return collectNotNullArgs(base, methodInfo.nullability.withNotNull(anno, ISE_CLASS_NAME)); } return base; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String anno, boolean visible) { AnnotationVisitor base = super.visitTypeAnnotation(typeRef, typePath, anno, visible); if (typePath != null) return base; TypeReference ref = new TypeReference(typeRef); if (methodCanHaveNullability && ref.getSort() == TypeReference.METHOD_RETURN) { if (notNullAnnotations.contains(anno)) { return collectNotNullArgs(base, methodInfo.nullability.withNotNull(anno, ISE_CLASS_NAME)); } else if (seemsNullable(anno)) { methodInfo.nullability.hasTypeUseNullable = true; } } else if (ref.getSort() == TypeReference.METHOD_FORMAL_PARAMETER) { return checkParameterNullability(ref.getFormalParameterIndex() + methodInfo.paramAnnotationOffset, anno, base, true); } return base; } private boolean seemsNullable(String anno) { String shortName = getAnnoShortName(anno); // use hardcoded short names until it causes trouble // this is to avoid cumbersome passing of configured nullable names from the IDE return shortName.contains("Nullable") || shortName.equals("CheckForNull"); } private AnnotationVisitor collectNotNullArgs(AnnotationVisitor base, final NotNullState state) { return new AnnotationVisitor(Opcodes.API_VERSION, base) { @Override public void visit(String methodName, Object o) { if (ANNOTATION_DEFAULT_METHOD.equals(methodName) && !((String) o).isEmpty()) { state.message = (String) o; } else if ("exception".equals(methodName) && o instanceof Type && !((Type)o).getClassName().equals(Exception.class.getName())) { state.exceptionType = ((Type)o).getInternalName(); } super.visit(methodName, o); } }; } private AnnotationVisitor checkParameterNullability(int parameter, String anno, AnnotationVisitor av, boolean typeUse) { if (parameter >= 0 && parameter < args.length && isReferenceType(args[parameter])) { if (notNullAnnotations.contains(anno)) { return collectNotNullArgs(av, methodInfo.obtainParameterNullability(parameter).withNotNull(anno, IAE_CLASS_NAME)); } else if (typeUse && seemsNullable(anno)) { methodInfo.obtainParameterNullability(parameter).hasTypeUseNullable = true; } } return av; } @Override public void visitLocalVariable(String name2, String desc, String signature, Label start, Label end, int slotIndex) { Integer paramIndex = paramSlots.get(slotIndex); if (paramIndex != null) { methodInfo.paramNames.put(paramIndex, name2); } } }; } }, ClassReader.SKIP_FRAMES); return result; }