aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNima Karimipour <karimipour.nima@gmail.com>2021-12-22 11:02:10 -0600
committerGitHub <noreply@github.com>2021-12-22 12:02:10 -0500
commitda577508a4f189cecf761026feddbe4e016a5109 (patch)
tree151df25d1250c8f837a9110946c73f017df92e8a
parent283564913161d78c4d129aa27fa1bf46ff0708b2 (diff)
downloadnullaway-da577508a4f189cecf761026feddbe4e016a5109.tar.gz
Adds custom Nullable Annotation via Error Prone CLI flags (#522)
This PR adds the ability to configure additional custom `@Nullable/@Nonnull` annotations. In particular, this adds the following configuration options to NullAway: ``` -XepOpt:NullAway:CustomNullableAnnotations=[...] -XepOpt:NullAway:CustomNonnullAnnotations=[...] ``` Each of these options takes a comma-separated list of fully qualified annotation names, which NullAway should regard as nullable and non-null annotations (respectively), in *addition* to those it looks for by default. Neither configuration option is required when all relevant nullability annotations follow the conventions NullAway already expects (e.g. simple name `@Nullable` or `@NonNull` or any of our default supported annotations such as `@CheckForNull`). During auto-annotation testing, we have encountered a few projects which are partially annotated with nullability annotations with different simple names than the conventional `@Nullable` or `@NonNull` (and recognized variations, such as `@NotNull`). For example, [LibGdx](https://github.com/libgdx/libgdx) uses [@com.badlogic.gdx.utils.Null](https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils/Null.java). This is a custom nullability annotation which lives within the project itself and doesn't use the `@Nullable` simple name convention. Rather than explicitly encoding every such annotation in existence into our defaults, this PR allows supporting such cases with a simple configuration change.
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java15
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/Config.java16
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java10
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java4
-rw-r--r--nullaway/src/main/java/com/uber/nullaway/Nullness.java6
-rw-r--r--nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java58
6 files changed, 107 insertions, 2 deletions
diff --git a/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java b/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
index 0510b40..afc6cb1 100644
--- a/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
@@ -110,6 +110,11 @@ public abstract class AbstractConfig implements Config {
protected String errorURL;
+ /** --- Fully qualified names of custom nonnull/nullable annotation --- */
+ protected Set<String> customNonnullAnnotations;
+
+ protected Set<String> customNullableAnnotations;
+
protected static Pattern getPackagePattern(Set<String> packagePrefixes) {
// noinspection ConstantConditions
String choiceRegexp =
@@ -165,6 +170,16 @@ public abstract class AbstractConfig implements Config {
}
@Override
+ public boolean isCustomNullableAnnotation(String annotationName) {
+ return customNullableAnnotations.contains(annotationName);
+ }
+
+ @Override
+ public boolean isCustomNonnullAnnotation(String annotationName) {
+ return customNonnullAnnotations.contains(annotationName);
+ }
+
+ @Override
public boolean exhaustiveOverride() {
return isExhaustiveOverride;
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/Config.java b/nullaway/src/main/java/com/uber/nullaway/Config.java
index a82abc5..2d92da2 100644
--- a/nullaway/src/main/java/com/uber/nullaway/Config.java
+++ b/nullaway/src/main/java/com/uber/nullaway/Config.java
@@ -75,6 +75,22 @@ public interface Config {
boolean isInitializerMethodAnnotation(String annotationName);
/**
+ * Checks if the annotation is a custom @Nullable annotation.
+ *
+ * @param annotationName fully-qualified annotation name
+ * @return true if annotation should be considered as a custom nullable annotation.
+ */
+ boolean isCustomNullableAnnotation(String annotationName);
+
+ /**
+ * Checks if the annotation is a custom @Nonnull annotation.
+ *
+ * @param annotationName fully-qualified annotation name
+ * @return true if annotation should be considered as a custom nonnull annotation.
+ */
+ boolean isCustomNonnullAnnotation(String annotationName);
+
+ /**
* Checks if the annotation is an excluded field annotation.
*
* @param annotationName fully-qualified annotation name
diff --git a/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java b/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
index 6f20b81..d6cd7e8 100644
--- a/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
@@ -104,6 +104,16 @@ public class DummyOptionsConfig implements Config {
}
@Override
+ public boolean isCustomNullableAnnotation(String annotationName) {
+ return false;
+ }
+
+ @Override
+ public boolean isCustomNonnullAnnotation(String annotationName) {
+ return false;
+ }
+
+ @Override
public boolean suggestSuppressions() {
throw new IllegalStateException(error_msg);
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
index 6893860..075743e 100644
--- a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
@@ -51,6 +51,8 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig {
static final String FL_ACKNOWLEDGE_ANDROID_RECENT = EP_FL_NAMESPACE + ":AcknowledgeAndroidRecent";
static final String FL_EXCLUDED_FIELD_ANNOT = EP_FL_NAMESPACE + ":ExcludedFieldAnnotations";
static final String FL_INITIALIZER_ANNOT = EP_FL_NAMESPACE + ":CustomInitializerAnnotations";
+ static final String FL_NULLABLE_ANNOT = EP_FL_NAMESPACE + ":CustomNullableAnnotations";
+ static final String FL_NONNULL_ANNOT = EP_FL_NAMESPACE + ":CustomNonnullAnnotations";
static final String FL_CTNN_METHOD = EP_FL_NAMESPACE + ":CastToNonNullMethod";
static final String FL_EXTERNAL_INIT_ANNOT = EP_FL_NAMESPACE + ":ExternalInitAnnotations";
static final String FL_CONTRACT_ANNOT = EP_FL_NAMESPACE + ":CustomContractAnnotations";
@@ -152,6 +154,8 @@ final class ErrorProneCLIFlagsConfig extends AbstractConfig {
flags, FL_CLASS_ANNOTATIONS_TO_EXCLUDE, DEFAULT_CLASS_ANNOTATIONS_TO_EXCLUDE);
initializerAnnotations =
getFlagStringSet(flags, FL_INITIALIZER_ANNOT, DEFAULT_INITIALIZER_ANNOT);
+ customNullableAnnotations = getFlagStringSet(flags, FL_NULLABLE_ANNOT, ImmutableSet.of());
+ customNonnullAnnotations = getFlagStringSet(flags, FL_NONNULL_ANNOT, ImmutableSet.of());
externalInitAnnotations =
getFlagStringSet(flags, FL_EXTERNAL_INIT_ANNOT, DEFAULT_EXTERNAL_INIT_ANNOT);
contractAnnotations = getFlagStringSet(flags, FL_CONTRACT_ANNOT, DEFAULT_CONTRACT_ANNOT);
diff --git a/nullaway/src/main/java/com/uber/nullaway/Nullness.java b/nullaway/src/main/java/com/uber/nullaway/Nullness.java
index 41423ee..d8a042a 100644
--- a/nullaway/src/main/java/com/uber/nullaway/Nullness.java
+++ b/nullaway/src/main/java/com/uber/nullaway/Nullness.java
@@ -157,7 +157,8 @@ public enum Nullness implements AbstractValue<Nullness> {
// matches javax.annotation.CheckForNull and edu.umd.cs.findbugs.annotations.CheckForNull
|| annotName.endsWith(".CheckForNull")
|| (config.acknowledgeAndroidRecent()
- && annotName.equals("androidx.annotation.RecentlyNullable"));
+ && annotName.equals("androidx.annotation.RecentlyNullable"))
+ || config.isCustomNullableAnnotation(annotName);
}
/**
@@ -171,7 +172,8 @@ public enum Nullness implements AbstractValue<Nullness> {
|| annotName.endsWith(".NotNull")
|| annotName.endsWith(".Nonnull")
|| (config.acknowledgeAndroidRecent()
- && annotName.equals("androidx.annotation.RecentlyNonNull"));
+ && annotName.equals("androidx.annotation.RecentlyNonNull"))
+ || config.isCustomNonnullAnnotation(annotName);
}
/**
diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
index 9c97620..3521f66 100644
--- a/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
+++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayTest.java
@@ -3170,4 +3170,62 @@ public class NullAwayTest {
"}")
.doTest();
}
+
+ @Test
+ public void testCustomNullableAnnotation() {
+ makeTestHelperWithArgs(
+ Arrays.asList(
+ "-d",
+ temporaryFolder.getRoot().getAbsolutePath(),
+ "-XepOpt:NullAway:AnnotatedPackages=com.uber",
+ "-XepOpt:NullAway:CustomNullableAnnotations=qual.Null"))
+ .addSourceLines("qual/Null.java", "package qual;", "public @interface Null {", "}")
+ .addSourceLines(
+ "Test.java",
+ "package com.uber;",
+ "import qual.Null;",
+ "class Test {",
+ " @Null Object foo;", // No error, should detect @Null
+ " @Null Object baz(){",
+ " bar(foo);",
+ " return null;", // No error, should detect @Null
+ " }",
+ " String bar(@Null Object item){",
+ " // BUG: Diagnostic contains: dereferenced expression item is @Nullable",
+ " return item.toString();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testCustomNonnullAnnotation() {
+ makeTestHelperWithArgs(
+ Arrays.asList(
+ "-d",
+ temporaryFolder.getRoot().getAbsolutePath(),
+ "-XepOpt:NullAway:AnnotatedPackages=com.uber",
+ "-XepOpt:NullAway:UnannotatedClasses=com.uber.Other",
+ "-XepOpt:NullAway:CustomNonnullAnnotations=qual.NoNull",
+ "-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true"))
+ .addSourceLines("qual/NoNull.java", "package qual;", "public @interface NoNull {", "}")
+ .addSourceLines(
+ "Other.java",
+ "package com.uber;",
+ "import qual.NoNull;",
+ "public class Other {",
+ " void bar(@NoNull Object item) { }",
+ "}")
+ .addSourceLines(
+ "Test.java",
+ "package com.uber;",
+ "class Test {",
+ " Other other = new Other();",
+ " void foo(){",
+ " // BUG: Diagnostic contains: passing @Nullable parameter 'null'",
+ " other.bar(null);",
+ " }",
+ "}")
+ .doTest();
+ }
}