blob: 75523e858c00bc9ff3cb887474e37b9d3c161b9b [file] [log] [blame]
package org.checkerframework.checker.mustcall;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ObjectCreationNode;
import org.checkerframework.dataflow.cfg.node.StringConversionNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.trees.TreeBuilder;
/**
* Transfer function for the must-call type system. Its primary purposes are (1) to create temporary
* variables for expressions (which allow those expressions to have refined information in the
* store, which the consistency checker can use), and (2) to reset refined information when a method
* annotated with @CreatesObligation is called.
*/
public class MustCallTransfer extends CFTransfer {
/** For building new AST nodes. */
private final TreeBuilder treeBuilder;
/** The type factory. */
private MustCallAnnotatedTypeFactory atypeFactory;
/**
* A cache for the default type for java.lang.String, to avoid needing to look it up for every
* implicit string conversion. See {@link #getDefaultStringType(StringConversionNode)}.
*/
private @MonotonicNonNull AnnotationMirror defaultStringType;
/**
* Create a MustCallTransfer.
*
* @param analysis the analysis
*/
public MustCallTransfer(CFAnalysis analysis) {
super(analysis);
atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory();
ProcessingEnvironment env = atypeFactory.getChecker().getProcessingEnvironment();
treeBuilder = new TreeBuilder(env);
}
@Override
public TransferResult<CFValue, CFStore> visitStringConversion(
StringConversionNode n, TransferInput<CFValue, CFStore> p) {
// Implicit String conversions should assume that the String's type is
// whatever the default for String is, not that the conversion is polymorphic.
TransferResult<CFValue, CFStore> result = super.visitStringConversion(n, p);
LocalVariableNode temp = getOrCreateTempVar(n);
if (temp != null) {
AnnotationMirror defaultStringType = getDefaultStringType(n);
JavaExpression localExp = JavaExpression.fromNode(temp);
insertIntoStores(result, localExp, defaultStringType);
}
return result;
}
/**
* Returns the default type for java.lang.String, which is cached in this class to avoid
* recomputing it. If the cache is currently unset, this method sets it.
*
* @param n a string conversion node, from which the type is computed if it is required
* @return the type of java.lang.String
*/
private AnnotationMirror getDefaultStringType(StringConversionNode n) {
if (this.defaultStringType == null) {
this.defaultStringType =
atypeFactory
.getAnnotatedType(TypesUtils.getTypeElement(n.getType()))
.getAnnotationInHierarchy(atypeFactory.TOP);
}
return this.defaultStringType;
}
@Override
public TransferResult<CFValue, CFStore> visitMethodInvocation(
MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, in);
updateStoreWithTempVar(result, n);
if (!atypeFactory.getChecker().hasOption(MustCallChecker.NO_ACCUMULATION_FRAMES)) {
List<JavaExpression> targetExprs =
CreatesObligationElementSupplier.getCreatesObligationExpressions(
n, atypeFactory, atypeFactory);
for (JavaExpression targetExpr : targetExprs) {
AnnotationMirror defaultType =
atypeFactory
.getAnnotatedType(TypesUtils.getTypeElement(targetExpr.getType()))
.getAnnotationInHierarchy(atypeFactory.TOP);
if (result.containsTwoStores()) {
CFStore thenStore = result.getThenStore();
lubWithStoreValue(thenStore, targetExpr, defaultType);
CFStore elseStore = result.getElseStore();
lubWithStoreValue(elseStore, targetExpr, defaultType);
} else {
CFStore store = result.getRegularStore();
lubWithStoreValue(store, targetExpr, defaultType);
}
}
}
return result;
}
/**
* Computes the LUB of the current value in the store for expr, if it exists, and defaultType.
* Inserts that LUB into the store as the new value for expr.
*
* @param store a CFStore
* @param expr an expression that might be in the store
* @param defaultType the default type of the expression's static type
*/
private void lubWithStoreValue(CFStore store, JavaExpression expr, AnnotationMirror defaultType) {
CFValue value = store.getValue(expr);
CFValue defaultTypeAsCFValue =
analysis.createSingleAnnotationValue(defaultType, expr.getType());
CFValue newValue = defaultTypeAsCFValue.leastUpperBound(value);
store.clearValue(expr);
store.insertValue(expr, newValue);
}
@Override
public TransferResult<CFValue, CFStore> visitObjectCreation(
ObjectCreationNode node, TransferInput<CFValue, CFStore> input) {
TransferResult<CFValue, CFStore> result = super.visitObjectCreation(node, input);
updateStoreWithTempVar(result, node);
return result;
}
@Override
public TransferResult<CFValue, CFStore> visitTernaryExpression(
TernaryExpressionNode node, TransferInput<CFValue, CFStore> input) {
TransferResult<CFValue, CFStore> result = super.visitTernaryExpression(node, input);
updateStoreWithTempVar(result, node);
return result;
}
/**
* This method either creates or looks up the temp var t for node, and then updates the store to
* give t the same type as node.
*
* @param node the node to be assigned to a temporary variable
* @param result the transfer result containing the store to be modified
*/
public void updateStoreWithTempVar(TransferResult<CFValue, CFStore> result, Node node) {
// Must-call obligations on primitives are not supported.
if (!TypesUtils.isPrimitiveOrBoxed(node.getType())) {
LocalVariableNode temp = getOrCreateTempVar(node);
if (temp != null) {
JavaExpression localExp = JavaExpression.fromNode(temp);
AnnotationMirror anm =
atypeFactory
.getAnnotatedType(node.getTree())
.getAnnotationInHierarchy(atypeFactory.TOP);
insertIntoStores(result, localExp, anm == null ? atypeFactory.TOP : anm);
}
}
}
/**
* Either returns the temporary variable associated with node, or creates one if one does not
* exist.
*
* @param node a node, which must be an expression (not a statement)
* @return a temporary variable node representing {@code node} that can be placed into a store
*/
private @Nullable LocalVariableNode getOrCreateTempVar(Node node) {
LocalVariableNode localVariableNode = atypeFactory.tempVars.get(node.getTree());
if (localVariableNode == null) {
VariableTree temp = createTemporaryVar(node);
if (temp != null) {
IdentifierTree identifierTree = treeBuilder.buildVariableUse(temp);
localVariableNode = new LocalVariableNode(identifierTree);
localVariableNode.setInSource(true);
atypeFactory.tempVars.put(node.getTree(), localVariableNode);
}
}
return localVariableNode;
}
/**
* Creates a variable declaration for the given expression node, if possible.
*
* @param node an expression node
* @return a variable tree for the node, or null if an appropriate containing element cannot be
* located
*/
protected @Nullable VariableTree createTemporaryVar(Node node) {
ExpressionTree tree = (ExpressionTree) node.getTree();
TypeMirror treeType = TreeUtils.typeOf(tree);
Element enclosingElement;
TreePath path = atypeFactory.getPath(tree);
if (path == null) {
enclosingElement = TreeUtils.elementFromTree(tree).getEnclosingElement();
} else {
ClassTree classTree = TreePathUtil.enclosingClass(path);
enclosingElement = TreeUtils.elementFromTree(classTree);
}
if (enclosingElement == null) {
return null;
}
// Declare and initialize a new, unique variable
VariableTree tmpVarTree =
treeBuilder.buildVariableDecl(treeType, uniqueName("temp-var"), enclosingElement, tree);
return tmpVarTree;
}
/** A unique identifier counter for node names. */
protected long uid = 0;
/**
* Creates a unique, abitrary string that can be used as a name for a temporary variable, using
* the given prefix. Can be used up to Long.MAX_VALUE times.
*
* @param prefix the prefix for the name
* @return a unique name that starts with the prefix
*/
protected String uniqueName(String prefix) {
return prefix + "-" + uid++;
}
}