private static AccessPath buildAccessPathRecursive()

in nullaway/src/main/java/com/uber/nullaway/dataflow/AccessPath.java [374:501]


  private static AccessPath buildAccessPathRecursive(
      Node node,
      ArrayDeque<AccessPathElement> elements,
      AccessPathContext apContext,
      @Nullable MapKey mapKey) {
    AccessPath result;
    if (node instanceof FieldAccessNode) {
      FieldAccessNode fieldAccess = (FieldAccessNode) node;
      if (fieldAccess.isStatic()) {
        // this is the root
        result = new AccessPath(fieldAccess.getElement(), ImmutableList.copyOf(elements), mapKey);
      } else {
        // instance field access
        elements.push(new AccessPathElement(fieldAccess.getElement()));
        result =
            buildAccessPathRecursive(
                stripCasts(fieldAccess.getReceiver()), elements, apContext, mapKey);
      }
    } else if (node instanceof MethodInvocationNode) {
      MethodInvocationNode invocation = (MethodInvocationNode) node;
      AccessPathElement accessPathElement;
      MethodAccessNode accessNode = invocation.getTarget();
      if (invocation.getArguments().size() == 0) {
        Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(invocation.getTree());
        if (symbol.isStatic()) {
          // a zero-argument static method call can be the root of an access path
          return new AccessPath(symbol, ImmutableList.copyOf(elements), mapKey);
        } else {
          accessPathElement = new AccessPathElement(accessNode.getMethod());
        }
      } else {
        List<String> constantArgumentValues = new ArrayList<>();
        for (Node argumentNode : invocation.getArguments()) {
          Tree tree = argumentNode.getTree();
          if (tree == null) {
            return null; // Not an AP
          } else if (tree.getKind().equals(Tree.Kind.METHOD_INVOCATION)) {
            // Check for boxing call
            MethodInvocationTree methodInvocationTree = (MethodInvocationTree) tree;
            if (methodInvocationTree.getArguments().size() == 1
                && isBoxingMethod(ASTHelpers.getSymbol(methodInvocationTree))) {
              tree = methodInvocationTree.getArguments().get(0);
            }
          }
          switch (tree.getKind()) {
            case BOOLEAN_LITERAL:
            case CHAR_LITERAL:
            case DOUBLE_LITERAL:
            case FLOAT_LITERAL:
            case INT_LITERAL:
            case LONG_LITERAL:
            case STRING_LITERAL:
              constantArgumentValues.add(((LiteralTree) tree).getValue().toString());
              break;
            case NULL_LITERAL:
              // Um, probably not? Return null for now.
              return null; // Not an AP
            case MEMBER_SELECT: // check for Foo.CONST
            case IDENTIFIER: // check for CONST
              // Check for a constant field (static final)
              Symbol symbol = ASTHelpers.getSymbol(tree);
              if (symbol instanceof Symbol.VarSymbol
                  && symbol.getKind().equals(ElementKind.FIELD)) {
                Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) symbol;
                // From docs: getConstantValue() returns the value of this variable if this is a
                // static final field initialized to a compile-time constant. Returns null
                // otherwise.
                // This means that foo(FOUR) will match foo(4) iff FOUR=4 is a compile time
                // constant :)
                Object constantValue = varSymbol.getConstantValue();
                if (constantValue != null) {
                  constantArgumentValues.add(constantValue.toString());
                  break;
                }
                // The above will not work for static final fields of reference type, since they are
                // initialized at class-initialization time, not compile time. Properly handling
                // such fields would further require proving deep immutability for the object type
                // itself. We use a handler-augment list of safe types:
                Set<Modifier> modifiersSet = varSymbol.getModifiers();
                if (modifiersSet.contains(Modifier.STATIC)
                    && modifiersSet.contains(Modifier.FINAL)
                    && apContext.isStructurallyImmutableType(varSymbol.type)) {
                  String immutableFieldFQN =
                      varSymbol.enclClass().flatName().toString()
                          + "."
                          + varSymbol.flatName().toString();
                  constantArgumentValues.add(
                      immutableFieldNameAsConstantArgument(immutableFieldFQN));
                  break;
                }
              }
              // Cascade to default, symbol is not a constant field
              // fall through
            default:
              return null; // Not an AP
          }
        }
        accessPathElement = new AccessPathElement(accessNode.getMethod(), constantArgumentValues);
      }
      elements.push(accessPathElement);
      result =
          buildAccessPathRecursive(
              stripCasts(accessNode.getReceiver()), elements, apContext, mapKey);
    } else if (node instanceof LocalVariableNode) {
      result =
          new AccessPath(
              ((LocalVariableNode) node).getElement(), ImmutableList.copyOf(elements), mapKey);
    } else if (node instanceof ClassNameNode) {
      // It is useful to make an access path if elements.size() > 1 and elements.getFirst() is
      // "this".  In this case, we may have an access of a field of an enclosing class from a nested
      // class.  Tracking an access path for "Foo.this" alone is not useful, since that can never be
      // null.  Also, we do not attempt to canonicalize cases where "Foo.this" is used to refer to
      // the receiver of the current method instead of "this".
      if (elements.size() > 1
          && elements.getFirst().getJavaElement().getSimpleName().contentEquals("this")) {
        Element rootElement = elements.pop().getJavaElement();
        result = new AccessPath(rootElement, ImmutableList.copyOf(elements), mapKey);
      } else {
        result = null;
      }
    } else if (node instanceof ThisNode || node instanceof SuperNode) {
      result = new AccessPath(null, ImmutableList.copyOf(elements), mapKey);
    } else {
      // don't handle any other cases
      result = null;
    }
    return result;
  }