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;
}