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

org.checkerframework.dataflow.util.PurityChecker Maven / Gradle / Ivy

Go to download

dataflow-shaded is a dataflow framework based on the javac compiler. It differs from the org.checkerframework:dataflow artifact in two ways. First, the packages in this artifact have been renamed to org.checkerframework.shaded.*. Second, unlike the dataflow artifact, this artifact contains the dependencies it requires.

There is a newer version: 3.42.0-eisop5
Show newest version
package org.checkerframework.dataflow.util;

import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;

import org.checkerframework.dataflow.qual.Deterministic;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.javacutil.AnnotationProvider;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.plumelib.util.IPair;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

/**
 * 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
 */
public class PurityChecker {

    /**
     * Compute whether the given statement is side-effect-free, deterministic, or both. Returns a
     * result that can be queried.
     *
     * @param statement the statement to check
     * @param annoProvider the annotation provider
     * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree
     * @param assumeDeterministic true if all methods should be assumed to be @Deterministic
     * @param assumePureGetters true if all getter methods should be assumed to be @Pure
     * @return information about whether the given statement is side-effect-free, deterministic, or
     *     both
     */
    public static PurityResult checkPurity(
            TreePath statement,
            AnnotationProvider annoProvider,
            boolean assumeSideEffectFree,
            boolean assumeDeterministic,
            boolean assumePureGetters) {
        PurityCheckerHelper helper =
                new PurityCheckerHelper(
                        annoProvider, assumeSideEffectFree, assumeDeterministic, assumePureGetters);
        helper.scan(statement, null);
        return helper.purityResult;
    }

    /**
     * Result of the {@link PurityChecker}. Can be 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 {

        /** Reasons that the referenced method is not side-effect-free. */
        protected final List> notSEFreeReasons = new ArrayList<>(1);

        /** Reasons that the referenced method is not deterministic. */
        protected final List> notDetReasons = new ArrayList<>(1);

        /** Reasons that the referenced method is not side-effect-free and deterministic. */
        protected final List> notBothReasons = new ArrayList<>(1);

        /**
         * Contains all the varieties of purity that the expression has. Starts out with all
         * varieties, and elements are removed from it as violations are found.
         */
        protected EnumSet kinds = EnumSet.allOf(Pure.Kind.class);

        /**
         * Return the kinds of purity that the method has.
         *
         * @return the kinds of purity that the method has
         */
        public EnumSet getKinds() {
            return kinds;
        }

        /**
         * Is the method pure w.r.t. a given set of kinds?
         *
         * @param otherKinds the varieties of purity to check
         * @return true if the method is pure with respect to all the given kinds
         */
        public boolean isPure(EnumSet otherKinds) {
            return kinds.containsAll(otherKinds);
        }

        /**
         * Get the reasons why the method is not side-effect-free.
         *
         * @return the reasons why the method is not side-effect-free
         */
        public List> getNotSEFreeReasons() {
            return notSEFreeReasons;
        }

        /**
         * Add a reason why the method is not side-effect-free.
         *
         * @param t a tree
         * @param msgId why the tree is not side-effect-free
         */
        public void addNotSEFreeReason(Tree t, String msgId) {
            notSEFreeReasons.add(IPair.of(t, msgId));
            kinds.remove(Pure.Kind.SIDE_EFFECT_FREE);
        }

        /**
         * Get the reasons why the method is not deterministic.
         *
         * @return the reasons why the method is not deterministic
         */
        public List> getNotDetReasons() {
            return notDetReasons;
        }

        /**
         * Add a reason why the method is not deterministic.
         *
         * @param t a tree
         * @param msgId why the tree is not deterministic
         */
        public void addNotDetReason(Tree t, String msgId) {
            notDetReasons.add(IPair.of(t, msgId));
            kinds.remove(Pure.Kind.DETERMINISTIC);
        }

        /**
         * Get the reasons why the method is not both side-effect-free and deterministic.
         *
         * @return the reasons why the method is not both side-effect-free and deterministic
         */
        public List> getNotBothReasons() {
            return notBothReasons;
        }

        /**
         * Add a reason why the method is not both side-effect-free and deterministic.
         *
         * @param t tree
         * @param msgId why the tree is not deterministic and side-effect-free
         */
        public void addNotBothReason(Tree t, String msgId) {
            notBothReasons.add(IPair.of(t, msgId));
            kinds.remove(Pure.Kind.DETERMINISTIC);
            kinds.remove(Pure.Kind.SIDE_EFFECT_FREE);
        }

        @Override
        public String toString() {
            return String.join(
                    System.lineSeparator(),
                    "PurityResult{",
                    "  notSEF: " + notSEFreeReasons,
                    "  notDet: " + notDetReasons,
                    "  notBoth: " + notBothReasons,
                    "}");
        }
    }

    // TODO: It would be possible to improve efficiency by visiting fewer nodes.  This would require
    // overriding more visit* methods.  I'm not sure whether such an optimization would be worth it.

    /**
     * Helper class to keep {@link PurityChecker}'s interface clean.
     *
     * 

The scanner is run on a single statement, not on a class or method. */ protected static class PurityCheckerHelper extends TreePathScanner { /** The purity result. */ PurityResult purityResult = new PurityResult(); /** The annotation provider (typically an AnnotatedTypeFactory). */ 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; /** * True if all methods should be assumed to be @Deterministic, for the purposes of * org.checkerframework.dataflow analysis. */ private final boolean assumeDeterministic; /** * True if all getter methods should be assumed to be @SideEffectFree and @Deterministic, * for the purposes of org.checkerframework.dataflow analysis. */ private final boolean assumePureGetters; /** * Create a PurityCheckerHelper. * * @param annoProvider the annotation provider * @param assumeSideEffectFree true if all methods should be assumed to be @SideEffectFree * @param assumeDeterministic true if all methods should be assumed to be @Deterministic * @param assumePureGetters true if getter methods should be assumed to be @Pure */ public PurityCheckerHelper( AnnotationProvider annoProvider, boolean assumeSideEffectFree, boolean assumeDeterministic, boolean assumePureGetters) { this.annoProvider = annoProvider; this.assumeSideEffectFree = assumeSideEffectFree; this.assumeDeterministic = assumeDeterministic; this.assumePureGetters = assumePureGetters; } @Override public Void visitCatch(CatchTree tree, Void ignore) { purityResult.addNotDetReason(tree, "catch"); return super.visitCatch(tree, ignore); } /** Represents a method that is both deterministic and side-effect free. */ private static final EnumSet detAndSeFree = EnumSet.of(Pure.Kind.DETERMINISTIC, Pure.Kind.SIDE_EFFECT_FREE); @Override public Void visitMethodInvocation(MethodInvocationTree tree, Void ignore) { ExecutableElement elt = TreeUtils.elementFromUse(tree); if (!PurityUtils.hasPurityAnnotation(annoProvider, elt)) { purityResult.addNotBothReason(tree, "call"); } else { EnumSet purityKinds = ((assumeDeterministic && assumeSideEffectFree) || (assumePureGetters && ElementUtils.isGetter(elt))) // Avoid computation if not necessary ? detAndSeFree : PurityUtils.getPurityKinds(annoProvider, elt); boolean det = assumeDeterministic || purityKinds.contains(Pure.Kind.DETERMINISTIC); boolean seFree = assumeSideEffectFree || purityKinds.contains(Pure.Kind.SIDE_EFFECT_FREE); if (!det && !seFree) { purityResult.addNotBothReason(tree, "call"); } else if (!det) { purityResult.addNotDetReason(tree, "call"); } else if (!seFree) { purityResult.addNotSEFreeReason(tree, "call"); } } return super.visitMethodInvocation(tree, ignore); } @Override public Void visitNewClass(NewClassTree tree, Void ignore) { // Ordinarily, "new MyClass()" is forbidden. It is permitted, however, when it is the // expression in "throw EXPR;". (In the future, more expressions could be permitted.) // // The expression in "throw EXPR;" is allowed to be non-@Deterministic, so long as it is // not within a catch block that could catch an exception that the statement throws. // For example, EXPR can be object creation (a "new" expression) or can call a // non-deterministic method. // // Coarse rule (currently implemented): // * permit only "throw new SomeExpression(args)", where the constructor is // @SideEffectFree and the args are pure, and forbid all enclosing try statements // that have a catch clause. // More precise rule: // * permit other non-deterministic expresssions within throw (at which time move this // logic to visitThrow()). // * the only bad try statements are those with a catch block that is: // * unchecked exceptions // * checked = Exception or lower, but excluding RuntimeException and its // subclasses // * super- or sub-classes of the type of _expr_ // * if _expr_ is exactly "new SomeException", this can be changed to just // "superclasses of SomeException". // * super- or sub-classes of exceptions declared to be thrown by any component of // _expr_. // * need to check every containing try statement, not just the nearest enclosing // one. // Object creation is usually prohibited, but permit "throw new SomeException();" if it // is not contained within any try statement that has a catch clause. (There is no need // to check the latter condition, because the Purity Checker forbids all catch // statements.) Tree parent = getCurrentPath().getParentPath().getLeaf(); boolean okThrowDeterministic = parent.getKind() == Tree.Kind.THROW; ExecutableElement ctorElement = TreeUtils.elementFromUse(tree); boolean deterministic = assumeDeterministic || okThrowDeterministic // No need to check assumePureGetters because a constructor is never a // getter. || PurityUtils.isDeterministic(annoProvider, ctorElement); boolean sideEffectFree = assumeSideEffectFree || PurityUtils.isSideEffectFree(annoProvider, ctorElement); // This does not use "addNotBothReason" because the reasons are different: one is // because the constructor is called at all, and the other is because the constuctor is // not side-effect-free. if (!deterministic) { purityResult.addNotDetReason(tree, "object.creation"); } if (!sideEffectFree) { purityResult.addNotSEFreeReason(tree, "call"); } // TODO: if okThrowDeterministic, permit arguments to the newClass to be // non-deterministic (don't add those to purityResult), but still don't permit them to // have side effects. This should probably wait until a rewrite of the Purity Checker. return super.visitNewClass(tree, ignore); } @Override public Void visitAssignment(AssignmentTree tree, Void ignore) { ExpressionTree variable = tree.getVariable(); assignmentCheck(variable); return super.visitAssignment(tree, ignore); } @Override public Void visitUnary(UnaryTree tree, Void ignore) { switch (tree.getKind()) { case POSTFIX_DECREMENT: case POSTFIX_INCREMENT: case PREFIX_DECREMENT: case PREFIX_INCREMENT: ExpressionTree expression = tree.getExpression(); assignmentCheck(expression); break; default: // Nothing to do break; } return super.visitUnary(tree, ignore); } /** * Check whether {@code variable} is permitted on the left-hand-side of an assignment. * * @param variable the lhs to check */ protected void assignmentCheck(ExpressionTree variable) { variable = TreeUtils.withoutParens(variable); VariableElement fieldElt = TreeUtils.asFieldAccess(variable); if (fieldElt != null && isFieldInCurrentClass(fieldElt) && TreePathUtil.inConstructor(getCurrentPath())) { // assigning a field in a constructor // TODO: add a check for ArrayAccessTree too. return; } if (TreeUtils.isFieldAccess(variable)) { // lhs is a field access purityResult.addNotBothReason(variable, "assign.field"); } else if (variable instanceof ArrayAccessTree) { // lhs is array access purityResult.addNotBothReason(variable, "assign.array"); } else { // lhs is a local variable assert isLocalVariable(variable); } } /** * Returns true if the given field is defined by the current class. * * @param fieldElt a field * @return true if the given field is defined by the current class */ private boolean isFieldInCurrentClass(VariableElement fieldElt) { ClassTree currentTypeTree = TreePathUtil.enclosingClass(getCurrentPath()); assert currentTypeTree != null : "@AssumeAssertion(nullness)"; TypeElement currentType = TreeUtils.elementFromDeclaration(currentTypeTree); assert currentType != null : "@AssumeAssertion(nullness)"; TypeElement definesField = ElementUtils.enclosingTypeElement(fieldElt); assert definesField != null : "@AssumeAssertion(nullness)"; return currentType.equals(definesField); } /** * Checks if the argument is a local variable. * * @param variable the tree to check * @return true if the argument is a local variable */ protected boolean isLocalVariable(ExpressionTree variable) { return variable instanceof IdentifierTree && !TreeUtils.isFieldAccess(variable); } @Override public Void visitCompoundAssignment(CompoundAssignmentTree tree, Void ignore) { ExpressionTree variable = tree.getVariable(); assignmentCheck(variable); return super.visitCompoundAssignment(tree, ignore); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy