All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.uber.nullaway.dataflow.AccessPath Maven / Gradle / Ivy

There is a newer version: 0.12.3
Show newest version
/*
 * Copyright (c) 2017 Uber Technologies, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.uber.nullaway.dataflow;

import static com.uber.nullaway.NullabilityUtil.castToNonNull;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.VisitorState;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.uber.nullaway.NullabilityUtil;
import com.uber.nullaway.annotations.JacocoIgnoreGenerated;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import org.checkerframework.nullaway.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.nullaway.dataflow.cfg.node.ClassNameNode;
import org.checkerframework.nullaway.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.nullaway.dataflow.cfg.node.IntegerLiteralNode;
import org.checkerframework.nullaway.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.nullaway.dataflow.cfg.node.LongLiteralNode;
import org.checkerframework.nullaway.dataflow.cfg.node.MethodAccessNode;
import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.nullaway.dataflow.cfg.node.Node;
import org.checkerframework.nullaway.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.nullaway.dataflow.cfg.node.SuperNode;
import org.checkerframework.nullaway.dataflow.cfg.node.ThisNode;
import org.checkerframework.nullaway.dataflow.cfg.node.TypeCastNode;
import org.checkerframework.nullaway.dataflow.cfg.node.VariableDeclarationNode;
import org.checkerframework.nullaway.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.nullaway.javacutil.TreeUtils;
import org.jspecify.annotations.Nullable;

/**
 * Represents an extended notion of an access path, which we track for nullness.
 *
 * 

Typically, access paths are of the form x.f.g.h, where x is a variable and f, g, and h are * field names. Here, we also allow no-argument methods to appear in the access path, as well as * method calls passed only statically constant parameters, so an AP can be of the form * x.f().g.h([int_expr|string_expr]) in general. * *

We do not allow array accesses in access paths for the moment. */ public final class AccessPath implements MapKey { private static final Supplier INTEGER_TYPE_SUPPLIER = Suppliers.typeFromString("java.lang.Integer"); private static final Supplier LONG_TYPE_SUPPLIER = Suppliers.typeFromString("java.lang.Long"); /** * A prefix added for elements appearing in method invocation APs which represent fields that can * be proven to be class-initialization time constants (i.e. static final fields of a type known * to be structurally immutable, such as io.grpc.Metadata.Key). * *

This prefix helps avoid collisions between common field names and common strings, e.g. * "KEY_1" and the field KEY_1. */ private static final String IMMUTABLE_FIELD_PREFIX = "static final [immutable] field: "; /** * Encode a static final field as a constant argument on a method's AccessPathElement * *

The field must be of a type known to be structurally immutable, in addition to being * declared static and final for this encoding to make any sense. We do not verify this here, and * rather operate only on the field's fully qualified name, as this is intended to be a quick * utility method. * * @param fieldFQN the field's Fully Qualified Name * @return a string suitable to be included as part of the constant arguments of an * AccessPathElement, assuming the field is indeed static final and of an structurally * immutable type */ public static String immutableFieldNameAsConstantArgument(String fieldFQN) { return IMMUTABLE_FIELD_PREFIX + fieldFQN; } /** Root of the access path. If {@code null}, the root is the receiver argument */ private final @Nullable Element root; private final ImmutableList elements; /** * if present, the argument to the map get() method call that is the final element of this path */ private final @Nullable MapKey mapGetArg; private AccessPath(@Nullable Element root, ImmutableList elements) { this(root, elements, null); } private AccessPath( @Nullable Element root, ImmutableList elements, @Nullable MapKey mapGetArg) { this.root = root; this.elements = elements; this.mapGetArg = mapGetArg; } /** * Construct the access path of a local. * * @param node the local * @return access path representing the local */ public static AccessPath fromLocal(LocalVariableNode node) { return new AccessPath(node.getElement(), ImmutableList.of()); } /** * Construct the access path of a variable declaration. * * @param node the variable declaration * @return access path representing the variable declaration */ static AccessPath fromVarDecl(VariableDeclarationNode node) { Element elem = TreeUtils.elementFromDeclaration(node.getTree()); return new AccessPath(elem, ImmutableList.of()); } /** * Construct the access path of a field access. * * @param node the field access * @param apContext the current access path context information (see {@link * AccessPath.AccessPathContext}). * @return access path for the field access, or null if it cannot be represented */ static @Nullable AccessPath fromFieldAccess(FieldAccessNode node, AccessPathContext apContext) { return fromNodeAndContext(node, apContext); } private static @Nullable AccessPath fromNodeAndContext(Node node, AccessPathContext apContext) { return buildAccessPathRecursive(node, new ArrayDeque<>(), apContext, null); } /** * Construct the access path of a method call. * * @param node the method call * @param apContext the current access path context information (see {@link * AccessPath.AccessPathContext}). * @return access path for the method call, or null if it cannot be represented */ static @Nullable AccessPath fromMethodCall( MethodInvocationNode node, VisitorState state, AccessPathContext apContext) { if (isMapGet(ASTHelpers.getSymbol(node.getTree()), state)) { return fromMapGetCall(node, state, apContext); } return fromVanillaMethodCall(node, apContext); } private static @Nullable AccessPath fromVanillaMethodCall( MethodInvocationNode node, AccessPathContext apContext) { return fromNodeAndContext(node, apContext); } /** * Returns an access path rooted at {@code newRoot} with the same elements and map-get argument as * {@code origAP} */ static AccessPath switchRoot(AccessPath origAP, Element newRoot) { return new AccessPath(newRoot, origAP.elements, origAP.mapGetArg); } /** * Construct the access path given a {@code base.element} structure. * * @param base the base expression for the access path * @param element the final element of the access path (a field or method) * @param apContext the current access path context information (see {@link * AccessPath.AccessPathContext}). * @return the {@link AccessPath} {@code base.element} */ public static @Nullable AccessPath fromBaseAndElement( Node base, Element element, AccessPathContext apContext) { return fromNodeElementAndContext(base, new FieldOrMethodCallElement(element), apContext); } private static @Nullable AccessPath fromNodeElementAndContext( Node base, AccessPathElement apElement, AccessPathContext apContext) { ArrayDeque elements = new ArrayDeque<>(); elements.push(apElement); return buildAccessPathRecursive(base, elements, apContext, null); } /** * Construct the access path given a {@code base.method(CONS)} structure. * *

IMPORTANT: Be careful with this method, the argument list is not the variable names of the * method arguments, but rather the string representation of primitive-type compile-time constants * or the name of static final fields of structurally immutable types (see {@link * #buildAccessPathRecursive(Node, ArrayDeque, AccessPathContext, MapKey)}). * *

This is used by a few specialized Handlers to set nullability around particular paths * involving constants. * * @param base the base expression for the access path * @param method the last method call in the access path * @param constantArguments a list of constant arguments passed to the method call * @param apContext the current access path context information (see {@link * AccessPath.AccessPathContext}). * @return the {@link AccessPath} {@code base.method(CONS)} */ public static @Nullable AccessPath fromBaseMethodAndConstantArgs( Node base, Element method, List constantArguments, AccessPathContext apContext) { return fromNodeElementAndContext( base, new FieldOrMethodCallElement(method, constantArguments), apContext); } /** * Construct the access path for map.get(x) from an invocation of put(x) * or containsKey(x). * * @param node a node invoking containsKey() or put() on a map * @param apContext the current access path context information (see {@link * AccessPath.AccessPathContext}). * @return an AccessPath representing invoking get() on the same type of map as from node, passing * the same first argument as is passed in node */ public static @Nullable AccessPath getForMapInvocation( MethodInvocationNode node, VisitorState state, AccessPathContext apContext) { // For the receiver type for get, use the declared type of the receiver of the containsKey() // call. Note that this may differ from the containing class of the resolved containsKey() // method, which can be in a superclass (e.g., LinkedHashMap does not override containsKey()) // assumption: map type will not both override containsKey() and inherit get() return fromMapGetCall(node, state, apContext); } private static Node stripCasts(Node node) { while (node instanceof TypeCastNode) { node = ((TypeCastNode) node).getOperand(); } return node; } private static @Nullable MapKey argumentToMapKeySpecifier( Node argument, VisitorState state, AccessPathContext apContext) { // Required to have Node type match Tree type in some instances. if (argument instanceof WideningConversionNode) { argument = ((WideningConversionNode) argument).getOperand(); } // A switch at the Tree level should be faster than multiple if checks at the Node level. switch (castToNonNull(argument.getTree()).getKind()) { case STRING_LITERAL: return new StringMapKey(((StringLiteralNode) argument).getValue()); case INT_LITERAL: return new NumericMapKey(((IntegerLiteralNode) argument).getValue()); case LONG_LITERAL: return new NumericMapKey(((LongLiteralNode) argument).getValue()); case METHOD_INVOCATION: MethodAccessNode target = ((MethodInvocationNode) argument).getTarget(); List arguments = ((MethodInvocationNode) argument).getArguments(); // Check for int/long boxing. if (target.getMethod().getSimpleName().toString().equals("valueOf") && arguments.size() == 1) { Type ownerType = ((Symbol.MethodSymbol) target.getMethod()).owner.type; if (ASTHelpers.isSameType(ownerType, INTEGER_TYPE_SUPPLIER.get(state), state) || ASTHelpers.isSameType(ownerType, LONG_TYPE_SUPPLIER.get(state), state)) { return argumentToMapKeySpecifier(arguments.get(0), state, apContext); } } // Fine to fallthrough: default: // Every other type of expression, including variables, field accesses, new A(...), etc. return getAccessPathForNode(argument, state, apContext); // Every AP is a MapKey too } } private static @Nullable AccessPath fromMapGetCall( MethodInvocationNode node, VisitorState state, AccessPathContext apContext) { Node argument = node.getArgument(0); MapKey mapKey = argumentToMapKeySpecifier(argument, state, apContext); if (mapKey == null) { return null; } MethodAccessNode target = node.getTarget(); Node receiver = stripCasts(target.getReceiver()); return buildAccessPathRecursive(receiver, new ArrayDeque<>(), apContext, mapKey); } /** * Gets corresponding AccessPath for node, if it exists. Handles calls to Map.get() * * * @param node AST node * @param state the visitor state * @param apContext the current access path context information (see {@link * AccessPath.AccessPathContext}). * @return corresponding AccessPath if it exists; null otherwise */ public static @Nullable AccessPath getAccessPathForNode( Node node, VisitorState state, AccessPathContext apContext) { if (node instanceof LocalVariableNode) { return fromLocal((LocalVariableNode) node); } else if (node instanceof FieldAccessNode) { return fromFieldAccess((FieldAccessNode) node, apContext); } else if (node instanceof MethodInvocationNode) { return fromMethodCall((MethodInvocationNode) node, state, apContext); } else if (node instanceof ArrayAccessNode) { return fromArrayAccess((ArrayAccessNode) node, apContext); } else { return null; } } private static @Nullable AccessPath fromArrayAccess( ArrayAccessNode node, AccessPathContext apContext) { return fromNodeAndContext(node, apContext); } private static @Nullable Element getElementFromArrayNode(Node arrayNode) { if (arrayNode instanceof LocalVariableNode) { return ((LocalVariableNode) arrayNode).getElement(); } else if (arrayNode instanceof FieldAccessNode) { return ((FieldAccessNode) arrayNode).getElement(); } else { return null; } } /** * Constructs an access path ending with the class field element in the argument. The receiver is * the method receiver itself. * * @param element the receiver element. * @return access path representing the class field */ public static AccessPath fromFieldElement(VariableElement element) { Preconditions.checkArgument( element.getKind().isField(), "element must be of type: FIELD but received: " + element.getKind()); return new AccessPath(null, ImmutableList.of(new FieldOrMethodCallElement(element))); } private static boolean isBoxingMethod(Symbol.MethodSymbol methodSymbol) { if (methodSymbol.isStatic() && methodSymbol.getSimpleName().contentEquals("valueOf")) { Symbol.PackageSymbol enclosingPackage = ASTHelpers.enclosingPackage(methodSymbol.enclClass()); return enclosingPackage != null && enclosingPackage.fullname.contentEquals("java.lang"); } return false; } /** * A helper function that recursively builds an AccessPath from a CFG node. * * @param node the CFG node * @param elements elements to append to the final access path. * @param apContext context information, used to handle cases with constant arguments * @param mapKey map key to be used as the map-get argument, or {@code null} if there is no key * @return the final access path */ private static @Nullable AccessPath buildAccessPathRecursive( Node node, ArrayDeque 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 FieldOrMethodCallElement(fieldAccess.getElement())); result = buildAccessPathRecursive( stripCasts(fieldAccess.getReceiver()), elements, apContext, mapKey); } } else if (node instanceof ArrayAccessNode) { ArrayAccessNode arrayAccess = (ArrayAccessNode) node; Node arrayNode = stripCasts(arrayAccess.getArray()); Node indexNode = arrayAccess.getIndex(); Element arrayElement = getElementFromArrayNode(arrayNode); Element indexElement = getElementFromArrayNode(indexNode); if (arrayElement == null) { return null; } if (indexNode instanceof IntegerLiteralNode) { IntegerLiteralNode intIndexNode = (IntegerLiteralNode) indexNode; elements.push(ArrayIndexElement.withIntegerIndex(arrayElement, intIndexNode.getValue())); } else { if (indexElement != null) { elements.push(ArrayIndexElement.withVariableIndex(arrayElement, indexElement)); } else { return null; } } result = buildAccessPathRecursive(arrayNode, 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 FieldOrMethodCallElement(accessNode.getMethod()); } } else { List 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 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 FieldOrMethodCallElement(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; } /** * Creates an access path representing a Map get call, where the key is obtained by calling {@code * next()} on some {@code Iterator}. Used to support reasoning about iteration over a map's key * set using an enhanced-for loop. * * @param mapNode Node representing the map * @param iterVar local variable holding the iterator * @param apContext access path context * @return access path representing the get call, or {@code null} if the map node cannot be * represented with an access path */ public static @Nullable AccessPath mapWithIteratorContentsKey( Node mapNode, LocalVariableNode iterVar, AccessPathContext apContext) { IteratorContentsKey iterContentsKey = new IteratorContentsKey((VariableElement) iterVar.getElement()); return buildAccessPathRecursive(mapNode, new ArrayDeque<>(), apContext, iterContentsKey); } /** * Creates an access path identical to {@code accessPath} (which must represent a map get), but * replacing its map {@code get()} argument with {@code mapKey} */ public static AccessPath replaceMapKey(AccessPath accessPath, MapKey mapKey) { return new AccessPath(accessPath.getRoot(), accessPath.getElements(), mapKey); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } AccessPath that = (AccessPath) o; return Objects.equals(root, that.root) && elements.equals(that.elements) && Objects.equals(mapGetArg, that.mapGetArg); } @Override public int hashCode() { int result = 1; result = 31 * result + (root != null ? root.hashCode() : 0); result = 31 * result + elements.hashCode(); result = 31 * result + (mapGetArg != null ? mapGetArg.hashCode() : 0); return result; } /** * Returns the root element of the access path. If the root is the receiver argument, returns * {@code null}. */ public @Nullable Element getRoot() { return root; } public ImmutableList getElements() { return elements; } public @Nullable MapKey getMapGetArg() { return mapGetArg; } @Override public String toString() { return "AccessPath{" + "root=" + (root == null ? "this" : root) + ", elements=" + elements + ", mapGetArg=" + mapGetArg + '}'; } private static boolean isMapGet(Symbol.MethodSymbol symbol, VisitorState state) { return NullabilityUtil.isMapMethod(symbol, state, "get", 1); } public static boolean isContainsKey(Symbol.MethodSymbol symbol, VisitorState state) { return NullabilityUtil.isMapMethod(symbol, state, "containsKey", 1); } public static boolean isMapPut(Symbol.MethodSymbol symbol, VisitorState state) { return NullabilityUtil.isMapMethod(symbol, state, "put", 2) || NullabilityUtil.isMapMethod(symbol, state, "putIfAbsent", 2); } public static boolean isMapComputeIfAbsent(Symbol.MethodSymbol symbol, VisitorState state) { return NullabilityUtil.isMapMethod(symbol, state, "computeIfAbsent", 2); } private static final class StringMapKey implements MapKey { private final String key; public StringMapKey(String key) { this.key = key; } @Override public int hashCode() { return this.key.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof StringMapKey) { return this.key.equals(((StringMapKey) obj).key); } return false; } } private static final class NumericMapKey implements MapKey { private final long key; public NumericMapKey(long key) { this.key = key; } @Override public int hashCode() { return Long.hashCode(this.key); } /** * We ignore this method for code coverage since there is non-determinism somewhere deep in a * Map implementation such that, depending on how AccessPaths get bucketed in the Map (which * depends on non-deterministic hash codes), sometimes this method is called and sometimes it is * not. */ @Override @JacocoIgnoreGenerated public boolean equals(Object obj) { if (obj instanceof NumericMapKey) { return this.key == ((NumericMapKey) obj).key; } return false; } } /** * Represents all possible values that could be returned by calling {@code next()} on an {@code * Iterator} variable */ public static final class IteratorContentsKey implements MapKey { /** * Element for the local variable holding the {@code Iterator}. We only support locals for now, * as this class is designed specifically for reasoning about iterating over map keys using an * enhanced-for loop over a {@code keySet()}, and for such cases the iterator is always stored * locally */ private final VariableElement iteratorVarElement; IteratorContentsKey(VariableElement iteratorVarElement) { this.iteratorVarElement = iteratorVarElement; } public VariableElement getIteratorVarElement() { return iteratorVarElement; } /** * We ignore this method for code coverage since there is non-determinism somewhere deep in a * Map implementation such that, depending on how AccessPaths get bucketed in the Map (which * depends on non-deterministic hash codes), sometimes this method is called and sometimes it is * not. */ @Override @JacocoIgnoreGenerated public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } IteratorContentsKey that = (IteratorContentsKey) o; return iteratorVarElement.equals(that.iteratorVarElement); } @Override public int hashCode() { return iteratorVarElement.hashCode(); } } /** * Represents a per-javac instance of an AccessPath context options. * *

This includes, for example, data on known structurally immutable types. */ public static final class AccessPathContext { private final ImmutableSet immutableTypes; private AccessPathContext(ImmutableSet immutableTypes) { this.immutableTypes = immutableTypes; } public boolean isStructurallyImmutableType(Type type) { return immutableTypes.contains(type.tsym.toString()); } public static Builder builder() { return new AccessPathContext.Builder(); } /** class for building up instances of the AccessPathContext. */ public static final class Builder { private @Nullable ImmutableSet immutableTypes; Builder() {} /** * Passes the set of structurally immutable types registered into this AccessPathContext. * *

See {@link com.uber.nullaway.handlers.Handler#onRegisterImmutableTypes} for more info. * * @param immutableTypes the immutable types known to our dataflow analysis. */ public Builder setImmutableTypes(ImmutableSet immutableTypes) { this.immutableTypes = immutableTypes; return this; } /** * Construct the immutable AccessPathContext instance. * * @return an access path context constructed from everything added to the builder */ public AccessPathContext build() { if (immutableTypes == null) { throw new IllegalStateException("must set immutable types before building"); } return new AccessPathContext(immutableTypes); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy