• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.uber.nullaway.handlers;
2 
3 /*
4  * Copyright (c) 2019 Uber Technologies, Inc.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 import com.google.errorprone.util.ASTHelpers;
26 import com.sun.tools.javac.code.Symbol;
27 import com.sun.tools.javac.util.Name;
28 import com.uber.nullaway.annotations.Initializer;
29 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
30 import org.checkerframework.nullaway.dataflow.cfg.node.Node;
31 
32 /**
33  * A utility class that holds the names from the Table. Currently, {@link
34  * com.uber.nullaway.handlers.AssertionHandler} requires it, while {@link
35  * com.uber.nullaway.handlers.OptionalEmptinessHandler} uses it only when AssertionHandler is
36  * enabled.
37  */
38 class MethodNameUtil {
39 
40   // Strings corresponding to the names of the methods (and their owners) used to identify
41   // assertions in this handler.
42   private static final String IS_NOT_NULL_METHOD = "isNotNull";
43   private static final String IS_OWNER_TRUTH_SUBJECT = "com.google.common.truth.Subject";
44   private static final String IS_OWNER_ASSERTJ_ABSTRACT_ASSERT =
45       "org.assertj.core.api.AbstractAssert";
46   private static final String IS_INSTANCE_OF_METHOD = "isInstanceOf";
47   private static final String IS_INSTANCE_OF_ANY_METHOD = "isInstanceOfAny";
48   private static final String IS_TRUE_METHOD = "isTrue";
49   private static final String IS_FALSE_METHOD = "isFalse";
50   private static final String IS_TRUE_OWNER_TRUTH = "com.google.common.truth.BooleanSubject";
51   private static final String IS_TRUE_OWNER_ASSERTJ = "org.assertj.core.api.AbstractBooleanAssert";
52   private static final String BOOLEAN_VALUE_OF_METHOD = "valueOf";
53   private static final String BOOLEAN_VALUE_OF_OWNER = "java.lang.Boolean";
54   private static final String IS_PRESENT_METHOD = "isPresent";
55   private static final String IS_NOT_EMPTY_METHOD = "isNotEmpty";
56   private static final String IS_PRESENT_OWNER_ASSERTJ =
57       "org.assertj.core.api.AbstractOptionalAssert";
58   private static final String ASSERT_THAT_METHOD = "assertThat";
59   private static final String AS_METHOD = "as";
60   private static final String DESCRIBED_AS_METHOD = "describedAs";
61 
62   private static final String ASSERT_THAT_OWNER_TRUTH = "com.google.common.truth.Truth";
63   private static final String ASSERT_THAT_OWNER_ASSERTJ = "org.assertj.core.api.Assertions";
64 
65   private static final String HAMCREST_ASSERT_CLASS = "org.hamcrest.MatcherAssert";
66   private static final String JUNIT_ASSERT_CLASS = "org.junit.Assert";
67   private static final String JUNIT5_ASSERTION_CLASS = "org.junit.jupiter.api.Assertions";
68 
69   private static final String ASSERT_TRUE_METHOD = "assertTrue";
70   private static final String ASSERT_FALSE_METHOD = "assertFalse";
71 
72   private static final String MATCHERS_CLASS = "org.hamcrest.Matchers";
73   private static final String CORE_MATCHERS_CLASS = "org.hamcrest.CoreMatchers";
74   private static final String CORE_IS_NULL_CLASS = "org.hamcrest.core.IsNull";
75   private static final String IS_MATCHER = "is";
76   private static final String IS_A_MATCHER = "isA";
77   private static final String NOT_MATCHER = "not";
78   private static final String NOT_NULL_VALUE_MATCHER = "notNullValue";
79   private static final String NULL_VALUE_MATCHER = "nullValue";
80   private static final String INSTANCE_OF_MATCHER = "instanceOf";
81 
82   // Names of the methods (and their owners) used to identify assertions in this handler. Name used
83   // here refers to com.sun.tools.javac.util.Name. Comparing methods using Names is faster than
84   // comparing using strings.
85   private Name isNotNull;
86 
87   private Name isInstanceOf;
88   private Name isInstanceOfAny;
89   private Name isOwnerTruthSubject;
90   private Name isOwnerAssertJAbstractAssert;
91 
92   private Name isTrue;
93   private Name isFalse;
94   private Name isTrueOwnerTruth;
95   private Name isTrueOwnerAssertJ;
96   private Name isPresent;
97   private Name isNotEmpty;
98   private Name isPresentOwnerAssertJ;
99 
100   private Name isBooleanValueOfMethod;
101   private Name isBooleanValueOfOwner;
102 
103   private Name assertThat;
104   private Name assertThatOwnerTruth;
105   private Name assertThatOwnerAssertJ;
106 
107   private Name as;
108   private Name describedAs;
109 
110   // Names for junit assertion libraries.
111   private Name hamcrestAssertClass;
112   private Name junitAssertClass;
113   private Name junit5AssertionClass;
114 
115   private Name assertTrue;
116   private Name assertFalse;
117 
118   // Names for hamcrest matchers.
119   private Name matchersClass;
120   private Name coreMatchersClass;
121   private Name coreIsNullClass;
122   private Name isMatcher;
123   private Name isAMatcher;
124   private Name notMatcher;
125   private Name notNullValueMatcher;
126   private Name nullValueMatcher;
127   private Name instanceOfMatcher;
128 
129   @Initializer
initializeMethodNames(Name.Table table)130   void initializeMethodNames(Name.Table table) {
131     isNotNull = table.fromString(IS_NOT_NULL_METHOD);
132     isOwnerTruthSubject = table.fromString(IS_OWNER_TRUTH_SUBJECT);
133     isOwnerAssertJAbstractAssert = table.fromString(IS_OWNER_ASSERTJ_ABSTRACT_ASSERT);
134 
135     isInstanceOf = table.fromString(IS_INSTANCE_OF_METHOD);
136     isInstanceOfAny = table.fromString(IS_INSTANCE_OF_ANY_METHOD);
137 
138     isTrue = table.fromString(IS_TRUE_METHOD);
139     isFalse = table.fromString(IS_FALSE_METHOD);
140     isTrueOwnerTruth = table.fromString(IS_TRUE_OWNER_TRUTH);
141     isTrueOwnerAssertJ = table.fromString(IS_TRUE_OWNER_ASSERTJ);
142 
143     isBooleanValueOfMethod = table.fromString(BOOLEAN_VALUE_OF_METHOD);
144     isBooleanValueOfOwner = table.fromString(BOOLEAN_VALUE_OF_OWNER);
145 
146     assertThat = table.fromString(ASSERT_THAT_METHOD);
147     assertThatOwnerTruth = table.fromString(ASSERT_THAT_OWNER_TRUTH);
148     assertThatOwnerAssertJ = table.fromString(ASSERT_THAT_OWNER_ASSERTJ);
149 
150     as = table.fromString(AS_METHOD);
151     describedAs = table.fromString(DESCRIBED_AS_METHOD);
152 
153     isPresent = table.fromString(IS_PRESENT_METHOD);
154     isNotEmpty = table.fromString(IS_NOT_EMPTY_METHOD);
155     isPresentOwnerAssertJ = table.fromString(IS_PRESENT_OWNER_ASSERTJ);
156 
157     hamcrestAssertClass = table.fromString(HAMCREST_ASSERT_CLASS);
158     junitAssertClass = table.fromString(JUNIT_ASSERT_CLASS);
159     junit5AssertionClass = table.fromString(JUNIT5_ASSERTION_CLASS);
160 
161     assertTrue = table.fromString(ASSERT_TRUE_METHOD);
162     assertFalse = table.fromString(ASSERT_FALSE_METHOD);
163 
164     matchersClass = table.fromString(MATCHERS_CLASS);
165     coreMatchersClass = table.fromString(CORE_MATCHERS_CLASS);
166     coreIsNullClass = table.fromString(CORE_IS_NULL_CLASS);
167     isMatcher = table.fromString(IS_MATCHER);
168     isAMatcher = table.fromString(IS_A_MATCHER);
169     notMatcher = table.fromString(NOT_MATCHER);
170     notNullValueMatcher = table.fromString(NOT_NULL_VALUE_MATCHER);
171     nullValueMatcher = table.fromString(NULL_VALUE_MATCHER);
172     instanceOfMatcher = table.fromString(INSTANCE_OF_MATCHER);
173   }
174 
isMethodIsNotNull(Symbol.MethodSymbol methodSymbol)175   boolean isMethodIsNotNull(Symbol.MethodSymbol methodSymbol) {
176     return matchesMethod(methodSymbol, isNotNull, isOwnerTruthSubject)
177         || matchesMethod(methodSymbol, isNotNull, isOwnerAssertJAbstractAssert);
178   }
179 
isMethodIsInstanceOf(Symbol.MethodSymbol methodSymbol)180   boolean isMethodIsInstanceOf(Symbol.MethodSymbol methodSymbol) {
181     return matchesMethod(methodSymbol, isInstanceOf, isOwnerTruthSubject)
182         || matchesMethod(methodSymbol, isInstanceOf, isOwnerAssertJAbstractAssert)
183         // Truth doesn't seem to have isInstanceOfAny
184         || matchesMethod(methodSymbol, isInstanceOfAny, isOwnerAssertJAbstractAssert);
185   }
186 
isMethodAssertTrue(Symbol.MethodSymbol methodSymbol)187   boolean isMethodAssertTrue(Symbol.MethodSymbol methodSymbol) {
188     return matchesMethod(methodSymbol, assertTrue, junitAssertClass)
189         || matchesMethod(methodSymbol, assertTrue, junit5AssertionClass);
190   }
191 
isMethodAssertFalse(Symbol.MethodSymbol methodSymbol)192   boolean isMethodAssertFalse(Symbol.MethodSymbol methodSymbol) {
193     return matchesMethod(methodSymbol, assertFalse, junitAssertClass)
194         || matchesMethod(methodSymbol, assertFalse, junit5AssertionClass);
195   }
196 
isMethodThatEnsuresOptionalPresent(Symbol.MethodSymbol methodSymbol)197   boolean isMethodThatEnsuresOptionalPresent(Symbol.MethodSymbol methodSymbol) {
198     // same owner
199     return matchesMethod(methodSymbol, isPresent, isPresentOwnerAssertJ)
200         || matchesMethod(methodSymbol, isNotEmpty, isPresentOwnerAssertJ);
201   }
202 
isMethodIsTrue(Symbol.MethodSymbol methodSymbol)203   boolean isMethodIsTrue(Symbol.MethodSymbol methodSymbol) {
204     return matchesMethod(methodSymbol, isTrue, isTrueOwnerTruth)
205         || matchesMethod(methodSymbol, isTrue, isTrueOwnerAssertJ);
206   }
207 
isMethodIsFalse(Symbol.MethodSymbol methodSymbol)208   boolean isMethodIsFalse(Symbol.MethodSymbol methodSymbol) {
209     // same owners as isTrue
210     return matchesMethod(methodSymbol, isFalse, isTrueOwnerTruth)
211         || matchesMethod(methodSymbol, isFalse, isTrueOwnerAssertJ);
212   }
213 
isMethodBooleanValueOf(Symbol.MethodSymbol methodSymbol)214   boolean isMethodBooleanValueOf(Symbol.MethodSymbol methodSymbol) {
215     return matchesMethod(methodSymbol, isBooleanValueOfMethod, isBooleanValueOfOwner);
216   }
217 
isMethodAssertThat(Symbol.MethodSymbol methodSymbol)218   boolean isMethodAssertThat(Symbol.MethodSymbol methodSymbol) {
219     return matchesMethod(methodSymbol, assertThat, assertThatOwnerTruth)
220         || matchesMethod(methodSymbol, assertThat, assertThatOwnerAssertJ);
221   }
222 
223   /**
224    * Returns true if the method is describedAs() or as() from AssertJ. Note that this implementation
225    * does not check the ower, as there are many possible implementations. This method should only be
226    * used in a caller content where it is clear that the operation is related to use of AssertJ.
227    *
228    * @param methodSymbol symbol for the method
229    * @return {@code true} iff the method is describedAs() or as() from AssertJ
230    */
isMethodAssertJDescribedAs(Symbol.MethodSymbol methodSymbol)231   public boolean isMethodAssertJDescribedAs(Symbol.MethodSymbol methodSymbol) {
232     return methodSymbol.name.equals(as) || methodSymbol.name.equals(describedAs);
233   }
234 
isMethodHamcrestAssertThat(Symbol.MethodSymbol methodSymbol)235   boolean isMethodHamcrestAssertThat(Symbol.MethodSymbol methodSymbol) {
236     return matchesMethod(methodSymbol, assertThat, hamcrestAssertClass);
237   }
238 
isMethodJunitAssertThat(Symbol.MethodSymbol methodSymbol)239   boolean isMethodJunitAssertThat(Symbol.MethodSymbol methodSymbol) {
240     return matchesMethod(methodSymbol, assertThat, junitAssertClass);
241   }
242 
isMatcherIsNotNull(Node node)243   boolean isMatcherIsNotNull(Node node) {
244     // Matches with
245     //   * is(not(nullValue()))
246     //   * is(notNullValue())
247     if (matchesMatcherMethod(node, isMatcher, matchersClass)
248         || matchesMatcherMethod(node, isMatcher, coreMatchersClass)) {
249       // All overloads of `is` method have exactly one argument.
250       return isMatcherNotNull(((MethodInvocationNode) node).getArgument(0));
251     }
252     return false;
253   }
254 
isMatcherNotNull(Node node)255   private boolean isMatcherNotNull(Node node) {
256     // Matches with
257     //   * not(nullValue())
258     //   * notNullValue()
259     if (matchesMatcherMethod(node, notMatcher, matchersClass)
260         || matchesMatcherMethod(node, notMatcher, coreMatchersClass)) {
261       // All overloads of `not` method have exactly one argument.
262       return isMatcherNull(((MethodInvocationNode) node).getArgument(0));
263     }
264     return matchesMatcherMethod(node, notNullValueMatcher, matchersClass)
265         || matchesMatcherMethod(node, notNullValueMatcher, coreMatchersClass)
266         || matchesMatcherMethod(node, notNullValueMatcher, coreIsNullClass);
267   }
268 
isMatcherNull(Node node)269   private boolean isMatcherNull(Node node) {
270     // Matches with nullValue()
271     return matchesMatcherMethod(node, nullValueMatcher, matchersClass)
272         || matchesMatcherMethod(node, nullValueMatcher, coreMatchersClass)
273         || matchesMatcherMethod(node, nullValueMatcher, coreIsNullClass);
274   }
275 
isMatcherIsInstanceOf(Node node)276   boolean isMatcherIsInstanceOf(Node node) {
277     // Matches with
278     //   * is(instanceOf(Some.class))
279     //   * isA(Some.class)
280     if (matchesMatcherMethod(node, isMatcher, matchersClass)
281         || matchesMatcherMethod(node, isMatcher, coreMatchersClass)) {
282       // All overloads of `is` method have exactly one argument.
283       Node inner = ((MethodInvocationNode) node).getArgument(0);
284       return matchesMatcherMethod(inner, instanceOfMatcher, matchersClass)
285           || matchesMatcherMethod(inner, instanceOfMatcher, coreMatchersClass);
286     }
287     return (matchesMatcherMethod(node, isAMatcher, matchersClass)
288         || matchesMatcherMethod(node, isAMatcher, coreMatchersClass));
289   }
290 
matchesMatcherMethod(Node node, Name matcherName, Name matcherClass)291   private boolean matchesMatcherMethod(Node node, Name matcherName, Name matcherClass) {
292     if (node instanceof MethodInvocationNode) {
293       MethodInvocationNode methodInvocationNode = (MethodInvocationNode) node;
294       Symbol.MethodSymbol callee = ASTHelpers.getSymbol(methodInvocationNode.getTree());
295       return matchesMethod(callee, matcherName, matcherClass);
296     }
297     return false;
298   }
299 
matchesMethod( Symbol.MethodSymbol methodSymbol, Name toMatchMethodName, Name toMatchOwnerName)300   private boolean matchesMethod(
301       Symbol.MethodSymbol methodSymbol, Name toMatchMethodName, Name toMatchOwnerName) {
302     return methodSymbol.name.equals(toMatchMethodName)
303         && methodSymbol.owner.getQualifiedName().equals(toMatchOwnerName);
304   }
305 
isUtilInitialized()306   boolean isUtilInitialized() {
307     return isNotNull != null;
308   }
309 }
310