Handle interaction between AcknowledgeRestrictiveAnnotations and TreatGeneratedAsUnannotated (#254)
If both the `TreatGeneratedAsUnannotated` and `AcknowledgeRestrictiveAnnotations` are set, previously we would still use any `@Nullable` annotations in generated code when checking clients. This PR implements the desired behavior, which is to ignore those `@Nullable` annotations.
This points to a deeper issue in our terminology (kind of weird to use annotations from "unannotated" code) and config options. But fully addressing that is outside the scope of this PR.
diff --git a/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java b/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
index 0614e9a..2cc6ab9 100644
--- a/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
@@ -248,4 +248,9 @@
public String getErrorURL() {
return errorURL;
}
+
+ @Override
+ public boolean treatGeneratedAsUnannotated() {
+ return treatGeneratedAsUnannotated;
+ }
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/Config.java b/nullaway/src/main/java/com/uber/nullaway/Config.java
index ced23a0..3e7210b 100644
--- a/nullaway/src/main/java/com/uber/nullaway/Config.java
+++ b/nullaway/src/main/java/com/uber/nullaway/Config.java
@@ -135,4 +135,7 @@
/** @return the URL to show with NullAway error messages */
String getErrorURL();
+
+ /** @return true if generated code should be treated as unannotated */
+ boolean treatGeneratedAsUnannotated();
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java b/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
index ea62380..bd5f75c 100644
--- a/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
@@ -143,4 +143,9 @@
public String getErrorURL() {
throw new IllegalStateException(error_msg);
}
+
+ @Override
+ public boolean treatGeneratedAsUnannotated() {
+ throw new IllegalStateException(error_msg);
+ }
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java b/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
index 1e6a4bb..f22eedc 100644
--- a/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
+++ b/nullaway/src/main/java/com/uber/nullaway/NullabilityUtil.java
@@ -199,4 +199,14 @@
return !config.fromAnnotatedPackage(outermostClassSymbol)
|| config.isUnannotatedClass(outermostClassSymbol);
}
+
+ /**
+ * @param symbol symbol for entity
+ * @return true if symbol represents an entity from a class annotated with {@code @Generated};
+ * false otherwise
+ */
+ public static boolean isGenerated(Symbol symbol) {
+ Symbol.ClassSymbol outermostClassSymbol = getOutermostClassSymbol(symbol);
+ return ASTHelpers.hasDirectAnnotationWithSimpleName(outermostClassSymbol, "Generated");
+ }
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java
index f148895..d7ac355 100644
--- a/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java
+++ b/nullaway/src/main/java/com/uber/nullaway/handlers/RestrictiveAnnotationHandler.java
@@ -70,7 +70,13 @@
if (expr.getKind().equals(Tree.Kind.METHOD_INVOCATION)) {
Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol((MethodInvocationTree) expr);
if (NullabilityUtil.isUnannotated(methodSymbol, config)) {
- return Nullness.hasNullableAnnotation(methodSymbol) || exprMayBeNull;
+ // with the generated-as-unannotated option enabled, we want to ignore
+ // annotations in generated code
+ if (config.treatGeneratedAsUnannotated() && NullabilityUtil.isGenerated(methodSymbol)) {
+ return exprMayBeNull;
+ } else {
+ return Nullness.hasNullableAnnotation(methodSymbol) || exprMayBeNull;
+ }
} else {
return exprMayBeNull;
}
diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
index c825419..dcdab86 100644
--- a/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
+++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
@@ -316,6 +316,35 @@
}
@Test
+ public void generatedAsUnannotatedPlusRestrictive() {
+ compilationHelper
+ .setArgs(
+ Arrays.asList(
+ "-d",
+ temporaryFolder.getRoot().getAbsolutePath(),
+ "-XepOpt:NullAway:AnnotatedPackages=com.uber",
+ "-XepOpt:NullAway:TreatGeneratedAsUnannotated=true",
+ "-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true"))
+ .addSourceLines(
+ "Generated.java",
+ "package com.uber;",
+ "@javax.annotation.Generated(\"foo\")",
+ "public class Generated {",
+ " @javax.annotation.Nullable",
+ " public Object retNull() {",
+ " return null;",
+ " }",
+ "}")
+ .addSourceLines(
+ "Test.java",
+ "package com.uber;",
+ "class Test {",
+ " void foo() { (new Generated()).retNull().toString(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
public void basicContractAnnotation() {
compilationHelper
.setArgs(