• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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