/* * Copyright 2014 Google Inc. All rights reserved. * * 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.MoreObjects.firstNonNull; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.Tree; import com.sun.source.util.TreeScanner; import com.sun.tools.javac.tree.JCTree.JCExpression; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; /** * Cheap visitor to verify that a tree or set of trees could conceivably match a placeholder, by * potentially matching at least all of the required expressions, and not matching forbidden * expressions, i.e. other variables known to Refaster. * * @author lowasser@google.com (Louis Wasserman) */ final class PlaceholderVerificationVisitor extends TreeScanner<Boolean, Unifier> { private final Collection<UExpression> unmatched; private final ImmutableCollection<UExpression> allowed; /** * @param required UExpressions that must potentially match at least one expression in the tested * code. * @param allowed UExpressions that are explicitly allowed, and excepted from the ban on * references to known variables */ PlaceholderVerificationVisitor(Collection<? extends UExpression> required, Collection<? extends UExpression> allowed) { this.unmatched = new LinkedList<>(required); this.allowed = ImmutableList.copyOf(allowed); assert this.allowed.containsAll(unmatched); } public boolean allRequiredMatched() { return unmatched.isEmpty(); } private boolean couldUnify(UExpression expr, Tree tree, Unifier unifier) { return expr.unify(tree, unifier.fork()).first().isPresent(); } @Override public Boolean scan(Tree node, Unifier unifier) { Iterator<UExpression> iterator = unmatched.iterator(); while (iterator.hasNext()) { if (couldUnify(iterator.next(), node, unifier)) { iterator.remove(); return true; } } for (UExpression expr : allowed) { if (couldUnify(expr, node, unifier)) { return true; } } if (node instanceof JCExpression) { JCExpression expr = (JCExpression) node; for (UFreeIdent.Key key : Iterables.filter(unifier.getBindings().keySet(), UFreeIdent.Key.class)) { JCExpression keyBinding = unifier.getBinding(key); if (PlaceholderUnificationVisitor.equivalentExprs(unifier, expr, keyBinding)) { return false; } } } return firstNonNull(super.scan(node, unifier), true); } @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 false; } } return true; } @Override public Boolean reduce(Boolean r1, Boolean r2) { return firstNonNull(r1, true) && firstNonNull(r2, true); } }