Repository: uber/NullAway (Java)

Issue:
JSpecifyMode: Checking of :: reference to unmarked method is too strict
```java
Function<@Nullable Object, Boolean> myFilter = Objects::isNull;
// warning: [NullAway] parameter arg0 of referenced method is @NonNull, but parameter in functional interface method java.util.function.Function.apply(T) is @Nullable
```

This should be allowed, since `Objects.isNull(Object)` is unmarked (and does accept a nullable object), but it's being treated as if its parameter were `@NonNull` annotated.

**Workaround:** Using an explicit lambda+call, `o -> Objects.isNull(o)`, will work.




Fix (patch):
diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
index c211f0f7cc..32f97a524d 100644
--- a/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
+++ b/nullaway/src/main/java/com/uber/nullaway/ErrorProneCLIFlagsConfig.java
@@ -500,7 +500,8 @@ public boolean suggestSuppressions() {
 
   @Override
   public boolean acknowledgeRestrictiveAnnotations() {
-    return isAcknowledgeRestrictive;
+    // restrictive annotations must always be acknowledged in JSpecify mode
+    return isAcknowledgeRestrictive || jspecifyMode;
   }
 
   @Override
diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java
index 8acee99a77..904144f2e3 100644
--- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java
+++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java
@@ -776,6 +776,8 @@ public Description matchParameterizedType(ParameterizedTypeTree tree, VisitorSta
    *     LambdaExpressionTree}; otherwise {@code null}
    * @param memberReferenceTree if the overriding method is a member reference (which "overrides" a
    *     functional interface method), the {@link MemberReferenceTree}; otherwise {@code null}
+   * @param state visitor state
+   * @param overridingMethod if available, the symbol for the overriding method
    * @return discovered error, or {@link Description#NO_MATCH} if no error
    */
   private Description checkParamOverriding(
@@ -783,13 +785,18 @@ private Description checkParamOverriding(
       Symbol.MethodSymbol overriddenMethod,
       @Nullable LambdaExpressionTree lambdaExpressionTree,
       @Nullable MemberReferenceTree memberReferenceTree,
-      VisitorState state) {
+      VisitorState state,
+      Symbol.@Nullable MethodSymbol overridingMethod) {
     com.sun.tools.javac.util.List<VarSymbol> superParamSymbols = overriddenMethod.getParameters();
     boolean unboundMemberRef =
         (memberReferenceTree != null)
             && ((JCTree.JCMemberReference) memberReferenceTree).kind.isUnbound();
     boolean isOverriddenMethodAnnotated =
         !codeAnnotationInfo.isSymbolUnannotated(overriddenMethod, config, handler);
+    boolean isOverridingMethodAnnotated =
+        (overridingMethod != null
+                && !codeAnnotationInfo.isSymbolUnannotated(overridingMethod, config, handler))
+            || lambdaExpressionTree != null;
 
     // Get argument nullability for the overridden method.  If overriddenMethodArgNullnessMap[i] is
     // null, parameter i is treated as unannotated.
@@ -885,7 +892,7 @@ private Description checkParamOverriding(
           lambdaExpressionTree != null
               && NullabilityUtil.lambdaParamIsImplicitlyTyped(
                   lambdaExpressionTree.getParameters().get(methodParamInd));
-      if (!Nullness.hasNullableAnnotation(paramSymbol, config) && !implicitlyTypedLambdaParam) {
+      if (!implicitlyTypedLambdaParam && paramIsNonNull(paramSymbol, isOverridingMethodAnnotated)) {
         String message =
             "parameter "
                 + paramSymbol.name.toString()
@@ -915,6 +922,16 @@ private Description checkParamOverriding(
     return Description.NO_MATCH;
   }
 
+  private boolean paramIsNonNull(VarSymbol paramSymbol, boolean isMethodAnnotated) {
+    if (isMethodAnnotated) {
+      return !Nullness.hasNullableAnnotation(paramSymbol, config);
+    } else if (config.acknowledgeRestrictiveAnnotations()) {
+      // can still be @NonNull if there is a restrictive annotation
+      return Nullness.hasNonNullAnnotation(paramSymbol, config);
+    }
+    return false;
+  }
+
   static Trees getTreesInstance(VisitorState state) {
     return Trees.instance(JavacProcessingEnvironment.instance(state.context));
   }
@@ -1023,7 +1040,8 @@ public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState
             funcInterfaceMethod,
             tree,
             null,
-            state);
+            state,
+            null);
     if (description != Description.NO_MATCH) {
       return description;
     }
@@ -1117,7 +1135,12 @@ && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL)
     // if any parameter in the super method is annotated @Nullable,
     // overriding method cannot assume @Nonnull
     return checkParamOverriding(
-        overridingMethod.getParameters(), overriddenMethod, null, memberReferenceTree, state);
+        overridingMethod.getParameters(),
+        overriddenMethod,
+        null,
+        memberReferenceTree,
+        state,
+        overridingMethod);
   }
 
   private boolean overriddenMethodReturnsNonNull(
diff --git a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java
index 8e49108eee..8cc07bbae4 100644
--- a/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java
+++ b/nullaway/src/main/java/com/uber/nullaway/handlers/Handlers.java
@@ -46,12 +46,8 @@ public static Handler buildDefault(Config config) {
     ImmutableList.Builder<Handler> handlerListBuilder = ImmutableList.builder();
     MethodNameUtil methodNameUtil = new MethodNameUtil();
 
-    // Acknowledge restrictive annotations if in JSpecify mode or if the user has explicitly
-    // requested it
-    boolean acknowledgeRestrictive =
-        config.acknowledgeRestrictiveAnnotations() || config.isJSpecifyMode();
     RestrictiveAnnotationHandler restrictiveAnnotationHandler = null;
-    if (acknowledgeRestrictive) {
+    if (config.acknowledgeRestrictiveAnnotations()) {
       // This runs before LibraryModelsHandler, so that library models can override third-party
       // bytecode annotations
       restrictiveAnnotationHandler = new RestrictiveAnnotationHandler(config);