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