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

com.google.errorprone.refaster.PlaceholderUnificationVisitor Maven / Gradle / Ivy

There is a newer version: 2.27.1
Show newest version
/*
 * Copyright 2014 The Error Prone Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.errorprone.refaster;

import static com.google.common.base.Preconditions.checkState;

import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.errorprone.refaster.PlaceholderUnificationVisitor.State;
import com.google.errorprone.refaster.UPlaceholderExpression.PlaceholderParamIdent;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.RuntimeVersion;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.DoWhileLoopTree;
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.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.StatementTree;
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.TreeVisitor;
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.code.Types;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCArrayAccess;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCAssignOp;
import com.sun.tools.javac.tree.JCTree.JCBinary;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCCatch;
import com.sun.tools.javac.tree.JCTree.JCConditional;
import com.sun.tools.javac.tree.JCTree.JCDoWhileLoop;
import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCForLoop;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCInstanceOf;
import com.sun.tools.javac.tree.JCTree.JCLabeledStatement;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCNewArray;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCParens;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSwitch;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.JCTree.JCTypeCast;
import com.sun.tools.javac.tree.JCTree.JCUnary;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCWhileLoop;
import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;
import java.util.EnumSet;
import java.util.Map;
import java.util.function.BiFunction;
import javax.annotation.Nullable;

/**
 * Given a tree as input, returns all the ways this placeholder invocation could be matched with
 * that tree, represented as States, containing the {@code Unifier}, the list of all parameters of
 * the placeholder method that were unified with subtrees of the given tree, and a {@code
 * JCExpression} representing the implementation of the placeholder method, with references to the
 * placeholder parameters replaced with a corresponding {@code PlaceholderParamIdent}.
 */
@AutoValue
abstract class PlaceholderUnificationVisitor
    extends SimpleTreeVisitor>, State> {

  /**
   * Represents the state of a placeholder unification in progress, including the current unifier
   * state, the parameters of the placeholder method that have been bound, and a result used to
   * store additional state.
   */
  @AutoValue
  abstract static class State {
    static  State create(
        List seenParameters, Unifier unifier, @Nullable R result) {
      return new AutoValue_PlaceholderUnificationVisitor_State<>(seenParameters, unifier, result);
    }

    public abstract List seenParameters();

    public abstract Unifier unifier();

    @Nullable
    public abstract R result();

    public  State withResult(R2 result) {
      return create(seenParameters(), unifier(), result);
    }

    public State fork() {
      return create(seenParameters(), unifier().fork(), result());
    }
  }

  public static PlaceholderUnificationVisitor create(
      TreeMaker maker, Map arguments) {
    return new AutoValue_PlaceholderUnificationVisitor(maker, ImmutableMap.copyOf(arguments));
  }

  abstract TreeMaker maker();

  abstract ImmutableMap arguments();

  /**
   * Returns all the ways this tree might be unified with the arguments to this placeholder
   * invocation. That is, if the placeholder invocation looks like {@code placeholder(arg1, arg2,
   * ...)}, then the {@code Choice} will contain any ways this tree can be unified with {@code
   * arg1}, {@code arg2}, or the other arguments.
   */
  Choice> tryBindArguments(ExpressionTree node, State state) {
    return Choice.from(arguments().entrySet())
        .thenChoose(
            (Map.Entry entry) ->
                unifyParam(entry.getKey(), entry.getValue(), node, state.fork()));
  }

  private Choice> unifyParam(
      UVariableDecl placeholderParam,
      UExpression placeholderArg,
      ExpressionTree toUnify,
      State state) {
    return placeholderArg
        .unify(toUnify, state.unifier())
        .transform(
            (Unifier unifier) ->
                State.create(
                    state.seenParameters().prepend(placeholderParam),
                    unifier,
                    new PlaceholderParamIdent(placeholderParam, unifier.getContext())));
  }

  public Choice> unify(@Nullable Tree node, State state) {
    if (node instanceof ExpressionTree) {
      return unifyExpression((ExpressionTree) node, state);
    } else if (node == null) {
      return Choice.of(state.withResult(null));
    } else {
      return node.accept(this, state);
    }
  }

  public Choice>> unify(
      @Nullable Iterable nodes, State state) {
    if (nodes == null) {
      return Choice.of(state.>withResult(null));
    }
    Choice>> choice = Choice.of(state.withResult(List.nil()));
    for (Tree node : nodes) {
      choice =
          choice.thenChoose(
              (State> s) ->
                  unify(node, s)
                      .transform(
                          treeState ->
                              treeState.withResult(s.result().prepend(treeState.result()))));
    }
    return choice.transform(s -> s.withResult(s.result().reverse()));
  }

  static boolean equivalentExprs(Unifier unifier, JCExpression expr1, JCExpression expr2) {
    return expr1.type != null
        && expr2.type != null
        && Types.instance(unifier.getContext()).isSameType(expr2.type, expr1.type)
        && expr2.toString().equals(expr1.toString());
  }

  /**
   * Verifies that the given tree does not directly conflict with an already-bound {@code
   * UFreeIdent} or {@code ULocalVarIdent}.
   */
  static final TreeVisitor FORBIDDEN_REFERENCE_VISITOR =
      new SimpleTreeVisitor() {
        @Override
        protected Boolean defaultAction(Tree node, Unifier unifier) {
          if (!(node instanceof JCExpression)) {
            return false;
          }
          JCExpression expr = (JCExpression) node;
          for (UFreeIdent.Key key :
              Iterables.filter(unifier.getBindings().keySet(), UFreeIdent.Key.class)) {
            JCExpression keyBinding = unifier.getBinding(key);
            if (equivalentExprs(unifier, expr, keyBinding)) {
              return true;
            }
          }
          return false;
        }

        @Override
        public Boolean visitIdentifier(IdentifierTree node, Unifier unifier) {
          for (LocalVarBinding localBinding :
              Iterables.filter(unifier.getBindings().values(), LocalVarBinding.class)) {
            if (localBinding.getSymbol().equals(ASTHelpers.getSymbol(node))) {
              return true;
            }
          }
          return defaultAction(node, unifier);
        }
      };

  /**
   * Returns all the ways this placeholder invocation might unify with the specified tree: either by
   * unifying the entire tree with an argument to the placeholder invocation, or by recursing on the
   * subtrees.
   */
  @SuppressWarnings("unchecked")
  public Choice> unifyExpression(
      @Nullable ExpressionTree node, State state) {
    if (node == null) {
      return Choice.of(state.withResult(null));
    }
    Choice> tryBindArguments =
        tryBindArguments(node, state);
    if (!node.accept(FORBIDDEN_REFERENCE_VISITOR, state.unifier())) {
      return tryBindArguments.or((Choice) node.accept(this, state));
    } else {
      return tryBindArguments;
    }
  }

  /**
   * Returns all the ways this placeholder invocation might unify with the specified list of trees.
   */
  public Choice>> unifyExpressions(
      @Nullable Iterable nodes, State state) {
    return unify(nodes, state)
        .transform(s -> s.withResult(List.convert(JCExpression.class, s.result())));
  }

  @SuppressWarnings("unchecked")
  public Choice> unifyStatement(
      @Nullable StatementTree node, State state) {
    return (Choice>) unify(node, state);
  }

  public Choice>> unifyStatements(
      @Nullable Iterable nodes, State state) {
    return chooseSubtrees(
        state, s -> unify(nodes, s), stmts -> List.convert(JCStatement.class, stmts));
  }

  @Override
  protected Choice> defaultAction(Tree node, State state) {
    return Choice.of(state.withResult((JCTree) node));
  }

  /**
   * This method, and its overloads, take
   *
   * 
    *
  1. an initial state *
  2. functions that, given one state, return a branch choosing a subtree *
  3. a function that takes pieces of a tree type and recomposes them *
*/ private static Choice> chooseSubtrees( State state, Function, Choice>> choice1, Function finalizer) { return choice1.apply(state).transform(s -> s.withResult(finalizer.apply(s.result()))); } private static Choice> chooseSubtrees( State state, Function, Choice>> choice1, Function, Choice>> choice2, BiFunction finalizer) { return choice1 .apply(state) .thenChoose( s1 -> choice2 .apply(s1) .transform(s2 -> s2.withResult(finalizer.apply(s1.result(), s2.result())))); } @FunctionalInterface private interface TriFunction { R apply(T1 t1, T2 t2, T3 t3); } private static Choice> chooseSubtrees( State state, Function, Choice>> choice1, Function, Choice>> choice2, Function, Choice>> choice3, TriFunction finalizer) { return choice1 .apply(state) .thenChoose( s1 -> choice2 .apply(s1) .thenChoose( s2 -> choice3 .apply(s2) .transform( s3 -> s3.withResult( finalizer.apply( s1.result(), s2.result(), s3.result()))))); } @FunctionalInterface private interface QuadFunction { R apply(T1 t1, T2 t2, T3 t3, T4 t4); } private static Choice> chooseSubtrees( State state, Function, Choice>> choice1, Function, Choice>> choice2, Function, Choice>> choice3, Function, Choice>> choice4, QuadFunction finalizer) { return choice1 .apply(state) .thenChoose( s1 -> choice2 .apply(s1) .thenChoose( s2 -> choice3 .apply(s2) .thenChoose( s3 -> choice4 .apply(s3) .transform( s4 -> s4.withResult( finalizer.apply( s1.result(), s2.result(), s3.result(), s4.result())))))); } @Override public Choice> visitArrayAccess(ArrayAccessTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), s -> unifyExpression(node.getIndex(), s), maker()::Indexed); } @Override public Choice> visitBinary(BinaryTree node, State state) { Tag tag = ((JCBinary) node).getTag(); return chooseSubtrees( state, s -> unifyExpression(node.getLeftOperand(), s), s -> unifyExpression(node.getRightOperand(), s), (l, r) -> maker().Binary(tag, l, r)); } @Override public Choice> visitMethodInvocation( MethodInvocationTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getMethodSelect(), s), s -> unifyExpressions(node.getArguments(), s), (select, args) -> maker().Apply(null, select, args)); } @Override public Choice> visitMemberSelect(MemberSelectTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), expr -> maker().Select(expr, (Name) node.getIdentifier())); } @Override public Choice> visitParenthesized(ParenthesizedTree node, State state) { return chooseSubtrees(state, s -> unifyExpression(node.getExpression(), s), maker()::Parens); } private static final ImmutableSet MUTATING_UNARY_TAGS = ImmutableSet.copyOf(EnumSet.of(Tag.PREINC, Tag.PREDEC, Tag.POSTINC, Tag.POSTDEC)); @Override public Choice> visitUnary(UnaryTree node, State state) { Tag tag = ((JCUnary) node).getTag(); return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), expr -> maker().Unary(tag, expr)) .condition( s -> !MUTATING_UNARY_TAGS.contains(tag) || !(s.result().getExpression() instanceof PlaceholderParamIdent)); } @Override public Choice> visitTypeCast(TypeCastTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), expr -> maker().TypeCast((JCTree) node.getType(), expr)); } @Override public Choice> visitInstanceOf(InstanceOfTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), expr -> maker().TypeTest(expr, (JCTree) node.getType())); } @Override public Choice> visitNewClass(NewClassTree node, State state) { if (node.getEnclosingExpression() != null || (node.getTypeArguments() != null && !node.getTypeArguments().isEmpty()) || node.getClassBody() != null) { return Choice.none(); } return chooseSubtrees( state, s -> unifyExpression(node.getIdentifier(), s), s -> unifyExpressions(node.getArguments(), s), (ident, args) -> maker().NewClass(null, null, ident, args, null)); } @Override public Choice> visitNewArray(NewArrayTree node, State state) { return chooseSubtrees( state, s -> unifyExpressions(node.getDimensions(), s), s -> unifyExpressions(node.getInitializers(), s), (dims, inits) -> maker().NewArray((JCExpression) node.getType(), dims, inits)); } @Override public Choice> visitConditionalExpression( ConditionalExpressionTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getCondition(), s), s -> unifyExpression(node.getTrueExpression(), s), s -> unifyExpression(node.getFalseExpression(), s), maker()::Conditional); } @Override public Choice> visitAssignment(AssignmentTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getVariable(), s), s -> unifyExpression(node.getExpression(), s), maker()::Assign) .condition(s -> !(s.result().getVariable() instanceof PlaceholderParamIdent)); } @Override public Choice> visitCompoundAssignment( CompoundAssignmentTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getVariable(), s), s -> unifyExpression(node.getExpression(), s), (variable, expr) -> maker().Assignop(((JCAssignOp) node).getTag(), variable, expr)) .condition(assignOp -> !(assignOp.result().getVariable() instanceof PlaceholderParamIdent)); } @Override public Choice> visitExpressionStatement( ExpressionStatementTree node, State state) { return chooseSubtrees(state, s -> unifyExpression(node.getExpression(), s), maker()::Exec); } @Override public Choice> visitBlock(BlockTree node, State state) { return chooseSubtrees( state, s -> unifyStatements(node.getStatements(), s), stmts -> maker().Block(0, stmts)); } @Override public Choice> visitThrow(ThrowTree node, State state) { return chooseSubtrees(state, s -> unifyExpression(node.getExpression(), s), maker()::Throw); } @Override public Choice> visitEnhancedForLoop( EnhancedForLoopTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), s -> unifyStatement(node.getStatement(), s), (expr, stmt) -> maker().ForeachLoop((JCVariableDecl) node.getVariable(), expr, stmt)); } @Override public Choice> visitIf(IfTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getCondition(), s), s -> unifyStatement(node.getThenStatement(), s), s -> unifyStatement(node.getElseStatement(), s), maker()::If); } @Override public Choice> visitDoWhileLoop(DoWhileLoopTree node, State state) { return chooseSubtrees( state, s -> unifyStatement(node.getStatement(), s), s -> unifyExpression(node.getCondition(), s), maker()::DoLoop); } @Override public Choice> visitForLoop(ForLoopTree node, State state) { return chooseSubtrees( state, s -> unifyStatements(node.getInitializer(), s), s -> unifyExpression(node.getCondition(), s), s -> unifyStatements(node.getUpdate(), s), s -> unifyStatement(node.getStatement(), s), (inits, cond, update, stmt) -> maker().ForLoop(inits, cond, List.convert(JCExpressionStatement.class, update), stmt)); } @Override public Choice> visitLabeledStatement( LabeledStatementTree node, State state) { return chooseSubtrees( state, s -> unifyStatement(node.getStatement(), s), stmt -> maker().Labelled((Name) node.getLabel(), stmt)); } @Override public Choice> visitVariable(VariableTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getInitializer(), s), init -> maker() .VarDef( (JCModifiers) node.getModifiers(), (Name) node.getName(), (JCExpression) node.getType(), init)); } @Override public Choice> visitWhileLoop(WhileLoopTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getCondition(), s), s -> unifyStatement(node.getStatement(), s), maker()::WhileLoop); } @Override public Choice> visitSynchronized(SynchronizedTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), s -> unifyStatement(node.getBlock(), s), (expr, block) -> maker().Synchronized(expr, (JCBlock) block)); } @Override public Choice> visitReturn(ReturnTree node, State state) { return chooseSubtrees(state, s -> unifyExpression(node.getExpression(), s), maker()::Return); } @Override public Choice> visitTry(TryTree node, State state) { return chooseSubtrees( state, s -> unify(node.getResources(), s), s -> unifyStatement(node.getBlock(), s), s -> unify(node.getCatches(), s), s -> unifyStatement(node.getFinallyBlock(), s), (resources, block, catches, finallyBlock) -> maker() .Try( resources, (JCBlock) block, List.convert(JCCatch.class, catches), (JCBlock) finallyBlock)); } @Override public Choice> visitCatch(CatchTree node, State state) { return chooseSubtrees( state, s -> unifyStatement(node.getBlock(), s), block -> maker().Catch((JCVariableDecl) node.getParameter(), (JCBlock) block)); } @Override public Choice> visitSwitch(SwitchTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getExpression(), s), s -> unify(node.getCases(), s), (expr, cases) -> maker().Switch(expr, List.convert(JCCase.class, cases))); } @Override public Choice> visitCase(CaseTree node, State state) { return chooseSubtrees( state, s -> unifyStatements(node.getStatements(), s), stmts -> makeCase(node, stmts)); } private JCCase makeCase(CaseTree node, List stmts) { try { if (RuntimeVersion.isAtLeast12()) { Enum caseKind = (Enum) CaseTree.class.getMethod("getCaseKind").invoke(node); checkState( caseKind.name().contentEquals("STATEMENT"), "expression switches are not supported yet"); return (JCCase) TreeMaker.class .getMethod( "Case", Class.forName("com.sun.source.tree.CaseTree.CaseKind"), List.class, List.class, JCTree.class) .invoke( maker(), caseKind, List.of((JCExpression) node.getExpression()), stmts, /* body */ null); } else { return (JCCase) TreeMaker.class .getMethod("Case", JCExpression.class, List.class) .invoke(maker(), node.getExpression(), stmts); } } catch (ReflectiveOperationException e) { throw new LinkageError(e.getMessage(), e); } } @Override public Choice> visitLambdaExpression(LambdaExpressionTree node, State state) { return chooseSubtrees( state, s -> unify(node.getBody(), s), body -> maker() .Lambda( List.convert( JCVariableDecl.class, (List) node.getParameters()), body)); } @Override public Choice> visitMemberReference( MemberReferenceTree node, State state) { return chooseSubtrees( state, s -> unifyExpression(node.getQualifierExpression(), s), expr -> maker() .Reference( node.getMode(), (Name) node.getName(), expr, List.convert( JCExpression.class, (List) node.getTypeArguments()))); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy