• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

uber / NullAway / #1105

pending completion
#1105

Pull #771

github-actions

web-flow
Merge ad670289a into 0466a02cb
Pull Request #771: Introduce FluentFutureHandler as a workaround for Guava Futures/FluentFuture

5611 of 6047 relevant lines covered (92.79%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.32
nullaway/../nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java
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 java.util.ArrayDeque;
39
import java.util.ArrayList;
40
import java.util.List;
41
import java.util.Objects;
42
import java.util.Set;
43
import javax.annotation.Nullable;
44
import javax.lang.model.element.Element;
45
import javax.lang.model.element.ElementKind;
46
import javax.lang.model.element.Modifier;
47
import javax.lang.model.element.VariableElement;
48
import org.checkerframework.nullaway.dataflow.cfg.node.FieldAccessNode;
49
import org.checkerframework.nullaway.dataflow.cfg.node.IntegerLiteralNode;
50
import org.checkerframework.nullaway.dataflow.cfg.node.LocalVariableNode;
51
import org.checkerframework.nullaway.dataflow.cfg.node.LongLiteralNode;
52
import org.checkerframework.nullaway.dataflow.cfg.node.MethodAccessNode;
53
import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
54
import org.checkerframework.nullaway.dataflow.cfg.node.Node;
55
import org.checkerframework.nullaway.dataflow.cfg.node.StringLiteralNode;
56
import org.checkerframework.nullaway.dataflow.cfg.node.SuperNode;
57
import org.checkerframework.nullaway.dataflow.cfg.node.ThisNode;
58
import org.checkerframework.nullaway.dataflow.cfg.node.TypeCastNode;
59
import org.checkerframework.nullaway.dataflow.cfg.node.VariableDeclarationNode;
60
import org.checkerframework.nullaway.dataflow.cfg.node.WideningConversionNode;
61
import org.checkerframework.nullaway.javacutil.TreeUtils;
62

63
/**
64
 * Represents an extended notion of an access path, which we track for nullness.
65
 *
66
 * <p>Typically, access paths are of the form x.f.g.h, where x is a variable and f, g, and h are
67
 * field names. Here, we also allow no-argument methods to appear in the access path, as well as
68
 * method calls passed only statically constant parameters, so an AP can be of the form
69
 * x.f().g.h([int_expr|string_expr]) in general.
70
 *
71
 * <p>We do not allow array accesses in access paths for the moment.
72
 */
73
public final class AccessPath implements MapKey {
74

75
  /**
76
   * A prefix added for elements appearing in method invocation APs which represent fields that can
77
   * be proven to be class-initialization time constants (i.e. static final fields of a type known
78
   * to be structurally immutable, such as io.grpc.Metadata.Key).
79
   *
80
   * <p>This prefix helps avoid collisions between common field names and common strings, e.g.
81
   * "KEY_1" and the field KEY_1.
82
   */
83
  private static final String IMMUTABLE_FIELD_PREFIX = "static final [immutable] field: ";
84

85
  /**
86
   * Encode a static final field as a constant argument on a method's AccessPathElement
87
   *
88
   * <p>The field must be of a type known to be structurally immutable, in addition to being
89
   * declared static and final for this encoding to make any sense. We do not verify this here, and
90
   * rather operate only on the field's fully qualified name, as this is intended to be a quick
91
   * utility method.
92
   *
93
   * @param fieldFQN the field's Fully Qualified Name
94
   * @return a string suitable to be included as part of the constant arguments of an
95
   *     AccessPathElement, assuming the field is indeed static final and of an structurally
96
   *     immutable type
97
   */
98
  public static String immutableFieldNameAsConstantArgument(String fieldFQN) {
99
    return IMMUTABLE_FIELD_PREFIX + fieldFQN;
1✔
100
  }
101

102
  /** Root of the access path. If {@code null}, the root is the receiver argument */
103
  @Nullable private final Element root;
104

105
  private final ImmutableList<AccessPathElement> elements;
106

107
  /**
108
   * if present, the argument to the map get() method call that is the final element of this path
109
   */
110
  @Nullable private final MapKey mapGetArg;
111

112
  private AccessPath(@Nullable Element root, ImmutableList<AccessPathElement> elements) {
113
    this(root, elements, null);
1✔
114
  }
1✔
115

116
  private AccessPath(
117
      @Nullable Element root,
118
      ImmutableList<AccessPathElement> elements,
119
      @Nullable MapKey mapGetArg) {
1✔
120
    this.root = root;
1✔
121
    this.elements = elements;
1✔
122
    this.mapGetArg = mapGetArg;
1✔
123
  }
1✔
124

125
  /**
126
   * Construct the access path of a local.
127
   *
128
   * @param node the local
129
   * @return access path representing the local
130
   */
131
  public static AccessPath fromLocal(LocalVariableNode node) {
132
    return new AccessPath(node.getElement(), ImmutableList.of());
1✔
133
  }
134

135
  /**
136
   * Construct the access path of a variable declaration.
137
   *
138
   * @param node the variable declaration
139
   * @return access path representing the variable declaration
140
   */
141
  static AccessPath fromVarDecl(VariableDeclarationNode node) {
142
    Element elem = TreeUtils.elementFromDeclaration(node.getTree());
1✔
143
    return new AccessPath(elem, ImmutableList.of());
1✔
144
  }
145

146
  /**
147
   * Construct the access path of a field access.
148
   *
149
   * @param node the field access
150
   * @param apContext the current access path context information (see {@link
151
   *     AccessPath.AccessPathContext}).
152
   * @return access path for the field access, or <code>null</code> if it cannot be represented
153
   */
154
  @Nullable
155
  static AccessPath fromFieldAccess(FieldAccessNode node, AccessPathContext apContext) {
156
    return fromNodeAndContext(node, apContext);
1✔
157
  }
158

159
  @Nullable
160
  private static AccessPath fromNodeAndContext(Node node, AccessPathContext apContext) {
161
    return buildAccessPathRecursive(node, new ArrayDeque<>(), apContext, null);
1✔
162
  }
163

164
  /**
165
   * Construct the access path of a method call.
166
   *
167
   * @param node the method call
168
   * @param apContext the current access path context information (see {@link
169
   *     AccessPath.AccessPathContext}).
170
   * @return access path for the method call, or <code>null</code> if it cannot be represented
171
   */
172
  @Nullable
173
  static AccessPath fromMethodCall(
174
      MethodInvocationNode node, VisitorState state, AccessPathContext apContext) {
175
    if (isMapGet(ASTHelpers.getSymbol(node.getTree()), state)) {
1✔
176
      return fromMapGetCall(node, state, apContext);
1✔
177
    }
178
    return fromVanillaMethodCall(node, apContext);
1✔
179
  }
180

181
  @Nullable
182
  private static AccessPath fromVanillaMethodCall(
183
      MethodInvocationNode node, AccessPathContext apContext) {
184
    return fromNodeAndContext(node, apContext);
1✔
185
  }
186

187
  /**
188
   * Returns an access path rooted at {@code newRoot} with the same elements and map-get argument as
189
   * {@code origAP}
190
   */
191
  static AccessPath switchRoot(AccessPath origAP, Element newRoot) {
192
    return new AccessPath(newRoot, origAP.elements, origAP.mapGetArg);
1✔
193
  }
194
  /**
195
   * Construct the access path given a {@code base.element} structure.
196
   *
197
   * @param base the base expression for the access path
198
   * @param element the final element of the access path (a field or method)
199
   * @param apContext the current access path context information (see {@link
200
   *     AccessPath.AccessPathContext}).
201
   * @return the {@link AccessPath} {@code base.element}
202
   */
203
  @Nullable
204
  public static AccessPath fromBaseAndElement(
205
      Node base, Element element, AccessPathContext apContext) {
206
    return fromNodeElementAndContext(base, new AccessPathElement(element), apContext);
1✔
207
  }
208

209
  @Nullable
210
  private static AccessPath fromNodeElementAndContext(
211
      Node base, AccessPathElement apElement, AccessPathContext apContext) {
212
    ArrayDeque<AccessPathElement> elements = new ArrayDeque<>();
1✔
213
    elements.push(apElement);
1✔
214
    return buildAccessPathRecursive(base, elements, apContext, null);
1✔
215
  }
216

217
  /**
218
   * Construct the access path given a {@code base.method(CONS)} structure.
219
   *
220
   * <p>IMPORTANT: Be careful with this method, the argument list is not the variable names of the
221
   * method arguments, but rather the string representation of primitive-type compile-time constants
222
   * or the name of static final fields of structurally immutable types (see {@link
223
   * #buildAccessPathRecursive(Node, ArrayDeque, AccessPathContext, MapKey)}).
224
   *
225
   * <p>This is used by a few specialized Handlers to set nullability around particular paths
226
   * involving constants.
227
   *
228
   * @param base the base expression for the access path
229
   * @param method the last method call in the access path
230
   * @param constantArguments a list of <b>constant</b> arguments passed to the method call
231
   * @param apContext the current access path context information (see {@link
232
   *     AccessPath.AccessPathContext}).
233
   * @return the {@link AccessPath} {@code base.method(CONS)}
234
   */
235
  @Nullable
236
  public static AccessPath fromBaseMethodAndConstantArgs(
237
      Node base, Element method, List<String> constantArguments, AccessPathContext apContext) {
238
    return fromNodeElementAndContext(
1✔
239
        base, new AccessPathElement(method, constantArguments), apContext);
240
  }
241

242
  /**
243
   * Construct the access path for <code>map.get(x)</code> from an invocation of <code>put(x)</code>
244
   * or <code>containsKey(x)</code>.
245
   *
246
   * @param node a node invoking containsKey() or put() on a map
247
   * @param apContext the current access path context information (see {@link
248
   *     AccessPath.AccessPathContext}).
249
   * @return an AccessPath representing invoking get() on the same type of map as from node, passing
250
   *     the same first argument as is passed in node
251
   */
252
  @Nullable
253
  public static AccessPath getForMapInvocation(
254
      MethodInvocationNode node, VisitorState state, AccessPathContext apContext) {
255
    // For the receiver type for get, use the declared type of the receiver of the containsKey()
256
    // call. Note that this may differ from the containing class of the resolved containsKey()
257
    // method, which can be in a superclass (e.g., LinkedHashMap does not override containsKey())
258
    // assumption: map type will not both override containsKey() and inherit get()
259
    return fromMapGetCall(node, state, apContext);
1✔
260
  }
261

262
  private static Node stripCasts(Node node) {
263
    while (node instanceof TypeCastNode) {
1✔
264
      node = ((TypeCastNode) node).getOperand();
1✔
265
    }
266
    return node;
1✔
267
  }
268

269
  @Nullable
270
  private static MapKey argumentToMapKeySpecifier(
271
      Node argument, VisitorState state, AccessPathContext apContext) {
272
    // Required to have Node type match Tree type in some instances.
273
    if (argument instanceof WideningConversionNode) {
1✔
274
      argument = ((WideningConversionNode) argument).getOperand();
1✔
275
    }
276
    // A switch at the Tree level should be faster than multiple if checks at the Node level.
277
    switch (castToNonNull(argument.getTree()).getKind()) {
1✔
278
      case STRING_LITERAL:
279
        return new StringMapKey(((StringLiteralNode) argument).getValue());
1✔
280
      case INT_LITERAL:
281
        return new NumericMapKey(((IntegerLiteralNode) argument).getValue());
1✔
282
      case LONG_LITERAL:
283
        return new NumericMapKey(((LongLiteralNode) argument).getValue());
×
284
      case METHOD_INVOCATION:
285
        MethodAccessNode target = ((MethodInvocationNode) argument).getTarget();
1✔
286
        Node receiver = stripCasts(target.getReceiver());
1✔
287
        List<Node> arguments = ((MethodInvocationNode) argument).getArguments();
1✔
288
        // Check for int/long boxing.
289
        if (target.getMethod().getSimpleName().toString().equals("valueOf")
1✔
290
            && arguments.size() == 1
1✔
291
            && castToNonNull(receiver.getTree()).getKind().equals(Tree.Kind.IDENTIFIER)
1✔
292
            && (receiver.toString().equals("Integer") || receiver.toString().equals("Long"))) {
1✔
293
          return argumentToMapKeySpecifier(arguments.get(0), state, apContext);
1✔
294
        }
295
        // Fine to fallthrough:
296
      default:
297
        // Every other type of expression, including variables, field accesses, new A(...), etc.
298
        return getAccessPathForNode(argument, state, apContext); // Every AP is a MapKey too
1✔
299
    }
300
  }
301

302
  @Nullable
303
  private static AccessPath fromMapGetCall(
304
      MethodInvocationNode node, VisitorState state, AccessPathContext apContext) {
305
    Node argument = node.getArgument(0);
1✔
306
    MapKey mapKey = argumentToMapKeySpecifier(argument, state, apContext);
1✔
307
    if (mapKey == null) {
1✔
308
      return null;
1✔
309
    }
310
    MethodAccessNode target = node.getTarget();
1✔
311
    Node receiver = stripCasts(target.getReceiver());
1✔
312
    return buildAccessPathRecursive(receiver, new ArrayDeque<>(), apContext, mapKey);
1✔
313
  }
314

315
  /**
316
   * Gets corresponding AccessPath for node, if it exists. Handles calls to <code>Map.get()
317
   * </code>
318
   *
319
   * @param node AST node
320
   * @param state the visitor state
321
   * @param apContext the current access path context information (see {@link
322
   *     AccessPath.AccessPathContext}).
323
   * @return corresponding AccessPath if it exists; <code>null</code> otherwise
324
   */
325
  @Nullable
326
  public static AccessPath getAccessPathForNode(
327
      Node node, VisitorState state, AccessPathContext apContext) {
328
    if (node instanceof LocalVariableNode) {
1✔
329
      return fromLocal((LocalVariableNode) node);
1✔
330
    } else if (node instanceof FieldAccessNode) {
1✔
331
      return fromFieldAccess((FieldAccessNode) node, apContext);
1✔
332
    } else if (node instanceof MethodInvocationNode) {
1✔
333
      return fromMethodCall((MethodInvocationNode) node, state, apContext);
1✔
334
    } else {
335
      return null;
1✔
336
    }
337
  }
338

339
  /**
340
   * Constructs an access path ending with the class field element in the argument. The receiver is
341
   * the method receiver itself.
342
   *
343
   * @param element the receiver element.
344
   * @return access path representing the class field
345
   */
346
  public static AccessPath fromFieldElement(VariableElement element) {
347
    Preconditions.checkArgument(
1✔
348
        element.getKind().isField(),
1✔
349
        "element must be of type: FIELD but received: " + element.getKind());
1✔
350
    return new AccessPath(null, ImmutableList.of(new AccessPathElement(element)));
1✔
351
  }
352

353
  private static boolean isBoxingMethod(Symbol.MethodSymbol methodSymbol) {
354
    if (methodSymbol.isStatic() && methodSymbol.getSimpleName().contentEquals("valueOf")) {
1✔
355
      Symbol.PackageSymbol enclosingPackage = ASTHelpers.enclosingPackage(methodSymbol.enclClass());
1✔
356
      return enclosingPackage != null && enclosingPackage.fullname.contentEquals("java.lang");
1✔
357
    }
358
    return false;
1✔
359
  }
360

361
  /**
362
   * A helper function that recursively builds an AccessPath from a CFG node.
363
   *
364
   * @param node the CFG node
365
   * @param elements elements to append to the final access path.
366
   * @param apContext context information, used to handle cases with constant arguments
367
   * @param mapKey map key to be used as the map-get argument, or {@code null} if there is no key
368
   * @return the final access path
369
   */
370
  @Nullable
371
  private static AccessPath buildAccessPathRecursive(
372
      Node node,
373
      ArrayDeque<AccessPathElement> elements,
374
      AccessPathContext apContext,
375
      @Nullable MapKey mapKey) {
376
    AccessPath result;
377
    if (node instanceof FieldAccessNode) {
1✔
378
      FieldAccessNode fieldAccess = (FieldAccessNode) node;
1✔
379
      if (fieldAccess.isStatic()) {
1✔
380
        // this is the root
381
        result = new AccessPath(fieldAccess.getElement(), ImmutableList.copyOf(elements), mapKey);
1✔
382
      } else {
383
        // instance field access
384
        elements.push(new AccessPathElement(fieldAccess.getElement()));
1✔
385
        result =
1✔
386
            buildAccessPathRecursive(
1✔
387
                stripCasts(fieldAccess.getReceiver()), elements, apContext, mapKey);
1✔
388
      }
389
    } else if (node instanceof MethodInvocationNode) {
1✔
390
      MethodInvocationNode invocation = (MethodInvocationNode) node;
1✔
391
      AccessPathElement accessPathElement;
392
      MethodAccessNode accessNode = invocation.getTarget();
1✔
393
      if (invocation.getArguments().size() == 0) {
1✔
394
        Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(invocation.getTree());
1✔
395
        if (symbol.isStatic()) {
1✔
396
          // a zero-argument static method call can be the root of an access path
397
          return new AccessPath(symbol, ImmutableList.copyOf(elements), mapKey);
1✔
398
        } else {
399
          accessPathElement = new AccessPathElement(accessNode.getMethod());
1✔
400
        }
401
      } else {
1✔
402
        List<String> constantArgumentValues = new ArrayList<>();
1✔
403
        for (Node argumentNode : invocation.getArguments()) {
1✔
404
          Tree tree = argumentNode.getTree();
1✔
405
          if (tree == null) {
1✔
406
            return null; // Not an AP
×
407
          } else if (tree.getKind().equals(Tree.Kind.METHOD_INVOCATION)) {
1✔
408
            // Check for boxing call
409
            MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
1✔
410
            if (methodInvocationTree.getArguments().size() == 1
1✔
411
                && isBoxingMethod(ASTHelpers.getSymbol(methodInvocationTree))) {
1✔
412
              tree = methodInvocationTree.getArguments().get(0);
1✔
413
            }
414
          }
415
          switch (tree.getKind()) {
1✔
416
            case BOOLEAN_LITERAL:
417
            case CHAR_LITERAL:
418
            case DOUBLE_LITERAL:
419
            case FLOAT_LITERAL:
420
            case INT_LITERAL:
421
            case LONG_LITERAL:
422
            case STRING_LITERAL:
423
              constantArgumentValues.add(((LiteralTree) tree).getValue().toString());
1✔
424
              break;
1✔
425
            case NULL_LITERAL:
426
              // Um, probably not? Return null for now.
427
              return null; // Not an AP
1✔
428
            case MEMBER_SELECT: // check for Foo.CONST
429
            case IDENTIFIER: // check for CONST
430
              // Check for a constant field (static final)
431
              Symbol symbol = ASTHelpers.getSymbol(tree);
1✔
432
              if (symbol instanceof Symbol.VarSymbol
1✔
433
                  && symbol.getKind().equals(ElementKind.FIELD)) {
1✔
434
                Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbol;
1✔
435
                // From docs: getConstantValue() returns the value of this variable if this is a
436
                // static final field initialized to a compile-time constant. Returns null
437
                // otherwise.
438
                // This means that foo(FOUR) will match foo(4) iff FOUR=4 is a compile time
439
                // constant :)
440
                Object constantValue = varSymbol.getConstantValue();
1✔
441
                if (constantValue != null) {
1✔
442
                  constantArgumentValues.add(constantValue.toString());
1✔
443
                  break;
1✔
444
                }
445
                // The above will not work for static final fields of reference type, since they are
446
                // initialized at class-initialization time, not compile time. Properly handling
447
                // such fields would further require proving deep immutability for the object type
448
                // itself. We use a handler-augment list of safe types:
449
                Set<Modifier> modifiersSet = varSymbol.getModifiers();
1✔
450
                if (modifiersSet.contains(Modifier.STATIC)
1✔
451
                    && modifiersSet.contains(Modifier.FINAL)
1✔
452
                    && apContext.isStructurallyImmutableType(varSymbol.type)) {
1✔
453
                  String immutableFieldFQN =
1✔
454
                      varSymbol.enclClass().flatName().toString()
1✔
455
                          + "."
456
                          + varSymbol.flatName().toString();
1✔
457
                  constantArgumentValues.add(
1✔
458
                      immutableFieldNameAsConstantArgument(immutableFieldFQN));
1✔
459
                  break;
1✔
460
                }
461
              }
462
              // Cascade to default, symbol is not a constant field
463
              // fall through
464
            default:
465
              return null; // Not an AP
1✔
466
          }
467
        }
1✔
468
        accessPathElement = new AccessPathElement(accessNode.getMethod(), constantArgumentValues);
1✔
469
      }
470
      elements.push(accessPathElement);
1✔
471
      result =
1✔
472
          buildAccessPathRecursive(
1✔
473
              stripCasts(accessNode.getReceiver()), elements, apContext, mapKey);
1✔
474
    } else if (node instanceof LocalVariableNode) {
1✔
475
      result =
1✔
476
          new AccessPath(
477
              ((LocalVariableNode) node).getElement(), ImmutableList.copyOf(elements), mapKey);
1✔
478
    } else if (node instanceof ThisNode || node instanceof SuperNode) {
1✔
479
      result = new AccessPath(null, ImmutableList.copyOf(elements), mapKey);
1✔
480
    } else {
481
      // don't handle any other cases
482
      result = null;
1✔
483
    }
484
    return result;
1✔
485
  }
486

487
  /**
488
   * Creates an access path representing a Map get call, where the key is obtained by calling {@code
489
   * next()} on some {@code Iterator}. Used to support reasoning about iteration over a map's key
490
   * set using an enhanced-for loop.
491
   *
492
   * @param mapNode Node representing the map
493
   * @param iterVar local variable holding the iterator
494
   * @param apContext access path context
495
   * @return access path representing the get call, or {@code null} if the map node cannot be
496
   *     represented with an access path
497
   */
498
  @Nullable
499
  public static AccessPath mapWithIteratorContentsKey(
500
      Node mapNode, LocalVariableNode iterVar, AccessPathContext apContext) {
501
    IteratorContentsKey iterContentsKey =
1✔
502
        new IteratorContentsKey((VariableElement) iterVar.getElement());
1✔
503
    return buildAccessPathRecursive(mapNode, new ArrayDeque<>(), apContext, iterContentsKey);
1✔
504
  }
505

506
  /**
507
   * Creates an access path identical to {@code accessPath} (which must represent a map get), but
508
   * replacing its map {@code get()} argument with {@code mapKey}
509
   */
510
  public static AccessPath replaceMapKey(AccessPath accessPath, MapKey mapKey) {
511
    return new AccessPath(accessPath.getRoot(), accessPath.getElements(), mapKey);
1✔
512
  }
513

514
  @Override
515
  public boolean equals(Object o) {
516
    if (this == o) {
1✔
517
      return true;
1✔
518
    }
519
    if (o == null || getClass() != o.getClass()) {
1✔
520
      return false;
1✔
521
    }
522
    AccessPath that = (AccessPath) o;
1✔
523
    return Objects.equals(root, that.root)
1✔
524
        && elements.equals(that.elements)
1✔
525
        && Objects.equals(mapGetArg, that.mapGetArg);
1✔
526
  }
527

528
  @Override
529
  public int hashCode() {
530
    int result = 1;
1✔
531
    result = 31 * result + (root != null ? root.hashCode() : 0);
1✔
532
    result = 31 * result + elements.hashCode();
1✔
533
    result = 31 * result + (mapGetArg != null ? mapGetArg.hashCode() : 0);
1✔
534
    return result;
1✔
535
  }
536

537
  /**
538
   * Returns the root element of the access path. If the root is the receiver argument, returns
539
   * {@code null}.
540
   */
541
  @Nullable
542
  public Element getRoot() {
543
    return root;
1✔
544
  }
545

546
  public ImmutableList<AccessPathElement> getElements() {
547
    return elements;
1✔
548
  }
549

550
  @Nullable
551
  public MapKey getMapGetArg() {
552
    return mapGetArg;
1✔
553
  }
554

555
  @Override
556
  public String toString() {
557
    return "AccessPath{"
×
558
        + "root="
559
        + (root == null ? "this" : root)
×
560
        + ", elements="
561
        + elements
562
        + ", mapGetArg="
563
        + mapGetArg
564
        + '}';
565
  }
566

567
  private static boolean isMapGet(Symbol.MethodSymbol symbol, VisitorState state) {
568
    return NullabilityUtil.isMapMethod(symbol, state, "get", 1);
1✔
569
  }
570

571
  public static boolean isContainsKey(Symbol.MethodSymbol symbol, VisitorState state) {
572
    return NullabilityUtil.isMapMethod(symbol, state, "containsKey", 1);
1✔
573
  }
574

575
  public static boolean isMapPut(Symbol.MethodSymbol symbol, VisitorState state) {
576
    return NullabilityUtil.isMapMethod(symbol, state, "put", 2)
1✔
577
        || NullabilityUtil.isMapMethod(symbol, state, "putIfAbsent", 2);
1✔
578
  }
579

580
  public static boolean isMapComputeIfAbsent(Symbol.MethodSymbol symbol, VisitorState state) {
581
    return NullabilityUtil.isMapMethod(symbol, state, "computeIfAbsent", 2);
1✔
582
  }
583

584
  private static final class StringMapKey implements MapKey {
585

586
    private final String key;
587

588
    public StringMapKey(String key) {
1✔
589
      this.key = key;
1✔
590
    }
1✔
591

592
    @Override
593
    public int hashCode() {
594
      return this.key.hashCode();
1✔
595
    }
596

597
    @Override
598
    public boolean equals(Object obj) {
599
      if (obj instanceof StringMapKey) {
1✔
600
        return this.key.equals(((StringMapKey) obj).key);
1✔
601
      }
602
      return false;
1✔
603
    }
604
  }
605

606
  private static final class NumericMapKey implements MapKey {
607

608
    private final long key;
609

610
    public NumericMapKey(long key) {
1✔
611
      this.key = key;
1✔
612
    }
1✔
613

614
    @Override
615
    public int hashCode() {
616
      return Long.hashCode(this.key);
1✔
617
    }
618

619
    @Override
620
    public boolean equals(Object obj) {
621
      if (obj instanceof NumericMapKey) {
1✔
622
        return this.key == ((NumericMapKey) obj).key;
1✔
623
      }
624
      return false;
×
625
    }
626
  }
627

628
  /**
629
   * Represents all possible values that could be returned by calling {@code next()} on an {@code
630
   * Iterator} variable
631
   */
632
  public static final class IteratorContentsKey implements MapKey {
633

634
    /**
635
     * Element for the local variable holding the {@code Iterator}. We only support locals for now,
636
     * as this class is designed specifically for reasoning about iterating over map keys using an
637
     * enhanced-for loop over a {@code keySet()}, and for such cases the iterator is always stored
638
     * locally
639
     */
640
    private final VariableElement iteratorVarElement;
641

642
    IteratorContentsKey(VariableElement iteratorVarElement) {
1✔
643
      this.iteratorVarElement = iteratorVarElement;
1✔
644
    }
1✔
645

646
    public VariableElement getIteratorVarElement() {
647
      return iteratorVarElement;
1✔
648
    }
649

650
    @Override
651
    public boolean equals(Object o) {
652
      if (this == o) {
1✔
653
        return true;
×
654
      }
655
      if (o == null || getClass() != o.getClass()) {
1✔
656
        return false;
1✔
657
      }
658
      IteratorContentsKey that = (IteratorContentsKey) o;
1✔
659
      return iteratorVarElement.equals(that.iteratorVarElement);
1✔
660
    }
661

662
    @Override
663
    public int hashCode() {
664
      return iteratorVarElement.hashCode();
1✔
665
    }
666
  }
667

668
  /**
669
   * Represents a per-javac instance of an AccessPath context options.
670
   *
671
   * <p>This includes, for example, data on known structurally immutable types.
672
   */
673
  public static final class AccessPathContext {
674

675
    private final ImmutableSet<String> immutableTypes;
676

677
    private AccessPathContext(ImmutableSet<String> immutableTypes) {
1✔
678
      this.immutableTypes = immutableTypes;
1✔
679
    }
1✔
680

681
    public boolean isStructurallyImmutableType(Type type) {
682
      return immutableTypes.contains(type.tsym.toString());
1✔
683
    }
684

685
    public static Builder builder() {
686
      return new AccessPathContext.Builder();
1✔
687
    }
688

689
    /** class for building up instances of the AccessPathContext. */
690
    public static final class Builder {
691

692
      @Nullable private ImmutableSet<String> immutableTypes;
693

694
      Builder() {}
1✔
695

696
      /**
697
       * Passes the set of structurally immutable types registered into this AccessPathContext.
698
       *
699
       * <p>See {@link com.uber.nullaway.handlers.Handler#onRegisterImmutableTypes} for more info.
700
       *
701
       * @param immutableTypes the immutable types known to our dataflow analysis.
702
       */
703
      public Builder setImmutableTypes(ImmutableSet<String> immutableTypes) {
704
        this.immutableTypes = immutableTypes;
1✔
705
        return this;
1✔
706
      }
707

708
      /**
709
       * Construct the immutable AccessPathContext instance.
710
       *
711
       * @return an access path context constructed from everything added to the builder
712
       */
713
      public AccessPathContext build() {
714
        if (immutableTypes == null) {
1✔
715
          throw new IllegalStateException("must set immutable types before building");
×
716
        }
717
        return new AccessPathContext(immutableTypes);
1✔
718
      }
719
    }
720
  }
721
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc