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