• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.uber.nullaway.handlers.temporary;
2 
3 import com.google.errorprone.VisitorState;
4 import com.google.errorprone.util.ASTHelpers;
5 import com.sun.source.tree.LambdaExpressionTree;
6 import com.sun.source.tree.MethodInvocationTree;
7 import com.sun.tools.javac.code.Symbol;
8 import com.uber.nullaway.NullabilityUtil;
9 import com.uber.nullaway.Nullness;
10 import com.uber.nullaway.handlers.BaseNoOpHandler;
11 import java.util.Arrays;
12 import javax.lang.model.element.Name;
13 
14 /**
15  * This handler provides a temporary workaround due to our lack of support for generics, which
16  * allows natural usage of Futures/FluentFuture Guava APIs. It can potentially introduce false
17  * negatives, however, and should be deprecated as soon as full generic support is available.
18  *
19  * <p>This works by special casing the return nullability of {@link com.google.common.base.Function}
20  * and {@link com.google.common.util.concurrent.AsyncFunction} to be e.g. {@code Function<@Nullable
21  * T>} whenever these functional interfaces are implemented as a lambda expression passed to a list
22  * of specific methods of {@link com.google.common.util.concurrent.FluentFuture} or {@link
23  * com.google.common.util.concurrent.Futures}. We cannot currently check that {@code T} for {@code
24  * FluentFuture<T>} is a {@code @Nullable} type, so this is unsound. However, we have found many
25  * cases in practice where these lambdas include {@code null} returns, which were already being
26  * ignored (due to a bug) before PR #765. This handler offers the best possible support for these
27  * cases, at least until our generics support is mature enough to handle them.
28  *
29  * <p>Note: Package {@code com.uber.nullaway.handlers.temporary} is meant for this sort of temporary
30  * workaround handler, to be removed as future NullAway features make them unnecessary. This is a
31  * hack, but the best of a bunch of bad options.
32  */
33 public class FluentFutureHandler extends BaseNoOpHandler {
34 
35   private static final String GUAVA_FUNCTION_CLASS_NAME = "com.google.common.base.Function";
36   private static final String GUAVA_ASYNC_FUNCTION_CLASS_NAME =
37       "com.google.common.util.concurrent.AsyncFunction";
38   private static final String FLUENT_FUTURE_CLASS_NAME =
39       "com.google.common.util.concurrent.FluentFuture";
40   private static final String FUTURES_CLASS_NAME = "com.google.common.util.concurrent.Futures";
41   private static final String FUNCTION_APPLY_METHOD_NAME = "apply";
42   private static final String[] FLUENT_FUTURE_INCLUDE_LIST_METHODS = {
43     "catching", "catchingAsync", "transform", "transformAsync"
44   };
45 
isGuavaFunctionDotApply(Symbol.MethodSymbol methodSymbol)46   private static boolean isGuavaFunctionDotApply(Symbol.MethodSymbol methodSymbol) {
47     Name className = methodSymbol.enclClass().flatName();
48     return (className.contentEquals(GUAVA_FUNCTION_CLASS_NAME)
49             || className.contentEquals(GUAVA_ASYNC_FUNCTION_CLASS_NAME))
50         && methodSymbol.name.contentEquals(FUNCTION_APPLY_METHOD_NAME);
51   }
52 
isFluentFutureIncludeListMethod(Symbol.MethodSymbol methodSymbol)53   private static boolean isFluentFutureIncludeListMethod(Symbol.MethodSymbol methodSymbol) {
54     Name className = methodSymbol.enclClass().flatName();
55     return (className.contentEquals(FLUENT_FUTURE_CLASS_NAME)
56             || className.contentEquals(FUTURES_CLASS_NAME))
57         && Arrays.stream(FLUENT_FUTURE_INCLUDE_LIST_METHODS)
58             .anyMatch(s -> methodSymbol.name.contentEquals(s));
59   }
60 
61   @Override
onOverrideMethodReturnNullability( Symbol.MethodSymbol methodSymbol, VisitorState state, boolean isAnnotated, Nullness returnNullness)62   public Nullness onOverrideMethodReturnNullability(
63       Symbol.MethodSymbol methodSymbol,
64       VisitorState state,
65       boolean isAnnotated,
66       Nullness returnNullness) {
67     // We only care about lambda's implementing Guava's Function
68     if (!isGuavaFunctionDotApply(methodSymbol)) {
69       return returnNullness;
70     }
71     // Check if we are inside a lambda passed as an argument to a method call:
72     LambdaExpressionTree enclosingLambda =
73         ASTHelpers.findEnclosingNode(state.getPath(), LambdaExpressionTree.class);
74     if (enclosingLambda == null
75         || !NullabilityUtil.getFunctionalInterfaceMethod(enclosingLambda, state.getTypes())
76             .equals(methodSymbol)) {
77       return returnNullness;
78     }
79     MethodInvocationTree methodInvocation =
80         ASTHelpers.findEnclosingNode(state.getPath(), MethodInvocationTree.class);
81     if (methodInvocation == null || !methodInvocation.getArguments().contains(enclosingLambda)) {
82       return returnNullness;
83     }
84     // Check if that method call is one of the FluentFuture APIs we care about
85     Symbol.MethodSymbol lambdaConsumerMethodSymbol = ASTHelpers.getSymbol(methodInvocation);
86     if (!isFluentFutureIncludeListMethod(lambdaConsumerMethodSymbol)) {
87       return returnNullness;
88     }
89     return Nullness.NULLABLE;
90   }
91 }
92