Allow Library Models to override annotations. (#624)
Currently `LibraryModels` can only give information regarding **unannotated** source code. This PR enables `LibraryModels` to override annotations in an **annotated** source code. This behavior is controlled by a flag.
To enable this feature, the following error prone flag must be passed:
```shell
-XepOpt:NullAway:AllowLibraryModelsOverrideAnnotations=true
```
The main motivation for this PR is to prepare the foundation for [Annotator](https://github.com/nimakarimipour/NullAwayAnnotator) to perform upstream API analysis and is an alternative way for #620.
diff --git a/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java b/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
index 57af691..3611c5f 100644
--- a/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/AbstractConfig.java
@@ -129,6 +129,8 @@
protected FixSerializationConfig fixSerializationConfig;
+ protected boolean acknowledgeLibraryModelsOfAnnotatedCode;
+
@Override
public boolean serializationIsActive() {
return serializationActivationFlag;
@@ -349,4 +351,9 @@
public boolean acknowledgeAndroidRecent() {
return acknowledgeAndroidRecent;
}
+
+ @Override
+ public boolean acknowledgeLibraryModelsOfAnnotatedCode() {
+ return acknowledgeLibraryModelsOfAnnotatedCode;
+ }
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/Config.java b/nullaway/src/main/java/com/uber/nullaway/Config.java
index f727093..5055b86 100644
--- a/nullaway/src/main/java/com/uber/nullaway/Config.java
+++ b/nullaway/src/main/java/com/uber/nullaway/Config.java
@@ -292,4 +292,12 @@
* similarly for {@code @RecentlyNonNull}
*/
boolean acknowledgeAndroidRecent();
+
+ /**
+ * Checks if {@link LibraryModels} can override annotations on annotated source code.
+ *
+ * @return true if NullAway should use information provided by {@link LibraryModels} on annotated
+ * source code.
+ */
+ boolean acknowledgeLibraryModelsOfAnnotatedCode();
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java b/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
index 9fcfb543..0aebd44 100644
--- a/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/DummyOptionsConfig.java
@@ -215,4 +215,9 @@
public boolean acknowledgeAndroidRecent() {
throw new IllegalStateException(ERROR_MESSAGE);
}
+
+ @Override
+ public boolean acknowledgeLibraryModelsOfAnnotatedCode() {
+ throw new IllegalStateException(ERROR_MESSAGE);
+ }
}
diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
index 06923c3..49ba811 100644
--- a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
@@ -84,6 +84,9 @@
static final String FL_FIX_SERIALIZATION_CONFIG_PATH =
EP_FL_NAMESPACE + ":FixSerializationConfigPath";
+ static final String FL_ACKNOWLEDGE_LIBRARY_MODELS_OF_ANNOTATED_CODE =
+ EP_FL_NAMESPACE + ":AcknowledgeLibraryModelsOfAnnotatedCode";
+
private static final String DELIMITER = ",";
static final ImmutableSet<String> DEFAULT_CLASS_ANNOTATIONS_TO_EXCLUDE =
@@ -248,6 +251,8 @@
+ FL_SUGGEST_SUPPRESSIONS
+ ")");
}
+ acknowledgeLibraryModelsOfAnnotatedCode =
+ flags.getBoolean(FL_ACKNOWLEDGE_LIBRARY_MODELS_OF_ANNOTATED_CODE).orElse(false);
}
private static ImmutableSet<String> getFlagStringSet(ErrorProneFlags flags, String flagName) {
diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java
index 9a075c2..3252e4f 100644
--- a/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java
+++ b/nullaway/src/main/java/com/uber/nullaway/handlers/LibraryModelsHandler.java
@@ -107,7 +107,9 @@
if (expr.getKind() == Tree.Kind.METHOD_INVOCATION) {
OptimizedLibraryModels optLibraryModels = getOptLibraryModels(state.context);
Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) ASTHelpers.getSymbol(expr);
- if (!getClassAnnotationInfo(state.context).isSymbolUnannotated(methodSymbol, this.config)) {
+ if (!config.acknowledgeLibraryModelsOfAnnotatedCode()
+ && !getClassAnnotationInfo(state.context)
+ .isSymbolUnannotated(methodSymbol, this.config)) {
// We only look at library models for unannotated (i.e. third-party) code.
return exprMayBeNull;
} else if (optLibraryModels.hasNullableReturn(methodSymbol, state.getTypes())
@@ -167,7 +169,8 @@
AccessPathNullnessPropagation.Updates bothUpdates) {
Symbol.MethodSymbol callee = ASTHelpers.getSymbol(node.getTree());
Preconditions.checkNotNull(callee);
- if (!getClassAnnotationInfo(context).isSymbolUnannotated(callee, this.config)) {
+ if (!config.acknowledgeLibraryModelsOfAnnotatedCode()
+ && !getClassAnnotationInfo(context).isSymbolUnannotated(callee, this.config)) {
// Ignore annotated methods, library models should only apply to "unannotated" code.
return NullnessHint.UNKNOWN;
}
diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayAcknowledgeRestrictiveAnnotationsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayAcknowledgeRestrictiveAnnotationsTests.java
index 479e5ca..1c1b099 100644
--- a/nullaway/src/test/java/com/uber/nullaway/NullAwayAcknowledgeRestrictiveAnnotationsTests.java
+++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayAcknowledgeRestrictiveAnnotationsTests.java
@@ -1,6 +1,5 @@
package com.uber.nullaway;
-import com.uber.nullaway.testlibrarymodels.TestLibraryModels;
import java.util.Arrays;
import org.junit.Test;
@@ -292,44 +291,4 @@
"}")
.doTest();
}
-
- @Test
- public void libraryModelsOverrideRestrictiveAnnotations() {
- makeTestHelperWithArgs(
- Arrays.asList(
- "-processorpath",
- TestLibraryModels.class
- .getProtectionDomain()
- .getCodeSource()
- .getLocation()
- .getPath(),
- "-d",
- temporaryFolder.getRoot().getAbsolutePath(),
- "-XepOpt:NullAway:AnnotatedPackages=com.uber",
- "-XepOpt:NullAway:UnannotatedSubPackages=com.uber.lib.unannotated",
- "-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true"))
- .addSourceLines(
- "Test.java",
- "package com.uber;",
- "import com.uber.lib.unannotated.RestrictivelyAnnotatedFIWithModelOverride;",
- "import javax.annotation.Nullable;",
- "public class Test {",
- " void bar(RestrictivelyAnnotatedFIWithModelOverride f) {",
- " // Param is @NullableDecl in bytecode, overridden by library model",
- " // BUG: Diagnostic contains: passing @Nullable parameter 'null' where @NonNull",
- " f.apply(null);",
- " }",
- " void foo() {",
- " RestrictivelyAnnotatedFIWithModelOverride func = (x) -> {",
- " // Param is @NullableDecl in bytecode, overridden by library model, thus safe",
- " return x.toString();",
- " };",
- " }",
- " void baz() {",
- " // Safe to pass, since Function can't have a null instance parameter",
- " bar(Object::toString);",
- " }",
- "}")
- .doTest();
- }
}
diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java
new file mode 100644
index 0000000..6cafcef
--- /dev/null
+++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayCustomLibraryModelsTests.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2022 Uber Technologies, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.uber.nullaway;
+
+import com.google.errorprone.CompilationTestHelper;
+import com.uber.nullaway.testlibrarymodels.TestLibraryModels;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class NullAwayCustomLibraryModelsTests extends NullAwayTestsBase {
+
+ private CompilationTestHelper makeLibraryModelsTestHelperWithArgs(List<String> args) {
+ // Adding directly to args will throw an UnsupportedOperationException, since that list is
+ // created by calling Arrays.asList (for consistency with the rest of NullAway's test cases),
+ // which produces a list which doesn't support add/addAll. Because of this, before we add our
+ // additional arguments, we must first copy the list into a mutable ArrayList.
+ List<String> extendedArguments = new ArrayList<>(args);
+ extendedArguments.addAll(
+ 0,
+ Arrays.asList(
+ "-processorpath",
+ TestLibraryModels.class.getProtectionDomain().getCodeSource().getLocation().getPath()));
+ return makeTestHelperWithArgs(extendedArguments);
+ }
+
+ @Test
+ public void allowLibraryModelsOverrideAnnotations() {
+ makeLibraryModelsTestHelperWithArgs(
+ Arrays.asList(
+ "-d",
+ temporaryFolder.getRoot().getAbsolutePath(),
+ "-XepOpt:NullAway:AnnotatedPackages=com.uber",
+ "-XepOpt:NullAway:AcknowledgeLibraryModelsOfAnnotatedCode=true"))
+ .addSourceLines(
+ "Foo.java",
+ "package com.uber;",
+ "public class Foo {",
+ " Object field = new Object();",
+ " Object bar() {",
+ " return new Object();",
+ " }",
+ " Object nullableReturn() {",
+ " // BUG: Diagnostic contains: returning @Nullable",
+ " return bar();",
+ " }",
+ " void run() {",
+ " // just to make sure, flow analysis is also impacted by library models information",
+ " Object temp = bar();",
+ " // BUG: Diagnostic contains: assigning @Nullable",
+ " this.field = temp;",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void allowLibraryModelsOverrideAnnotationsFlagTest() {
+ makeLibraryModelsTestHelperWithArgs(
+ Arrays.asList(
+ "-d",
+ temporaryFolder.getRoot().getAbsolutePath(),
+ "-XepOpt:NullAway:AnnotatedPackages=com.uber",
+ "-XepOpt:NullAway:AcknowledgeLibraryModelsOfAnnotatedCode=false"))
+ .addSourceLines(
+ "Foo.java",
+ "package com.uber;",
+ "public class Foo {",
+ " Object field = new Object();",
+ " Object bar() {",
+ " return new Object();",
+ " }",
+ " Object nullableReturn() {",
+ " return bar();",
+ " }",
+ " void run() {",
+ " // just to make sure, flow analysis is not impacted by library models information",
+ " Object temp = bar();",
+ " this.field = temp;",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void libraryModelsOverrideRestrictiveAnnotations() {
+ makeLibraryModelsTestHelperWithArgs(
+ Arrays.asList(
+ "-d",
+ temporaryFolder.getRoot().getAbsolutePath(),
+ "-XepOpt:NullAway:AnnotatedPackages=com.uber",
+ "-XepOpt:NullAway:UnannotatedSubPackages=com.uber.lib.unannotated",
+ "-XepOpt:NullAway:AcknowledgeRestrictiveAnnotations=true"))
+ .addSourceLines(
+ "Test.java",
+ "package com.uber;",
+ "import com.uber.lib.unannotated.RestrictivelyAnnotatedFIWithModelOverride;",
+ "import javax.annotation.Nullable;",
+ "public class Test {",
+ " void bar(RestrictivelyAnnotatedFIWithModelOverride f) {",
+ " // Param is @NullableDecl in bytecode, overridden by library model",
+ " // BUG: Diagnostic contains: passing @Nullable parameter 'null' where @NonNull",
+ " f.apply(null);",
+ " }",
+ " void foo() {",
+ " RestrictivelyAnnotatedFIWithModelOverride func = (x) -> {",
+ " // Param is @NullableDecl in bytecode, overridden by library model, thus safe",
+ " return x.toString();",
+ " };",
+ " }",
+ " void baz() {",
+ " // Safe to pass, since Function can't have a null instance parameter",
+ " bar(Object::toString);",
+ " }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java b/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
index 5fbe670..debf6b1 100644
--- a/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
+++ b/test-library-models/src/main/java/com/uber/nullaway/testlibrarymodels/TestLibraryModels.java
@@ -63,7 +63,7 @@
@Override
public ImmutableSet<MethodRef> nullableReturns() {
- return ImmutableSet.of();
+ return ImmutableSet.of(methodRef("com.uber.Foo", "bar()"));
}
@Override