From c1125fe474f8be10d92b2a2fd6083fd92a4fc870 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Tue, 21 Jan 2014 14:36:25 -0600 Subject: [PATCH] Add check for erroneous overriding NotNull and Nullable annotations --- .../v4/runtime/misc/NullUsageProcessor.java | 128 +++++++++++++++++- 1 file changed, 126 insertions(+), 2 deletions(-) diff --git a/runtime/JavaAnnotations/src/org/antlr/v4/runtime/misc/NullUsageProcessor.java b/runtime/JavaAnnotations/src/org/antlr/v4/runtime/misc/NullUsageProcessor.java index 423c2c525..d024c23f4 100644 --- a/runtime/JavaAnnotations/src/org/antlr/v4/runtime/misc/NullUsageProcessor.java +++ b/runtime/JavaAnnotations/src/org/antlr/v4/runtime/misc/NullUsageProcessor.java @@ -35,6 +35,7 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -46,7 +47,13 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -59,6 +66,8 @@ import java.util.Set; *
  • Error: an element is annotated with both {@link NotNull} and {@link Nullable}.
  • *
  • Error: an method which returns {@code void} is annotated with {@link NotNull} or {@link Nullable}.
  • *
  • Error: an element with a primitive type is annotated with {@link Nullable}.
  • + *
  • Error: a parameter is annotated with {@link NotNull}, but the method overrides or implements a method where the parameter is annotated {@link Nullable}.
  • + *
  • Error: a method is annotated with {@link Nullable}, but the method overrides or implements a method that is annotated with {@link NotNull}.
  • *
  • Warning: an element with a primitive type is annotated with {@link NotNull}.
  • * * @@ -70,6 +79,9 @@ public class NullUsageProcessor extends AbstractProcessor { public static final String NotNullClassName = "org.antlr.v4.runtime.misc.NotNull"; public static final String NullableClassName = "org.antlr.v4.runtime.misc.Nullable"; + private TypeElement notNullType; + private TypeElement nullableType; + public NullUsageProcessor() { } @@ -79,8 +91,8 @@ public class NullUsageProcessor extends AbstractProcessor { return true; } - TypeElement notNullType = processingEnv.getElementUtils().getTypeElement(NotNullClassName); - TypeElement nullableType = processingEnv.getElementUtils().getTypeElement(NullableClassName); + notNullType = processingEnv.getElementUtils().getTypeElement(NotNullClassName); + nullableType = processingEnv.getElementUtils().getTypeElement(NullableClassName); Set notNullElements = roundEnv.getElementsAnnotatedWith(notNullType); Set nullableElements = roundEnv.getElementsAnnotatedWith(nullableType); @@ -97,6 +109,18 @@ public class NullUsageProcessor extends AbstractProcessor { checkPrimitiveTypeAnnotations(nullableElements, Diagnostic.Kind.ERROR, nullableType); checkPrimitiveTypeAnnotations(notNullElements, Diagnostic.Kind.WARNING, notNullType); + // method name -> method -> annotated elements of method + Map>> namedMethodMap = + new HashMap>>(); + addElementsToNamedMethodMap(notNullElements, namedMethodMap); + addElementsToNamedMethodMap(nullableElements, namedMethodMap); + + for (Map.Entry>> entry : namedMethodMap.entrySet()) { + for (Map.Entry> subentry : entry.getValue().entrySet()) { + checkOverriddenMethods(subentry.getKey()); + } + } + return true; } @@ -167,4 +191,104 @@ public class NullUsageProcessor extends AbstractProcessor { } } } + + private void addElementsToNamedMethodMap(Set elements, Map>> namedMethodMap) { + for (Element element : elements) { + ExecutableElement method; + switch (element.getKind()) { + case PARAMETER: + method = (ExecutableElement)element.getEnclosingElement(); + assert method.getKind() == ElementKind.METHOD; + break; + + case METHOD: + method = (ExecutableElement)element; + break; + + default: + continue; + } + + Map> annotatedMethodWithName = + namedMethodMap.get(method.getSimpleName().toString()); + if (annotatedMethodWithName == null) { + annotatedMethodWithName = new HashMap>(); + namedMethodMap.put(method.getSimpleName().toString(), annotatedMethodWithName); + } + + List annotatedElementsOfMethod = annotatedMethodWithName.get(method); + if (annotatedElementsOfMethod == null) { + annotatedElementsOfMethod = new ArrayList(); + annotatedMethodWithName.put(method, annotatedElementsOfMethod); + } + + annotatedElementsOfMethod.add(element); + } + } + + private void checkOverriddenMethods(ExecutableElement method) { + TypeElement declaringType = (TypeElement)method.getEnclosingElement(); + typeLoop: + for (TypeMirror supertypeMirror : getAllSupertypes(processingEnv.getTypeUtils().getDeclaredType(declaringType))) { + for (Element element : ((TypeElement)processingEnv.getTypeUtils().asElement(supertypeMirror)).getEnclosedElements()) { + if (element instanceof ExecutableElement) { + if (processingEnv.getElementUtils().overrides(method, (ExecutableElement)element, declaringType)) { + checkOverriddenMethod(method, (ExecutableElement)element); + continue typeLoop; + } + } + } + } + } + + private List getAllSupertypes(TypeMirror type) { + Set supertypes = new HashSet(); + Deque worklist = new ArrayDeque(); + worklist.add(type); + while (!worklist.isEmpty()) { + List next = processingEnv.getTypeUtils().directSupertypes(worklist.poll()); + if (supertypes.addAll(next)) { + worklist.addAll(next); + } + } + + return new ArrayList(supertypes); + } + + private void checkOverriddenMethod(ExecutableElement overrider, ExecutableElement overridden) { + // check method annotation + if (isNullable(overrider) && isNotNull(overridden)) { + String error = String.format("method annotated with %s cannot override or implement a method annotated with %s", nullableType.getSimpleName(), notNullType.getSimpleName()); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, error, overrider); + } + + List overriderParameters = overrider.getParameters(); + List overriddenParameters = overridden.getParameters(); + for (int i = 0; i < overriderParameters.size(); i++) { + if (isNotNull(overriderParameters.get(i)) && isNullable(overriddenParameters.get(i))) { + String error = String.format("parameter annotated with %s cannot override or implement a parameter annotated with %s", notNullType.getSimpleName(), nullableType.getSimpleName()); + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, error, overrider); + } + } + } + + private boolean isNotNull(Element element) { + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + if (annotationMirror.getAnnotationType().asElement() == notNullType) { + return true; + } + } + + return false; + } + + private boolean isNullable(Element element) { + for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { + if (annotationMirror.getAnnotationType().asElement() == nullableType) { + return true; + } + } + + return false; + } }