blob: 6dda2b2ecb2824f315a1318898af734622497641 [file] [log] [blame]
package org.checkerframework.framework.flow;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.ForwardTransferFunction;
import org.checkerframework.dataflow.analysis.RegularTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.UnderlyingAST;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGLambda;
import org.checkerframework.dataflow.cfg.UnderlyingAST.CFGMethod;
import org.checkerframework.dataflow.cfg.UnderlyingAST.Kind;
import org.checkerframework.dataflow.cfg.node.AbstractNodeVisitor;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.CaseNode;
import org.checkerframework.dataflow.cfg.node.ClassNameNode;
import org.checkerframework.dataflow.cfg.node.ConditionalNotNode;
import org.checkerframework.dataflow.cfg.node.EqualToNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.InstanceOfNode;
import org.checkerframework.dataflow.cfg.node.LambdaResultExpressionNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.NarrowingConversionNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.NotEqualNode;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.ReturnNode;
import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.node.VariableDeclarationNode;
import org.checkerframework.dataflow.cfg.node.WideningConversionNode;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.framework.util.Contract;
import org.checkerframework.framework.util.Contract.ConditionalPostcondition;
import org.checkerframework.framework.util.Contract.Postcondition;
import org.checkerframework.framework.util.Contract.Precondition;
import org.checkerframework.framework.util.ContractsFromMethod;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
/**
* The default analysis transfer function for the Checker Framework propagates information through
* assignments and uses the {@link AnnotatedTypeFactory} to provide checker-specific logic how to
* combine types (e.g., what is the type of a string concatenation, given the types of the two
* operands) and as an abstraction function (e.g., determine the annotations on literals).
*
* <p>Design note: CFAbstractTransfer and its subclasses are supposed to act as transfer functions.
* But, since the AnnotatedTypeFactory already existed and performed checker-independent type
* propagation, CFAbstractTransfer delegates work to it instead of duplicating some logic in
* CFAbstractTransfer. The checker-specific subclasses of CFAbstractTransfer do implement transfer
* function logic themselves.
*/
public abstract class CFAbstractTransfer<
V extends CFAbstractValue<V>,
S extends CFAbstractStore<V, S>,
T extends CFAbstractTransfer<V, S, T>>
extends AbstractNodeVisitor<TransferResult<V, S>, TransferInput<V, S>>
implements ForwardTransferFunction<V, S> {
/** The analysis used by this transfer function. */
protected final CFAbstractAnalysis<V, S, T> analysis;
/**
* Should the analysis use sequential Java semantics (i.e., assume that only one thread is running
* at all times)?
*/
protected final boolean sequentialSemantics;
/** Indicates that the whole-program inference is on. */
private final boolean infer;
/**
* Create a CFAbstractTransfer.
*
* @param analysis the analysis used by this transfer function
*/
protected CFAbstractTransfer(CFAbstractAnalysis<V, S, T> analysis) {
this(analysis, false);
}
/**
* Constructor that allows forcing concurrent semantics to be on for this instance of
* CFAbstractTransfer.
*
* @param analysis the analysis used by this transfer function
* @param forceConcurrentSemantics whether concurrent semantics should be forced to be on. If
* false, concurrent semantics are turned off by default, but the user can still turn them on
* via {@code -AconcurrentSemantics}. If true, the user cannot turn off concurrent semantics.
*/
protected CFAbstractTransfer(
CFAbstractAnalysis<V, S, T> analysis, boolean forceConcurrentSemantics) {
this.analysis = analysis;
this.sequentialSemantics =
!(forceConcurrentSemantics || analysis.checker.hasOption("concurrentSemantics"));
this.infer = analysis.checker.hasOption("infer");
}
/**
* Returns true if the transfer function uses sequential semantics, false if it uses concurrent
* semantics. Useful when creating an empty store, since a store makes different decisions
* depending on whether sequential or concurrent semantics are used.
*
* @return true if the transfer function uses sequential semantics, false if it uses concurrent
* semantics
*/
public boolean usesSequentialSemantics() {
return sequentialSemantics;
}
/**
* A hook for subclasses to modify the result of the transfer function. This method is called
* before returning the abstract value {@code value} as the result of the transfer function.
*
* <p>If a subclass overrides this method, the subclass should also override {@link
* #finishValue(CFAbstractValue,CFAbstractStore,CFAbstractStore)}.
*
* @param value a value to possibly modify
* @param store the store
* @return the possibly-modified value
*/
protected @Nullable V finishValue(@Nullable V value, S store) {
return value;
}
/**
* A hook for subclasses to modify the result of the transfer function. This method is called
* before returning the abstract value {@code value} as the result of the transfer function.
*
* <p>If a subclass overrides this method, the subclass should also override {@link
* #finishValue(CFAbstractValue,CFAbstractStore)}.
*
* @param value the value to finish
* @param thenStore the "then" store
* @param elseStore the "else" store
* @return the possibly-modified value
*/
protected @Nullable V finishValue(@Nullable V value, S thenStore, S elseStore) {
return value;
}
/**
* Returns the abstract value of a non-leaf tree {@code tree}, as computed by the {@link
* AnnotatedTypeFactory}.
*
* @return the abstract value of a non-leaf tree {@code tree}, as computed by the {@link
* AnnotatedTypeFactory}
*/
protected V getValueFromFactory(Tree tree, Node node) {
GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory =
analysis.atypeFactory;
Tree preTree = analysis.getCurrentTree();
Pair<Tree, AnnotatedTypeMirror> preContext = factory.getVisitorState().getAssignmentContext();
analysis.setCurrentTree(tree);
// is there an assignment context node available?
if (node != null && node.getAssignmentContext() != null) {
// Get the declared type of the assignment context by looking up the assignment context tree's
// type in the factory while flow is disabled.
Tree contextTree = node.getAssignmentContext().getContextTree();
AnnotatedTypeMirror assignmentContext = null;
if (contextTree != null) {
assignmentContext = factory.getAnnotatedTypeLhs(contextTree);
} else {
Element assignmentContextElement = node.getAssignmentContext().getElementForType();
if (assignmentContextElement != null) {
// if contextTree is null, use the element to get the type
assignmentContext = factory.getAnnotatedType(assignmentContextElement);
}
}
if (assignmentContext != null) {
if (assignmentContext instanceof AnnotatedExecutableType) {
// For a MethodReturnContext, we get the full type of the
// method, but we only want the return type.
assignmentContext = ((AnnotatedExecutableType) assignmentContext).getReturnType();
}
factory
.getVisitorState()
.setAssignmentContext(
Pair.of(node.getAssignmentContext().getContextTree(), assignmentContext));
}
}
AnnotatedTypeMirror at;
if (node instanceof MethodInvocationNode
&& ((MethodInvocationNode) node).getIterableExpression() != null) {
ExpressionTree iter = ((MethodInvocationNode) node).getIterableExpression();
at = factory.getIterableElementType(iter);
} else if (node instanceof ArrayAccessNode
&& ((ArrayAccessNode) node).getArrayExpression() != null) {
ExpressionTree array = ((ArrayAccessNode) node).getArrayExpression();
at = factory.getIterableElementType(array);
} else {
at = factory.getAnnotatedType(tree);
}
analysis.setCurrentTree(preTree);
factory.getVisitorState().setAssignmentContext(preContext);
return analysis.createAbstractValue(at);
}
/**
* Returns an abstract value with the given {@code type} and the annotations from {@code
* annotatedValue}.
*
* @param type the type to return
* @param annotatedValue the annotations to return
* @return an abstract value with the given {@code type} and the annotations from {@code
* annotatedValue}
* @deprecated use {@link #getWidenedValue} or {@link #getNarrowedValue}
*/
@Deprecated // 2020-10-02
protected V getValueWithSameAnnotations(TypeMirror type, V annotatedValue) {
if (annotatedValue == null) {
return null;
}
return analysis.createAbstractValue(annotatedValue.getAnnotations(), type);
}
/** The fixed initial store. */
private S fixedInitialStore = null;
/** Set a fixed initial Store. */
public void setFixedInitialStore(S s) {
fixedInitialStore = s;
}
/** The initial store maps method formal parameters to their currently most refined type. */
@Override
public S initialStore(UnderlyingAST underlyingAST, @Nullable List<LocalVariableNode> parameters) {
if (underlyingAST.getKind() != Kind.LAMBDA && underlyingAST.getKind() != Kind.METHOD) {
if (fixedInitialStore != null) {
return fixedInitialStore;
} else {
return analysis.createEmptyStore(sequentialSemantics);
}
}
S info;
if (underlyingAST.getKind() == Kind.METHOD) {
if (fixedInitialStore != null) {
// copy knowledge
info = analysis.createCopiedStore(fixedInitialStore);
} else {
info = analysis.createEmptyStore(sequentialSemantics);
}
AnnotatedTypeFactory factory = analysis.getTypeFactory();
for (LocalVariableNode p : parameters) {
AnnotatedTypeMirror anno = factory.getAnnotatedType(p.getElement());
info.initializeMethodParameter(p, analysis.createAbstractValue(anno));
}
// add properties known through precondition
CFGMethod method = (CFGMethod) underlyingAST;
MethodTree methodDeclTree = method.getMethod();
ExecutableElement methodElem = TreeUtils.elementFromDeclaration(methodDeclTree);
addInformationFromPreconditions(info, factory, method, methodDeclTree, methodElem);
final ClassTree classTree = method.getClassTree();
addFieldValues(info, factory, classTree, methodDeclTree);
addFinalLocalValues(info, methodElem);
if (shouldPerformWholeProgramInference(methodDeclTree, methodElem)) {
Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
AnnotatedTypes.overriddenMethods(
analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem);
for (Map.Entry<AnnotatedDeclaredType, ExecutableElement> pair :
overriddenMethods.entrySet()) {
AnnotatedExecutableType overriddenMethod =
AnnotatedTypes.asMemberOf(
analysis.atypeFactory.getProcessingEnv().getTypeUtils(),
analysis.atypeFactory,
pair.getKey(),
pair.getValue());
// Infers parameter and receiver types of the method based
// on the overridden method.
analysis
.atypeFactory
.getWholeProgramInference()
.updateFromOverride(methodDeclTree, methodElem, overriddenMethod);
}
}
} else if (underlyingAST.getKind() == Kind.LAMBDA) {
// Create a copy and keep only the field values (nothing else applies).
info = analysis.createCopiedStore(fixedInitialStore);
// Allow that local variables are retained; they are effectively final,
// otherwise Java wouldn't allow access from within the lambda.
// TODO: what about the other information? Can code further down be simplified?
// info.localVariableValues.clear();
info.classValues.clear();
info.arrayValues.clear();
info.methodValues.clear();
AnnotatedTypeFactory factory = analysis.getTypeFactory();
for (LocalVariableNode p : parameters) {
AnnotatedTypeMirror anno = factory.getAnnotatedType(p.getElement());
info.initializeMethodParameter(p, analysis.createAbstractValue(anno));
}
CFGLambda lambda = (CFGLambda) underlyingAST;
@SuppressWarnings("interning:assignment") // used in == tests
@InternedDistinct Tree enclosingTree =
TreePathUtil.enclosingOfKind(
factory.getPath(lambda.getLambdaTree()),
new HashSet<>(
Arrays.asList(
Tree.Kind.METHOD,
// Tree.Kind for which TreeUtils.isClassTree is true
Tree.Kind.CLASS,
Tree.Kind.INTERFACE,
Tree.Kind.ANNOTATION_TYPE,
Tree.Kind.ENUM)));
Element enclosingElement = null;
if (enclosingTree.getKind() == Tree.Kind.METHOD) {
// If it is in an initializer, we need to use locals from the initializer.
enclosingElement = TreeUtils.elementFromTree(enclosingTree);
} else if (TreeUtils.isClassTree(enclosingTree)) {
// Try to find an enclosing initializer block.
// Would love to know if there was a better way.
// Find any enclosing element of the lambda (using trees).
// Then go up the elements to find an initializer element (which can't be found with the
// tree).
TreePath loopTree = factory.getPath(lambda.getLambdaTree()).getParentPath();
Element anEnclosingElement = null;
while (loopTree.getLeaf() != enclosingTree) {
Element sym = TreeUtils.elementFromTree(loopTree.getLeaf());
if (sym != null) {
anEnclosingElement = sym;
break;
}
loopTree = loopTree.getParentPath();
}
while (anEnclosingElement != null
&& !anEnclosingElement.equals(TreeUtils.elementFromTree(enclosingTree))) {
if (anEnclosingElement.getKind() == ElementKind.INSTANCE_INIT
|| anEnclosingElement.getKind() == ElementKind.STATIC_INIT) {
enclosingElement = anEnclosingElement;
break;
}
anEnclosingElement = anEnclosingElement.getEnclosingElement();
}
}
if (enclosingElement != null) {
addFinalLocalValues(info, enclosingElement);
}
// We want the initialization stuff, but need to throw out any refinements.
Map<FieldAccess, V> fieldValuesClone = new HashMap<>(info.fieldValues);
for (Map.Entry<FieldAccess, V> fieldValue : fieldValuesClone.entrySet()) {
AnnotatedTypeMirror declaredType = factory.getAnnotatedType(fieldValue.getKey().getField());
V lubbedValue =
analysis.createAbstractValue(declaredType).leastUpperBound(fieldValue.getValue());
info.fieldValues.put(fieldValue.getKey(), lubbedValue);
}
} else {
assert false : "Unexpected tree: " + underlyingAST;
info = null;
}
return info;
}
private void addFieldValues(
S info, AnnotatedTypeFactory factory, ClassTree classTree, MethodTree methodTree) {
// Add knowledge about final fields, or values of non-final fields
// if we are inside a constructor (information about initializers)
TypeMirror classType = TreeUtils.typeOf(classTree);
List<Pair<VariableElement, V>> fieldValues = analysis.getFieldValues();
for (Pair<VariableElement, V> p : fieldValues) {
VariableElement element = p.first;
V value = p.second;
if (ElementUtils.isFinal(element) || TreeUtils.isConstructor(methodTree)) {
JavaExpression receiver;
if (ElementUtils.isStatic(element)) {
receiver = new ClassName(classType);
} else {
receiver = new ThisReference(classType);
}
TypeMirror fieldType = ElementUtils.getType(element);
JavaExpression field = new FieldAccess(receiver, fieldType, element);
info.insertValue(field, value);
}
}
// add properties about fields (static information from type)
boolean isNotFullyInitializedReceiver = isNotFullyInitializedReceiver(methodTree);
if (isNotFullyInitializedReceiver && !TreeUtils.isConstructor(methodTree)) {
// cannot add information about fields if the receiver isn't initialized
// and the method isn't a constructor
return;
}
for (Tree member : classTree.getMembers()) {
if (member instanceof VariableTree) {
VariableTree vt = (VariableTree) member;
final VariableElement element = TreeUtils.elementFromDeclaration(vt);
AnnotatedTypeMirror type =
((GenericAnnotatedTypeFactory<?, ?, ?, ?>) factory).getAnnotatedTypeLhs(vt);
TypeMirror fieldType = ElementUtils.getType(element);
JavaExpression receiver;
if (ElementUtils.isStatic(element)) {
receiver = new ClassName(classType);
} else {
receiver = new ThisReference(classType);
}
V value = analysis.createAbstractValue(type);
if (value == null) {
continue;
}
if (TreeUtils.isConstructor(methodTree)) {
// If we are in a constructor, then we can still use the static type, but only if there is
// also an initializer that already does some initialization.
boolean found = false;
for (Pair<VariableElement, V> fieldValue : fieldValues) {
if (fieldValue.first.equals(element)) {
value = value.leastUpperBound(fieldValue.second);
found = true;
break;
}
}
if (!found) {
// no initializer found, cannot use static type
continue;
}
}
JavaExpression field = new FieldAccess(receiver, fieldType, element);
info.insertValue(field, value);
}
}
}
private void addFinalLocalValues(S info, Element enclosingElement) {
// add information about effectively final variables (from outer scopes)
for (Map.Entry<Element, V> e : analysis.atypeFactory.getFinalLocalValues().entrySet()) {
Element elem = e.getKey();
// TODO: There is a design flaw where the values of final local values leaks
// into other methods of the same class. For example, in
// class a { void b() {...} void c() {...} }
// final local values from b() would be visible in the store for c(),
// even though they should only be visible in b() and in classes
// defined inside the method body of b().
// This is partly because GenericAnnotatedTypeFactory.performFlowAnalysis does not call itself
// recursively to analyze inner classes, but instead pops classes off of a queue, and the
// information about known final local values is stored by GenericAnnotatedTypeFactory.analyze
// in GenericAnnotatedTypeFactory.flowResult, which is visible to all classes in the queue
// regardless of their level of recursion.
// We work around this here by ensuring that we only add a final local value to a method's
// store if that method is enclosed by the method where the local variables were declared.
// Find the enclosing method of the element
Element enclosingMethodOfVariableDeclaration = elem.getEnclosingElement();
if (enclosingMethodOfVariableDeclaration != null) {
// Now find all the enclosing methods of the code we are analyzing. If any one of
// them matches the above, then the final local variable value applies.
Element enclosingMethodOfCurrentMethod = enclosingElement;
while (enclosingMethodOfCurrentMethod != null) {
if (enclosingMethodOfVariableDeclaration.equals(enclosingMethodOfCurrentMethod)) {
LocalVariable l = new LocalVariable(elem);
info.insertValue(l, e.getValue());
break;
}
enclosingMethodOfCurrentMethod = enclosingMethodOfCurrentMethod.getEnclosingElement();
}
}
}
}
/**
* Returns true if the receiver of a method or constructor might not yet be fully initialized.
*
* @param methodDeclTree the declaration of the method or constructor
* @return true if the receiver of a method or constructormight not yet be fully initialized
*/
protected boolean isNotFullyInitializedReceiver(MethodTree methodDeclTree) {
return TreeUtils.isConstructor(methodDeclTree);
}
/**
* Add the information from all the preconditions of a method to the initial store in the method
* body.
*
* @param initialStore the initial store for the method body
* @param factory the type factory
* @param methodAst the AST for a method declaration
* @param methodDeclTree the declaration of the method; is a field of {@code methodAst}
* @param methodElement the element for the method
*/
protected void addInformationFromPreconditions(
S initialStore,
AnnotatedTypeFactory factory,
CFGMethod methodAst,
MethodTree methodDeclTree,
ExecutableElement methodElement) {
ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod();
Set<Precondition> preconditions = contractsUtils.getPreconditions(methodElement);
StringToJavaExpression stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker);
for (Precondition p : preconditions) {
String stringExpr = p.expressionString;
AnnotationMirror annotation =
p.viewpointAdaptDependentTypeAnnotation(
analysis.atypeFactory, stringToJavaExpr, /*errorTree=*/ null);
JavaExpression exprJe;
try {
// TODO: currently, these expressions are parsed at the declaration (i.e. here) and for
// every use. this could be optimized to store the result the first time.
// (same for other annotations)
exprJe = StringToJavaExpression.atMethodBody(stringExpr, methodDeclTree, analysis.checker);
} catch (JavaExpressionParseException e) {
// Errors are reported by BaseTypeVisitor.checkContractsAtMethodDeclaration().
continue;
}
initialStore.insertValuePermitNondeterministic(exprJe, annotation);
}
}
/**
* The default visitor returns the input information unchanged, or in the case of conditional
* input information, merged.
*/
@Override
public TransferResult<V, S> visitNode(Node n, TransferInput<V, S> in) {
V value = null;
// TODO: handle implicit/explicit this and go to correct factory method
Tree tree = n.getTree();
if (tree != null) {
if (TreeUtils.canHaveTypeAnnotation(tree)) {
value = getValueFromFactory(tree, n);
}
}
return createTransferResult(value, in);
}
/**
* Creates a TransferResult.
*
* <p>This default implementation returns the input information unchanged, or in the case of
* conditional input information, merged.
*
* @param value the value; possibly null
* @param in the transfer input
* @return the input information, as a TransferResult
*/
protected TransferResult<V, S> createTransferResult(@Nullable V value, TransferInput<V, S> in) {
if (in.containsTwoStores()) {
S thenStore = in.getThenStore();
S elseStore = in.getElseStore();
return new ConditionalTransferResult<>(
finishValue(value, thenStore, elseStore), thenStore, elseStore);
} else {
S info = in.getRegularStore();
return new RegularTransferResult<>(finishValue(value, info), info);
}
}
/**
* Creates a TransferResult just like the given one, but with the given value.
*
* <p>This default implementation returns the input information unchanged, or in the case of
* conditional input information, merged.
*
* @param value the value; possibly null
* @param in the TransferResult to copy
* @return the input informatio
*/
protected TransferResult<V, S> recreateTransferResult(
@Nullable V value, TransferResult<V, S> in) {
if (in.containsTwoStores()) {
S thenStore = in.getThenStore();
S elseStore = in.getElseStore();
return new ConditionalTransferResult<>(
finishValue(value, thenStore, elseStore), thenStore, elseStore);
} else {
S info = in.getRegularStore();
return new RegularTransferResult<>(finishValue(value, info), info);
}
}
@Override
public TransferResult<V, S> visitClassName(ClassNameNode n, TransferInput<V, S> in) {
// The tree underlying a class name is a type tree.
V value = null;
Tree tree = n.getTree();
if (tree != null) {
if (TreeUtils.canHaveTypeAnnotation(tree)) {
GenericAnnotatedTypeFactory<V, S, T, ? extends CFAbstractAnalysis<V, S, T>> factory =
analysis.atypeFactory;
analysis.setCurrentTree(tree);
AnnotatedTypeMirror at = factory.getAnnotatedTypeFromTypeTree(tree);
analysis.setCurrentTree(null);
value = analysis.createAbstractValue(at);
}
}
return createTransferResult(value, in);
}
@Override
public TransferResult<V, S> visitFieldAccess(FieldAccessNode n, TransferInput<V, S> p) {
S store = p.getRegularStore();
V storeValue = store.getValue(n);
// look up value in factory, and take the more specific one
// TODO: handle cases, where this is not allowed (e.g. constructors in non-null type systems)
V factoryValue = getValueFromFactory(n.getTree(), n);
V value = moreSpecificValue(factoryValue, storeValue);
return new RegularTransferResult<>(finishValue(value, store), store);
}
@Override
public TransferResult<V, S> visitArrayAccess(ArrayAccessNode n, TransferInput<V, S> p) {
S store = p.getRegularStore();
V storeValue = store.getValue(n);
// look up value in factory, and take the more specific one
V factoryValue = getValueFromFactory(n.getTree(), n);
V value = moreSpecificValue(factoryValue, storeValue);
return new RegularTransferResult<>(finishValue(value, store), store);
}
/** Use the most specific type information available according to the store. */
@Override
public TransferResult<V, S> visitLocalVariable(LocalVariableNode n, TransferInput<V, S> in) {
S store = in.getRegularStore();
V valueFromStore = store.getValue(n);
V valueFromFactory = getValueFromFactory(n.getTree(), n);
V value = moreSpecificValue(valueFromFactory, valueFromStore);
return new RegularTransferResult<>(finishValue(value, store), store);
}
@Override
public TransferResult<V, S> visitThis(ThisNode n, TransferInput<V, S> in) {
S store = in.getRegularStore();
V valueFromStore = store.getValue(n);
V valueFromFactory = null;
V value = null;
Tree tree = n.getTree();
if (tree != null && TreeUtils.canHaveTypeAnnotation(tree)) {
valueFromFactory = getValueFromFactory(tree, n);
}
if (valueFromFactory == null) {
value = valueFromStore;
} else {
value = moreSpecificValue(valueFromFactory, valueFromStore);
}
return new RegularTransferResult<>(finishValue(value, store), store);
}
@Override
public TransferResult<V, S> visitTernaryExpression(
TernaryExpressionNode n, TransferInput<V, S> p) {
TransferResult<V, S> result = super.visitTernaryExpression(n, p);
S thenStore = result.getThenStore();
S elseStore = result.getElseStore();
V thenValue = p.getValueOfSubNode(n.getThenOperand());
V elseValue = p.getValueOfSubNode(n.getElseOperand());
V resultValue = null;
if (thenValue != null && elseValue != null) {
// The resulting abstract value is the merge of the 'then' and 'else' branch.
resultValue = thenValue.leastUpperBound(elseValue);
}
V finishedValue = finishValue(resultValue, thenStore, elseStore);
return new ConditionalTransferResult<>(finishedValue, thenStore, elseStore);
}
/** Reverse the role of the 'thenStore' and 'elseStore'. */
@Override
public TransferResult<V, S> visitConditionalNot(ConditionalNotNode n, TransferInput<V, S> p) {
TransferResult<V, S> result = super.visitConditionalNot(n, p);
S thenStore = result.getThenStore();
S elseStore = result.getElseStore();
return new ConditionalTransferResult<>(result.getResultValue(), elseStore, thenStore);
}
@Override
public TransferResult<V, S> visitEqualTo(EqualToNode n, TransferInput<V, S> p) {
TransferResult<V, S> res = super.visitEqualTo(n, p);
Node leftN = n.getLeftOperand();
Node rightN = n.getRightOperand();
V leftV = p.getValueOfSubNode(leftN);
V rightV = p.getValueOfSubNode(rightN);
if (res.containsTwoStores()
&& (NodeUtils.isConstantBoolean(leftN, false)
|| NodeUtils.isConstantBoolean(rightN, false))) {
S thenStore = res.getElseStore();
S elseStore = res.getThenStore();
res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
}
// if annotations differ, use the one that is more precise for both
// sides (and add it to the store if possible)
res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false);
res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false);
return res;
}
@Override
public TransferResult<V, S> visitNotEqual(NotEqualNode n, TransferInput<V, S> p) {
TransferResult<V, S> res = super.visitNotEqual(n, p);
Node leftN = n.getLeftOperand();
Node rightN = n.getRightOperand();
V leftV = p.getValueOfSubNode(leftN);
V rightV = p.getValueOfSubNode(rightN);
if (res.containsTwoStores()
&& (NodeUtils.isConstantBoolean(leftN, true)
|| NodeUtils.isConstantBoolean(rightN, true))) {
S thenStore = res.getElseStore();
S elseStore = res.getThenStore();
res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
}
// if annotations differ, use the one that is more precise for both
// sides (and add it to the store if possible)
res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true);
res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true);
return res;
}
/**
* Refine the annotation of {@code secondNode} if the annotation {@code secondValue} is less
* precise than {@code firstValue}. This is possible, if {@code secondNode} is an expression that
* is tracked by the store (e.g., a local variable or a field). Clients usually call this twice
* with {@code firstNode} and {@code secondNode} reversed, to refine each of them.
*
* <p>Note that when overriding this method, when a new type is inserted into the store, {@link
* #splitAssignments} should be called, and the new type should be inserted into the store for
* each of the resulting nodes.
*
* @param firstNode the node that might be more precise
* @param secondNode the node whose type to possibly refine
* @param firstValue the abstract value that might be more precise
* @param secondValue the abstract value that might be less precise
* @param res the previous result
* @param notEqualTo if true, indicates that the logic is flipped (i.e., the information is added
* to the {@code elseStore} instead of the {@code thenStore}) for a not-equal comparison.
* @return the conditional transfer result (if information has been added), or {@code null}
*/
protected TransferResult<V, S> strengthenAnnotationOfEqualTo(
TransferResult<V, S> res,
Node firstNode,
Node secondNode,
V firstValue,
V secondValue,
boolean notEqualTo) {
if (firstValue != null) {
// Only need to insert if the second value is actually different.
if (!firstValue.equals(secondValue)) {
List<Node> secondParts = splitAssignments(secondNode);
for (Node secondPart : secondParts) {
JavaExpression secondInternal = JavaExpression.fromNode(secondPart);
if (!secondInternal.isDeterministic(analysis.atypeFactory)) {
continue;
}
if (CFAbstractStore.canInsertJavaExpression(secondInternal)) {
S thenStore = res.getThenStore();
S elseStore = res.getElseStore();
if (notEqualTo) {
elseStore.insertValue(secondInternal, firstValue);
} else {
thenStore.insertValue(secondInternal, firstValue);
}
// To handle `(a = b = c) == x`, repeat for all insertable receivers of
// splitted assignments instead of returning.
res = new ConditionalTransferResult<>(res.getResultValue(), thenStore, elseStore);
}
}
}
}
return res;
}
/**
* Takes a node, and either returns the node itself again (as a singleton list), or if the node is
* an assignment node, returns the lhs and rhs (where splitAssignments is applied recursively to
* the rhs -- that is, the rhs may not appear in the result, but rather its lhs and rhs may).
*
* @param node possibly an assignment node
* @return a list containing all the right- and left-hand sides in the given assignment node; it
* contains just the node itself if it is not an assignment)
*/
protected List<Node> splitAssignments(Node node) {
if (node instanceof AssignmentNode) {
List<Node> result = new ArrayList<>(2);
AssignmentNode a = (AssignmentNode) node;
result.add(a.getTarget());
result.addAll(splitAssignments(a.getExpression()));
return result;
} else {
return Collections.singletonList(node);
}
}
@Override
public TransferResult<V, S> visitAssignment(AssignmentNode n, TransferInput<V, S> in) {
Node lhs = n.getTarget();
Node rhs = n.getExpression();
S info = in.getRegularStore();
V rhsValue = in.getValueOfSubNode(rhs);
if (shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) {
// Fields defined in interfaces are LocalVariableNodes with ElementKind of FIELD.
if (lhs instanceof FieldAccessNode
|| (lhs instanceof LocalVariableNode
&& ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.FIELD)) {
// Updates inferred field type
analysis
.atypeFactory
.getWholeProgramInference()
.updateFromFieldAssignment(lhs, rhs, analysis.getContainingClass(n.getTree()));
} else if (lhs instanceof LocalVariableNode
&& ((LocalVariableNode) lhs).getElement().getKind() == ElementKind.PARAMETER) {
// lhs is a formal parameter of some method
VariableElement param = (VariableElement) ((LocalVariableNode) lhs).getElement();
analysis
.atypeFactory
.getWholeProgramInference()
.updateFromFormalParameterAssignment((LocalVariableNode) lhs, rhs, param);
}
}
processCommonAssignment(in, lhs, rhs, info, rhsValue);
return new RegularTransferResult<>(finishValue(rhsValue, info), info);
}
@Override
public TransferResult<V, S> visitReturn(ReturnNode n, TransferInput<V, S> p) {
TransferResult<V, S> result = super.visitReturn(n, p);
if (shouldPerformWholeProgramInference(n.getTree())) {
// Retrieves class containing the method
ClassTree classTree = analysis.getContainingClass(n.getTree());
// classTree is null e.g. if this is a return statement in a lambda.
if (classTree == null) {
return result;
}
ClassSymbol classSymbol = (ClassSymbol) TreeUtils.elementFromTree(classTree);
ExecutableElement methodElem =
TreeUtils.elementFromDeclaration(analysis.getContainingMethod(n.getTree()));
Map<AnnotatedDeclaredType, ExecutableElement> overriddenMethods =
AnnotatedTypes.overriddenMethods(
analysis.atypeFactory.getElementUtils(), analysis.atypeFactory, methodElem);
// Updates the inferred return type of the method
analysis
.atypeFactory
.getWholeProgramInference()
.updateFromReturn(
n, classSymbol, analysis.getContainingMethod(n.getTree()), overriddenMethods);
}
return result;
}
@Override
public TransferResult<V, S> visitLambdaResultExpression(
LambdaResultExpressionNode n, TransferInput<V, S> in) {
return n.getResult().accept(this, in);
}
@Override
public TransferResult<V, S> visitStringConcatenateAssignment(
StringConcatenateAssignmentNode n, TransferInput<V, S> in) {
// This gets the type of LHS + RHS
TransferResult<V, S> result = super.visitStringConcatenateAssignment(n, in);
Node lhs = n.getLeftOperand();
Node rhs = n.getRightOperand();
// update the results store if the assignment target is something we can process
S info = result.getRegularStore();
// ResultValue is the type of LHS + RHS
V resultValue = result.getResultValue();
if (lhs instanceof FieldAccessNode
&& shouldPerformWholeProgramInference(n.getTree(), lhs.getTree())) {
// Updates inferred field type
analysis
.atypeFactory
.getWholeProgramInference()
.updateFromFieldAssignment(
(FieldAccessNode) lhs, rhs, analysis.getContainingClass(n.getTree()));
}
processCommonAssignment(in, lhs, rhs, info, resultValue);
return new RegularTransferResult<>(finishValue(resultValue, info), info);
}
/**
* Determine abstract value of right-hand side and update the store accordingly to the assignment.
*/
protected void processCommonAssignment(
TransferInput<V, S> in, Node lhs, Node rhs, S info, V rhsValue) {
// update information in the store
info.updateForAssignment(lhs, rhsValue);
}
@Override
public TransferResult<V, S> visitObjectCreation(ObjectCreationNode n, TransferInput<V, S> p) {
if (shouldPerformWholeProgramInference(n.getTree())) {
ExecutableElement constructorElt =
analysis.getTypeFactory().constructorFromUse(n.getTree()).executableType.getElement();
analysis
.atypeFactory
.getWholeProgramInference()
.updateFromObjectCreation(n, constructorElt, p.getRegularStore());
}
return super.visitObjectCreation(n, p);
}
@Override
public TransferResult<V, S> visitMethodInvocation(
MethodInvocationNode n, TransferInput<V, S> in) {
S store = in.getRegularStore();
ExecutableElement method = n.getTarget().getMethod();
// Perform WPI before the store has been side-effected.
if (shouldPerformWholeProgramInference(n.getTree(), method)) {
// Updates the inferred parameter types of the invoked method.
analysis.atypeFactory.getWholeProgramInference().updateFromMethodInvocation(n, method, store);
}
Tree invocationTree = n.getTree();
// Determine the abstract value for the method call.
// look up the call's value from factory
V factoryValue = (invocationTree == null) ? null : getValueFromFactory(invocationTree, n);
// look up the call's value in the store (if possible)
V storeValue = store.getValue(n);
V resValue = moreSpecificValue(factoryValue, storeValue);
store.updateForMethodCall(n, analysis.atypeFactory, resValue);
// add new information based on postcondition
processPostconditions(n, store, method, invocationTree);
S thenStore = store;
S elseStore = thenStore.copy();
// add new information based on conditional postcondition
processConditionalPostconditions(n, method, invocationTree, thenStore, elseStore);
return new ConditionalTransferResult<>(
finishValue(resValue, thenStore, elseStore), thenStore, elseStore);
}
@Override
public TransferResult<V, S> visitInstanceOf(InstanceOfNode node, TransferInput<V, S> in) {
TransferResult<V, S> result = super.visitInstanceOf(node, in);
// The "reference type" is the type after "instanceof".
Tree refTypeTree = node.getTree().getType();
if (refTypeTree.getKind() == Tree.Kind.ANNOTATED_TYPE) {
AnnotatedTypeMirror refType = analysis.atypeFactory.getAnnotatedType(refTypeTree);
AnnotatedTypeMirror expType =
analysis.atypeFactory.getAnnotatedType(node.getTree().getExpression());
if (analysis.atypeFactory.getTypeHierarchy().isSubtype(refType, expType)
&& !refType.getAnnotations().equals(expType.getAnnotations())
&& !expType.getAnnotations().isEmpty()) {
JavaExpression expr = JavaExpression.fromTree(node.getTree().getExpression());
for (AnnotationMirror anno : refType.getAnnotations()) {
in.getRegularStore().insertOrRefine(expr, anno);
}
return new RegularTransferResult<>(result.getResultValue(), in.getRegularStore());
}
}
return result;
}
/**
* Returns true if whole-program inference should be performed. If the tree is in the scope of
* a @SuppressWarnings, then this method returns false.
*
* @param tree a tree
* @return whether to perform whole-program inference on the tree
*/
private boolean shouldPerformWholeProgramInference(Tree tree) {
return infer && (tree == null || !analysis.checker.shouldSuppressWarnings(tree, ""));
}
/**
* Returns true if whole-program inference should be performed. If the expressionTree or lhsTree
* is in the scope of a @SuppressWarnings, then this method returns false.
*
* @param expressionTree the right-hand side of an assignment
* @param lhsTree the left-hand side of an assignment
* @return whether to perform whole-program inference
*/
private boolean shouldPerformWholeProgramInference(Tree expressionTree, Tree lhsTree) {
// Check that infer is true and the tree isn't in scope of a @SuppressWarnings
// before calling InternalUtils.symbol(lhs).
if (!shouldPerformWholeProgramInference(expressionTree)) {
return false;
}
Element elt = TreeUtils.elementFromTree(lhsTree);
return !analysis.checker.shouldSuppressWarnings(elt, "");
}
/**
* Returns true if whole-program inference should be performed. If the tree or element is in the
* scope of a @SuppressWarnings, then this method returns false.
*
* @param tree a tree
* @param elt its element
* @return whether to perform whole-program inference
*/
private boolean shouldPerformWholeProgramInference(Tree tree, Element elt) {
return shouldPerformWholeProgramInference(tree)
&& !analysis.checker.shouldSuppressWarnings(elt, "");
}
/**
* Add information from the postconditions of a method to the store after an invocation.
*
* @param invocationNode a method call
* @param store a store; is side-effected by this method
* @param methodElement the method being called
* @param invocationTree the tree for the method call
*/
protected void processPostconditions(
MethodInvocationNode invocationNode,
S store,
ExecutableElement methodElement,
Tree invocationTree) {
ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod();
Set<Postcondition> postconditions = contractsUtils.getPostconditions(methodElement);
processPostconditionsAndConditionalPostconditions(
invocationNode, invocationTree, store, null, postconditions);
}
/**
* Add information from the conditional postconditions of a method to the stores after an
* invocation.
*
* @param invocationNode a method call
* @param methodElement the method being called
* @param invocationTree the tree for the method call
* @param thenStore the "then" store; is side-effected by this method
* @param elseStore the "else" store; is side-effected by this method
*/
protected void processConditionalPostconditions(
MethodInvocationNode invocationNode,
ExecutableElement methodElement,
Tree invocationTree,
S thenStore,
S elseStore) {
ContractsFromMethod contractsUtils = analysis.atypeFactory.getContractsFromMethod();
Set<ConditionalPostcondition> conditionalPostconditions =
contractsUtils.getConditionalPostconditions(methodElement);
processPostconditionsAndConditionalPostconditions(
invocationNode, invocationTree, thenStore, elseStore, conditionalPostconditions);
}
/**
* Add information from the postconditions and conditional postconditions of a method to the
* stores after an invocation.
*
* @param invocationNode a method call
* @param invocationTree the tree for the method call
* @param thenStore the "then" store; is side-effected by this method
* @param elseStore the "else" store; is side-effected by this method
* @param postconditions the postconditions
*/
private void processPostconditionsAndConditionalPostconditions(
MethodInvocationNode invocationNode,
Tree invocationTree,
S thenStore,
S elseStore,
Set<? extends Contract> postconditions) {
StringToJavaExpression stringToJavaExpr =
stringExpr ->
StringToJavaExpression.atMethodInvocation(stringExpr, invocationNode, analysis.checker);
for (Contract p : postconditions) {
// Viewpoint-adapt to the method use (the call site).
AnnotationMirror anno =
p.viewpointAdaptDependentTypeAnnotation(
analysis.atypeFactory, stringToJavaExpr, /*errorTree=*/ null);
String expressionString = p.expressionString;
try {
JavaExpression je = stringToJavaExpr.toJavaExpression(expressionString);
// "insertOrRefine" is called so that the postcondition information is added to any
// existing information rather than replacing it. If the called method is not
// side-effect-free, then the values that might have been changed by the method call
// are removed from the store before this method is called.
if (p.kind == Contract.Kind.CONDITIONALPOSTCONDITION) {
if (((ConditionalPostcondition) p).resultValue) {
thenStore.insertOrRefinePermitNondeterministic(je, anno);
} else {
elseStore.insertOrRefinePermitNondeterministic(je, anno);
}
} else {
thenStore.insertOrRefinePermitNondeterministic(je, anno);
}
} catch (JavaExpressionParseException e) {
// report errors here
if (e.isFlowParseError()) {
Object[] args = new Object[e.args.length + 1];
args[0] =
ElementUtils.getSimpleSignature(TreeUtils.elementFromUse(invocationNode.getTree()));
System.arraycopy(e.args, 0, args, 1, e.args.length);
analysis.checker.reportError(invocationTree, "flowexpr.parse.error.postcondition", args);
} else {
analysis.checker.report(invocationTree, e.getDiagMessage());
}
}
}
}
/**
* A case produces no value, but it may imply some facts about the argument to the switch
* statement.
*/
@Override
public TransferResult<V, S> visitCase(CaseNode n, TransferInput<V, S> in) {
S store = in.getRegularStore();
TransferResult<V, S> result =
new ConditionalTransferResult<>(
finishValue(null, store), in.getThenStore(), in.getElseStore(), false);
V caseValue = in.getValueOfSubNode(n.getCaseOperand());
AssignmentNode assign = (AssignmentNode) n.getSwitchOperand();
V switchValue = store.getValue(JavaExpression.fromNode(assign.getTarget()));
result =
strengthenAnnotationOfEqualTo(
result, n.getCaseOperand(), assign.getExpression(), caseValue, switchValue, false);
// Update value of switch temporary variable
result =
strengthenAnnotationOfEqualTo(
result, n.getCaseOperand(), assign.getTarget(), caseValue, switchValue, false);
return result;
}
/**
* In a cast {@code (@A C) e} of some expression {@code e} to a new type {@code @A C}, we usually
* take the annotation of the type {@code C} (here {@code @A}). However, if the inferred
* annotation of {@code e} is more precise, we keep that one.
*/
// @Override
// public TransferResult<V, S> visitTypeCast(TypeCastNode n,
// TransferInput<V, S> p) {
// TransferResult<V, S> result = super.visitTypeCast(n, p);
// V value = result.getResultValue();
// V operandValue = p.getValueOfSubNode(n.getOperand());
// // Normally we take the value of the type cast node. However, if the old
// // flow-refined value was more precise, we keep that value.
// V resultValue = moreSpecificValue(value, operandValue);
// result.setResultValue(resultValue);
// return result;
// }
/**
* Returns the abstract value of {@code (value1, value2)} that is more specific. If the two are
* incomparable, then {@code value1} is returned.
*/
public V moreSpecificValue(V value1, V value2) {
if (value1 == null) {
return value2;
}
if (value2 == null) {
return value1;
}
return value1.mostSpecific(value2, value1);
}
@Override
public TransferResult<V, S> visitVariableDeclaration(
VariableDeclarationNode n, TransferInput<V, S> p) {
S store = p.getRegularStore();
return new RegularTransferResult<>(finishValue(null, store), store);
}
@Override
public TransferResult<V, S> visitWideningConversion(
WideningConversionNode n, TransferInput<V, S> p) {
TransferResult<V, S> result = super.visitWideningConversion(n, p);
// Combine annotations from the operand with the wide type
V operandValue = p.getValueOfSubNode(n.getOperand());
V widenedValue = getWidenedValue(n.getType(), operandValue);
result.setResultValue(widenedValue);
return result;
}
/**
* Returns an abstract value with the given {@code type} and the annotations from {@code
* annotatedValue}, adapted for narrowing. This is only called at a narrowing conversion.
*
* @param type the type to narrow to
* @param annotatedValue the type to narrow from
* @return an abstract value with the given {@code type} and the annotations from {@code
* annotatedValue}; returns null if {@code annotatedValue} is null
*/
protected V getNarrowedValue(TypeMirror type, V annotatedValue) {
if (annotatedValue == null) {
return null;
}
Set<AnnotationMirror> narrowedAnnos =
analysis.atypeFactory.getNarrowedAnnotations(
annotatedValue.getAnnotations(),
annotatedValue.getUnderlyingType().getKind(),
type.getKind());
return analysis.createAbstractValue(narrowedAnnos, type);
}
/**
* Returns an abstract value with the given {@code type} and the annotations from {@code
* annotatedValue}, adapted for widening. This is only called at a widening conversion.
*
* @param type the type to widen to
* @param annotatedValue the type to widen from
* @return an abstract value with the given {@code type} and the annotations from {@code
* annotatedValue}; returns null if {@code annotatedValue} is null
*/
protected V getWidenedValue(TypeMirror type, V annotatedValue) {
if (annotatedValue == null) {
return null;
}
Set<AnnotationMirror> widenedAnnos =
analysis.atypeFactory.getWidenedAnnotations(
annotatedValue.getAnnotations(),
annotatedValue.getUnderlyingType().getKind(),
type.getKind());
return analysis.createAbstractValue(widenedAnnos, type);
}
@Override
public TransferResult<V, S> visitNarrowingConversion(
NarrowingConversionNode n, TransferInput<V, S> p) {
TransferResult<V, S> result = super.visitNarrowingConversion(n, p);
// Combine annotations from the operand with the narrow type
V operandValue = p.getValueOfSubNode(n.getOperand());
V narrowedValue = getNarrowedValue(n.getType(), operandValue);
result.setResultValue(narrowedValue);
return result;
}
@Override
public TransferResult<V, S> visitStringConversion(StringConversionNode n, TransferInput<V, S> p) {
TransferResult<V, S> result = super.visitStringConversion(n, p);
result.setResultValue(p.getValueOfSubNode(n.getOperand()));
return result;
}
/**
* Inserts newAnno as the value into all stores (conditional or not) in the result for node. This
* is a utility method for subclasses.
*
* @param result the TransferResult holding the stores to modify
* @param target the receiver whose value should be modified
* @param newAnno the new value
*/
public static void insertIntoStores(
TransferResult<CFValue, CFStore> result, JavaExpression target, AnnotationMirror newAnno) {
if (result.containsTwoStores()) {
result.getThenStore().insertValue(target, newAnno);
result.getElseStore().insertValue(target, newAnno);
} else {
result.getRegularStore().insertValue(target, newAnno);
}
}
}