/** * Pattern matches to prevent false positives of the forms: * * <pre> * (a == b) || a.equals(b) * (a == b) || (a != null ? a.equals(b) : false) * (a == b) || (a != null && a.equals(b)) * </pre> * * Returns true iff the given node fits this pattern. * * @param node * @return true iff the node fits the pattern (a == b || a.equals(b)) */ private boolean suppressEarlyEquals(final BinaryTree node) { // Only handle == binary trees if (node.getKind() != Tree.Kind.EQUAL_TO) return false; // should strip parens final ExpressionTree left = unparenthesize(node.getLeftOperand()); final ExpressionTree right = unparenthesize(node.getRightOperand()); // looking for ((a == b || a.equals(b)) Heuristics.Matcher matcher = new Heuristics.Matcher() { // Returns true if e is either "e1 != null" or "e2 != null" private boolean isNeqNull(ExpressionTree e, ExpressionTree e1, ExpressionTree e2) { e = unparenthesize(e); if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) { return false; } ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand(); ExpressionTree neqRight = ((BinaryTree) e).getRightOperand(); return (((sameTree(neqLeft, e1) || sameTree(neqLeft, e2)) && neqRight.getKind() == Tree.Kind.NULL_LITERAL) // also check for "null != e1" and "null != e2" || ((sameTree(neqRight, e1) || sameTree(neqRight, e2)) && neqLeft.getKind() == Tree.Kind.NULL_LITERAL)); } @Override public Boolean visitBinary(BinaryTree tree, Void p) { ExpressionTree leftTree = tree.getLeftOperand(); ExpressionTree rightTree = tree.getRightOperand(); if (tree.getKind() == Tree.Kind.CONDITIONAL_OR) { if (sameTree(leftTree, node)) { // left is "a==b" // check right, which should be a.equals(b) or b.equals(a) or similar return visit(rightTree, p); } else { return false; } } if (tree.getKind() == Tree.Kind.CONDITIONAL_AND) { // looking for: (a != null && a.equals(b))) if (isNeqNull(leftTree, left, right)) { return visit(rightTree, p); } return false; } return false; } @Override public Boolean visitConditionalExpression(ConditionalExpressionTree tree, Void p) { // looking for: (a != null ? a.equals(b) : false) ExpressionTree cond = tree.getCondition(); ExpressionTree trueExp = tree.getTrueExpression(); ExpressionTree falseExp = tree.getFalseExpression(); if (isNeqNull(cond, left, right) && (falseExp.getKind() == Tree.Kind.BOOLEAN_LITERAL) && ((LiteralTree) falseExp).getValue().equals(false)) { return visit(trueExp, p); } return false; } @Override public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { if (!isInvocationOfEquals(tree)) { return false; } List<? extends ExpressionTree> args = tree.getArguments(); if (args.size() != 1) { return false; } ExpressionTree arg = args.get(0); // if (arg.getKind() != Tree.Kind.IDENTIFIER) { // return false; // } // Element argElt = TreeUtils.elementFromUse((IdentifierTree) arg); ExpressionTree exp = tree.getMethodSelect(); if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { return false; } MemberSelectTree member = (MemberSelectTree) exp; ExpressionTree receiver = member.getExpression(); // Element refElt = TreeUtils.elementFromUse(receiver); // if (!((refElt.equals(lhs) && argElt.equals(rhs)) || // ((refElt.equals(rhs) && argElt.equals(lhs))))) { // return false; // } if (sameTree(receiver, left) && sameTree(arg, right)) { return true; } if (sameTree(receiver, right) && sameTree(arg, left)) { return true; } return false; } }; boolean okay = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.CONDITIONAL_OR, matcher)) .match(getCurrentPath()); return okay; }
/** * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == 0)}. * Returns true iff the given node fits this pattern. * * @param node * @return true iff the node fits the pattern (a == b || a.compareTo(b) == 0) */ private boolean suppressEarlyCompareTo(final BinaryTree node) { // Only handle == binary trees if (node.getKind() != Tree.Kind.EQUAL_TO) return false; Tree left = node.getLeftOperand(); Tree right = node.getRightOperand(); // Only valid if we're comparing identifiers. if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { return false; } final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); // looking for ((a == b || a.compareTo(b) == 0) Heuristics.Matcher matcher = new Heuristics.Matcher() { @Override public Boolean visitBinary(BinaryTree tree, Void p) { if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0 ExpressionTree leftTree = tree.getLeftOperand(); // looking for a.compareTo(b) or b.compareTo(a) ExpressionTree rightTree = tree.getRightOperand(); // looking for 0 if (rightTree.getKind() != Tree.Kind.INT_LITERAL) { return false; } LiteralTree rightLiteral = (LiteralTree) rightTree; if (!rightLiteral.getValue().equals(0)) { return false; } return visit(leftTree, p); } else { // a == b || a.compareTo(b) == 0 ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b ExpressionTree rightTree = tree.getRightOperand(); // looking for a.compareTo(b) == 0 or b.compareTo(a) == 0 if (leftTree != node) { return false; } if (rightTree.getKind() != Tree.Kind.EQUAL_TO) { return false; } return visit(rightTree, p); } } @Override public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { if (!isInvocationOfCompareTo(tree)) { return false; } List<? extends ExpressionTree> args = tree.getArguments(); if (args.size() != 1) { return false; } ExpressionTree arg = args.get(0); if (arg.getKind() != Tree.Kind.IDENTIFIER) { return false; } Element argElt = TreeUtils.elementFromUse(arg); ExpressionTree exp = tree.getMethodSelect(); if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { return false; } MemberSelectTree member = (MemberSelectTree) exp; if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) { return false; } Element refElt = TreeUtils.elementFromUse(member.getExpression()); if (!((refElt.equals(lhs) && argElt.equals(rhs)) || ((refElt.equals(rhs) && argElt.equals(lhs))))) { return false; } return true; } }; boolean okay = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.CONDITIONAL_OR, matcher)) .match(getCurrentPath()); return okay; }
// TODO: handle != comparisons too! private boolean suppressInsideComparison(final BinaryTree node) { // Only handle == binary trees if (node.getKind() != Tree.Kind.EQUAL_TO) return false; Tree left = node.getLeftOperand(); Tree right = node.getRightOperand(); // Only valid if we're comparing identifiers. if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) return false; // If we're not directly in an if statement in a method (ignoring // parens and blocks), terminate. // TODO: only if it's the first statement in the block if (!Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) return false; ExecutableElement enclosing = TreeUtils.elementFromDeclaration(visitorState.getMethodTree()); assert enclosing != null; final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); // Matcher to check for if statement that returns zero Heuristics.Matcher matcher = new Heuristics.Matcher() { @Override public Boolean visitIf(IfTree tree, Void p) { return visit(tree.getThenStatement(), p); } @Override public Boolean visitBlock(BlockTree tree, Void p) { if (tree.getStatements().size() > 0) return visit(tree.getStatements().get(0), p); return false; } @Override public Boolean visitReturn(ReturnTree tree, Void p) { ExpressionTree expr = tree.getExpression(); return (expr != null && expr.getKind() == Tree.Kind.INT_LITERAL && ((LiteralTree) expr).getValue().equals(0)); } }; // Determine whether or not the "then" statement of the if has a single // "return 0" statement (for the Comparator.compare heuristic). if (overrides(enclosing, Comparator.class, "compare")) { final boolean returnsZero = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.IF, matcher)) .match(getCurrentPath()); if (!returnsZero) return false; assert enclosing.getParameters().size() == 2; Element p1 = enclosing.getParameters().get(0); Element p2 = enclosing.getParameters().get(1); return (p1.equals(lhs) && p2.equals(rhs)) || (p2.equals(lhs) && p1.equals(rhs)); } else if (overrides(enclosing, Object.class, "equals")) { assert enclosing.getParameters().size() == 1; Element param = enclosing.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (param.equals(lhs) && thisElt.equals(rhs)); } else if (overrides(enclosing, Comparable.class, "compareTo")) { final boolean returnsZero = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.IF, matcher)) .match(getCurrentPath()); if (!returnsZero) { return false; } assert enclosing.getParameters().size() == 1; Element param = enclosing.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (param.equals(lhs) && thisElt.equals(rhs)); } return false; }