1 /* 2 * Copyright (c) 2017 Uber Technologies, Inc. 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 * THE SOFTWARE. 21 */ 22 23 package com.uber.nullaway; 24 25 import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; 26 import static com.sun.source.tree.Tree.Kind.EXPRESSION_STATEMENT; 27 import static com.sun.source.tree.Tree.Kind.IDENTIFIER; 28 import static com.sun.source.tree.Tree.Kind.OTHER; 29 import static com.sun.source.tree.Tree.Kind.PARENTHESIZED; 30 import static com.sun.source.tree.Tree.Kind.TYPE_CAST; 31 import static com.uber.nullaway.ASTHelpersBackports.hasDirectAnnotationWithSimpleName; 32 import static com.uber.nullaway.ASTHelpersBackports.isStatic; 33 import static com.uber.nullaway.ErrorBuilder.errMsgForInitializer; 34 import static com.uber.nullaway.NullabilityUtil.castToNonNull; 35 36 import com.google.auto.service.AutoService; 37 import com.google.auto.value.AutoValue; 38 import com.google.common.base.Preconditions; 39 import com.google.common.base.Verify; 40 import com.google.common.collect.ImmutableList; 41 import com.google.common.collect.ImmutableMultimap; 42 import com.google.common.collect.ImmutableSet; 43 import com.google.common.collect.LinkedHashMultimap; 44 import com.google.common.collect.Multimap; 45 import com.google.common.collect.SetMultimap; 46 import com.google.common.collect.Sets; 47 import com.google.errorprone.BugPattern; 48 import com.google.errorprone.ErrorProneFlags; 49 import com.google.errorprone.VisitorState; 50 import com.google.errorprone.bugpatterns.BugChecker; 51 import com.google.errorprone.matchers.Description; 52 import com.google.errorprone.matchers.Matcher; 53 import com.google.errorprone.matchers.Matchers; 54 import com.google.errorprone.suppliers.Suppliers; 55 import com.google.errorprone.util.ASTHelpers; 56 import com.sun.source.tree.ArrayAccessTree; 57 import com.sun.source.tree.AssignmentTree; 58 import com.sun.source.tree.BinaryTree; 59 import com.sun.source.tree.BlockTree; 60 import com.sun.source.tree.ClassTree; 61 import com.sun.source.tree.CompoundAssignmentTree; 62 import com.sun.source.tree.ConditionalExpressionTree; 63 import com.sun.source.tree.EnhancedForLoopTree; 64 import com.sun.source.tree.ExpressionStatementTree; 65 import com.sun.source.tree.ExpressionTree; 66 import com.sun.source.tree.ForLoopTree; 67 import com.sun.source.tree.IdentifierTree; 68 import com.sun.source.tree.IfTree; 69 import com.sun.source.tree.LambdaExpressionTree; 70 import com.sun.source.tree.MemberReferenceTree; 71 import com.sun.source.tree.MemberSelectTree; 72 import com.sun.source.tree.MethodInvocationTree; 73 import com.sun.source.tree.MethodTree; 74 import com.sun.source.tree.NewClassTree; 75 import com.sun.source.tree.ParameterizedTypeTree; 76 import com.sun.source.tree.ParenthesizedTree; 77 import com.sun.source.tree.ReturnTree; 78 import com.sun.source.tree.StatementTree; 79 import com.sun.source.tree.SwitchTree; 80 import com.sun.source.tree.Tree; 81 import com.sun.source.tree.TryTree; 82 import com.sun.source.tree.TypeCastTree; 83 import com.sun.source.tree.UnaryTree; 84 import com.sun.source.tree.VariableTree; 85 import com.sun.source.tree.WhileLoopTree; 86 import com.sun.source.util.TreePath; 87 import com.sun.source.util.Trees; 88 import com.sun.tools.javac.code.Symbol; 89 import com.sun.tools.javac.code.Symbol.ClassSymbol; 90 import com.sun.tools.javac.code.Symbol.VarSymbol; 91 import com.sun.tools.javac.code.Type; 92 import com.sun.tools.javac.processing.JavacProcessingEnvironment; 93 import com.sun.tools.javac.tree.JCTree; 94 import com.uber.nullaway.ErrorMessage.MessageTypes; 95 import com.uber.nullaway.dataflow.AccessPathNullnessAnalysis; 96 import com.uber.nullaway.dataflow.EnclosingEnvironmentNullness; 97 import com.uber.nullaway.generics.GenericsChecks; 98 import com.uber.nullaway.handlers.Handler; 99 import com.uber.nullaway.handlers.Handlers; 100 import java.util.ArrayList; 101 import java.util.LinkedHashMap; 102 import java.util.LinkedHashSet; 103 import java.util.List; 104 import java.util.Map; 105 import java.util.Objects; 106 import java.util.Optional; 107 import java.util.Set; 108 import java.util.function.Predicate; 109 import java.util.stream.Collectors; 110 import java.util.stream.StreamSupport; 111 import javax.annotation.Nullable; 112 import javax.inject.Inject; 113 import javax.lang.model.element.AnnotationMirror; 114 import javax.lang.model.element.Element; 115 import javax.lang.model.element.ElementKind; 116 import javax.lang.model.element.Modifier; 117 import javax.lang.model.element.NestingKind; 118 import javax.lang.model.type.TypeKind; 119 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode; 120 import org.checkerframework.nullaway.javacutil.ElementUtils; 121 import org.checkerframework.nullaway.javacutil.TreeUtils; 122 123 /** 124 * Checker for nullability errors. It assumes that any field, method parameter, or return type that 125 * may be null is annotated with {@link Nullable}, and then checks the following rules: 126 * 127 * <ul> 128 * <li>no assignment of a nullable expression into a non-null field 129 * <li>no passing a nullable expression into a non-null parameter 130 * <li>no returning a nullable expression from a method with non-null return type 131 * <li>no field access or method invocation on an expression that is nullable 132 * </ul> 133 * 134 * <p>This checker also detects errors related to field initialization. For any @NonNull instance 135 * field <code>f</code>, this checker ensures that at least one of the following cases holds: 136 * 137 * <ol> 138 * <li><code>f</code> is directly initialized at its declaration 139 * <li><code>f</code> is always initialized in all constructors 140 * <li><code>f</code> is always initialized in some method annotated with @Initializer 141 * </ol> 142 * 143 * <p>For any @NonNull static field <code>f</code>, this checker ensures that at least one of the 144 * following cases holds: 145 * 146 * <ol> 147 * <li><code>f</code> is directly initialized at its declaration 148 * <li><code>f</code> is always initialized in some static initializer block 149 * </ol> 150 */ 151 @AutoService(BugChecker.class) 152 @BugPattern( 153 name = "NullAway", 154 altNames = {"CheckNullabilityTypes"}, 155 summary = "Nullability type error.", 156 tags = BugPattern.StandardTags.LIKELY_ERROR, 157 severity = WARNING) 158 @SuppressWarnings("BugPatternNaming") // remove once we require EP 2.11+ 159 public class NullAway extends BugChecker 160 implements BugChecker.MethodInvocationTreeMatcher, 161 BugChecker.AssignmentTreeMatcher, 162 BugChecker.MemberSelectTreeMatcher, 163 BugChecker.ArrayAccessTreeMatcher, 164 BugChecker.ReturnTreeMatcher, 165 BugChecker.ClassTreeMatcher, 166 BugChecker.MethodTreeMatcher, 167 BugChecker.VariableTreeMatcher, 168 BugChecker.NewClassTreeMatcher, 169 BugChecker.BinaryTreeMatcher, 170 BugChecker.UnaryTreeMatcher, 171 BugChecker.ConditionalExpressionTreeMatcher, 172 BugChecker.IfTreeMatcher, 173 BugChecker.WhileLoopTreeMatcher, 174 BugChecker.ForLoopTreeMatcher, 175 BugChecker.EnhancedForLoopTreeMatcher, 176 BugChecker.LambdaExpressionTreeMatcher, 177 BugChecker.IdentifierTreeMatcher, 178 BugChecker.MemberReferenceTreeMatcher, 179 BugChecker.CompoundAssignmentTreeMatcher, 180 BugChecker.SwitchTreeMatcher, 181 BugChecker.TypeCastTreeMatcher, 182 BugChecker.ParameterizedTypeTreeMatcher { 183 184 static final String INITIALIZATION_CHECK_NAME = "NullAway.Init"; 185 static final String OPTIONAL_CHECK_NAME = "NullAway.Optional"; 186 // Unmatched, used for when we only want full checker suppressions to work 187 static final String CORE_CHECK_NAME = "NullAway.<core>"; 188 189 private static final Matcher<ExpressionTree> THIS_MATCHER = NullAway::isThisIdentifierMatcher; 190 191 private final Predicate<MethodInvocationNode> nonAnnotatedMethod; 192 193 /** 194 * Possible levels of null-marking / annotatedness for a class. This may be set to FULLY_MARKED or 195 * FULLY_UNMARKED optimistically but then adjusted to PARTIALLY_MARKED later based on annotations 196 * within the class; see {@link #matchClass(ClassTree, VisitorState)} 197 */ 198 private enum NullMarking { 199 /** full class is annotated for nullness checking */ 200 FULLY_MARKED, 201 /** full class is unannotated */ 202 FULLY_UNMARKED, 203 /** 204 * class has a mix of annotatedness, depending on presence of {@link 205 * org.jspecify.annotations.NullMarked} annotations 206 */ 207 PARTIALLY_MARKED 208 } 209 210 /** 211 * Null-marking level for the current top-level class. The initial value of this field doesn't 212 * matter, as it will be set appropriately in {@link #matchClass(ClassTree, VisitorState)} 213 */ 214 private NullMarking nullMarkingForTopLevelClass = NullMarking.FULLY_MARKED; 215 216 /** 217 * We store the CodeAnnotationInfo object in a field for convenience; it is initialized in {@link 218 * #matchClass(ClassTree, VisitorState)} 219 */ 220 // suppress initialization warning rather than casting everywhere; we know matchClass() will 221 // always be called before the field gets dereferenced 222 @SuppressWarnings("NullAway.Init") 223 private CodeAnnotationInfo codeAnnotationInfo; 224 225 private final Config config; 226 227 /** Returns the configuration being used for this analysis. */ getConfig()228 public Config getConfig() { 229 return config; 230 } 231 232 private final ErrorBuilder errorBuilder; 233 234 /** 235 * The handler passed to our analysis (usually a {@code CompositeHandler} including handlers for 236 * various APIs. 237 */ 238 private final Handler handler; 239 240 /** 241 * entities relevant to field initialization per class. cached for performance. nulled out in 242 * {@link #matchClass(ClassTree, VisitorState)} 243 */ 244 private final Map<Symbol.ClassSymbol, FieldInitEntities> class2Entities = new LinkedHashMap<>(); 245 246 /** 247 * fields not initialized by constructors, per class. cached for performance. nulled out in {@link 248 * #matchClass(ClassTree, VisitorState)} 249 */ 250 private final SetMultimap<Symbol.ClassSymbol, Symbol> class2ConstructorUninit = 251 LinkedHashMultimap.create(); 252 253 /** 254 * maps each top-level initialization member (constructor, init block, field decl with initializer 255 * expression) to the set of @NonNull fields known to be initialized before that member executes. 256 * 257 * <p>cached for performance. nulled out in {@link #matchClass(ClassTree, VisitorState)} 258 */ 259 private final Map<Symbol.ClassSymbol, Multimap<Tree, Element>> initTree2PrevFieldInit = 260 new LinkedHashMap<>(); 261 262 /** 263 * dynamically computer/overriden nullness facts for certain expressions, such as specific method 264 * calls where we can infer a more precise set of facts than those given by the method's 265 * annotations. 266 */ 267 private final Map<ExpressionTree, Nullness> computedNullnessMap = new LinkedHashMap<>(); 268 269 /** 270 * Used to check if a symbol represents a module in {@link #matchMemberSelect(MemberSelectTree, 271 * VisitorState)}. We need to use reflection to preserve compatibility with Java 8. 272 * 273 * <p>TODO remove this once NullAway requires JDK 11 274 */ 275 @Nullable private final Class<?> moduleElementClass; 276 277 /** 278 * Error Prone requires us to have an empty constructor for each Plugin, in addition to the 279 * constructor taking an ErrorProneFlags object. This constructor should not be used anywhere 280 * else. Checker objects constructed with this constructor will fail with IllegalStateException if 281 * ever used for analysis. 282 */ NullAway()283 public NullAway() { 284 config = new DummyOptionsConfig(); 285 handler = Handlers.buildEmpty(); 286 nonAnnotatedMethod = this::isMethodUnannotated; 287 errorBuilder = new ErrorBuilder(config, "", ImmutableSet.of()); 288 moduleElementClass = null; 289 } 290 291 @Inject // For future Error Prone versions in which checkers are loaded using Guice NullAway(ErrorProneFlags flags)292 public NullAway(ErrorProneFlags flags) { 293 config = new ErrorProneCLIFlagsConfig(flags); 294 handler = Handlers.buildDefault(config); 295 nonAnnotatedMethod = this::isMethodUnannotated; 296 errorBuilder = new ErrorBuilder(config, canonicalName(), allNames()); 297 Class<?> moduleElementClass = null; 298 try { 299 moduleElementClass = 300 getClass().getClassLoader().loadClass("javax.lang.model.element.ModuleElement"); 301 } catch (ClassNotFoundException e) { 302 // can occur pre JDK 11 303 } 304 this.moduleElementClass = moduleElementClass; 305 } 306 isMethodUnannotated(MethodInvocationNode invocationNode)307 private boolean isMethodUnannotated(MethodInvocationNode invocationNode) { 308 return invocationNode == null 309 || codeAnnotationInfo.isSymbolUnannotated( 310 ASTHelpers.getSymbol(invocationNode.getTree()), config); 311 } 312 withinAnnotatedCode(VisitorState state)313 private boolean withinAnnotatedCode(VisitorState state) { 314 switch (nullMarkingForTopLevelClass) { 315 case FULLY_MARKED: 316 return true; 317 case FULLY_UNMARKED: 318 return false; 319 case PARTIALLY_MARKED: 320 return checkMarkingForPath(state); 321 } 322 // unreachable but needed to make code compile 323 throw new IllegalStateException("unexpected marking state " + nullMarkingForTopLevelClass); 324 } 325 checkMarkingForPath(VisitorState state)326 private boolean checkMarkingForPath(VisitorState state) { 327 TreePath path = state.getPath(); 328 Tree currentTree = path.getLeaf(); 329 // Find the closest class or method symbol, since those are the only ones we have code 330 // annotation info for. 331 // For the purposes of determining whether we are inside annotated code or not, when matching 332 // a class its enclosing class is itself (otherwise we might not process initialization for 333 // top-level classes in general, or @NullMarked inner classes), same for the enclosing method of 334 // a method. 335 // We use instanceof, since there are multiple Kind's which represent ClassTree's: ENUM, 336 // INTERFACE, etc, and we are actually interested in all of them. 337 while (!(currentTree instanceof ClassTree || currentTree instanceof MethodTree)) { 338 path = path.getParentPath(); 339 if (path == null) { 340 // Not within a class or method (e.g. the package identifier or an import statement) 341 return false; 342 } 343 currentTree = path.getLeaf(); 344 } 345 Symbol enclosingMarkableSymbol = ASTHelpers.getSymbol(currentTree); 346 if (enclosingMarkableSymbol == null) { 347 return false; 348 } 349 return !codeAnnotationInfo.isSymbolUnannotated(enclosingMarkableSymbol, config); 350 } 351 352 @Override linkUrl()353 public String linkUrl() { 354 // add a space to make it clickable from iTerm 355 return config.getErrorURL() + " "; 356 } 357 358 /** 359 * We are trying to see if (1) we are in a method guaranteed to return something non-null, and (2) 360 * this return statement can return something null. 361 */ 362 @Override matchReturn(ReturnTree tree, VisitorState state)363 public Description matchReturn(ReturnTree tree, VisitorState state) { 364 if (!withinAnnotatedCode(state)) { 365 return Description.NO_MATCH; 366 } 367 handler.onMatchReturn(this, tree, state); 368 ExpressionTree retExpr = tree.getExpression(); 369 // let's do quick checks on returned expression first 370 if (retExpr == null) { 371 return Description.NO_MATCH; 372 } 373 // now let's check the enclosing method 374 TreePath enclosingMethodOrLambda = 375 NullabilityUtil.findEnclosingMethodOrLambdaOrInitializer(state.getPath()); 376 if (enclosingMethodOrLambda == null) { 377 throw new RuntimeException("no enclosing method, lambda or initializer!"); 378 } 379 if (!(enclosingMethodOrLambda.getLeaf() instanceof MethodTree 380 || enclosingMethodOrLambda.getLeaf() instanceof LambdaExpressionTree)) { 381 throw new RuntimeException( 382 "return statement outside of a method or lambda! (e.g. in an initializer block)"); 383 } 384 Tree leaf = enclosingMethodOrLambda.getLeaf(); 385 Symbol.MethodSymbol methodSymbol; 386 LambdaExpressionTree lambdaTree = null; 387 if (leaf instanceof MethodTree) { 388 MethodTree enclosingMethod = (MethodTree) leaf; 389 methodSymbol = ASTHelpers.getSymbol(enclosingMethod); 390 } else { 391 // we have a lambda 392 lambdaTree = (LambdaExpressionTree) leaf; 393 methodSymbol = NullabilityUtil.getFunctionalInterfaceMethod(lambdaTree, state.getTypes()); 394 } 395 return checkReturnExpression(retExpr, methodSymbol, lambdaTree, tree, state); 396 } 397 398 @Override matchMethodInvocation(MethodInvocationTree tree, VisitorState state)399 public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { 400 if (!withinAnnotatedCode(state)) { 401 return Description.NO_MATCH; 402 } 403 Symbol.MethodSymbol methodSymbol = getSymbolForMethodInvocation(tree, state); 404 handler.onMatchMethodInvocation(this, tree, state, methodSymbol); 405 // assuming this list does not include the receiver 406 List<? extends ExpressionTree> actualParams = tree.getArguments(); 407 return handleInvocation(tree, state, methodSymbol, actualParams); 408 } 409 getSymbolForMethodInvocation( MethodInvocationTree tree, VisitorState state)410 private static Symbol.MethodSymbol getSymbolForMethodInvocation( 411 MethodInvocationTree tree, VisitorState state) { 412 Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); 413 Verify.verify(methodSymbol != null, "not expecting unresolved method here"); 414 // For interface methods, if the method is an implicit method corresponding to a method from 415 // java.lang.Object, use the symbol for the java.lang.Object method instead. We do this to 416 // properly treat the method as unannotated, which is particularly important for equals() 417 // methods. This is an adaptation to a change in JDK 18; see 418 // https://bugs.openjdk.org/browse/JDK-8272564 419 if (methodSymbol.owner.isInterface()) { 420 Symbol.MethodSymbol baseSymbol = (Symbol.MethodSymbol) methodSymbol.baseSymbol(); 421 if (baseSymbol != methodSymbol && baseSymbol.owner == state.getSymtab().objectType.tsym) { 422 methodSymbol = baseSymbol; 423 } 424 } 425 return methodSymbol; 426 } 427 428 @Override matchNewClass(NewClassTree tree, VisitorState state)429 public Description matchNewClass(NewClassTree tree, VisitorState state) { 430 if (!withinAnnotatedCode(state)) { 431 return Description.NO_MATCH; 432 } 433 Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); 434 if (methodSymbol == null) { 435 throw new RuntimeException("not expecting unresolved method here"); 436 } 437 List<? extends ExpressionTree> actualParams = tree.getArguments(); 438 if (tree.getClassBody() != null && actualParams.size() > 0) { 439 // passing parameters to constructor of anonymous class 440 // this constructor just invokes the constructor of the superclass, and 441 // in the AST does not have the parameter nullability annotations from the superclass. 442 // so, treat as if the superclass constructor is being invoked directly 443 // see https://github.com/uber/NullAway/issues/102 444 methodSymbol = getSymbolOfSuperConstructor(methodSymbol, state); 445 } 446 return handleInvocation(tree, state, methodSymbol, actualParams); 447 } 448 449 /** 450 * Updates the {@link EnclosingEnvironmentNullness} with an entry for lambda or anonymous class, 451 * capturing nullability info for locals just before the declaration of the entity 452 * 453 * @param treePath either a lambda or a local / anonymous class, identified by its tree path 454 * @param state visitor state 455 */ updateEnvironmentMapping(TreePath treePath, VisitorState state)456 private void updateEnvironmentMapping(TreePath treePath, VisitorState state) { 457 AccessPathNullnessAnalysis analysis = getNullnessAnalysis(state); 458 // two notes: 459 // 1. we are free to take local variable information from the program point before 460 // the lambda / class declaration as only effectively final variables can be accessed 461 // from the nested scope, so the program point doesn't matter 462 // 2. we keep info on all locals rather than just effectively final ones for simplicity 463 EnclosingEnvironmentNullness.instance(state.context) 464 .addEnvironmentMapping( 465 treePath.getLeaf(), analysis.getNullnessInfoBeforeNewContext(treePath, state, handler)); 466 } 467 getSymbolOfSuperConstructor( Symbol.MethodSymbol anonClassConstructorSymbol, VisitorState state)468 private Symbol.MethodSymbol getSymbolOfSuperConstructor( 469 Symbol.MethodSymbol anonClassConstructorSymbol, VisitorState state) { 470 // get the statements in the body of the anonymous class constructor 471 List<? extends StatementTree> statements = 472 getTreesInstance(state).getTree(anonClassConstructorSymbol).getBody().getStatements(); 473 // there should be exactly one statement, which is an invocation of the super constructor 474 if (statements.size() == 1) { 475 StatementTree stmt = statements.get(0); 476 if (stmt instanceof ExpressionStatementTree) { 477 ExpressionTree expression = ((ExpressionStatementTree) stmt).getExpression(); 478 if (expression instanceof MethodInvocationTree) { 479 return ASTHelpers.getSymbol((MethodInvocationTree) expression); 480 } 481 } 482 } 483 throw new IllegalStateException("unexpected anonymous class constructor body " + statements); 484 } 485 486 @Override matchAssignment(AssignmentTree tree, VisitorState state)487 public Description matchAssignment(AssignmentTree tree, VisitorState state) { 488 if (!withinAnnotatedCode(state)) { 489 return Description.NO_MATCH; 490 } 491 Type lhsType = ASTHelpers.getType(tree.getVariable()); 492 if (lhsType != null && lhsType.isPrimitive()) { 493 doUnboxingCheck(state, tree.getExpression()); 494 } 495 // generics check 496 if (lhsType != null && lhsType.getTypeArguments().length() > 0 && config.isJSpecifyMode()) { 497 GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state); 498 } 499 500 Symbol assigned = ASTHelpers.getSymbol(tree.getVariable()); 501 if (assigned == null || assigned.getKind() != ElementKind.FIELD) { 502 // not a field of nullable type 503 return Description.NO_MATCH; 504 } 505 506 if (Nullness.hasNullableAnnotation(assigned, config) 507 || handler.onOverrideFieldNullability(assigned)) { 508 // field already annotated 509 return Description.NO_MATCH; 510 } 511 ExpressionTree expression = tree.getExpression(); 512 if (mayBeNullExpr(state, expression)) { 513 String message = "assigning @Nullable expression to @NonNull field"; 514 return errorBuilder.createErrorDescriptionForNullAssignment( 515 new ErrorMessage(MessageTypes.ASSIGN_FIELD_NULLABLE, message), 516 expression, 517 buildDescription(tree), 518 state, 519 ASTHelpers.getSymbol(tree.getVariable())); 520 } 521 handler.onNonNullFieldAssignment(assigned, getNullnessAnalysis(state), state); 522 return Description.NO_MATCH; 523 } 524 525 @Override matchCompoundAssignment(CompoundAssignmentTree tree, VisitorState state)526 public Description matchCompoundAssignment(CompoundAssignmentTree tree, VisitorState state) { 527 if (!withinAnnotatedCode(state)) { 528 return Description.NO_MATCH; 529 } 530 Type lhsType = ASTHelpers.getType(tree.getVariable()); 531 Type stringType = Suppliers.STRING_TYPE.get(state); 532 if (lhsType != null && !state.getTypes().isSameType(lhsType, stringType)) { 533 // both LHS and RHS could get unboxed 534 doUnboxingCheck(state, tree.getVariable(), tree.getExpression()); 535 } 536 return Description.NO_MATCH; 537 } 538 539 @Override matchArrayAccess(ArrayAccessTree tree, VisitorState state)540 public Description matchArrayAccess(ArrayAccessTree tree, VisitorState state) { 541 if (!withinAnnotatedCode(state)) { 542 return Description.NO_MATCH; 543 } 544 Description description = matchDereference(tree.getExpression(), tree, state); 545 // also check for unboxing of array index expression 546 doUnboxingCheck(state, tree.getIndex()); 547 return description; 548 } 549 550 @Override matchMemberSelect(MemberSelectTree tree, VisitorState state)551 public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) { 552 if (!withinAnnotatedCode(state)) { 553 return Description.NO_MATCH; 554 } 555 Symbol symbol = ASTHelpers.getSymbol(tree); 556 // Some checks for cases where we know this cannot be a null dereference. The tree's symbol may 557 // be null in cases where the tree represents part of a package name, e.g., in the package 558 // declaration in a class, or in a requires clause in a module-info.java file; it should never 559 // be null for a real field dereference or method call 560 if (symbol == null 561 || symbol.getSimpleName().toString().equals("class") 562 || symbol.isEnum() 563 || isModuleSymbol(symbol)) { 564 return Description.NO_MATCH; 565 } 566 567 Description badDeref = matchDereference(tree.getExpression(), tree, state); 568 if (!badDeref.equals(Description.NO_MATCH)) { 569 return badDeref; 570 } 571 // if we're accessing a field of this, make sure we're not reading the field before init 572 if (tree.getExpression() instanceof IdentifierTree 573 && ((IdentifierTree) tree.getExpression()).getName().toString().equals("this")) { 574 return checkForReadBeforeInit(tree, state); 575 } 576 return Description.NO_MATCH; 577 } 578 579 /** 580 * Checks if {@code symbol} represents a JDK 9+ module using reflection. 581 * 582 * <p>TODO just check using instanceof once NullAway requires JDK 11 583 */ isModuleSymbol(Symbol symbol)584 private boolean isModuleSymbol(Symbol symbol) { 585 return moduleElementClass != null && moduleElementClass.isAssignableFrom(symbol.getClass()); 586 } 587 588 /** 589 * Look for @NullMarked or @NullUnmarked annotations at the method level and adjust our scan for 590 * annotated code accordingly (fast scan for a fully annotated/unannotated top-level class or 591 * slower scan for mixed nullmarkedness code). 592 */ checkForMethodNullMarkedness(MethodTree tree, VisitorState state)593 private void checkForMethodNullMarkedness(MethodTree tree, VisitorState state) { 594 boolean markedMethodInUnmarkedContext = false; 595 Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); 596 switch (nullMarkingForTopLevelClass) { 597 case FULLY_MARKED: 598 if (hasDirectAnnotationWithSimpleName( 599 methodSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)) { 600 nullMarkingForTopLevelClass = NullMarking.PARTIALLY_MARKED; 601 } 602 break; 603 case FULLY_UNMARKED: 604 if (hasDirectAnnotationWithSimpleName( 605 methodSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) { 606 nullMarkingForTopLevelClass = NullMarking.PARTIALLY_MARKED; 607 markedMethodInUnmarkedContext = true; 608 } 609 break; 610 case PARTIALLY_MARKED: 611 if (hasDirectAnnotationWithSimpleName( 612 methodSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) { 613 // We still care here if this is a transition between @NullUnmarked and @NullMarked code, 614 // within partially marked code, see checks below for markedMethodInUnmarkedContext. 615 if (!codeAnnotationInfo.isClassNullAnnotated(methodSymbol.enclClass(), config)) { 616 markedMethodInUnmarkedContext = true; 617 } 618 } 619 break; 620 } 621 if (markedMethodInUnmarkedContext) { 622 // If this is a @NullMarked method of a @NullUnmarked local or anonymous class, we need to set 623 // its environment mapping, since we skipped it during matchClass. 624 TreePath pathToEnclosingClass = 625 ASTHelpers.findPathFromEnclosingNodeToTopLevel(state.getPath(), ClassTree.class); 626 ClassTree enclosingClass = (ClassTree) pathToEnclosingClass.getLeaf(); 627 if (enclosingClass == null) { 628 return; 629 } 630 NestingKind nestingKind = ASTHelpers.getSymbol(enclosingClass).getNestingKind(); 631 if (nestingKind.equals(NestingKind.LOCAL) || nestingKind.equals(NestingKind.ANONYMOUS)) { 632 updateEnvironmentMapping(pathToEnclosingClass, state); 633 } 634 } 635 } 636 637 @Override matchMethod(MethodTree tree, VisitorState state)638 public Description matchMethod(MethodTree tree, VisitorState state) { 639 checkForMethodNullMarkedness(tree, state); 640 if (!withinAnnotatedCode(state)) { 641 return Description.NO_MATCH; 642 } 643 // if the method is overriding some other method, 644 // check that nullability annotations are consistent with 645 // overridden method (if overridden method is in an annotated 646 // package) 647 Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree); 648 handler.onMatchMethod(this, tree, state, methodSymbol); 649 boolean isOverriding = ASTHelpers.hasAnnotation(methodSymbol, "java.lang.Override", state); 650 boolean exhaustiveOverride = config.exhaustiveOverride(); 651 if (isOverriding || !exhaustiveOverride) { 652 Symbol.MethodSymbol closestOverriddenMethod = 653 NullabilityUtil.getClosestOverriddenMethod(methodSymbol, state.getTypes()); 654 if (closestOverriddenMethod != null) { 655 if (config.isJSpecifyMode()) { 656 // Check that any generic type parameters in the return type and parameter types are 657 // identical (invariant) across the overriding and overridden methods 658 GenericsChecks.checkTypeParameterNullnessForMethodOverriding( 659 tree, methodSymbol, closestOverriddenMethod, this, state); 660 } 661 return checkOverriding(closestOverriddenMethod, methodSymbol, null, state); 662 } 663 } 664 return Description.NO_MATCH; 665 } 666 667 @Override matchSwitch(SwitchTree tree, VisitorState state)668 public Description matchSwitch(SwitchTree tree, VisitorState state) { 669 if (!withinAnnotatedCode(state)) { 670 return Description.NO_MATCH; 671 } 672 673 ExpressionTree switchSelectorExpression = tree.getExpression(); 674 // For a statement `switch (e) { ... }`, javac returns `(e)` as the selector expression. We 675 // strip the outermost parentheses for a nicer-looking error message. 676 if (switchSelectorExpression instanceof ParenthesizedTree) { 677 switchSelectorExpression = ((ParenthesizedTree) switchSelectorExpression).getExpression(); 678 } 679 680 if (mayBeNullExpr(state, switchSelectorExpression)) { 681 final String message = 682 "switch expression " + state.getSourceForNode(switchSelectorExpression) + " is @Nullable"; 683 ErrorMessage errorMessage = 684 new ErrorMessage(MessageTypes.SWITCH_EXPRESSION_NULLABLE, message); 685 686 return errorBuilder.createErrorDescription( 687 errorMessage, 688 switchSelectorExpression, 689 buildDescription(switchSelectorExpression), 690 state, 691 null); 692 } 693 694 return Description.NO_MATCH; 695 } 696 697 @Override matchTypeCast(TypeCastTree tree, VisitorState state)698 public Description matchTypeCast(TypeCastTree tree, VisitorState state) { 699 if (!withinAnnotatedCode(state)) { 700 return Description.NO_MATCH; 701 } 702 Type castExprType = ASTHelpers.getType(tree); 703 if (castExprType != null && castExprType.isPrimitive()) { 704 // casting to a primitive type performs unboxing 705 doUnboxingCheck(state, tree.getExpression()); 706 } 707 return Description.NO_MATCH; 708 } 709 710 @Override matchParameterizedType(ParameterizedTypeTree tree, VisitorState state)711 public Description matchParameterizedType(ParameterizedTypeTree tree, VisitorState state) { 712 if (!withinAnnotatedCode(state)) { 713 return Description.NO_MATCH; 714 } 715 if (config.isJSpecifyMode()) { 716 GenericsChecks.checkInstantiationForParameterizedTypedTree(tree, state, this, config); 717 } 718 return Description.NO_MATCH; 719 } 720 721 /** 722 * checks that an overriding method does not override a {@code @Nullable} parameter with a 723 * {@code @NonNull} parameter 724 * 725 * @param overridingParamSymbols parameters of the overriding method 726 * @param overriddenMethod method being overridden 727 * @param lambdaExpressionTree if the overriding method is a lambda, the {@link 728 * LambdaExpressionTree}; otherwise {@code null} 729 * @param memberReferenceTree if the overriding method is a member reference (which "overrides" a 730 * functional interface method), the {@link MemberReferenceTree}; otherwise {@code null} 731 * @return discovered error, or {@link Description#NO_MATCH} if no error 732 */ checkParamOverriding( List<VarSymbol> overridingParamSymbols, Symbol.MethodSymbol overriddenMethod, @Nullable LambdaExpressionTree lambdaExpressionTree, @Nullable MemberReferenceTree memberReferenceTree, VisitorState state)733 private Description checkParamOverriding( 734 List<VarSymbol> overridingParamSymbols, 735 Symbol.MethodSymbol overriddenMethod, 736 @Nullable LambdaExpressionTree lambdaExpressionTree, 737 @Nullable MemberReferenceTree memberReferenceTree, 738 VisitorState state) { 739 com.sun.tools.javac.util.List<VarSymbol> superParamSymbols = overriddenMethod.getParameters(); 740 final boolean unboundMemberRef = 741 (memberReferenceTree != null) 742 && ((JCTree.JCMemberReference) memberReferenceTree).kind.isUnbound(); 743 final boolean isOverriddenMethodAnnotated = 744 !codeAnnotationInfo.isSymbolUnannotated(overriddenMethod, config); 745 746 // Get argument nullability for the overridden method. If overriddenMethodArgNullnessMap[i] is 747 // null, parameter i is treated as unannotated. 748 Nullness[] overriddenMethodArgNullnessMap = new Nullness[superParamSymbols.size()]; 749 750 // Collect @Nullable params of overridden method iff the overridden method is in annotated code 751 // (otherwise, whether we acknowledge @Nullable in unannotated code or not depends on the 752 // -XepOpt:NullAway:AcknowledgeRestrictiveAnnotations flag and its handler). 753 if (isOverriddenMethodAnnotated) { 754 for (int i = 0; i < superParamSymbols.size(); i++) { 755 Nullness paramNullness; 756 if (Nullness.paramHasNullableAnnotation(overriddenMethod, i, config)) { 757 paramNullness = Nullness.NULLABLE; 758 } else if (config.isJSpecifyMode()) { 759 // Check if the parameter type is a type variable and the corresponding generic type 760 // argument is @Nullable 761 if (memberReferenceTree != null) { 762 // For a method reference, we get generic type arguments from the javac's inferred type 763 // for the tree, which seems to properly preserve type-use annotations 764 paramNullness = 765 GenericsChecks.getGenericMethodParameterNullness( 766 i, overriddenMethod, ASTHelpers.getType(memberReferenceTree), state, config); 767 } else { 768 // Use the enclosing class of the overriding method to find generic type arguments 769 paramNullness = 770 GenericsChecks.getGenericMethodParameterNullness( 771 i, overriddenMethod, overridingParamSymbols.get(i).owner.owner, state, config); 772 } 773 } else { 774 paramNullness = Nullness.NONNULL; 775 } 776 overriddenMethodArgNullnessMap[i] = paramNullness; 777 } 778 } 779 780 // Check handlers for any further/overriding nullness information 781 overriddenMethodArgNullnessMap = 782 handler.onOverrideMethodInvocationParametersNullability( 783 state.context, 784 overriddenMethod, 785 isOverriddenMethodAnnotated, 786 overriddenMethodArgNullnessMap); 787 788 // If we have an unbound method reference, the first parameter of the overridden method must be 789 // @NonNull, as this parameter will be used as a method receiver inside the generated lambda. 790 // e.g. String::length is implemented as (@NonNull s -> s.length()) when used as a 791 // SomeFunc<String> and thus incompatible with, for example, SomeFunc.apply(@Nullable T). 792 if (unboundMemberRef && Objects.equals(overriddenMethodArgNullnessMap[0], Nullness.NULLABLE)) { 793 String message = 794 "unbound instance method reference cannot be used, as first parameter of " 795 + "functional interface method " 796 + ASTHelpers.enclosingClass(overriddenMethod) 797 + "." 798 + overriddenMethod.toString() 799 + " is @Nullable"; 800 return errorBuilder.createErrorDescription( 801 new ErrorMessage(MessageTypes.WRONG_OVERRIDE_PARAM, message), 802 buildDescription(memberReferenceTree), 803 state, 804 null); 805 } 806 807 // for unbound member references, we need to adjust parameter indices by 1 when matching with 808 // overridden method 809 final int startParam = unboundMemberRef ? 1 : 0; 810 811 for (int i = 0; i < superParamSymbols.size(); i++) { 812 if (!Objects.equals(overriddenMethodArgNullnessMap[i], Nullness.NULLABLE)) { 813 // No need to check, unless the argument of the overridden method is effectively @Nullable, 814 // in which case it can't be overridding a @NonNull arg. 815 continue; 816 } 817 int methodParamInd = i - startParam; 818 VarSymbol paramSymbol = overridingParamSymbols.get(methodParamInd); 819 // in the case where we have a parameter of a lambda expression, we do 820 // *not* force the parameter to be annotated with @Nullable; instead we "inherit" 821 // nullability from the corresponding functional interface method. 822 // So, we report an error if the @Nullable annotation is missing *and* 823 // we don't have a lambda with implicitly typed parameters 824 boolean implicitlyTypedLambdaParam = 825 lambdaExpressionTree != null 826 && NullabilityUtil.lambdaParamIsImplicitlyTyped( 827 lambdaExpressionTree.getParameters().get(methodParamInd)); 828 if (!Nullness.hasNullableAnnotation(paramSymbol, config) && !implicitlyTypedLambdaParam) { 829 final String message = 830 "parameter " 831 + paramSymbol.name.toString() 832 + (memberReferenceTree != null ? " of referenced method" : "") 833 + " is @NonNull, but parameter in " 834 + ((lambdaExpressionTree != null || memberReferenceTree != null) 835 ? "functional interface " 836 : "superclass ") 837 + "method " 838 + ASTHelpers.enclosingClass(overriddenMethod) 839 + "." 840 + overriddenMethod.toString() 841 + " is @Nullable"; 842 Tree errorTree; 843 if (memberReferenceTree != null) { 844 errorTree = memberReferenceTree; 845 } else { 846 errorTree = getTreesInstance(state).getTree(paramSymbol); 847 } 848 return errorBuilder.createErrorDescription( 849 new ErrorMessage(MessageTypes.WRONG_OVERRIDE_PARAM, message), 850 buildDescription(errorTree), 851 state, 852 paramSymbol); 853 } 854 } 855 return Description.NO_MATCH; 856 } 857 getTreesInstance(VisitorState state)858 static Trees getTreesInstance(VisitorState state) { 859 return Trees.instance(JavacProcessingEnvironment.instance(state.context)); 860 } 861 getMethodReturnNullness( Symbol.MethodSymbol methodSymbol, VisitorState state, Nullness defaultForUnannotated)862 private Nullness getMethodReturnNullness( 863 Symbol.MethodSymbol methodSymbol, VisitorState state, Nullness defaultForUnannotated) { 864 final boolean isMethodAnnotated = !codeAnnotationInfo.isSymbolUnannotated(methodSymbol, config); 865 Nullness methodReturnNullness = 866 defaultForUnannotated; // Permissive default for unannotated code. 867 if (isMethodAnnotated) { 868 methodReturnNullness = 869 Nullness.hasNullableAnnotation(methodSymbol, config) 870 ? Nullness.NULLABLE 871 : Nullness.NONNULL; 872 } 873 return handler.onOverrideMethodReturnNullability( 874 methodSymbol, state, isMethodAnnotated, methodReturnNullness); 875 } 876 877 /** 878 * Checks that if a returned expression is {@code @Nullable}, the enclosing method does not have a 879 * {@code @NonNull} return type. Also performs an unboxing check on the returned expression. 880 * Finally, in JSpecify mode, also checks that the nullability of generic type arguments of the 881 * returned expression's type match the method return type. 882 * 883 * @param retExpr the expression being returned 884 * @param methodSymbol symbol for the enclosing method 885 * @param lambdaTree if return is inside a lambda, the tree for the lambda, otherwise {@code null} 886 * @param errorTree tree on which to report an error if needed 887 * @param state the visitor state 888 * @return {@link Description} of the returning {@code @Nullable} from {@code @NonNull} method 889 * error if one is to be reported, otherwise {@link Description#NO_MATCH} 890 */ checkReturnExpression( ExpressionTree retExpr, Symbol.MethodSymbol methodSymbol, @Nullable LambdaExpressionTree lambdaTree, Tree errorTree, VisitorState state)891 private Description checkReturnExpression( 892 ExpressionTree retExpr, 893 Symbol.MethodSymbol methodSymbol, 894 @Nullable LambdaExpressionTree lambdaTree, 895 Tree errorTree, 896 VisitorState state) { 897 Type returnType = methodSymbol.getReturnType(); 898 if (returnType.isPrimitive()) { 899 // check for unboxing 900 doUnboxingCheck(state, retExpr); 901 return Description.NO_MATCH; 902 } 903 if (ASTHelpers.isSameType(returnType, Suppliers.JAVA_LANG_VOID_TYPE.get(state), state)) { 904 // Temporarily treat a Void return type as if it were @Nullable Void. Change this once 905 // we are confident that all use cases can be type checked reasonably (may require generics 906 // support) 907 return Description.NO_MATCH; 908 } 909 910 // Check generic type arguments for returned expression here, since we need to check the type 911 // arguments regardless of the top-level nullability of the return type 912 GenericsChecks.checkTypeParameterNullnessForFunctionReturnType( 913 retExpr, methodSymbol, this, state); 914 915 // Now, perform the check for returning @Nullable from @NonNull. First, we check if the return 916 // type is @Nullable, and if so, bail out. 917 if (getMethodReturnNullness(methodSymbol, state, Nullness.NULLABLE).equals(Nullness.NULLABLE)) { 918 return Description.NO_MATCH; 919 } else if (config.isJSpecifyMode() 920 && lambdaTree != null 921 && GenericsChecks.getGenericMethodReturnTypeNullness( 922 methodSymbol, ASTHelpers.getType(lambdaTree), state, config) 923 .equals(Nullness.NULLABLE)) { 924 // In JSpecify mode, the return type of a lambda may be @Nullable via a type argument 925 return Description.NO_MATCH; 926 } 927 928 // Return type is @NonNull. Check if the expression is @Nullable 929 if (mayBeNullExpr(state, retExpr)) { 930 return errorBuilder.createErrorDescriptionForNullAssignment( 931 new ErrorMessage( 932 MessageTypes.RETURN_NULLABLE, 933 "returning @Nullable expression from method with @NonNull return type"), 934 retExpr, 935 buildDescription(errorTree), 936 state, 937 methodSymbol); 938 } 939 return Description.NO_MATCH; 940 } 941 942 @Override matchLambdaExpression(LambdaExpressionTree tree, VisitorState state)943 public Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state) { 944 if (!withinAnnotatedCode(state)) { 945 return Description.NO_MATCH; 946 } 947 Symbol.MethodSymbol funcInterfaceMethod = 948 NullabilityUtil.getFunctionalInterfaceMethod(tree, state.getTypes()); 949 // we need to update environment mapping before running the handler, as some handlers 950 // (like Rx nullability) run dataflow analysis 951 updateEnvironmentMapping(state.getPath(), state); 952 handler.onMatchLambdaExpression(this, tree, state, funcInterfaceMethod); 953 if (codeAnnotationInfo.isSymbolUnannotated(funcInterfaceMethod, config)) { 954 return Description.NO_MATCH; 955 } 956 Description description = 957 checkParamOverriding( 958 tree.getParameters().stream().map(ASTHelpers::getSymbol).collect(Collectors.toList()), 959 funcInterfaceMethod, 960 tree, 961 null, 962 state); 963 if (description != Description.NO_MATCH) { 964 return description; 965 } 966 // if the body has a return statement, that gets checked in matchReturn(). We need this code 967 // for lambdas with expression bodies 968 if (tree.getBodyKind() == LambdaExpressionTree.BodyKind.EXPRESSION 969 && funcInterfaceMethod.getReturnType().getKind() != TypeKind.VOID) { 970 ExpressionTree resExpr = (ExpressionTree) tree.getBody(); 971 return checkReturnExpression(resExpr, funcInterfaceMethod, tree, tree, state); 972 } 973 return Description.NO_MATCH; 974 } 975 976 /** 977 * for method references, we check that the referenced method correctly overrides the 978 * corresponding functional interface method 979 */ 980 @Override matchMemberReference(MemberReferenceTree tree, VisitorState state)981 public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) { 982 if (!withinAnnotatedCode(state)) { 983 return Description.NO_MATCH; 984 } 985 Symbol.MethodSymbol referencedMethod = ASTHelpers.getSymbol(tree); 986 Symbol.MethodSymbol funcInterfaceSymbol = 987 NullabilityUtil.getFunctionalInterfaceMethod(tree, state.getTypes()); 988 handler.onMatchMethodReference(this, tree, state, referencedMethod); 989 return checkOverriding(funcInterfaceSymbol, referencedMethod, tree, state); 990 } 991 992 /** 993 * check that nullability annotations of an overriding method are consistent with those in the 994 * overridden method (both return and parameters) 995 * 996 * @param overriddenMethod method being overridden 997 * @param overridingMethod overriding method 998 * @param memberReferenceTree if override is via a method reference, the relevant {@link 999 * MemberReferenceTree}; otherwise {@code null}. If non-null, overridingTree is the AST of the 1000 * referenced method 1001 * @param state visitor state. 1002 * @return discovered error, or {@link Description#NO_MATCH} if no error 1003 */ checkOverriding( Symbol.MethodSymbol overriddenMethod, Symbol.MethodSymbol overridingMethod, @Nullable MemberReferenceTree memberReferenceTree, VisitorState state)1004 private Description checkOverriding( 1005 Symbol.MethodSymbol overriddenMethod, 1006 Symbol.MethodSymbol overridingMethod, 1007 @Nullable MemberReferenceTree memberReferenceTree, 1008 VisitorState state) { 1009 // if the super method returns nonnull, overriding method better not return nullable 1010 // Note that, for the overriding method, the permissive default is non-null, 1011 // but it's nullable for the overridden one. 1012 if (overriddenMethodReturnsNonNull( 1013 overriddenMethod, overridingMethod.owner, memberReferenceTree, state) 1014 && getMethodReturnNullness(overridingMethod, state, Nullness.NONNULL) 1015 .equals(Nullness.NULLABLE) 1016 && (memberReferenceTree == null 1017 || getComputedNullness(memberReferenceTree).equals(Nullness.NULLABLE))) { 1018 String message; 1019 if (memberReferenceTree != null) { 1020 message = 1021 "referenced method returns @Nullable, but functional interface method " 1022 + ASTHelpers.enclosingClass(overriddenMethod) 1023 + "." 1024 + overriddenMethod.toString() 1025 + " returns @NonNull"; 1026 } else { 1027 message = 1028 "method returns @Nullable, but superclass method " 1029 + ASTHelpers.enclosingClass(overriddenMethod) 1030 + "." 1031 + overriddenMethod.toString() 1032 + " returns @NonNull"; 1033 } 1034 1035 Tree errorTree = 1036 memberReferenceTree != null 1037 ? memberReferenceTree 1038 : getTreesInstance(state).getTree(overridingMethod); 1039 return errorBuilder.createErrorDescription( 1040 new ErrorMessage(MessageTypes.WRONG_OVERRIDE_RETURN, message), 1041 buildDescription(errorTree), 1042 state, 1043 overriddenMethod); 1044 } 1045 // if any parameter in the super method is annotated @Nullable, 1046 // overriding method cannot assume @Nonnull 1047 return checkParamOverriding( 1048 overridingMethod.getParameters(), overriddenMethod, null, memberReferenceTree, state); 1049 } 1050 overriddenMethodReturnsNonNull( Symbol.MethodSymbol overriddenMethod, Symbol enclosingSymbol, @Nullable MemberReferenceTree memberReferenceTree, VisitorState state)1051 private boolean overriddenMethodReturnsNonNull( 1052 Symbol.MethodSymbol overriddenMethod, 1053 Symbol enclosingSymbol, 1054 @Nullable MemberReferenceTree memberReferenceTree, 1055 VisitorState state) { 1056 Nullness methodReturnNullness = 1057 getMethodReturnNullness(overriddenMethod, state, Nullness.NULLABLE); 1058 if (!methodReturnNullness.equals(Nullness.NONNULL)) { 1059 return false; 1060 } 1061 // In JSpecify mode, for generic methods, we additionally need to check the return nullness 1062 // using the type arguments from the type enclosing the overriding method 1063 if (config.isJSpecifyMode()) { 1064 if (memberReferenceTree != null) { 1065 // For a method reference, we get generic type arguments from javac's inferred type for the 1066 // tree, which properly preserves type-use annotations 1067 return GenericsChecks.getGenericMethodReturnTypeNullness( 1068 overriddenMethod, ASTHelpers.getType(memberReferenceTree), state, config) 1069 .equals(Nullness.NONNULL); 1070 } else { 1071 // Use the enclosing class of the overriding method to find generic type arguments 1072 return GenericsChecks.getGenericMethodReturnTypeNullness( 1073 overriddenMethod, enclosingSymbol, state, config) 1074 .equals(Nullness.NONNULL); 1075 } 1076 } 1077 return true; 1078 } 1079 1080 @Override matchIdentifier(IdentifierTree tree, VisitorState state)1081 public Description matchIdentifier(IdentifierTree tree, VisitorState state) { 1082 if (!withinAnnotatedCode(state)) { 1083 return Description.NO_MATCH; 1084 } 1085 return checkForReadBeforeInit(tree, state); 1086 } 1087 checkForReadBeforeInit(ExpressionTree tree, VisitorState state)1088 private Description checkForReadBeforeInit(ExpressionTree tree, VisitorState state) { 1089 // do a bunch of filtering. first, filter out anything outside an initializer 1090 TreePath path = state.getPath(); 1091 TreePath enclosingBlockPath; 1092 if (config.assertsEnabled()) { 1093 enclosingBlockPath = NullabilityUtil.findEnclosingMethodOrLambdaOrInitializer(path); 1094 } else { 1095 enclosingBlockPath = 1096 NullabilityUtil.findEnclosingMethodOrLambdaOrInitializer( 1097 path, ImmutableSet.of(Tree.Kind.ASSERT)); 1098 } 1099 if (enclosingBlockPath == null) { 1100 // is this possible? 1101 return Description.NO_MATCH; 1102 } 1103 if (!config.assertsEnabled() 1104 && enclosingBlockPath.getLeaf().getKind().equals(Tree.Kind.ASSERT)) { 1105 return Description.NO_MATCH; 1106 } 1107 if (!relevantInitializerMethodOrBlock(enclosingBlockPath, state)) { 1108 return Description.NO_MATCH; 1109 } 1110 1111 // now, make sure we have a field read 1112 Symbol symbol = ASTHelpers.getSymbol(tree); 1113 if (symbol == null) { 1114 return Description.NO_MATCH; 1115 } 1116 if (!symbol.getKind().equals(ElementKind.FIELD)) { 1117 return Description.NO_MATCH; 1118 } 1119 1120 // for static fields, make sure the enclosing init is a static method or block 1121 if (isStatic(symbol)) { 1122 Tree enclosing = enclosingBlockPath.getLeaf(); 1123 if (enclosing instanceof MethodTree 1124 && !ASTHelpers.getSymbol((MethodTree) enclosing).isStatic()) { 1125 return Description.NO_MATCH; 1126 } else if (enclosing instanceof BlockTree && !((BlockTree) enclosing).isStatic()) { 1127 return Description.NO_MATCH; 1128 } 1129 } 1130 if (okToReadBeforeInitialized(path, state)) { 1131 // writing the field, not reading it 1132 return Description.NO_MATCH; 1133 } 1134 1135 // check that the field might actually be problematic to read 1136 FieldInitEntities entities = 1137 castToNonNull(class2Entities.get(enclosingClassSymbol(enclosingBlockPath))); 1138 if (!(entities.nonnullInstanceFields().contains(symbol) 1139 || entities.nonnullStaticFields().contains(symbol))) { 1140 // field is either nullable or initialized at declaration 1141 return Description.NO_MATCH; 1142 } 1143 if (errorBuilder.symbolHasSuppressWarningsAnnotation(symbol, INITIALIZATION_CHECK_NAME)) { 1144 // also suppress checking read before init, as we may not find explicit initialization 1145 return Description.NO_MATCH; 1146 } 1147 return checkPossibleUninitFieldRead(tree, state, symbol, path, enclosingBlockPath); 1148 } 1149 enclosingClassSymbol(TreePath enclosingBlockPath)1150 private Symbol.ClassSymbol enclosingClassSymbol(TreePath enclosingBlockPath) { 1151 Tree leaf = enclosingBlockPath.getLeaf(); 1152 if (leaf instanceof BlockTree) { 1153 // parent must be a ClassTree 1154 Tree parent = enclosingBlockPath.getParentPath().getLeaf(); 1155 return ASTHelpers.getSymbol((ClassTree) parent); 1156 } else { 1157 return castToNonNull(ASTHelpers.enclosingClass(ASTHelpers.getSymbol(leaf))); 1158 } 1159 } 1160 relevantInitializerMethodOrBlock( TreePath enclosingBlockPath, VisitorState state)1161 private boolean relevantInitializerMethodOrBlock( 1162 TreePath enclosingBlockPath, VisitorState state) { 1163 Tree methodLambdaOrBlock = enclosingBlockPath.getLeaf(); 1164 if (methodLambdaOrBlock instanceof LambdaExpressionTree) { 1165 return false; 1166 } else if (methodLambdaOrBlock instanceof MethodTree) { 1167 MethodTree methodTree = (MethodTree) methodLambdaOrBlock; 1168 if (isConstructor(methodTree) && !constructorInvokesAnother(methodTree, state)) { 1169 return true; 1170 } 1171 1172 final Symbol.ClassSymbol enclClassSymbol = enclosingClassSymbol(enclosingBlockPath); 1173 1174 // Checking for initialization is only meaningful if the full class is null-annotated, which 1175 // might not be the case with @NullMarked methods inside @NullUnmarked classes (note that, 1176 // in those cases, we won't even have a populated class2Entities map). We skip this check if 1177 // we are not inside a @NullMarked/annotated *class*: 1178 if (nullMarkingForTopLevelClass == NullMarking.PARTIALLY_MARKED 1179 && !codeAnnotationInfo.isClassNullAnnotated(enclClassSymbol, config)) { 1180 return false; 1181 } 1182 1183 if (ASTHelpers.getSymbol(methodTree).isStatic()) { 1184 Set<MethodTree> staticInitializerMethods = 1185 castToNonNull(class2Entities.get(enclClassSymbol)).staticInitializerMethods(); 1186 return staticInitializerMethods.size() == 1 1187 && staticInitializerMethods.contains(methodTree); 1188 } else { 1189 Set<MethodTree> instanceInitializerMethods = 1190 castToNonNull(class2Entities.get(enclClassSymbol)).instanceInitializerMethods(); 1191 return instanceInitializerMethods.size() == 1 1192 && instanceInitializerMethods.contains(methodTree); 1193 } 1194 } else { 1195 // initializer or field declaration 1196 return true; 1197 } 1198 } 1199 checkPossibleUninitFieldRead( ExpressionTree tree, VisitorState state, Symbol symbol, TreePath path, TreePath enclosingBlockPath)1200 private Description checkPossibleUninitFieldRead( 1201 ExpressionTree tree, 1202 VisitorState state, 1203 Symbol symbol, 1204 TreePath path, 1205 TreePath enclosingBlockPath) { 1206 if (!fieldInitializedByPreviousInitializer(symbol, enclosingBlockPath, state) 1207 && !fieldAlwaysInitializedBeforeRead(symbol, path, state, enclosingBlockPath)) { 1208 ErrorMessage errorMessage = 1209 new ErrorMessage( 1210 MessageTypes.NONNULL_FIELD_READ_BEFORE_INIT, 1211 "read of @NonNull field " + symbol + " before initialization"); 1212 return errorBuilder.createErrorDescription(errorMessage, buildDescription(tree), state, null); 1213 } else { 1214 return Description.NO_MATCH; 1215 } 1216 } 1217 1218 /** 1219 * @param symbol the field being read 1220 * @param pathToRead TreePath to the read operation 1221 * @param state visitor state 1222 * @param enclosingBlockPath TreePath to enclosing initializer block 1223 * @return true if within the initializer, the field is always initialized before the read 1224 * operation, false otherwise 1225 */ fieldAlwaysInitializedBeforeRead( Symbol symbol, TreePath pathToRead, VisitorState state, TreePath enclosingBlockPath)1226 private boolean fieldAlwaysInitializedBeforeRead( 1227 Symbol symbol, TreePath pathToRead, VisitorState state, TreePath enclosingBlockPath) { 1228 AccessPathNullnessAnalysis nullnessAnalysis = getNullnessAnalysis(state); 1229 Set<Element> nonnullFields; 1230 if (isStatic(symbol)) { 1231 nonnullFields = nullnessAnalysis.getNonnullStaticFieldsBefore(pathToRead, state.context); 1232 } else { 1233 nonnullFields = new LinkedHashSet<>(); 1234 nonnullFields.addAll( 1235 nullnessAnalysis.getNonnullFieldsOfReceiverBefore(pathToRead, state.context)); 1236 nonnullFields.addAll(safeInitByCalleeBefore(pathToRead, state, enclosingBlockPath)); 1237 } 1238 return nonnullFields.contains(symbol); 1239 } 1240 1241 /** 1242 * computes those fields always initialized by callee safe init methods before a read operation 1243 * (pathToRead) is invoked. See <a 1244 * href="https://github.com/uber/NullAway/wiki/Error-Messages#initializer-method-does-not-guarantee-nonnull-field-is-initialized--nonnull-field--not-initialized">the 1245 * docs</a> for what is considered a safe initializer method. 1246 */ safeInitByCalleeBefore( TreePath pathToRead, VisitorState state, TreePath enclosingBlockPath)1247 private ImmutableSet<Element> safeInitByCalleeBefore( 1248 TreePath pathToRead, VisitorState state, TreePath enclosingBlockPath) { 1249 Set<Element> safeInitMethods = new LinkedHashSet<>(); 1250 Tree enclosingBlockOrMethod = enclosingBlockPath.getLeaf(); 1251 if (enclosingBlockOrMethod instanceof VariableTree) { 1252 return ImmutableSet.of(); 1253 } 1254 ImmutableSet.Builder<Element> resultBuilder = ImmutableSet.builder(); 1255 BlockTree blockTree = 1256 enclosingBlockOrMethod instanceof BlockTree 1257 ? (BlockTree) enclosingBlockOrMethod 1258 : ((MethodTree) enclosingBlockOrMethod).getBody(); 1259 List<? extends StatementTree> statements = blockTree.getStatements(); 1260 Tree readExprTree = pathToRead.getLeaf(); 1261 int readStartPos = getStartPos((JCTree) readExprTree); 1262 TreePath classTreePath = enclosingBlockPath; 1263 // look for the parent ClassTree node, which represents the enclosing class / enum / interface 1264 while (!(classTreePath.getLeaf() instanceof ClassTree)) { 1265 classTreePath = classTreePath.getParentPath(); 1266 if (classTreePath == null) { 1267 throw new IllegalStateException( 1268 "could not find enclosing class / enum / interface for " 1269 + state.getSourceForNode(enclosingBlockPath.getLeaf())); 1270 } 1271 } 1272 Symbol.ClassSymbol classSymbol = ASTHelpers.getSymbol((ClassTree) classTreePath.getLeaf()); 1273 for (int i = 0; i < statements.size(); i++) { 1274 StatementTree curStmt = statements.get(i); 1275 if (getStartPos((JCTree) curStmt) <= readStartPos) { 1276 Element privMethodElem = getInvokeOfSafeInitMethod(curStmt, classSymbol, state); 1277 if (privMethodElem != null) { 1278 safeInitMethods.add(privMethodElem); 1279 } 1280 // Hack: Handling try{...}finally{...} statement, see getSafeInitMethods 1281 if (curStmt.getKind().equals(Tree.Kind.TRY)) { 1282 TryTree tryTree = (TryTree) curStmt; 1283 // ToDo: Should we check initialization inside tryTree.getResources ? What is the scope of 1284 // that initialization? 1285 if (tryTree.getCatches().size() == 0) { 1286 if (tryTree.getBlock() != null) { 1287 resultBuilder.addAll( 1288 safeInitByCalleeBefore( 1289 pathToRead, state, new TreePath(enclosingBlockPath, tryTree.getBlock()))); 1290 } 1291 if (tryTree.getFinallyBlock() != null) { 1292 resultBuilder.addAll( 1293 safeInitByCalleeBefore( 1294 pathToRead, 1295 state, 1296 new TreePath(enclosingBlockPath, tryTree.getFinallyBlock()))); 1297 } 1298 } 1299 } 1300 } 1301 } 1302 addGuaranteedNonNullFromInvokes( 1303 state, getTreesInstance(state), safeInitMethods, getNullnessAnalysis(state), resultBuilder); 1304 return resultBuilder.build(); 1305 } 1306 getStartPos(JCTree tree)1307 private int getStartPos(JCTree tree) { 1308 return tree.pos().getStartPosition(); 1309 } 1310 1311 /** 1312 * @param fieldSymbol the field 1313 * @param initTreePath TreePath to the initializer method / block 1314 * @param state visitor state 1315 * @return true if the field is always initialized (by some other initializer) before the 1316 * initializer corresponding to initTreePath executes 1317 */ fieldInitializedByPreviousInitializer( Symbol fieldSymbol, TreePath initTreePath, VisitorState state)1318 private boolean fieldInitializedByPreviousInitializer( 1319 Symbol fieldSymbol, TreePath initTreePath, VisitorState state) { 1320 TreePath enclosingClassPath = initTreePath.getParentPath(); 1321 ClassTree enclosingClass = (ClassTree) enclosingClassPath.getLeaf(); 1322 ClassSymbol classSymbol = ASTHelpers.getSymbol(enclosingClass); 1323 Multimap<Tree, Element> tree2Init = 1324 initTree2PrevFieldInit.computeIfAbsent( 1325 classSymbol, sym -> computeTree2Init(enclosingClassPath, state)); 1326 return tree2Init.containsEntry(initTreePath.getLeaf(), fieldSymbol); 1327 } 1328 1329 /** 1330 * @param enclosingClassPath TreePath to class 1331 * @param state visitor state 1332 * @return a map from each initializer <em>i</em> to the fields known to be initialized before 1333 * <em>i</em> executes 1334 */ computeTree2Init( TreePath enclosingClassPath, VisitorState state)1335 private Multimap<Tree, Element> computeTree2Init( 1336 TreePath enclosingClassPath, VisitorState state) { 1337 ClassTree enclosingClass = (ClassTree) enclosingClassPath.getLeaf(); 1338 ImmutableMultimap.Builder<Tree, Element> builder = ImmutableMultimap.builder(); 1339 // NOTE: this set includes both instance and static fields 1340 Set<Element> initThusFar = new LinkedHashSet<>(); 1341 Set<MethodTree> constructors = new LinkedHashSet<>(); 1342 AccessPathNullnessAnalysis nullnessAnalysis = getNullnessAnalysis(state); 1343 // NOTE: we assume the members are returned in their syntactic order. This has held 1344 // true in our testing 1345 for (Tree memberTree : enclosingClass.getMembers()) { 1346 if (memberTree instanceof VariableTree || memberTree instanceof BlockTree) { 1347 // putAll does not keep a reference to initThusFar, so we don't need to make a copy here 1348 builder.putAll(memberTree, initThusFar); 1349 } 1350 if (memberTree instanceof BlockTree) { 1351 BlockTree blockTree = (BlockTree) memberTree; 1352 // add whatever gets initialized here 1353 TreePath memberPath = new TreePath(enclosingClassPath, memberTree); 1354 if (blockTree.isStatic()) { 1355 initThusFar.addAll( 1356 nullnessAnalysis.getNonnullStaticFieldsAtExit(memberPath, state.context)); 1357 } else { 1358 initThusFar.addAll( 1359 nullnessAnalysis.getNonnullFieldsOfReceiverAtExit(memberPath, state.context)); 1360 } 1361 } 1362 if (memberTree instanceof MethodTree) { 1363 MethodTree methodTree = (MethodTree) memberTree; 1364 if (isConstructor(methodTree)) { 1365 constructors.add(methodTree); 1366 } 1367 } 1368 } 1369 // all the initializer blocks have run before any code inside a constructor 1370 constructors.stream().forEach((c) -> builder.putAll(c, initThusFar)); 1371 Symbol.ClassSymbol classSymbol = ASTHelpers.getSymbol(enclosingClass); 1372 FieldInitEntities entities = castToNonNull(class2Entities.get(classSymbol)); 1373 if (entities.instanceInitializerMethods().size() == 1) { 1374 MethodTree initMethod = entities.instanceInitializerMethods().iterator().next(); 1375 // collect the fields that may not be initialized by *some* constructor NC 1376 Set<Symbol> constructorUninitSymbols = class2ConstructorUninit.get(classSymbol); 1377 // fields initialized after constructors is initThusFar + (nonNullFields - constructorUninit) 1378 Sets.SetView<Element> initAfterConstructors = 1379 Sets.union( 1380 initThusFar, 1381 Sets.difference(entities.nonnullInstanceFields(), constructorUninitSymbols)); 1382 builder.putAll(initMethod, initAfterConstructors); 1383 } 1384 if (entities.staticInitializerMethods().size() == 1) { 1385 MethodTree staticInitMethod = entities.staticInitializerMethods().iterator().next(); 1386 // constructors aren't relevant here; just use initThusFar 1387 builder.putAll(staticInitMethod, initThusFar); 1388 } 1389 return builder.build(); 1390 } 1391 1392 /** 1393 * @param path tree path to read operation 1394 * @param state the current VisitorState 1395 * @return true if it is permissible to perform this read before the field has been initialized, 1396 * false otherwise 1397 */ okToReadBeforeInitialized(TreePath path, VisitorState state)1398 private boolean okToReadBeforeInitialized(TreePath path, VisitorState state) { 1399 TreePath parentPath = path.getParentPath(); 1400 Tree leaf = path.getLeaf(); 1401 Tree parent = parentPath.getLeaf(); 1402 if (parent instanceof AssignmentTree) { 1403 // ok if it's actually a write 1404 AssignmentTree assignment = (AssignmentTree) parent; 1405 return assignment.getVariable().equals(leaf); 1406 } else if (parent instanceof BinaryTree) { 1407 // ok if we're comparing to null 1408 BinaryTree binaryTree = (BinaryTree) parent; 1409 Tree.Kind kind = binaryTree.getKind(); 1410 if (kind.equals(Tree.Kind.EQUAL_TO) || kind.equals(Tree.Kind.NOT_EQUAL_TO)) { 1411 ExpressionTree left = binaryTree.getLeftOperand(); 1412 ExpressionTree right = binaryTree.getRightOperand(); 1413 return (left.equals(leaf) && right.getKind().equals(Tree.Kind.NULL_LITERAL)) 1414 || (right.equals(leaf) && left.getKind().equals(Tree.Kind.NULL_LITERAL)); 1415 } 1416 } else if (parent instanceof MethodInvocationTree) { 1417 // ok if it's invoking castToNonNull and the read is the argument 1418 MethodInvocationTree methodInvoke = (MethodInvocationTree) parent; 1419 Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(methodInvoke); 1420 String qualifiedName = 1421 ASTHelpers.enclosingClass(methodSymbol) + "." + methodSymbol.getSimpleName().toString(); 1422 List<? extends ExpressionTree> arguments = methodInvoke.getArguments(); 1423 Integer castToNonNullArg; 1424 if (qualifiedName.equals(config.getCastToNonNullMethod()) 1425 && methodSymbol.getParameters().size() == 1) { 1426 castToNonNullArg = 0; 1427 } else { 1428 castToNonNullArg = 1429 handler.castToNonNullArgumentPositionsForMethod( 1430 this, state, methodSymbol, arguments, null); 1431 } 1432 if (castToNonNullArg != null && leaf.equals(arguments.get(castToNonNullArg))) { 1433 return true; 1434 } 1435 return false; 1436 } 1437 return false; 1438 } 1439 1440 @Override matchVariable(VariableTree tree, VisitorState state)1441 public Description matchVariable(VariableTree tree, VisitorState state) { 1442 if (!withinAnnotatedCode(state)) { 1443 return Description.NO_MATCH; 1444 } 1445 VarSymbol symbol = ASTHelpers.getSymbol(tree); 1446 if (tree.getInitializer() != null && config.isJSpecifyMode()) { 1447 GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state); 1448 } 1449 1450 if (symbol.type.isPrimitive() && tree.getInitializer() != null) { 1451 doUnboxingCheck(state, tree.getInitializer()); 1452 } 1453 if (!symbol.getKind().equals(ElementKind.FIELD)) { 1454 return Description.NO_MATCH; 1455 } 1456 ExpressionTree initializer = tree.getInitializer(); 1457 if (initializer != null) { 1458 if (!symbol.type.isPrimitive() && !skipDueToFieldAnnotation(symbol)) { 1459 if (mayBeNullExpr(state, initializer)) { 1460 final ErrorMessage errorMessage = 1461 new ErrorMessage( 1462 MessageTypes.ASSIGN_FIELD_NULLABLE, 1463 "assigning @Nullable expression to @NonNull field"); 1464 return errorBuilder.createErrorDescriptionForNullAssignment( 1465 errorMessage, initializer, buildDescription(tree), state, symbol); 1466 } 1467 } 1468 } 1469 return Description.NO_MATCH; 1470 } 1471 1472 /** 1473 * Check if an inner class's annotation means this Compilation Unit is partially annotated. 1474 * 1475 * <p>Returns true iff classSymbol has a direct @NullMarked or @NullUnmarked annotation which 1476 * differs from the {@link NullMarking} of the top-level class, meaning the compilation unit is 1477 * itself partially marked, and we need to switch to our slower mode for detecting whether we are 1478 * in unannotated code. 1479 * 1480 * @param classSymbol a ClassSymbol representing an inner class within the current compilation 1481 * unit. 1482 * @return true iff this inner class is @NullMarked and the top-level class unmarked or vice 1483 * versa. 1484 */ classAnnotationIntroducesPartialMarking(Symbol.ClassSymbol classSymbol)1485 private boolean classAnnotationIntroducesPartialMarking(Symbol.ClassSymbol classSymbol) { 1486 return (nullMarkingForTopLevelClass == NullMarking.FULLY_UNMARKED 1487 && hasDirectAnnotationWithSimpleName( 1488 classSymbol, NullabilityUtil.NULLMARKED_SIMPLE_NAME)) 1489 || (nullMarkingForTopLevelClass == NullMarking.FULLY_MARKED 1490 && hasDirectAnnotationWithSimpleName( 1491 classSymbol, NullabilityUtil.NULLUNMARKED_SIMPLE_NAME)); 1492 } 1493 1494 @Override matchClass(ClassTree tree, VisitorState state)1495 public Description matchClass(ClassTree tree, VisitorState state) { 1496 // Ensure codeAnnotationInfo is initialized here since it requires access to the Context, 1497 // which is not available in the constructor 1498 if (codeAnnotationInfo == null) { 1499 codeAnnotationInfo = CodeAnnotationInfo.instance(state.context); 1500 } 1501 // Check if the class is excluded according to the filter 1502 // if so, set the flag to match within the class to false 1503 // NOTE: for this mechanism to work, we rely on the enclosing ClassTree 1504 // always being visited before code within that class. We also 1505 // assume that a single checker object is not being 1506 // used from multiple threads 1507 // We don't want to update the flag for nested classes. 1508 // Ideally we would keep a stack of flags to handle nested types, 1509 // but this is not easy within the Error Prone APIs. 1510 // Instead, we use this flag as an optimization, skipping work if the 1511 // top-level class is to be skipped. If a nested class should be 1512 // skipped, we instead rely on last-minute suppression of the 1513 // error message, using the mechanism in 1514 // ErrorBuilder.hasPathSuppression(...) 1515 Symbol.ClassSymbol classSymbol = ASTHelpers.getSymbol(tree); 1516 NestingKind nestingKind = classSymbol.getNestingKind(); 1517 if (!nestingKind.isNested()) { 1518 // Here we optimistically set the marking to either FULLY_UNMARKED or FULLY_MARKED. If a 1519 // nested entity has a contradicting annotation, at that point we update the marking level to 1520 // PARTIALLY_MARKED, which will increase checking overhead for the remainder of the top-level 1521 // class 1522 nullMarkingForTopLevelClass = 1523 isExcludedClass(classSymbol) ? NullMarking.FULLY_UNMARKED : NullMarking.FULLY_MARKED; 1524 // since we are processing a new top-level class, invalidate any cached 1525 // results for previous classes 1526 handler.onMatchTopLevelClass(this, tree, state, classSymbol); 1527 getNullnessAnalysis(state).invalidateCaches(); 1528 initTree2PrevFieldInit.clear(); 1529 class2Entities.clear(); 1530 class2ConstructorUninit.clear(); 1531 computedNullnessMap.clear(); 1532 EnclosingEnvironmentNullness.instance(state.context).clear(); 1533 } else if (classAnnotationIntroducesPartialMarking(classSymbol)) { 1534 // Handle the case where the top-class is unannotated, but there is a @NullMarked annotation 1535 // on a nested class, or, conversely the top-level is annotated but there is a @NullUnmarked 1536 // annotation on a nested class. 1537 nullMarkingForTopLevelClass = NullMarking.PARTIALLY_MARKED; 1538 } 1539 if (withinAnnotatedCode(state)) { 1540 // we need to update the environment before checking field initialization, as the latter 1541 // may run dataflow analysis 1542 if (nestingKind.equals(NestingKind.LOCAL) || nestingKind.equals(NestingKind.ANONYMOUS)) { 1543 updateEnvironmentMapping(state.getPath(), state); 1544 } 1545 checkFieldInitialization(tree, state); 1546 } 1547 return Description.NO_MATCH; 1548 } 1549 1550 // UNBOXING CHECKS 1551 1552 @Override matchBinary(BinaryTree tree, VisitorState state)1553 public Description matchBinary(BinaryTree tree, VisitorState state) { 1554 if (!withinAnnotatedCode(state)) { 1555 return Description.NO_MATCH; 1556 } 1557 // Perform unboxing checks on operands if needed 1558 Type binaryExprType = ASTHelpers.getType(tree); 1559 // If the type of the expression is not primitive, we do not need to do unboxing checks. This 1560 // handles the case of `+` used for string concatenation 1561 if (binaryExprType == null || !binaryExprType.isPrimitive()) { 1562 return Description.NO_MATCH; 1563 } 1564 Tree.Kind kind = tree.getKind(); 1565 ExpressionTree leftOperand = tree.getLeftOperand(); 1566 ExpressionTree rightOperand = tree.getRightOperand(); 1567 if (kind.equals(Tree.Kind.EQUAL_TO) || kind.equals(Tree.Kind.NOT_EQUAL_TO)) { 1568 // here we need a check if one operand is of primitive type and the other is not, as that will 1569 // cause unboxing of the non-primitive operand 1570 Type leftType = ASTHelpers.getType(leftOperand); 1571 Type rightType = ASTHelpers.getType(rightOperand); 1572 if (leftType == null || rightType == null) { 1573 return Description.NO_MATCH; 1574 } 1575 if (leftType.isPrimitive() && !rightType.isPrimitive()) { 1576 doUnboxingCheck(state, rightOperand); 1577 } else if (rightType.isPrimitive() && !leftType.isPrimitive()) { 1578 doUnboxingCheck(state, leftOperand); 1579 } 1580 } else { 1581 // in all other cases, both operands should be checked 1582 doUnboxingCheck(state, leftOperand, rightOperand); 1583 } 1584 return Description.NO_MATCH; 1585 } 1586 1587 @Override matchUnary(UnaryTree tree, VisitorState state)1588 public Description matchUnary(UnaryTree tree, VisitorState state) { 1589 if (withinAnnotatedCode(state)) { 1590 doUnboxingCheck(state, tree.getExpression()); 1591 } 1592 return Description.NO_MATCH; 1593 } 1594 1595 @Override matchConditionalExpression( ConditionalExpressionTree tree, VisitorState state)1596 public Description matchConditionalExpression( 1597 ConditionalExpressionTree tree, VisitorState state) { 1598 if (withinAnnotatedCode(state)) { 1599 if (config.isJSpecifyMode()) { 1600 GenericsChecks.checkTypeParameterNullnessForConditionalExpression(tree, this, state); 1601 } 1602 doUnboxingCheck(state, tree.getCondition()); 1603 } 1604 return Description.NO_MATCH; 1605 } 1606 1607 @Override matchIf(IfTree tree, VisitorState state)1608 public Description matchIf(IfTree tree, VisitorState state) { 1609 if (withinAnnotatedCode(state)) { 1610 doUnboxingCheck(state, tree.getCondition()); 1611 } 1612 return Description.NO_MATCH; 1613 } 1614 1615 @Override matchWhileLoop(WhileLoopTree tree, VisitorState state)1616 public Description matchWhileLoop(WhileLoopTree tree, VisitorState state) { 1617 if (withinAnnotatedCode(state)) { 1618 doUnboxingCheck(state, tree.getCondition()); 1619 } 1620 return Description.NO_MATCH; 1621 } 1622 1623 @Override matchForLoop(ForLoopTree tree, VisitorState state)1624 public Description matchForLoop(ForLoopTree tree, VisitorState state) { 1625 if (withinAnnotatedCode(state) && tree.getCondition() != null) { 1626 doUnboxingCheck(state, tree.getCondition()); 1627 } 1628 return Description.NO_MATCH; 1629 } 1630 1631 @Override matchEnhancedForLoop(EnhancedForLoopTree tree, VisitorState state)1632 public Description matchEnhancedForLoop(EnhancedForLoopTree tree, VisitorState state) { 1633 if (!withinAnnotatedCode(state)) { 1634 return Description.NO_MATCH; 1635 } 1636 ExpressionTree expr = tree.getExpression(); 1637 final ErrorMessage errorMessage = 1638 new ErrorMessage( 1639 MessageTypes.DEREFERENCE_NULLABLE, 1640 "enhanced-for expression " + state.getSourceForNode(expr) + " is @Nullable"); 1641 if (mayBeNullExpr(state, expr)) { 1642 return errorBuilder.createErrorDescription(errorMessage, buildDescription(expr), state, null); 1643 } 1644 return Description.NO_MATCH; 1645 } 1646 1647 /** 1648 * Checks that all given expressions cannot be null, and for those that are {@code @Nullable}, 1649 * reports an unboxing error. 1650 * 1651 * @param state the visitor state, used to report errors via {@link 1652 * VisitorState#reportMatch(Description)} 1653 * @param expressions expressions to check 1654 */ doUnboxingCheck(VisitorState state, ExpressionTree... expressions)1655 private void doUnboxingCheck(VisitorState state, ExpressionTree... expressions) { 1656 for (ExpressionTree tree : expressions) { 1657 Type type = ASTHelpers.getType(tree); 1658 if (type == null) { 1659 throw new RuntimeException("was not expecting null type"); 1660 } 1661 if (!type.isPrimitive()) { 1662 if (mayBeNullExpr(state, tree)) { 1663 final ErrorMessage errorMessage = 1664 new ErrorMessage(MessageTypes.UNBOX_NULLABLE, "unboxing of a @Nullable value"); 1665 state.reportMatch( 1666 errorBuilder.createErrorDescription( 1667 errorMessage, buildDescription(tree), state, null)); 1668 } 1669 } 1670 } 1671 } 1672 1673 /** 1674 * handle either a method invocation or a 'new' invocation 1675 * 1676 * @param tree the corresponding MethodInvocationTree or NewClassTree 1677 * @param state visitor state 1678 * @param methodSymbol symbol for invoked method 1679 * @param actualParams parameters passed at call 1680 * @return description of error or NO_MATCH if no error 1681 */ handleInvocation( Tree tree, VisitorState state, Symbol.MethodSymbol methodSymbol, List<? extends ExpressionTree> actualParams)1682 private Description handleInvocation( 1683 Tree tree, 1684 VisitorState state, 1685 Symbol.MethodSymbol methodSymbol, 1686 List<? extends ExpressionTree> actualParams) { 1687 List<VarSymbol> formalParams = methodSymbol.getParameters(); 1688 1689 if (formalParams.size() != actualParams.size() 1690 && !methodSymbol.isVarArgs() 1691 && !methodSymbol.isStatic() 1692 && methodSymbol.isConstructor() 1693 && methodSymbol.enclClass().isInner()) { 1694 // In special cases like one in issue #366 1695 // formal params and actual params do not match while using JDK11+ 1696 // we bail out in this particular case 1697 return Description.NO_MATCH; 1698 } 1699 1700 final boolean isMethodAnnotated = !codeAnnotationInfo.isSymbolUnannotated(methodSymbol, config); 1701 // If argumentPositionNullness[i] == null, parameter i is unannotated 1702 Nullness[] argumentPositionNullness = new Nullness[formalParams.size()]; 1703 1704 if (isMethodAnnotated) { 1705 // compute which arguments are @NonNull 1706 for (int i = 0; i < formalParams.size(); i++) { 1707 VarSymbol param = formalParams.get(i); 1708 if (param.type.isPrimitive()) { 1709 doUnboxingCheck(state, actualParams.get(i)); 1710 argumentPositionNullness[i] = Nullness.NONNULL; 1711 } else if (ASTHelpers.isSameType( 1712 param.type, Suppliers.JAVA_LANG_VOID_TYPE.get(state), state)) { 1713 // Temporarily treat a Void argument type as if it were @Nullable Void. Handling of Void 1714 // without special-casing, as recommended by JSpecify might: a) require generics support 1715 // and, b) require checking that third-party libraries considered annotated adopt 1716 // JSpecify semantics. 1717 // See the suppression in https://github.com/uber/NullAway/pull/608 for an example of why 1718 // this is needed. 1719 argumentPositionNullness[i] = Nullness.NULLABLE; 1720 } else { 1721 // we need to call paramHasNullableAnnotation here since the invoked method may be defined 1722 // in a class file 1723 argumentPositionNullness[i] = 1724 Nullness.paramHasNullableAnnotation(methodSymbol, i, config) 1725 ? Nullness.NULLABLE 1726 : ((config.isJSpecifyMode() && tree instanceof MethodInvocationTree) 1727 ? GenericsChecks.getGenericParameterNullnessAtInvocation( 1728 i, methodSymbol, (MethodInvocationTree) tree, state, config) 1729 : Nullness.NONNULL); 1730 } 1731 } 1732 if (config.isJSpecifyMode()) { 1733 GenericsChecks.compareGenericTypeParameterNullabilityForCall( 1734 formalParams, actualParams, methodSymbol.isVarArgs(), this, state); 1735 } 1736 } 1737 1738 // Allow handlers to override the list of non-null argument positions 1739 argumentPositionNullness = 1740 handler.onOverrideMethodInvocationParametersNullability( 1741 state.context, methodSymbol, isMethodAnnotated, argumentPositionNullness); 1742 1743 // now actually check the arguments 1744 // NOTE: the case of an invocation on a possibly-null reference 1745 // is handled by matchMemberSelect() 1746 for (int argPos = 0; argPos < argumentPositionNullness.length; argPos++) { 1747 if (!Objects.equals(Nullness.NONNULL, argumentPositionNullness[argPos])) { 1748 continue; 1749 } 1750 ExpressionTree actual = null; 1751 boolean mayActualBeNull = false; 1752 if (argPos == formalParams.size() - 1 && methodSymbol.isVarArgs()) { 1753 // Check all vararg actual arguments for nullability 1754 if (actualParams.size() <= argPos) { 1755 continue; 1756 } 1757 for (ExpressionTree arg : actualParams.subList(argPos, actualParams.size())) { 1758 actual = arg; 1759 mayActualBeNull = mayBeNullExpr(state, actual); 1760 if (mayActualBeNull) { 1761 break; 1762 } 1763 } 1764 } else { 1765 actual = actualParams.get(argPos); 1766 mayActualBeNull = mayBeNullExpr(state, actual); 1767 } 1768 // This statement should be unreachable without assigning actual beforehand: 1769 Preconditions.checkNotNull(actual); 1770 // make sure we are passing a non-null value 1771 if (mayActualBeNull) { 1772 String message = 1773 "passing @Nullable parameter '" 1774 + state.getSourceForNode(actual) 1775 + "' where @NonNull is required"; 1776 ErrorMessage errorMessage = new ErrorMessage(MessageTypes.PASS_NULLABLE, message); 1777 state.reportMatch( 1778 errorBuilder.createErrorDescriptionForNullAssignment( 1779 errorMessage, actual, buildDescription(actual), state, formalParams.get(argPos))); 1780 } 1781 } 1782 // Check for @NonNull being passed to castToNonNull (if configured) 1783 return checkCastToNonNullTakesNullable(tree, state, methodSymbol, actualParams); 1784 } 1785 checkCastToNonNullTakesNullable( Tree tree, VisitorState state, Symbol.MethodSymbol methodSymbol, List<? extends ExpressionTree> actualParams)1786 private Description checkCastToNonNullTakesNullable( 1787 Tree tree, 1788 VisitorState state, 1789 Symbol.MethodSymbol methodSymbol, 1790 List<? extends ExpressionTree> actualParams) { 1791 String qualifiedName = 1792 ASTHelpers.enclosingClass(methodSymbol) + "." + methodSymbol.getSimpleName().toString(); 1793 Integer castToNonNullPosition; 1794 if (qualifiedName.equals(config.getCastToNonNullMethod()) 1795 && methodSymbol.getParameters().size() == 1) { 1796 // castToNonNull method passed to CLI config, it acts as a cast-to-non-null on its first 1797 // argument. Since this is a single argument method, we skip further querying of handlers. 1798 castToNonNullPosition = 0; 1799 } else { 1800 castToNonNullPosition = 1801 handler.castToNonNullArgumentPositionsForMethod( 1802 this, state, methodSymbol, actualParams, null); 1803 } 1804 if (castToNonNullPosition != null) { 1805 ExpressionTree actual = actualParams.get(castToNonNullPosition); 1806 TreePath enclosingMethodOrLambda = 1807 NullabilityUtil.findEnclosingMethodOrLambdaOrInitializer(state.getPath()); 1808 boolean isInitializer; 1809 if (enclosingMethodOrLambda == null) { 1810 throw new RuntimeException("no enclosing method, lambda or initializer!"); 1811 } else if (enclosingMethodOrLambda.getLeaf() instanceof LambdaExpressionTree) { 1812 isInitializer = false; 1813 } else if (enclosingMethodOrLambda.getLeaf() instanceof MethodTree) { 1814 MethodTree enclosingMethod = (MethodTree) enclosingMethodOrLambda.getLeaf(); 1815 isInitializer = isInitializerMethod(state, ASTHelpers.getSymbol(enclosingMethod)); 1816 } else { 1817 // Initializer block 1818 isInitializer = true; 1819 } 1820 if (!isInitializer && !mayBeNullExpr(state, actual)) { 1821 String message = 1822 "passing known @NonNull parameter '" 1823 + state.getSourceForNode(actual) 1824 + "' to CastToNonNullMethod (" 1825 + qualifiedName 1826 + ") at position " 1827 + castToNonNullPosition 1828 + ". This method argument should only take values that NullAway considers @Nullable " 1829 + "at the invocation site, but which are known not to be null at runtime."; 1830 return errorBuilder.createErrorDescription( 1831 new ErrorMessage(MessageTypes.CAST_TO_NONNULL_ARG_NONNULL, message), 1832 // The Tree passed as suggestTree is the expression being cast 1833 // to avoid recomputing the arg index: 1834 actual, 1835 buildDescription(tree), 1836 state, 1837 null); 1838 } 1839 } 1840 return Description.NO_MATCH; 1841 } 1842 1843 /** 1844 * check that all @NonNull fields of the class are properly initialized 1845 * 1846 * @param tree the class 1847 * @param state visitor state 1848 */ checkFieldInitialization(ClassTree tree, VisitorState state)1849 private void checkFieldInitialization(ClassTree tree, VisitorState state) { 1850 FieldInitEntities entities = collectEntities(tree, state); 1851 Symbol.ClassSymbol classSymbol = ASTHelpers.getSymbol(tree); 1852 class2Entities.put(classSymbol, entities); 1853 // set of all non-null instance fields f such that *some* constructor does not initialize f 1854 Set<Symbol> notInitializedInConstructors; 1855 SetMultimap<MethodTree, Symbol> constructorInitInfo; 1856 if (entities.constructors().isEmpty()) { 1857 constructorInitInfo = null; 1858 notInitializedInConstructors = entities.nonnullInstanceFields(); 1859 } else { 1860 constructorInitInfo = checkConstructorInitialization(entities, state); 1861 notInitializedInConstructors = ImmutableSet.copyOf(constructorInitInfo.values()); 1862 } 1863 // Filter out final fields, since javac will already check initialization 1864 notInitializedInConstructors = 1865 ImmutableSet.copyOf( 1866 Sets.filter( 1867 notInitializedInConstructors, 1868 symbol -> !symbol.getModifiers().contains(Modifier.FINAL))); 1869 class2ConstructorUninit.putAll(classSymbol, notInitializedInConstructors); 1870 Set<Symbol> notInitializedAtAll = 1871 notAssignedInAnyInitializer(entities, notInitializedInConstructors, state); 1872 SetMultimap<Element, Element> errorFieldsForInitializer = LinkedHashMultimap.create(); 1873 // non-null if we have a single initializer method 1874 Symbol.MethodSymbol singleInitializerMethod = null; 1875 if (entities.instanceInitializerMethods().size() == 1) { 1876 singleInitializerMethod = 1877 ASTHelpers.getSymbol(entities.instanceInitializerMethods().iterator().next()); 1878 } 1879 for (Symbol uninitField : notInitializedAtAll) { 1880 if (errorBuilder.symbolHasSuppressWarningsAnnotation( 1881 uninitField, INITIALIZATION_CHECK_NAME)) { 1882 continue; 1883 } 1884 if (singleInitializerMethod != null) { 1885 // report it on the initializer 1886 errorFieldsForInitializer.put(singleInitializerMethod, uninitField); 1887 } else if (constructorInitInfo == null) { 1888 // report it on the field, except in the case where the class is externalInit and 1889 // we have no initializer methods 1890 if (!(symbolHasExternalInitAnnotation(classSymbol) 1891 && entities.instanceInitializerMethods().isEmpty())) { 1892 errorBuilder.reportInitErrorOnField( 1893 uninitField, state, buildDescription(getTreesInstance(state).getTree(uninitField))); 1894 } 1895 } else { 1896 // report it on each constructor that does not initialize it 1897 for (MethodTree methodTree : constructorInitInfo.keySet()) { 1898 Set<Symbol> uninitFieldsForConstructor = constructorInitInfo.get(methodTree); 1899 if (uninitFieldsForConstructor.contains(uninitField)) { 1900 errorFieldsForInitializer.put(ASTHelpers.getSymbol(methodTree), uninitField); 1901 } 1902 } 1903 } 1904 } 1905 for (Element constructorElement : errorFieldsForInitializer.keySet()) { 1906 ImmutableList<Symbol> fieldSymbols = 1907 errorFieldsForInitializer.get(constructorElement).stream() 1908 .map(element -> ASTHelpers.getSymbol(getTreesInstance(state).getTree(element))) 1909 .collect(ImmutableList.toImmutableList()); 1910 1911 errorBuilder.reportInitializerError( 1912 (Symbol.MethodSymbol) constructorElement, 1913 errMsgForInitializer(errorFieldsForInitializer.get(constructorElement), state), 1914 state, 1915 buildDescription(getTreesInstance(state).getTree(constructorElement)), 1916 fieldSymbols); 1917 } 1918 // For static fields 1919 Set<Symbol> notInitializedStaticFields = notInitializedStatic(entities, state); 1920 for (Symbol uninitSField : notInitializedStaticFields) { 1921 // Always report it on the field for static fields (can't do @SuppressWarnings on a static 1922 // initialization block 1923 // anyways). 1924 errorBuilder.reportInitErrorOnField( 1925 uninitSField, state, buildDescription(getTreesInstance(state).getTree(uninitSField))); 1926 } 1927 } 1928 1929 /** 1930 * @param entities relevant entities from class 1931 * @param notInitializedInConstructors those fields not initialized in some constructor 1932 * @param state visitor state 1933 * @return those fields from notInitializedInConstructors that are not initialized in any 1934 * initializer method 1935 */ notAssignedInAnyInitializer( FieldInitEntities entities, Set<Symbol> notInitializedInConstructors, VisitorState state)1936 private Set<Symbol> notAssignedInAnyInitializer( 1937 FieldInitEntities entities, Set<Symbol> notInitializedInConstructors, VisitorState state) { 1938 Trees trees = getTreesInstance(state); 1939 Symbol.ClassSymbol classSymbol = entities.classSymbol(); 1940 ImmutableSet.Builder<Element> initInSomeInitializerBuilder = ImmutableSet.builder(); 1941 for (MethodTree initMethodTree : entities.instanceInitializerMethods()) { 1942 if (initMethodTree.getBody() == null) { 1943 continue; 1944 } 1945 addInitializedFieldsForBlock( 1946 state, 1947 trees, 1948 classSymbol, 1949 initInSomeInitializerBuilder, 1950 initMethodTree.getBody(), 1951 new TreePath(state.getPath(), initMethodTree)); 1952 } 1953 for (BlockTree block : entities.instanceInitializerBlocks()) { 1954 addInitializedFieldsForBlock( 1955 state, 1956 trees, 1957 classSymbol, 1958 initInSomeInitializerBuilder, 1959 block, 1960 new TreePath(state.getPath(), block)); 1961 } 1962 Set<Symbol> result = new LinkedHashSet<>(); 1963 ImmutableSet<Element> initInSomeInitializer = initInSomeInitializerBuilder.build(); 1964 for (Symbol fieldSymbol : notInitializedInConstructors) { 1965 if (!initInSomeInitializer.contains(fieldSymbol)) { 1966 result.add(fieldSymbol); 1967 } 1968 } 1969 return result; 1970 } 1971 addInitializedFieldsForBlock( VisitorState state, Trees trees, Symbol.ClassSymbol classSymbol, ImmutableSet.Builder<Element> initInSomeInitializerBuilder, BlockTree block, TreePath path)1972 private void addInitializedFieldsForBlock( 1973 VisitorState state, 1974 Trees trees, 1975 Symbol.ClassSymbol classSymbol, 1976 ImmutableSet.Builder<Element> initInSomeInitializerBuilder, 1977 BlockTree block, 1978 TreePath path) { 1979 AccessPathNullnessAnalysis nullnessAnalysis = getNullnessAnalysis(state); 1980 Set<Element> nonnullAtExit = 1981 nullnessAnalysis.getNonnullFieldsOfReceiverAtExit(path, state.context); 1982 initInSomeInitializerBuilder.addAll(nonnullAtExit); 1983 Set<Element> safeInitMethods = getSafeInitMethods(block, classSymbol, state); 1984 addGuaranteedNonNullFromInvokes( 1985 state, trees, safeInitMethods, nullnessAnalysis, initInSomeInitializerBuilder); 1986 } 1987 1988 /** 1989 * @param entities field init info 1990 * @param state visitor state 1991 * @return a map from each constructor C to the nonnull fields that C does *not* initialize 1992 */ checkConstructorInitialization( FieldInitEntities entities, VisitorState state)1993 private SetMultimap<MethodTree, Symbol> checkConstructorInitialization( 1994 FieldInitEntities entities, VisitorState state) { 1995 SetMultimap<MethodTree, Symbol> result = LinkedHashMultimap.create(); 1996 Set<Symbol> nonnullInstanceFields = entities.nonnullInstanceFields(); 1997 Trees trees = getTreesInstance(state); 1998 boolean isExternalInitClass = symbolHasExternalInitAnnotation(entities.classSymbol()); 1999 for (MethodTree constructor : entities.constructors()) { 2000 if (constructorInvokesAnother(constructor, state)) { 2001 continue; 2002 } 2003 if (constructor.getParameters().size() == 0 2004 && (isExternalInitClass 2005 || symbolHasExternalInitAnnotation(ASTHelpers.getSymbol(constructor)))) { 2006 // external framework initializes fields in this case 2007 continue; 2008 } 2009 Set<Element> guaranteedNonNull = 2010 guaranteedNonNullForConstructor(entities, state, trees, constructor); 2011 for (Symbol fieldSymbol : nonnullInstanceFields) { 2012 if (!guaranteedNonNull.contains(fieldSymbol)) { 2013 result.put(constructor, fieldSymbol); 2014 } 2015 } 2016 } 2017 return result; 2018 } 2019 symbolHasExternalInitAnnotation(Symbol symbol)2020 private boolean symbolHasExternalInitAnnotation(Symbol symbol) { 2021 return StreamSupport.stream( 2022 NullabilityUtil.getAllAnnotations(symbol, config).spliterator(), false) 2023 .map((anno) -> anno.getAnnotationType().toString()) 2024 .anyMatch(config::isExternalInitClassAnnotation); 2025 } 2026 guaranteedNonNullForConstructor( FieldInitEntities entities, VisitorState state, Trees trees, MethodTree constructor)2027 private ImmutableSet<Element> guaranteedNonNullForConstructor( 2028 FieldInitEntities entities, VisitorState state, Trees trees, MethodTree constructor) { 2029 Set<Element> safeInitMethods = 2030 getSafeInitMethods(constructor.getBody(), entities.classSymbol(), state); 2031 AccessPathNullnessAnalysis nullnessAnalysis = getNullnessAnalysis(state); 2032 ImmutableSet.Builder<Element> guaranteedNonNullBuilder = ImmutableSet.builder(); 2033 guaranteedNonNullBuilder.addAll( 2034 nullnessAnalysis.getNonnullFieldsOfReceiverAtExit( 2035 new TreePath(state.getPath(), constructor), state.context)); 2036 addGuaranteedNonNullFromInvokes( 2037 state, trees, safeInitMethods, nullnessAnalysis, guaranteedNonNullBuilder); 2038 return guaranteedNonNullBuilder.build(); 2039 } 2040 2041 /** does the constructor invoke another constructor in the same class via this(...)? */ constructorInvokesAnother(MethodTree constructor, VisitorState state)2042 private boolean constructorInvokesAnother(MethodTree constructor, VisitorState state) { 2043 BlockTree body = constructor.getBody(); 2044 List<? extends StatementTree> statements = body.getStatements(); 2045 if (statements.size() > 0) { 2046 StatementTree statementTree = statements.get(0); 2047 if (isThisCall(statementTree, state)) { 2048 return true; 2049 } 2050 } 2051 return false; 2052 } 2053 notInitializedStatic(FieldInitEntities entities, VisitorState state)2054 private Set<Symbol> notInitializedStatic(FieldInitEntities entities, VisitorState state) { 2055 Set<Symbol> nonNullStaticFields = entities.nonnullStaticFields(); 2056 Set<Element> initializedInStaticInitializers = new LinkedHashSet<Element>(); 2057 AccessPathNullnessAnalysis nullnessAnalysis = getNullnessAnalysis(state); 2058 for (BlockTree initializer : entities.staticInitializerBlocks()) { 2059 Set<Element> nonnullAtExit = 2060 nullnessAnalysis.getNonnullStaticFieldsAtExit( 2061 new TreePath(state.getPath(), initializer), state.context); 2062 initializedInStaticInitializers.addAll(nonnullAtExit); 2063 } 2064 for (MethodTree initializerMethod : entities.staticInitializerMethods()) { 2065 Set<Element> nonnullAtExit = 2066 nullnessAnalysis.getNonnullStaticFieldsAtExit( 2067 new TreePath(state.getPath(), initializerMethod), state.context); 2068 initializedInStaticInitializers.addAll(nonnullAtExit); 2069 } 2070 Set<Symbol> notInitializedStaticFields = new LinkedHashSet<Symbol>(); 2071 for (Symbol field : nonNullStaticFields) { 2072 if (!initializedInStaticInitializers.contains(field)) { 2073 notInitializedStaticFields.add(field); 2074 } 2075 } 2076 return notInitializedStaticFields; 2077 } 2078 addGuaranteedNonNullFromInvokes( VisitorState state, Trees trees, Set<Element> safeInitMethods, AccessPathNullnessAnalysis nullnessAnalysis, ImmutableSet.Builder<Element> guaranteedNonNullBuilder)2079 private void addGuaranteedNonNullFromInvokes( 2080 VisitorState state, 2081 Trees trees, 2082 Set<Element> safeInitMethods, 2083 AccessPathNullnessAnalysis nullnessAnalysis, 2084 ImmutableSet.Builder<Element> guaranteedNonNullBuilder) { 2085 for (Element invoked : safeInitMethods) { 2086 Tree invokedTree = trees.getTree(invoked); 2087 guaranteedNonNullBuilder.addAll( 2088 nullnessAnalysis.getNonnullFieldsOfReceiverAtExit( 2089 new TreePath(state.getPath(), invokedTree), state.context)); 2090 } 2091 } 2092 2093 /** 2094 * @param blockTree block of statements 2095 * @param state visitor state 2096 * @return Elements of safe init methods that are invoked as top-level statements in the method 2097 */ getSafeInitMethods( BlockTree blockTree, Symbol.ClassSymbol classSymbol, VisitorState state)2098 private Set<Element> getSafeInitMethods( 2099 BlockTree blockTree, Symbol.ClassSymbol classSymbol, VisitorState state) { 2100 Set<Element> result = new LinkedHashSet<>(); 2101 List<? extends StatementTree> statements = blockTree.getStatements(); 2102 for (StatementTree stmt : statements) { 2103 Element privMethodElem = getInvokeOfSafeInitMethod(stmt, classSymbol, state); 2104 if (privMethodElem != null) { 2105 result.add(privMethodElem); 2106 } 2107 // Hack: If we see a try{...}finally{...} statement, without a catch, we consider the methods 2108 // inside both blocks 2109 // as "top level" for the purposes of finding initialization methods. Any exception happening 2110 // there is also an 2111 // exception of the full method. 2112 if (stmt.getKind().equals(Tree.Kind.TRY)) { 2113 TryTree tryTree = (TryTree) stmt; 2114 if (tryTree.getCatches().size() == 0) { 2115 if (tryTree.getBlock() != null) { 2116 result.addAll(getSafeInitMethods(tryTree.getBlock(), classSymbol, state)); 2117 } 2118 if (tryTree.getFinallyBlock() != null) { 2119 result.addAll(getSafeInitMethods(tryTree.getFinallyBlock(), classSymbol, state)); 2120 } 2121 } 2122 } 2123 } 2124 return result; 2125 } 2126 2127 /** 2128 * A safe init method is an instance method that is either private or final (so no overriding is 2129 * possible) 2130 * 2131 * @param stmt the statement 2132 * @param enclosingClassSymbol symbol for enclosing constructor / initializer 2133 * @param state visitor state 2134 * @return element of safe init function if stmt invokes that function; null otherwise 2135 */ 2136 @Nullable getInvokeOfSafeInitMethod( StatementTree stmt, final Symbol.ClassSymbol enclosingClassSymbol, VisitorState state)2137 private Element getInvokeOfSafeInitMethod( 2138 StatementTree stmt, final Symbol.ClassSymbol enclosingClassSymbol, VisitorState state) { 2139 Matcher<ExpressionTree> invokeMatcher = 2140 (expressionTree, s) -> { 2141 if (!(expressionTree instanceof MethodInvocationTree)) { 2142 return false; 2143 } 2144 MethodInvocationTree methodInvocationTree = (MethodInvocationTree) expressionTree; 2145 Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(methodInvocationTree); 2146 Set<Modifier> modifiers = symbol.getModifiers(); 2147 Set<Modifier> classModifiers = enclosingClassSymbol.getModifiers(); 2148 if ((symbol.isPrivate() 2149 || modifiers.contains(Modifier.FINAL) 2150 || classModifiers.contains(Modifier.FINAL)) 2151 && !symbol.isStatic() 2152 && !modifiers.contains(Modifier.NATIVE)) { 2153 // check it's the same class (could be an issue with inner classes) 2154 if (castToNonNull(ASTHelpers.enclosingClass(symbol)).equals(enclosingClassSymbol)) { 2155 // make sure the receiver is 'this' 2156 ExpressionTree receiver = ASTHelpers.getReceiver(expressionTree); 2157 return receiver == null || isThisIdentifier(receiver); 2158 } 2159 } 2160 return false; 2161 }; 2162 if (stmt.getKind().equals(EXPRESSION_STATEMENT)) { 2163 ExpressionTree expression = ((ExpressionStatementTree) stmt).getExpression(); 2164 if (invokeMatcher.matches(expression, state)) { 2165 return ASTHelpers.getSymbol(expression); 2166 } 2167 } 2168 return null; 2169 } 2170 isThisCall(StatementTree statementTree, VisitorState state)2171 private boolean isThisCall(StatementTree statementTree, VisitorState state) { 2172 if (statementTree.getKind().equals(EXPRESSION_STATEMENT)) { 2173 ExpressionTree expression = ((ExpressionStatementTree) statementTree).getExpression(); 2174 return Matchers.methodInvocation(THIS_MATCHER).matches(expression, state); 2175 } 2176 return false; 2177 } 2178 collectEntities(ClassTree tree, VisitorState state)2179 private FieldInitEntities collectEntities(ClassTree tree, VisitorState state) { 2180 Symbol.ClassSymbol classSymbol = ASTHelpers.getSymbol(tree); 2181 Set<Symbol> nonnullInstanceFields = new LinkedHashSet<>(); 2182 Set<Symbol> nonnullStaticFields = new LinkedHashSet<>(); 2183 List<BlockTree> instanceInitializerBlocks = new ArrayList<>(); 2184 List<BlockTree> staticInitializerBlocks = new ArrayList<>(); 2185 Set<MethodTree> constructors = new LinkedHashSet<>(); 2186 Set<MethodTree> instanceInitializerMethods = new LinkedHashSet<>(); 2187 Set<MethodTree> staticInitializerMethods = new LinkedHashSet<>(); 2188 2189 // we assume getMembers() returns members in the same order as the declarations 2190 for (Tree memberTree : tree.getMembers()) { 2191 if (TreeUtils.isClassTree(memberTree)) { 2192 // do nothing 2193 continue; 2194 } 2195 switch (memberTree.getKind()) { 2196 case METHOD: 2197 // check if it is a constructor or an @Initializer method 2198 MethodTree methodTree = (MethodTree) memberTree; 2199 Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(methodTree); 2200 if (isConstructor(methodTree)) { 2201 constructors.add(methodTree); 2202 } else if (isInitializerMethod(state, symbol)) { 2203 if (symbol.isStatic()) { 2204 staticInitializerMethods.add(methodTree); 2205 } else { 2206 instanceInitializerMethods.add(methodTree); 2207 } 2208 } 2209 break; 2210 case VARIABLE: 2211 // field declaration 2212 VariableTree varTree = (VariableTree) memberTree; 2213 Symbol fieldSymbol = ASTHelpers.getSymbol(varTree); 2214 if (fieldSymbol.type.isPrimitive() || skipDueToFieldAnnotation(fieldSymbol)) { 2215 continue; 2216 } 2217 if (varTree.getInitializer() != null) { 2218 // note that we check that the initializer does the right thing in 2219 // matchVariable() 2220 continue; 2221 } 2222 if (isStatic(fieldSymbol)) { 2223 nonnullStaticFields.add(fieldSymbol); 2224 } else { 2225 nonnullInstanceFields.add(fieldSymbol); 2226 } 2227 break; 2228 case BLOCK: 2229 // initializer block 2230 BlockTree blockTree = (BlockTree) memberTree; 2231 if (blockTree.isStatic()) { 2232 staticInitializerBlocks.add(blockTree); 2233 } else { 2234 instanceInitializerBlocks.add(blockTree); 2235 } 2236 break; 2237 default: 2238 throw new RuntimeException( 2239 memberTree.getKind().toString() + " " + state.getSourceForNode(memberTree)); 2240 } 2241 } 2242 2243 return FieldInitEntities.create( 2244 classSymbol, 2245 ImmutableSet.copyOf(nonnullInstanceFields), 2246 ImmutableSet.copyOf(nonnullStaticFields), 2247 ImmutableList.copyOf(instanceInitializerBlocks), 2248 ImmutableList.copyOf(staticInitializerBlocks), 2249 ImmutableSet.copyOf(constructors), 2250 ImmutableSet.copyOf(instanceInitializerMethods), 2251 ImmutableSet.copyOf(staticInitializerMethods)); 2252 } 2253 isConstructor(MethodTree methodTree)2254 private boolean isConstructor(MethodTree methodTree) { 2255 return ASTHelpers.getSymbol(methodTree).isConstructor() 2256 && !ASTHelpers.isGeneratedConstructor(methodTree); 2257 } 2258 isInitializerMethod(VisitorState state, Symbol.MethodSymbol symbol)2259 private boolean isInitializerMethod(VisitorState state, Symbol.MethodSymbol symbol) { 2260 if (hasDirectAnnotationWithSimpleName(symbol, "Initializer") 2261 || config.isKnownInitializerMethod(symbol)) { 2262 return true; 2263 } 2264 for (AnnotationMirror anno : symbol.getAnnotationMirrors()) { 2265 String annoTypeStr = anno.getAnnotationType().toString(); 2266 if (config.isInitializerMethodAnnotation(annoTypeStr)) { 2267 return true; 2268 } 2269 } 2270 Symbol.MethodSymbol closestOverriddenMethod = 2271 NullabilityUtil.getClosestOverriddenMethod(symbol, state.getTypes()); 2272 if (closestOverriddenMethod == null) { 2273 return false; 2274 } 2275 return isInitializerMethod(state, closestOverriddenMethod); 2276 } 2277 skipDueToFieldAnnotation(Symbol fieldSymbol)2278 private boolean skipDueToFieldAnnotation(Symbol fieldSymbol) { 2279 return NullabilityUtil.getAllAnnotations(fieldSymbol, config) 2280 .map(anno -> anno.getAnnotationType().toString()) 2281 .anyMatch(config::isExcludedFieldAnnotation); 2282 } 2283 2284 // classSymbol must be a top-level class isExcludedClass(Symbol.ClassSymbol classSymbol)2285 private boolean isExcludedClass(Symbol.ClassSymbol classSymbol) { 2286 String className = classSymbol.getQualifiedName().toString(); 2287 if (config.isExcludedClass(className)) { 2288 return true; 2289 } 2290 if (!codeAnnotationInfo.isClassNullAnnotated(classSymbol, config)) { 2291 return true; 2292 } 2293 // check annotations 2294 ImmutableSet<String> excludedClassAnnotations = config.getExcludedClassAnnotations(); 2295 return classSymbol.getAnnotationMirrors().stream() 2296 .map(anno -> anno.getAnnotationType().toString()) 2297 .anyMatch(excludedClassAnnotations::contains); 2298 } 2299 mayBeNullExpr(VisitorState state, ExpressionTree expr)2300 private boolean mayBeNullExpr(VisitorState state, ExpressionTree expr) { 2301 expr = stripParensAndCasts(expr); 2302 if (ASTHelpers.constValue(expr) != null) { 2303 // This should include literals such as "true" or a string 2304 // obviously not null 2305 return false; 2306 } 2307 // return early for expressions that no handler overrides and will not need dataflow analysis 2308 switch (expr.getKind()) { 2309 case NULL_LITERAL: 2310 // obviously null 2311 return true; 2312 case ARRAY_ACCESS: 2313 // unsound! we cannot check for nullness of array contents yet 2314 case NEW_CLASS: 2315 case NEW_ARRAY: 2316 // for string concatenation, auto-boxing 2317 case LAMBDA_EXPRESSION: 2318 // Lambdas may return null, but the lambda literal itself should not be null 2319 case MEMBER_REFERENCE: 2320 // These cannot be null; the compiler would catch it 2321 case MULTIPLY_ASSIGNMENT: 2322 case DIVIDE_ASSIGNMENT: 2323 case REMAINDER_ASSIGNMENT: 2324 case PLUS_ASSIGNMENT: 2325 case MINUS_ASSIGNMENT: 2326 case LEFT_SHIFT_ASSIGNMENT: 2327 case RIGHT_SHIFT_ASSIGNMENT: 2328 case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: 2329 case AND_ASSIGNMENT: 2330 case XOR_ASSIGNMENT: 2331 case OR_ASSIGNMENT: 2332 // result of compound assignment cannot be null 2333 case PLUS: 2334 // rest are for auto-boxing 2335 case MINUS: 2336 case MULTIPLY: 2337 case DIVIDE: 2338 case REMAINDER: 2339 case CONDITIONAL_AND: 2340 case CONDITIONAL_OR: 2341 case BITWISE_COMPLEMENT: 2342 case LOGICAL_COMPLEMENT: 2343 case INSTANCE_OF: 2344 case PREFIX_INCREMENT: 2345 case PREFIX_DECREMENT: 2346 case POSTFIX_DECREMENT: 2347 case POSTFIX_INCREMENT: 2348 case EQUAL_TO: 2349 case NOT_EQUAL_TO: 2350 case GREATER_THAN: 2351 case GREATER_THAN_EQUAL: 2352 case LESS_THAN: 2353 case LESS_THAN_EQUAL: 2354 case UNARY_MINUS: 2355 case UNARY_PLUS: 2356 case AND: 2357 case OR: 2358 case XOR: 2359 case LEFT_SHIFT: 2360 case RIGHT_SHIFT: 2361 case UNSIGNED_RIGHT_SHIFT: 2362 // clearly not null 2363 return false; 2364 default: 2365 break; 2366 } 2367 // the logic here is to avoid doing dataflow analysis whenever possible 2368 Symbol exprSymbol = ASTHelpers.getSymbol(expr); 2369 boolean exprMayBeNull; 2370 switch (expr.getKind()) { 2371 case MEMBER_SELECT: 2372 if (exprSymbol == null) { 2373 throw new IllegalStateException( 2374 "unexpected null symbol for dereference expression " + state.getSourceForNode(expr)); 2375 } 2376 exprMayBeNull = 2377 NullabilityUtil.mayBeNullFieldFromType(exprSymbol, config, codeAnnotationInfo); 2378 break; 2379 case IDENTIFIER: 2380 if (exprSymbol == null) { 2381 throw new IllegalStateException( 2382 "unexpected null symbol for identifier " + state.getSourceForNode(expr)); 2383 } 2384 if (exprSymbol.getKind() == ElementKind.FIELD) { 2385 exprMayBeNull = 2386 NullabilityUtil.mayBeNullFieldFromType(exprSymbol, config, codeAnnotationInfo); 2387 } else { 2388 // rely on dataflow analysis for local variables 2389 exprMayBeNull = true; 2390 } 2391 break; 2392 case METHOD_INVOCATION: 2393 if (!(exprSymbol instanceof Symbol.MethodSymbol)) { 2394 throw new IllegalStateException( 2395 "unexpected symbol " 2396 + exprSymbol 2397 + " for method invocation " 2398 + state.getSourceForNode(expr)); 2399 } 2400 exprMayBeNull = 2401 mayBeNullMethodCall( 2402 (Symbol.MethodSymbol) exprSymbol, (MethodInvocationTree) expr, state); 2403 break; 2404 case CONDITIONAL_EXPRESSION: 2405 case ASSIGNMENT: 2406 exprMayBeNull = true; 2407 break; 2408 default: 2409 // match switch expressions by comparing strings, so the code compiles on JDK versions < 12 2410 if (expr.getKind().name().equals("SWITCH_EXPRESSION")) { 2411 exprMayBeNull = true; 2412 } else { 2413 throw new RuntimeException( 2414 "whoops, better handle " + expr.getKind() + " " + state.getSourceForNode(expr)); 2415 } 2416 } 2417 exprMayBeNull = handler.onOverrideMayBeNullExpr(this, expr, exprSymbol, state, exprMayBeNull); 2418 return exprMayBeNull && nullnessFromDataflow(state, expr); 2419 } 2420 mayBeNullMethodCall( Symbol.MethodSymbol exprSymbol, MethodInvocationTree invocationTree, VisitorState state)2421 private boolean mayBeNullMethodCall( 2422 Symbol.MethodSymbol exprSymbol, MethodInvocationTree invocationTree, VisitorState state) { 2423 if (codeAnnotationInfo.isSymbolUnannotated(exprSymbol, config)) { 2424 return false; 2425 } 2426 if (Nullness.hasNullableAnnotation(exprSymbol, config)) { 2427 return true; 2428 } 2429 if (config.isJSpecifyMode() 2430 && GenericsChecks.getGenericReturnNullnessAtInvocation( 2431 exprSymbol, invocationTree, state, config) 2432 .equals(Nullness.NULLABLE)) { 2433 return true; 2434 } 2435 return false; 2436 } 2437 nullnessFromDataflow(VisitorState state, ExpressionTree expr)2438 public boolean nullnessFromDataflow(VisitorState state, ExpressionTree expr) { 2439 Nullness nullness = 2440 getNullnessAnalysis(state).getNullness(new TreePath(state.getPath(), expr), state.context); 2441 if (nullness == null) { 2442 // this may be unsound, like for field initializers 2443 // figure out if we care 2444 return false; 2445 } 2446 return NullabilityUtil.nullnessToBool(nullness); 2447 } 2448 getNullnessAnalysis(VisitorState state)2449 public AccessPathNullnessAnalysis getNullnessAnalysis(VisitorState state) { 2450 return AccessPathNullnessAnalysis.instance(state, nonAnnotatedMethod, config, this.handler); 2451 } 2452 matchDereference( ExpressionTree baseExpression, ExpressionTree derefExpression, VisitorState state)2453 private Description matchDereference( 2454 ExpressionTree baseExpression, ExpressionTree derefExpression, VisitorState state) { 2455 Symbol baseExpressionSymbol = ASTHelpers.getSymbol(baseExpression); 2456 // Note that a null dereference is possible even if baseExpressionSymbol is null, 2457 // e.g., in cases where baseExpression contains conditional logic (like a ternary 2458 // expression, or a switch expression in JDK 12+) 2459 if (baseExpressionSymbol != null) { 2460 if (baseExpressionSymbol.type.isPrimitive() 2461 || baseExpressionSymbol.getKind() == ElementKind.PACKAGE 2462 || ElementUtils.isTypeElement(baseExpressionSymbol)) { 2463 // we know we don't have a null dereference here 2464 return Description.NO_MATCH; 2465 } 2466 } 2467 if (mayBeNullExpr(state, baseExpression)) { 2468 final String message = 2469 "dereferenced expression " + state.getSourceForNode(baseExpression) + " is @Nullable"; 2470 ErrorMessage errorMessage = new ErrorMessage(MessageTypes.DEREFERENCE_NULLABLE, message); 2471 2472 return errorBuilder.createErrorDescriptionForNullAssignment( 2473 errorMessage, baseExpression, buildDescription(derefExpression), state, null); 2474 } 2475 2476 Optional<ErrorMessage> handlerErrorMessage = 2477 handler.onExpressionDereference(derefExpression, baseExpression, state); 2478 if (handlerErrorMessage.isPresent()) { 2479 return errorBuilder.createErrorDescriptionForNullAssignment( 2480 handlerErrorMessage.get(), 2481 derefExpression, 2482 buildDescription(derefExpression), 2483 state, 2484 null); 2485 } 2486 2487 return Description.NO_MATCH; 2488 } 2489 isThisIdentifier(ExpressionTree expressionTree)2490 private static boolean isThisIdentifier(ExpressionTree expressionTree) { 2491 return expressionTree.getKind().equals(IDENTIFIER) 2492 && ((IdentifierTree) expressionTree).getName().toString().equals("this"); 2493 } 2494 isThisIdentifierMatcher( ExpressionTree expressionTree, VisitorState state)2495 private static boolean isThisIdentifierMatcher( 2496 ExpressionTree expressionTree, VisitorState state) { 2497 return isThisIdentifier(expressionTree); 2498 } 2499 getErrorBuilder()2500 public ErrorBuilder getErrorBuilder() { 2501 return errorBuilder; 2502 } 2503 2504 /** 2505 * strip out enclosing parentheses, type casts and Nullchk operators. 2506 * 2507 * @param expr a potentially parenthesised expression. 2508 * @return the same expression without parentheses. 2509 */ stripParensAndCasts(ExpressionTree expr)2510 private static ExpressionTree stripParensAndCasts(ExpressionTree expr) { 2511 boolean someChange = true; 2512 while (someChange) { 2513 someChange = false; 2514 if (expr.getKind().equals(PARENTHESIZED)) { 2515 expr = ((ParenthesizedTree) expr).getExpression(); 2516 someChange = true; 2517 } 2518 if (expr.getKind().equals(TYPE_CAST)) { 2519 expr = ((TypeCastTree) expr).getExpression(); 2520 someChange = true; 2521 } 2522 2523 // Strips Nullchk operator 2524 if (expr.getKind().equals(OTHER) && expr instanceof JCTree.JCUnary) { 2525 expr = ((JCTree.JCUnary) expr).getExpression(); 2526 someChange = true; 2527 } 2528 } 2529 return expr; 2530 } 2531 2532 /** 2533 * Returns the computed nullness information from an expression. If none is available, it returns 2534 * Nullable. 2535 * 2536 * <p>Computed information can be added by handlers or by the core, and should supersede that 2537 * comming from annotations. 2538 * 2539 * <p>The default value of an expression without additional computed nullness information is 2540 * always Nullable, since this method should only be called when the fact that the expression is 2541 * NonNull is not clear from looking at annotations. 2542 * 2543 * @param e an expression 2544 * @return computed nullness for e, if any, else Nullable 2545 */ getComputedNullness(ExpressionTree e)2546 public Nullness getComputedNullness(ExpressionTree e) { 2547 return computedNullnessMap.getOrDefault(e, Nullness.NULLABLE); 2548 } 2549 2550 /** 2551 * Add computed nullness information to an expression. 2552 * 2553 * <p>Used by handlers to communicate that an expression should has a more precise nullness than 2554 * what is known from source annotations. 2555 * 2556 * @param e any expression in the AST. 2557 * @param nullness the added nullness information. 2558 */ setComputedNullness(ExpressionTree e, Nullness nullness)2559 public void setComputedNullness(ExpressionTree e, Nullness nullness) { 2560 computedNullnessMap.put(e, nullness); 2561 } 2562 2563 @AutoValue 2564 abstract static class FieldInitEntities { 2565 create( Symbol.ClassSymbol classSymbol, Set<Symbol> nonnullInstanceFields, Set<Symbol> nonnullStaticFields, List<BlockTree> instanceInitializerBlocks, List<BlockTree> staticInitializerBlocks, Set<MethodTree> constructors, Set<MethodTree> instanceInitializerMethods, Set<MethodTree> staticInitializerMethods)2566 static FieldInitEntities create( 2567 Symbol.ClassSymbol classSymbol, 2568 Set<Symbol> nonnullInstanceFields, 2569 Set<Symbol> nonnullStaticFields, 2570 List<BlockTree> instanceInitializerBlocks, 2571 List<BlockTree> staticInitializerBlocks, 2572 Set<MethodTree> constructors, 2573 Set<MethodTree> instanceInitializerMethods, 2574 Set<MethodTree> staticInitializerMethods) { 2575 return new AutoValue_NullAway_FieldInitEntities( 2576 classSymbol, 2577 ImmutableSet.copyOf(nonnullInstanceFields), 2578 ImmutableSet.copyOf(nonnullStaticFields), 2579 ImmutableList.copyOf(instanceInitializerBlocks), 2580 ImmutableList.copyOf(staticInitializerBlocks), 2581 ImmutableSet.copyOf(constructors), 2582 ImmutableSet.copyOf(instanceInitializerMethods), 2583 ImmutableSet.copyOf(staticInitializerMethods)); 2584 } 2585 2586 /** Returns symbol for class. */ classSymbol()2587 abstract Symbol.ClassSymbol classSymbol(); 2588 2589 /** 2590 * Returns <code>@NonNull</code> instance fields that are not directly initialized at 2591 * declaration. 2592 */ nonnullInstanceFields()2593 abstract ImmutableSet<Symbol> nonnullInstanceFields(); 2594 2595 /** 2596 * Returns <code>@NonNull</code> static fields that are not directly initialized at declaration. 2597 */ nonnullStaticFields()2598 abstract ImmutableSet<Symbol> nonnullStaticFields(); 2599 2600 /** 2601 * Returns the list of instance initializer blocks (e.g. blocks of the form `class X { { //Code 2602 * } } ), in the order in which they appear in the class. 2603 */ instanceInitializerBlocks()2604 abstract ImmutableList<BlockTree> instanceInitializerBlocks(); 2605 2606 /** 2607 * Returns the list of static initializer blocks (e.g. blocks of the form `class X { static { 2608 * //Code } } ), in the order in which they appear in the class. 2609 */ staticInitializerBlocks()2610 abstract ImmutableList<BlockTree> staticInitializerBlocks(); 2611 2612 /** Returns constructors in the class. */ constructors()2613 abstract ImmutableSet<MethodTree> constructors(); 2614 2615 /** 2616 * Returns the list of non-static (instance) initializer methods. This includes methods 2617 * annotated @Initializer, as well as those specified by -XepOpt:NullAway:KnownInitializers or 2618 * annotated with annotations passed to -XepOpt:NullAway:CustomInitializerAnnotations. 2619 */ instanceInitializerMethods()2620 abstract ImmutableSet<MethodTree> instanceInitializerMethods(); 2621 2622 /** 2623 * Returns the list of static initializer methods. This includes static methods 2624 * annotated @Initializer, as well as those specified by -XepOpt:NullAway:KnownInitializers or 2625 * annotated with annotations passed to -XepOpt:NullAway:CustomInitializerAnnotations. 2626 */ staticInitializerMethods()2627 abstract ImmutableSet<MethodTree> staticInitializerMethods(); 2628 } 2629 } 2630