dataflow.src.org.checkerframework.dataflow.util.PurityChecker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
The Checker Framework enhances Java’s type system to
make it more powerful and useful. This lets software developers
detect and prevent errors in their Java programs.
The Checker Framework includes compiler plug-ins ("checkers")
that find bugs or verify their absence. It also permits you to
write your own compiler plug-ins.
package org.checkerframework.dataflow.util;
/*>>>
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
*/
import org.checkerframework.dataflow.qual.Deterministic;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.Pure.Kind;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.Pair;
import org.checkerframework.javacutil.TreeUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import javax.lang.model.element.Element;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EmptyStatementTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.tools.javac.tree.TreeScanner;
/**
* A visitor that determines the purity (as defined by {@link
* org.checkerframework.dataflow.qual.SideEffectFree}, {@link org.checkerframework.dataflow.qual.Deterministic},
* and {@link org.checkerframework.dataflow.qual.Pure}) of a statement or expression. The
* entry point is method {@link #checkPurity}.
*
* @see SideEffectFree
* @see Deterministic
* @see Pure
*
* @author Stefan Heule
*
*/
public class PurityChecker {
/**
* Compute whether the given statement is
* side-effect-free, deterministic, or both.
* Returns a result that can be queried.
*/
public static PurityResult checkPurity(Tree statement,
AnnotationProvider annoProvider, boolean assumeSideEffectFree) {
PurityCheckerHelper helper = new PurityCheckerHelper(annoProvider, assumeSideEffectFree);
PurityResult res = helper.scan(statement, new PurityResult());
return res;
}
/**
* Result of the {@link PurityChecker}.
* Can be queried queried regarding whether a given tree was
* side-effect-free, deterministic, or both; also gives reasons if
* the answer is "no".
*/
public static class PurityResult {
protected final List> notSeFreeReasons;
protected final List> notDetReasons;
protected final List> notBothReasons;
protected EnumSet types;
public PurityResult() {
notSeFreeReasons = new ArrayList<>();
notDetReasons = new ArrayList<>();
notBothReasons = new ArrayList<>();
types = EnumSet.allOf(Pure.Kind.class);
}
public EnumSet getTypes() {
return types;
}
/** Is the method pure w.r.t. a given set of types? */
public boolean isPure(Collection kinds) {
return types.containsAll(kinds);
}
/**
* Get the {@code reason}s why the method is not side-effect-free.
*/
public List> getNotSeFreeReasons() {
return notSeFreeReasons;
}
/**
* Add {@code reason} as a reason why the method is not side-effect
* free.
*/
public void addNotSeFreeReason(Tree t, String msgId) {
notSeFreeReasons.add(Pair.of(t, msgId));
types.remove(Kind.SIDE_EFFECT_FREE);
}
/**
* Get the {@code reason}s why the method is not deterministic.
*/
public List> getNotDetReasons() {
return notDetReasons;
}
/**
* Add {@code reason} as a reason why the method is not deterministic.
*/
public void addNotDetReason(Tree t, String msgId) {
notDetReasons.add(Pair.of(t, msgId));
types.remove(Kind.DETERMINISTIC);
}
/**
* Get the {@code reason}s why the method is not both side-effect-free
* and deterministic.
*/
public List> getNotBothReasons() {
return notBothReasons;
}
/**
* Add {@code reason} as a reason why the method is not both side-effect
* free and deterministic.
*/
public void addNotBothReason(Tree t, String msgId) {
notBothReasons.add(Pair.of(t, msgId));
types.remove(Kind.DETERMINISTIC);
types.remove(Kind.SIDE_EFFECT_FREE);
}
}
/**
* Helper class to keep {@link PurityChecker}'s interface clean. The
* implementation is heavily based on {@link TreeScanner}, but some parts of
* the AST are skipped (such as types or modifiers). Furthermore, scanning
* works differently in that the input parameter (usually named {@code p})
* gets "threaded through", instead of using {@code reduce}.
*/
protected static class PurityCheckerHelper
extends SimpleTreeVisitor {
protected final AnnotationProvider annoProvider;
/** True if all methods should be assumed to be @SideEffectFree,
* for the purposes of org.checkerframework.dataflow analysis. */
private final boolean assumeSideEffectFree;
protected /*@Nullable*/ List methodParameter;
public PurityCheckerHelper(AnnotationProvider annoProvider, boolean assumeSideEffectFree) {
this.annoProvider = annoProvider;
this.assumeSideEffectFree = assumeSideEffectFree;
}
/**
* Scan a single node.
*/
public PurityResult scan(Tree node, PurityResult p) {
return node == null ? p : node.accept(this, p);
}
/**
* Scan a list of nodes.
*/
public PurityResult scan(Iterable nodes, PurityResult p) {
PurityResult r = p;
if (nodes != null) {
for (Tree node : nodes) {
r = scan(node, r);
}
}
return r;
}
@Override
protected PurityResult defaultAction(Tree node, PurityResult p) {
assert false : "this type of tree is unexpected here";
return null;
}
@Override
public PurityResult visitClass(ClassTree node, PurityResult p) {
return p;
}
@Override
public PurityResult visitVariable(VariableTree node, PurityResult p) {
return scan(node.getInitializer(), p);
}
@Override
public PurityResult visitEmptyStatement(EmptyStatementTree node,
PurityResult p) {
return p;
}
@Override
public PurityResult visitBlock(BlockTree node, PurityResult p) {
return scan(node.getStatements(), p);
}
@Override
public PurityResult visitDoWhileLoop(DoWhileLoopTree node,
PurityResult p) {
PurityResult r = scan(node.getStatement(), p);
r = scan(node.getCondition(), r);
return r;
}
@Override
public PurityResult visitWhileLoop(WhileLoopTree node, PurityResult p) {
PurityResult r = scan(node.getCondition(), p);
r = scan(node.getStatement(), r);
return r;
}
@Override
public PurityResult visitForLoop(ForLoopTree node, PurityResult p) {
PurityResult r = scan(node.getInitializer(), p);
r = scan(node.getCondition(), r);
r = scan(node.getUpdate(), r);
r = scan(node.getStatement(), r);
return r;
}
@Override
public PurityResult visitEnhancedForLoop(EnhancedForLoopTree node,
PurityResult p) {
PurityResult r = scan(node.getVariable(), p);
r = scan(node.getExpression(), r);
r = scan(node.getStatement(), r);
return r;
}
@Override
public PurityResult visitLabeledStatement(LabeledStatementTree node,
PurityResult p) {
return scan(node.getStatement(), p);
}
@Override
public PurityResult visitSwitch(SwitchTree node, PurityResult p) {
PurityResult r = scan(node.getExpression(), p);
r = scan(node.getCases(), r);
return r;
}
@Override
public PurityResult visitCase(CaseTree node, PurityResult p) {
PurityResult r = scan(node.getExpression(), p);
r = scan(node.getStatements(), r);
return r;
}
@Override
public PurityResult visitSynchronized(SynchronizedTree node,
PurityResult p) {
PurityResult r = scan(node.getExpression(), p);
r = scan(node.getBlock(), r);
return r;
}
@Override
public PurityResult visitTry(TryTree node, PurityResult p) {
PurityResult r = scan(node.getResources(), p);
r = scan(node.getBlock(), r);
r = scan(node.getCatches(), r);
r = scan(node.getFinallyBlock(), r);
return r;
}
@Override
public PurityResult visitCatch(CatchTree node, PurityResult p) {
p.addNotDetReason(node, "catch");
PurityResult r = scan(node.getParameter(), p);
r = scan(node.getBlock(), r);
return r;
}
@Override
public PurityResult visitConditionalExpression(
ConditionalExpressionTree node, PurityResult p) {
PurityResult r = scan(node.getCondition(), p);
r = scan(node.getTrueExpression(), r);
r = scan(node.getFalseExpression(), r);
return r;
}
@Override
public PurityResult visitIf(IfTree node, PurityResult p) {
PurityResult r = scan(node.getCondition(), p);
r = scan(node.getThenStatement(), r);
r = scan(node.getElseStatement(), r);
return r;
}
@Override
public PurityResult visitExpressionStatement(
ExpressionStatementTree node, PurityResult p) {
return scan(node.getExpression(), p);
}
@Override
public PurityResult visitBreak(BreakTree node, PurityResult p) {
return p;
}
@Override
public PurityResult visitContinue(ContinueTree node, PurityResult p) {
return p;
}
@Override
public PurityResult visitReturn(ReturnTree node, PurityResult p) {
return scan(node.getExpression(), p);
}
@Override
public PurityResult visitThrow(ThrowTree node, PurityResult p) {
return scan(node.getExpression(), p);
}
@Override
public PurityResult visitAssert(AssertTree node, PurityResult p) {
PurityResult r = scan(node.getCondition(), p);
r = scan(node.getDetail(), r);
return r;
}
@Override
public PurityResult visitMethodInvocation(MethodInvocationTree node,
PurityResult p) {
Element elt = TreeUtils.elementFromUse(node);
String reason = "call";
if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) {
p.addNotBothReason(node, reason);
} else {
boolean det = PurityUtils.isDeterministic(annoProvider, elt);
boolean seFree = (assumeSideEffectFree
|| PurityUtils.isSideEffectFree(annoProvider,
elt));
if (!det && !seFree) {
p.addNotBothReason(node, reason);
} else if (!det) {
p.addNotDetReason(node, reason);
} else if (!seFree) {
p.addNotSeFreeReason(node, reason);
}
}
PurityResult r = scan(node.getMethodSelect(), p);
r = scan(node.getArguments(), r);
return r;
}
@Override
public PurityResult visitNewClass(NewClassTree node, PurityResult p) {
Element methodElement = InternalUtils.symbol(node);
boolean sideEffectFree = (assumeSideEffectFree
|| PurityUtils.isSideEffectFree(annoProvider,
methodElement));
if (sideEffectFree) {
p.addNotDetReason(node, "object.creation");
} else {
p.addNotBothReason(node, "object.creation");
}
PurityResult r = scan(node.getEnclosingExpression(), p);
r = scan(node.getArguments(), r);
r = scan(node.getClassBody(), r);
return r;
}
@Override
public PurityResult visitNewArray(NewArrayTree node, PurityResult p) {
PurityResult r = scan(node.getDimensions(), p);
r = scan(node.getInitializers(), r);
return r;
}
@Override
public PurityResult visitLambdaExpression(LambdaExpressionTree node,
PurityResult p) {
PurityResult r = scan(node.getParameters(), p);
r = scan(node.getBody(), r);
return r;
}
@Override
public PurityResult visitParenthesized(ParenthesizedTree node,
PurityResult p) {
return scan(node.getExpression(), p);
}
@Override
public PurityResult visitAssignment(AssignmentTree node, PurityResult p) {
ExpressionTree variable = node.getVariable();
p = assignmentCheck(p, variable);
PurityResult r = scan(variable, p);
r = scan(node.getExpression(), r);
return r;
}
protected PurityResult assignmentCheck(PurityResult p,
ExpressionTree variable) {
if (TreeUtils.isFieldAccess(variable)) {
// rhs is a field access
p.addNotBothReason(variable, "assign.field");
} else if (variable instanceof ArrayAccessTree) {
// rhs is array access
p.addNotBothReason(variable, "assign.array");
} else {
// rhs is a local variable
assert isLocalVariable(variable);
}
return p;
}
protected boolean isLocalVariable(ExpressionTree variable) {
return variable instanceof IdentifierTree
&& !TreeUtils.isFieldAccess(variable);
}
@Override
public PurityResult visitCompoundAssignment(
CompoundAssignmentTree node, PurityResult p) {
ExpressionTree variable = node.getVariable();
p = assignmentCheck(p, variable);
PurityResult r = scan(variable, p);
r = scan(node.getExpression(), r);
return r;
}
@Override
public PurityResult visitUnary(UnaryTree node, PurityResult p) {
return scan(node.getExpression(), p);
}
@Override
public PurityResult visitBinary(BinaryTree node, PurityResult p) {
PurityResult r = scan(node.getLeftOperand(), p);
r = scan(node.getRightOperand(), r);
return r;
}
@Override
public PurityResult visitTypeCast(TypeCastTree node, PurityResult p) {
PurityResult r = scan(node.getExpression(), p);
return r;
}
@Override
public PurityResult visitInstanceOf(InstanceOfTree node, PurityResult p) {
PurityResult r = scan(node.getExpression(), p);
return r;
}
@Override
public PurityResult visitArrayAccess(ArrayAccessTree node,
PurityResult p) {
PurityResult r = scan(node.getExpression(), p);
r = scan(node.getIndex(), r);
return r;
}
@Override
public PurityResult visitMemberSelect(MemberSelectTree node,
PurityResult p) {
return scan(node.getExpression(), p);
}
@Override
public PurityResult visitMemberReference(MemberReferenceTree node,
PurityResult p) {
assert false : "this type of tree is unexpected here";
return null;
}
@Override
public PurityResult visitIdentifier(IdentifierTree node, PurityResult p) {
return p;
}
@Override
public PurityResult visitLiteral(LiteralTree node, PurityResult p) {
return p;
}
}
}