private static List<? extends TypeMirror> computeBinaryOperator( Set<ElementKind> types, CompilationInfo info, TreePath parent, Tree error, int offset) { BinaryTree bt = (BinaryTree) parent.getLeaf(); TreePath typeToResolve = null; if (bt.getLeftOperand() == error) { typeToResolve = new TreePath(parent, bt.getRightOperand()); } if (bt.getRightOperand() == error) { typeToResolve = new TreePath(parent, bt.getLeftOperand()); } types.add(ElementKind.PARAMETER); types.add(ElementKind.LOCAL_VARIABLE); types.add(ElementKind.FIELD); return typeToResolve != null ? Collections.singletonList(info.getTrees().getTypeMirror(typeToResolve)) : null; }
@Override public Void visitBinary(BinaryTree node, Void p) { // No checking unless the operator is "==" or "!=". if (!(node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO)) return super.visitBinary(node, p); ExpressionTree leftOp = node.getLeftOperand(); ExpressionTree rightOp = node.getRightOperand(); // Check passes if either arg is null. if (leftOp.getKind() == Tree.Kind.NULL_LITERAL || rightOp.getKind() == Tree.Kind.NULL_LITERAL) return super.visitBinary(node, p); AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); // If either argument is a primitive, check passes due to auto-unboxing if (left.getKind().isPrimitive() || right.getKind().isPrimitive()) return super.visitBinary(node, p); if (!(shouldCheckFor(leftOp) && shouldCheckFor(rightOp))) return super.visitBinary(node, p); // Syntactic checks for legal uses of == if (suppressInsideComparison(node)) return super.visitBinary(node, p); if (suppressEarlyEquals(node)) return super.visitBinary(node, p); if (suppressEarlyCompareTo(node)) return super.visitBinary(node, p); if (suppressClassAnnotation(left, right)) { return super.visitBinary(node, p); } Element leftElt = null; Element rightElt = null; if (left instanceof org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType) { leftElt = ((DeclaredType) left.getUnderlyingType()).asElement(); } if (right instanceof org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType) { rightElt = ((DeclaredType) right.getUnderlyingType()).asElement(); } // if neither @Interned or @UsesObjectEquals, report error if (!(left.hasEffectiveAnnotation(INTERNED) || (leftElt != null && leftElt.getAnnotation(UsesObjectEquals.class) != null))) checker.report(Result.failure("not.interned", left), leftOp); if (!(right.hasEffectiveAnnotation(INTERNED) || (rightElt != null && rightElt.getAnnotation(UsesObjectEquals.class) != null))) checker.report(Result.failure("not.interned", right), rightOp); return super.visitBinary(node, p); }
/** @return true if binary operation could cause an unboxing operation */ private final boolean isUnboxingOperation(BinaryTree tree) { if (tree.getKind() == Tree.Kind.EQUAL_TO || tree.getKind() == Tree.Kind.NOT_EQUAL_TO) { // it is valid to check equality between two reference types, even // if one (or both) of them is null return isPrimitive(tree.getLeftOperand()) != isPrimitive(tree.getRightOperand()); } else { // All BinaryTree's are of type String, a primitive type or the // reference type equivalent of a primitive type. Furthermore, // Strings don't have a primitive type, and therefore only // BinaryTrees that aren't String can cause unboxing. return !isString(tree); } }
/** * Given a BinaryTree to match against and a list of two matchers, applies the matchers to the * operands in both orders. If both matchers match, returns a list with the operand that matched * each matcher in the corresponding position. * * @param tree a BinaryTree AST node * @param matchers a list of matchers * @param state the VisitorState * @return a list of matched operands, or null if at least one did not match */ public static List<ExpressionTree> matchBinaryTree( BinaryTree tree, List<Matcher<ExpressionTree>> matchers, VisitorState state) { ExpressionTree leftOperand = tree.getLeftOperand(); ExpressionTree rightOperand = tree.getRightOperand(); if (matchers.get(0).matches(leftOperand, state) && matchers.get(1).matches(rightOperand, state)) { return Arrays.asList(leftOperand, rightOperand); } else if (matchers.get(0).matches(rightOperand, state) && matchers.get(1).matches(leftOperand, state)) { return Arrays.asList(rightOperand, leftOperand); } return null; }
/** Case 6: Check for redundant nullness tests Case 7: unboxing case: primitive operations */ @Override public Void visitBinary(BinaryTree node, Void p) { final ExpressionTree leftOp = node.getLeftOperand(); final ExpressionTree rightOp = node.getRightOperand(); if (isUnboxingOperation(node)) { checkForNullability(leftOp, UNBOXING_OF_NULLABLE); checkForNullability(rightOp, UNBOXING_OF_NULLABLE); } checkForRedundantTests(node); return super.visitBinary(node, p); }
@Override public @Nullable FlowCondition visitBinary(BinaryTree node, Void p) { @Nullable ExpressionTree operand = null; if (node.getKind() == Tree.Kind.CONDITIONAL_AND) { @Nullable FlowCondition left = visit(node.getLeftOperand(), null); @Nullable FlowCondition right = visit(node.getRightOperand(), null); if (left != null) return left; else if (right != null) return right; } // One of the operands must be a null literal. if (node.getLeftOperand().getKind() == Tree.Kind.NULL_LITERAL) operand = node.getRightOperand(); else if (node.getRightOperand().getKind() == Tree.Kind.NULL_LITERAL) operand = node.getLeftOperand(); else return null; // The operator must be == or !=. if (node.getKind() == Tree.Kind.NOT_EQUAL_TO) return new FlowCondition(operand, true); else if (node.getKind() == Tree.Kind.EQUAL_TO) return new FlowCondition(operand, false); else return null; }
/** * Reports an error if a comparison of a @NonNull expression with the null literal is performed. */ protected void checkForRedundantTests(BinaryTree node) { final ExpressionTree leftOp = node.getLeftOperand(); final ExpressionTree rightOp = node.getRightOperand(); // respect command-line option if (!checker.getLintOption( AbstractNullnessChecker.LINT_REDUNDANTNULLCOMPARISON, AbstractNullnessChecker.LINT_DEFAULT_REDUNDANTNULLCOMPARISON)) { return; } // equality tests if ((node.getKind() == Tree.Kind.EQUAL_TO || node.getKind() == Tree.Kind.NOT_EQUAL_TO)) { AnnotatedTypeMirror left = atypeFactory.getAnnotatedType(leftOp); AnnotatedTypeMirror right = atypeFactory.getAnnotatedType(rightOp); if (leftOp.getKind() == Tree.Kind.NULL_LITERAL && right.hasEffectiveAnnotation(NONNULL)) checker.report(Result.warning(KNOWN_NONNULL, rightOp.toString()), node); else if (rightOp.getKind() == Tree.Kind.NULL_LITERAL && left.hasEffectiveAnnotation(NONNULL)) checker.report(Result.warning(KNOWN_NONNULL, leftOp.toString()), node); } }
/** * 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; }
/** * 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; }
// 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; }