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