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.dataflow; 24 25 import static com.uber.nullaway.NullabilityUtil.castToNonNull; 26 27 import com.google.common.base.Preconditions; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.ImmutableSet; 30 import com.google.errorprone.VisitorState; 31 import com.google.errorprone.util.ASTHelpers; 32 import com.sun.source.tree.LiteralTree; 33 import com.sun.source.tree.MethodInvocationTree; 34 import com.sun.source.tree.Tree; 35 import com.sun.tools.javac.code.Symbol; 36 import com.sun.tools.javac.code.Type; 37 import com.uber.nullaway.NullabilityUtil; 38 import com.uber.nullaway.annotations.JacocoIgnoreGenerated; 39 import java.util.ArrayDeque; 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Objects; 43 import java.util.Set; 44 import javax.annotation.Nullable; 45 import javax.lang.model.element.Element; 46 import javax.lang.model.element.ElementKind; 47 import javax.lang.model.element.Modifier; 48 import javax.lang.model.element.VariableElement; 49 import org.checkerframework.nullaway.dataflow.cfg.node.FieldAccessNode; 50 import org.checkerframework.nullaway.dataflow.cfg.node.IntegerLiteralNode; 51 import org.checkerframework.nullaway.dataflow.cfg.node.LocalVariableNode; 52 import org.checkerframework.nullaway.dataflow.cfg.node.LongLiteralNode; 53 import org.checkerframework.nullaway.dataflow.cfg.node.MethodAccessNode; 54 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode; 55 import org.checkerframework.nullaway.dataflow.cfg.node.Node; 56 import org.checkerframework.nullaway.dataflow.cfg.node.StringLiteralNode; 57 import org.checkerframework.nullaway.dataflow.cfg.node.SuperNode; 58 import org.checkerframework.nullaway.dataflow.cfg.node.ThisNode; 59 import org.checkerframework.nullaway.dataflow.cfg.node.TypeCastNode; 60 import org.checkerframework.nullaway.dataflow.cfg.node.VariableDeclarationNode; 61 import org.checkerframework.nullaway.dataflow.cfg.node.WideningConversionNode; 62 import org.checkerframework.nullaway.javacutil.TreeUtils; 63 64 /** 65 * Represents an extended notion of an access path, which we track for nullness. 66 * 67 * <p>Typically, access paths are of the form x.f.g.h, where x is a variable and f, g, and h are 68 * field names. Here, we also allow no-argument methods to appear in the access path, as well as 69 * method calls passed only statically constant parameters, so an AP can be of the form 70 * x.f().g.h([int_expr|string_expr]) in general. 71 * 72 * <p>We do not allow array accesses in access paths for the moment. 73 */ 74 public final class AccessPath implements MapKey { 75 76 /** 77 * A prefix added for elements appearing in method invocation APs which represent fields that can 78 * be proven to be class-initialization time constants (i.e. static final fields of a type known 79 * to be structurally immutable, such as io.grpc.Metadata.Key). 80 * 81 * <p>This prefix helps avoid collisions between common field names and common strings, e.g. 82 * "KEY_1" and the field KEY_1. 83 */ 84 private static final String IMMUTABLE_FIELD_PREFIX = "static final [immutable] field: "; 85 86 /** 87 * Encode a static final field as a constant argument on a method's AccessPathElement 88 * 89 * <p>The field must be of a type known to be structurally immutable, in addition to being 90 * declared static and final for this encoding to make any sense. We do not verify this here, and 91 * rather operate only on the field's fully qualified name, as this is intended to be a quick 92 * utility method. 93 * 94 * @param fieldFQN the field's Fully Qualified Name 95 * @return a string suitable to be included as part of the constant arguments of an 96 * AccessPathElement, assuming the field is indeed static final and of an structurally 97 * immutable type 98 */ immutableFieldNameAsConstantArgument(String fieldFQN)99 public static String immutableFieldNameAsConstantArgument(String fieldFQN) { 100 return IMMUTABLE_FIELD_PREFIX + fieldFQN; 101 } 102 103 /** Root of the access path. If {@code null}, the root is the receiver argument */ 104 @Nullable private final Element root; 105 106 private final ImmutableList<AccessPathElement> elements; 107 108 /** 109 * if present, the argument to the map get() method call that is the final element of this path 110 */ 111 @Nullable private final MapKey mapGetArg; 112 AccessPath(@ullable Element root, ImmutableList<AccessPathElement> elements)113 private AccessPath(@Nullable Element root, ImmutableList<AccessPathElement> elements) { 114 this(root, elements, null); 115 } 116 AccessPath( @ullable Element root, ImmutableList<AccessPathElement> elements, @Nullable MapKey mapGetArg)117 private AccessPath( 118 @Nullable Element root, 119 ImmutableList<AccessPathElement> elements, 120 @Nullable MapKey mapGetArg) { 121 this.root = root; 122 this.elements = elements; 123 this.mapGetArg = mapGetArg; 124 } 125 126 /** 127 * Construct the access path of a local. 128 * 129 * @param node the local 130 * @return access path representing the local 131 */ fromLocal(LocalVariableNode node)132 public static AccessPath fromLocal(LocalVariableNode node) { 133 return new AccessPath(node.getElement(), ImmutableList.of()); 134 } 135 136 /** 137 * Construct the access path of a variable declaration. 138 * 139 * @param node the variable declaration 140 * @return access path representing the variable declaration 141 */ fromVarDecl(VariableDeclarationNode node)142 static AccessPath fromVarDecl(VariableDeclarationNode node) { 143 Element elem = TreeUtils.elementFromDeclaration(node.getTree()); 144 return new AccessPath(elem, ImmutableList.of()); 145 } 146 147 /** 148 * Construct the access path of a field access. 149 * 150 * @param node the field access 151 * @param apContext the current access path context information (see {@link 152 * AccessPath.AccessPathContext}). 153 * @return access path for the field access, or <code>null</code> if it cannot be represented 154 */ 155 @Nullable fromFieldAccess(FieldAccessNode node, AccessPathContext apContext)156 static AccessPath fromFieldAccess(FieldAccessNode node, AccessPathContext apContext) { 157 return fromNodeAndContext(node, apContext); 158 } 159 160 @Nullable fromNodeAndContext(Node node, AccessPathContext apContext)161 private static AccessPath fromNodeAndContext(Node node, AccessPathContext apContext) { 162 return buildAccessPathRecursive(node, new ArrayDeque<>(), apContext, null); 163 } 164 165 /** 166 * Construct the access path of a method call. 167 * 168 * @param node the method call 169 * @param apContext the current access path context information (see {@link 170 * AccessPath.AccessPathContext}). 171 * @return access path for the method call, or <code>null</code> if it cannot be represented 172 */ 173 @Nullable fromMethodCall( MethodInvocationNode node, VisitorState state, AccessPathContext apContext)174 static AccessPath fromMethodCall( 175 MethodInvocationNode node, VisitorState state, AccessPathContext apContext) { 176 if (isMapGet(ASTHelpers.getSymbol(node.getTree()), state)) { 177 return fromMapGetCall(node, state, apContext); 178 } 179 return fromVanillaMethodCall(node, apContext); 180 } 181 182 @Nullable fromVanillaMethodCall( MethodInvocationNode node, AccessPathContext apContext)183 private static AccessPath fromVanillaMethodCall( 184 MethodInvocationNode node, AccessPathContext apContext) { 185 return fromNodeAndContext(node, apContext); 186 } 187 188 /** 189 * Returns an access path rooted at {@code newRoot} with the same elements and map-get argument as 190 * {@code origAP} 191 */ switchRoot(AccessPath origAP, Element newRoot)192 static AccessPath switchRoot(AccessPath origAP, Element newRoot) { 193 return new AccessPath(newRoot, origAP.elements, origAP.mapGetArg); 194 } 195 196 /** 197 * Construct the access path given a {@code base.element} structure. 198 * 199 * @param base the base expression for the access path 200 * @param element the final element of the access path (a field or method) 201 * @param apContext the current access path context information (see {@link 202 * AccessPath.AccessPathContext}). 203 * @return the {@link AccessPath} {@code base.element} 204 */ 205 @Nullable fromBaseAndElement( Node base, Element element, AccessPathContext apContext)206 public static AccessPath fromBaseAndElement( 207 Node base, Element element, AccessPathContext apContext) { 208 return fromNodeElementAndContext(base, new AccessPathElement(element), apContext); 209 } 210 211 @Nullable fromNodeElementAndContext( Node base, AccessPathElement apElement, AccessPathContext apContext)212 private static AccessPath fromNodeElementAndContext( 213 Node base, AccessPathElement apElement, AccessPathContext apContext) { 214 ArrayDeque<AccessPathElement> elements = new ArrayDeque<>(); 215 elements.push(apElement); 216 return buildAccessPathRecursive(base, elements, apContext, null); 217 } 218 219 /** 220 * Construct the access path given a {@code base.method(CONS)} structure. 221 * 222 * <p>IMPORTANT: Be careful with this method, the argument list is not the variable names of the 223 * method arguments, but rather the string representation of primitive-type compile-time constants 224 * or the name of static final fields of structurally immutable types (see {@link 225 * #buildAccessPathRecursive(Node, ArrayDeque, AccessPathContext, MapKey)}). 226 * 227 * <p>This is used by a few specialized Handlers to set nullability around particular paths 228 * involving constants. 229 * 230 * @param base the base expression for the access path 231 * @param method the last method call in the access path 232 * @param constantArguments a list of <b>constant</b> arguments passed to the method call 233 * @param apContext the current access path context information (see {@link 234 * AccessPath.AccessPathContext}). 235 * @return the {@link AccessPath} {@code base.method(CONS)} 236 */ 237 @Nullable fromBaseMethodAndConstantArgs( Node base, Element method, List<String> constantArguments, AccessPathContext apContext)238 public static AccessPath fromBaseMethodAndConstantArgs( 239 Node base, Element method, List<String> constantArguments, AccessPathContext apContext) { 240 return fromNodeElementAndContext( 241 base, new AccessPathElement(method, constantArguments), apContext); 242 } 243 244 /** 245 * Construct the access path for <code>map.get(x)</code> from an invocation of <code>put(x)</code> 246 * or <code>containsKey(x)</code>. 247 * 248 * @param node a node invoking containsKey() or put() on a map 249 * @param apContext the current access path context information (see {@link 250 * AccessPath.AccessPathContext}). 251 * @return an AccessPath representing invoking get() on the same type of map as from node, passing 252 * the same first argument as is passed in node 253 */ 254 @Nullable getForMapInvocation( MethodInvocationNode node, VisitorState state, AccessPathContext apContext)255 public static AccessPath getForMapInvocation( 256 MethodInvocationNode node, VisitorState state, AccessPathContext apContext) { 257 // For the receiver type for get, use the declared type of the receiver of the containsKey() 258 // call. Note that this may differ from the containing class of the resolved containsKey() 259 // method, which can be in a superclass (e.g., LinkedHashMap does not override containsKey()) 260 // assumption: map type will not both override containsKey() and inherit get() 261 return fromMapGetCall(node, state, apContext); 262 } 263 stripCasts(Node node)264 private static Node stripCasts(Node node) { 265 while (node instanceof TypeCastNode) { 266 node = ((TypeCastNode) node).getOperand(); 267 } 268 return node; 269 } 270 271 @Nullable argumentToMapKeySpecifier( Node argument, VisitorState state, AccessPathContext apContext)272 private static MapKey argumentToMapKeySpecifier( 273 Node argument, VisitorState state, AccessPathContext apContext) { 274 // Required to have Node type match Tree type in some instances. 275 if (argument instanceof WideningConversionNode) { 276 argument = ((WideningConversionNode) argument).getOperand(); 277 } 278 // A switch at the Tree level should be faster than multiple if checks at the Node level. 279 switch (castToNonNull(argument.getTree()).getKind()) { 280 case STRING_LITERAL: 281 return new StringMapKey(((StringLiteralNode) argument).getValue()); 282 case INT_LITERAL: 283 return new NumericMapKey(((IntegerLiteralNode) argument).getValue()); 284 case LONG_LITERAL: 285 return new NumericMapKey(((LongLiteralNode) argument).getValue()); 286 case METHOD_INVOCATION: 287 MethodAccessNode target = ((MethodInvocationNode) argument).getTarget(); 288 Node receiver = stripCasts(target.getReceiver()); 289 List<Node> arguments = ((MethodInvocationNode) argument).getArguments(); 290 // Check for int/long boxing. 291 if (target.getMethod().getSimpleName().toString().equals("valueOf") 292 && arguments.size() == 1 293 && castToNonNull(receiver.getTree()).getKind().equals(Tree.Kind.IDENTIFIER) 294 && (receiver.toString().equals("Integer") || receiver.toString().equals("Long"))) { 295 return argumentToMapKeySpecifier(arguments.get(0), state, apContext); 296 } 297 // Fine to fallthrough: 298 default: 299 // Every other type of expression, including variables, field accesses, new A(...), etc. 300 return getAccessPathForNode(argument, state, apContext); // Every AP is a MapKey too 301 } 302 } 303 304 @Nullable fromMapGetCall( MethodInvocationNode node, VisitorState state, AccessPathContext apContext)305 private static AccessPath fromMapGetCall( 306 MethodInvocationNode node, VisitorState state, AccessPathContext apContext) { 307 Node argument = node.getArgument(0); 308 MapKey mapKey = argumentToMapKeySpecifier(argument, state, apContext); 309 if (mapKey == null) { 310 return null; 311 } 312 MethodAccessNode target = node.getTarget(); 313 Node receiver = stripCasts(target.getReceiver()); 314 return buildAccessPathRecursive(receiver, new ArrayDeque<>(), apContext, mapKey); 315 } 316 317 /** 318 * Gets corresponding AccessPath for node, if it exists. Handles calls to <code>Map.get() 319 * </code> 320 * 321 * @param node AST node 322 * @param state the visitor state 323 * @param apContext the current access path context information (see {@link 324 * AccessPath.AccessPathContext}). 325 * @return corresponding AccessPath if it exists; <code>null</code> otherwise 326 */ 327 @Nullable getAccessPathForNode( Node node, VisitorState state, AccessPathContext apContext)328 public static AccessPath getAccessPathForNode( 329 Node node, VisitorState state, AccessPathContext apContext) { 330 if (node instanceof LocalVariableNode) { 331 return fromLocal((LocalVariableNode) node); 332 } else if (node instanceof FieldAccessNode) { 333 return fromFieldAccess((FieldAccessNode) node, apContext); 334 } else if (node instanceof MethodInvocationNode) { 335 return fromMethodCall((MethodInvocationNode) node, state, apContext); 336 } else { 337 return null; 338 } 339 } 340 341 /** 342 * Constructs an access path ending with the class field element in the argument. The receiver is 343 * the method receiver itself. 344 * 345 * @param element the receiver element. 346 * @return access path representing the class field 347 */ fromFieldElement(VariableElement element)348 public static AccessPath fromFieldElement(VariableElement element) { 349 Preconditions.checkArgument( 350 element.getKind().isField(), 351 "element must be of type: FIELD but received: " + element.getKind()); 352 return new AccessPath(null, ImmutableList.of(new AccessPathElement(element))); 353 } 354 isBoxingMethod(Symbol.MethodSymbol methodSymbol)355 private static boolean isBoxingMethod(Symbol.MethodSymbol methodSymbol) { 356 if (methodSymbol.isStatic() && methodSymbol.getSimpleName().contentEquals("valueOf")) { 357 Symbol.PackageSymbol enclosingPackage = ASTHelpers.enclosingPackage(methodSymbol.enclClass()); 358 return enclosingPackage != null && enclosingPackage.fullname.contentEquals("java.lang"); 359 } 360 return false; 361 } 362 363 /** 364 * A helper function that recursively builds an AccessPath from a CFG node. 365 * 366 * @param node the CFG node 367 * @param elements elements to append to the final access path. 368 * @param apContext context information, used to handle cases with constant arguments 369 * @param mapKey map key to be used as the map-get argument, or {@code null} if there is no key 370 * @return the final access path 371 */ 372 @Nullable buildAccessPathRecursive( Node node, ArrayDeque<AccessPathElement> elements, AccessPathContext apContext, @Nullable MapKey mapKey)373 private static AccessPath buildAccessPathRecursive( 374 Node node, 375 ArrayDeque<AccessPathElement> elements, 376 AccessPathContext apContext, 377 @Nullable MapKey mapKey) { 378 AccessPath result; 379 if (node instanceof FieldAccessNode) { 380 FieldAccessNode fieldAccess = (FieldAccessNode) node; 381 if (fieldAccess.isStatic()) { 382 // this is the root 383 result = new AccessPath(fieldAccess.getElement(), ImmutableList.copyOf(elements), mapKey); 384 } else { 385 // instance field access 386 elements.push(new AccessPathElement(fieldAccess.getElement())); 387 result = 388 buildAccessPathRecursive( 389 stripCasts(fieldAccess.getReceiver()), elements, apContext, mapKey); 390 } 391 } else if (node instanceof MethodInvocationNode) { 392 MethodInvocationNode invocation = (MethodInvocationNode) node; 393 AccessPathElement accessPathElement; 394 MethodAccessNode accessNode = invocation.getTarget(); 395 if (invocation.getArguments().size() == 0) { 396 Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(invocation.getTree()); 397 if (symbol.isStatic()) { 398 // a zero-argument static method call can be the root of an access path 399 return new AccessPath(symbol, ImmutableList.copyOf(elements), mapKey); 400 } else { 401 accessPathElement = new AccessPathElement(accessNode.getMethod()); 402 } 403 } else { 404 List<String> constantArgumentValues = new ArrayList<>(); 405 for (Node argumentNode : invocation.getArguments()) { 406 Tree tree = argumentNode.getTree(); 407 if (tree == null) { 408 return null; // Not an AP 409 } else if (tree.getKind().equals(Tree.Kind.METHOD_INVOCATION)) { 410 // Check for boxing call 411 MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree; 412 if (methodInvocationTree.getArguments().size() == 1 413 && isBoxingMethod(ASTHelpers.getSymbol(methodInvocationTree))) { 414 tree = methodInvocationTree.getArguments().get(0); 415 } 416 } 417 switch (tree.getKind()) { 418 case BOOLEAN_LITERAL: 419 case CHAR_LITERAL: 420 case DOUBLE_LITERAL: 421 case FLOAT_LITERAL: 422 case INT_LITERAL: 423 case LONG_LITERAL: 424 case STRING_LITERAL: 425 constantArgumentValues.add(((LiteralTree) tree).getValue().toString()); 426 break; 427 case NULL_LITERAL: 428 // Um, probably not? Return null for now. 429 return null; // Not an AP 430 case MEMBER_SELECT: // check for Foo.CONST 431 case IDENTIFIER: // check for CONST 432 // Check for a constant field (static final) 433 Symbol symbol = ASTHelpers.getSymbol(tree); 434 if (symbol instanceof Symbol.VarSymbol 435 && symbol.getKind().equals(ElementKind.FIELD)) { 436 Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbol; 437 // From docs: getConstantValue() returns the value of this variable if this is a 438 // static final field initialized to a compile-time constant. Returns null 439 // otherwise. 440 // This means that foo(FOUR) will match foo(4) iff FOUR=4 is a compile time 441 // constant :) 442 Object constantValue = varSymbol.getConstantValue(); 443 if (constantValue != null) { 444 constantArgumentValues.add(constantValue.toString()); 445 break; 446 } 447 // The above will not work for static final fields of reference type, since they are 448 // initialized at class-initialization time, not compile time. Properly handling 449 // such fields would further require proving deep immutability for the object type 450 // itself. We use a handler-augment list of safe types: 451 Set<Modifier> modifiersSet = varSymbol.getModifiers(); 452 if (modifiersSet.contains(Modifier.STATIC) 453 && modifiersSet.contains(Modifier.FINAL) 454 && apContext.isStructurallyImmutableType(varSymbol.type)) { 455 String immutableFieldFQN = 456 varSymbol.enclClass().flatName().toString() 457 + "." 458 + varSymbol.flatName().toString(); 459 constantArgumentValues.add( 460 immutableFieldNameAsConstantArgument(immutableFieldFQN)); 461 break; 462 } 463 } 464 // Cascade to default, symbol is not a constant field 465 // fall through 466 default: 467 return null; // Not an AP 468 } 469 } 470 accessPathElement = new AccessPathElement(accessNode.getMethod(), constantArgumentValues); 471 } 472 elements.push(accessPathElement); 473 result = 474 buildAccessPathRecursive( 475 stripCasts(accessNode.getReceiver()), elements, apContext, mapKey); 476 } else if (node instanceof LocalVariableNode) { 477 result = 478 new AccessPath( 479 ((LocalVariableNode) node).getElement(), ImmutableList.copyOf(elements), mapKey); 480 } else if (node instanceof ThisNode || node instanceof SuperNode) { 481 result = new AccessPath(null, ImmutableList.copyOf(elements), mapKey); 482 } else { 483 // don't handle any other cases 484 result = null; 485 } 486 return result; 487 } 488 489 /** 490 * Creates an access path representing a Map get call, where the key is obtained by calling {@code 491 * next()} on some {@code Iterator}. Used to support reasoning about iteration over a map's key 492 * set using an enhanced-for loop. 493 * 494 * @param mapNode Node representing the map 495 * @param iterVar local variable holding the iterator 496 * @param apContext access path context 497 * @return access path representing the get call, or {@code null} if the map node cannot be 498 * represented with an access path 499 */ 500 @Nullable mapWithIteratorContentsKey( Node mapNode, LocalVariableNode iterVar, AccessPathContext apContext)501 public static AccessPath mapWithIteratorContentsKey( 502 Node mapNode, LocalVariableNode iterVar, AccessPathContext apContext) { 503 IteratorContentsKey iterContentsKey = 504 new IteratorContentsKey((VariableElement) iterVar.getElement()); 505 return buildAccessPathRecursive(mapNode, new ArrayDeque<>(), apContext, iterContentsKey); 506 } 507 508 /** 509 * Creates an access path identical to {@code accessPath} (which must represent a map get), but 510 * replacing its map {@code get()} argument with {@code mapKey} 511 */ replaceMapKey(AccessPath accessPath, MapKey mapKey)512 public static AccessPath replaceMapKey(AccessPath accessPath, MapKey mapKey) { 513 return new AccessPath(accessPath.getRoot(), accessPath.getElements(), mapKey); 514 } 515 516 @Override equals(Object o)517 public boolean equals(Object o) { 518 if (this == o) { 519 return true; 520 } 521 if (o == null || getClass() != o.getClass()) { 522 return false; 523 } 524 AccessPath that = (AccessPath) o; 525 return Objects.equals(root, that.root) 526 && elements.equals(that.elements) 527 && Objects.equals(mapGetArg, that.mapGetArg); 528 } 529 530 @Override hashCode()531 public int hashCode() { 532 int result = 1; 533 result = 31 * result + (root != null ? root.hashCode() : 0); 534 result = 31 * result + elements.hashCode(); 535 result = 31 * result + (mapGetArg != null ? mapGetArg.hashCode() : 0); 536 return result; 537 } 538 539 /** 540 * Returns the root element of the access path. If the root is the receiver argument, returns 541 * {@code null}. 542 */ 543 @Nullable getRoot()544 public Element getRoot() { 545 return root; 546 } 547 getElements()548 public ImmutableList<AccessPathElement> getElements() { 549 return elements; 550 } 551 552 @Nullable getMapGetArg()553 public MapKey getMapGetArg() { 554 return mapGetArg; 555 } 556 557 @Override toString()558 public String toString() { 559 return "AccessPath{" 560 + "root=" 561 + (root == null ? "this" : root) 562 + ", elements=" 563 + elements 564 + ", mapGetArg=" 565 + mapGetArg 566 + '}'; 567 } 568 isMapGet(Symbol.MethodSymbol symbol, VisitorState state)569 private static boolean isMapGet(Symbol.MethodSymbol symbol, VisitorState state) { 570 return NullabilityUtil.isMapMethod(symbol, state, "get", 1); 571 } 572 isContainsKey(Symbol.MethodSymbol symbol, VisitorState state)573 public static boolean isContainsKey(Symbol.MethodSymbol symbol, VisitorState state) { 574 return NullabilityUtil.isMapMethod(symbol, state, "containsKey", 1); 575 } 576 isMapPut(Symbol.MethodSymbol symbol, VisitorState state)577 public static boolean isMapPut(Symbol.MethodSymbol symbol, VisitorState state) { 578 return NullabilityUtil.isMapMethod(symbol, state, "put", 2) 579 || NullabilityUtil.isMapMethod(symbol, state, "putIfAbsent", 2); 580 } 581 isMapComputeIfAbsent(Symbol.MethodSymbol symbol, VisitorState state)582 public static boolean isMapComputeIfAbsent(Symbol.MethodSymbol symbol, VisitorState state) { 583 return NullabilityUtil.isMapMethod(symbol, state, "computeIfAbsent", 2); 584 } 585 586 private static final class StringMapKey implements MapKey { 587 588 private final String key; 589 StringMapKey(String key)590 public StringMapKey(String key) { 591 this.key = key; 592 } 593 594 @Override hashCode()595 public int hashCode() { 596 return this.key.hashCode(); 597 } 598 599 @Override equals(Object obj)600 public boolean equals(Object obj) { 601 if (obj instanceof StringMapKey) { 602 return this.key.equals(((StringMapKey) obj).key); 603 } 604 return false; 605 } 606 } 607 608 private static final class NumericMapKey implements MapKey { 609 610 private final long key; 611 NumericMapKey(long key)612 public NumericMapKey(long key) { 613 this.key = key; 614 } 615 616 @Override hashCode()617 public int hashCode() { 618 return Long.hashCode(this.key); 619 } 620 621 /** 622 * We ignore this method for code coverage since there is non-determinism somewhere deep in a 623 * Map implementation such that, depending on how AccessPaths get bucketed in the Map (which 624 * depends on non-deterministic hash codes), sometimes this method is called and sometimes it is 625 * not. 626 */ 627 @Override 628 @JacocoIgnoreGenerated equals(Object obj)629 public boolean equals(Object obj) { 630 if (obj instanceof NumericMapKey) { 631 return this.key == ((NumericMapKey) obj).key; 632 } 633 return false; 634 } 635 } 636 637 /** 638 * Represents all possible values that could be returned by calling {@code next()} on an {@code 639 * Iterator} variable 640 */ 641 public static final class IteratorContentsKey implements MapKey { 642 643 /** 644 * Element for the local variable holding the {@code Iterator}. We only support locals for now, 645 * as this class is designed specifically for reasoning about iterating over map keys using an 646 * enhanced-for loop over a {@code keySet()}, and for such cases the iterator is always stored 647 * locally 648 */ 649 private final VariableElement iteratorVarElement; 650 IteratorContentsKey(VariableElement iteratorVarElement)651 IteratorContentsKey(VariableElement iteratorVarElement) { 652 this.iteratorVarElement = iteratorVarElement; 653 } 654 getIteratorVarElement()655 public VariableElement getIteratorVarElement() { 656 return iteratorVarElement; 657 } 658 659 /** 660 * We ignore this method for code coverage since there is non-determinism somewhere deep in a 661 * Map implementation such that, depending on how AccessPaths get bucketed in the Map (which 662 * depends on non-deterministic hash codes), sometimes this method is called and sometimes it is 663 * not. 664 */ 665 @Override 666 @JacocoIgnoreGenerated equals(Object o)667 public boolean equals(Object o) { 668 if (this == o) { 669 return true; 670 } 671 if (o == null || getClass() != o.getClass()) { 672 return false; 673 } 674 IteratorContentsKey that = (IteratorContentsKey) o; 675 return iteratorVarElement.equals(that.iteratorVarElement); 676 } 677 678 @Override hashCode()679 public int hashCode() { 680 return iteratorVarElement.hashCode(); 681 } 682 } 683 684 /** 685 * Represents a per-javac instance of an AccessPath context options. 686 * 687 * <p>This includes, for example, data on known structurally immutable types. 688 */ 689 public static final class AccessPathContext { 690 691 private final ImmutableSet<String> immutableTypes; 692 AccessPathContext(ImmutableSet<String> immutableTypes)693 private AccessPathContext(ImmutableSet<String> immutableTypes) { 694 this.immutableTypes = immutableTypes; 695 } 696 isStructurallyImmutableType(Type type)697 public boolean isStructurallyImmutableType(Type type) { 698 return immutableTypes.contains(type.tsym.toString()); 699 } 700 builder()701 public static Builder builder() { 702 return new AccessPathContext.Builder(); 703 } 704 705 /** class for building up instances of the AccessPathContext. */ 706 public static final class Builder { 707 708 @Nullable private ImmutableSet<String> immutableTypes; 709 Builder()710 Builder() {} 711 712 /** 713 * Passes the set of structurally immutable types registered into this AccessPathContext. 714 * 715 * <p>See {@link com.uber.nullaway.handlers.Handler#onRegisterImmutableTypes} for more info. 716 * 717 * @param immutableTypes the immutable types known to our dataflow analysis. 718 */ setImmutableTypes(ImmutableSet<String> immutableTypes)719 public Builder setImmutableTypes(ImmutableSet<String> immutableTypes) { 720 this.immutableTypes = immutableTypes; 721 return this; 722 } 723 724 /** 725 * Construct the immutable AccessPathContext instance. 726 * 727 * @return an access path context constructed from everything added to the builder 728 */ build()729 public AccessPathContext build() { 730 if (immutableTypes == null) { 731 throw new IllegalStateException("must set immutable types before building"); 732 } 733 return new AccessPathContext(immutableTypes); 734 } 735 } 736 } 737 } 738