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);
   }
 }
Example #4
0
 /**
  * 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;
  }