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

org.checkerframework.checker.interning.InterningVisitor Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java's type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.44.0
Show newest version
package org.checkerframework.checker.interning;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Scope;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import org.checkerframework.checker.interning.qual.CompareToMethod;
import org.checkerframework.checker.interning.qual.EqualsMethod;
import org.checkerframework.checker.interning.qual.InternMethod;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.interning.qual.InternedDistinct;
import org.checkerframework.checker.interning.qual.UsesObjectEquals;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.util.Heuristics;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

/**
 * Typechecks source code for interning violations. A type is considered interned if its primary
 * annotation is {@link Interned} or {@link InternedDistinct}. This visitor reports errors or
 * warnings for violations for the following cases:
 *
 * 
    *
  1. either argument to a "==" or "!=" comparison is not Interned (error * "not.interned"). As a special case, the comparison is permitted if either arugment is * InternedDistinct. *
  2. the receiver and argument for a call to an equals method are both Interned * (optional warning "unnecessary.equals") *
* * @see BaseTypeVisitor */ public final class InterningVisitor extends BaseTypeVisitor { /** The @Interned annotation. */ private final AnnotationMirror INTERNED = AnnotationBuilder.fromClass(elements, Interned.class); /** The @InternedDistinct annotation. */ private final AnnotationMirror INTERNED_DISTINCT = AnnotationBuilder.fromClass(elements, InternedDistinct.class); /** * The declared type of which the equality tests should be tested, if the user explicitly passed * one. The user can pass the class name via the {@code -Acheckclass=...} option. Null if no class * is specified, or the class specified isn't in the classpath. */ private final DeclaredType typeToCheck = typeToCheck(); /** The Comparable.compareTo method. */ private final ExecutableElement comparableCompareTo = TreeUtils.getMethod( "java.lang.Comparable", "compareTo", 1, checker.getProcessingEnvironment()); /** Create an InterningVisitor. */ public InterningVisitor(BaseTypeChecker checker) { super(checker); } /** * Returns true if interning should be verified for the input expression. By default, all classes * are checked for interning unless {@code -Acheckclass} is specified. * * @return true if interning should be verified for the input expression * @see What the Interning Checker * checks */ private boolean shouldCheckExpression(ExpressionTree tree) { if (typeToCheck == null) { return true; } TypeMirror type = TreeUtils.typeOf(tree); return types.isSubtype(type, typeToCheck) || types.isSubtype(typeToCheck, type); } /** Checks comparison operators, == and !=, for INTERNING violations. */ @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 (left.hasEffectiveAnnotation(INTERNED_DISTINCT) || right.hasEffectiveAnnotation(INTERNED_DISTINCT)) { return super.visitBinary(node, p); } // If shouldCheckExpression returns true for either the LHS or RHS, // this method proceeds with the interning check. // Justification: Consider the following scenario: // interface I { ... } // class A { ... } // class B extends A implements I { ... } // ... // I i; // A a; // ... // if (a == i) { ... } // The Java compiler does not issue a compilation error for the (a == i) comparison because, // even though A does not implement I, 'a' could be assigned an instance of B, and B does // implement I (note that the compiler does not need to know about the existence of B // in order to assume this). // Now suppose the user passes -AcheckClass=A on the command-line. // I is not a subtype or supertype of A, so shouldCheckExpression will not return true for I. // But the interning check must be performed, given the argument above. Therefore if // shouldCheckExpression returns true for either the LHS or the RHS, this method proceeds // with the interning check. if (!shouldCheckExpression(leftOp) && !shouldCheckExpression(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 (suppressEqualsIfClassIsAnnotated(left, right)) { return super.visitBinary(node, p); } Element leftElt = TypesUtils.getTypeElement(left.getUnderlyingType()); // If neither @Interned or @UsesObjectEquals, report error. if (!(left.hasEffectiveAnnotation(INTERNED) || (leftElt != null && atypeFactory.getDeclAnnotation(leftElt, UsesObjectEquals.class) != null))) { checker.reportError(leftOp, "not.interned"); } Element rightElt = TypesUtils.getTypeElement(right.getUnderlyingType()); if (!(right.hasEffectiveAnnotation(INTERNED) || (rightElt != null && atypeFactory.getDeclAnnotation(rightElt, UsesObjectEquals.class) != null))) { checker.reportError(rightOp, "not.interned"); } return super.visitBinary(node, p); } /** * If lint option "dotequals" is specified, warn if the .equals method is used where reference * equality is safe. */ @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { if (isInvocationOfEquals(node)) { AnnotatedTypeMirror receiverType = atypeFactory.getReceiverType(node); AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(node.getArguments().get(0)); if (this.checker.getLintOption("dotequals", true) && receiverType.hasEffectiveAnnotation(INTERNED) && comp.hasEffectiveAnnotation(INTERNED)) { checker.reportWarning(node, "unnecessary.equals"); } } return super.visitMethodInvocation(node, p); } // Ensure that method annotations are not written on methods they don't apply to. @Override public Void visitMethod(MethodTree node, Void p) { ExecutableElement methElt = TreeUtils.elementFromDeclaration(node); boolean hasCompareToMethodAnno = atypeFactory.getDeclAnnotation(methElt, CompareToMethod.class) != null; boolean hasEqualsMethodAnno = atypeFactory.getDeclAnnotation(methElt, EqualsMethod.class) != null; boolean hasInternMethodAnno = atypeFactory.getDeclAnnotation(methElt, InternMethod.class) != null; int params = methElt.getParameters().size(); if (hasCompareToMethodAnno && !(params == 1 || params == 2)) { checker.reportError( node, "invalid.method.annotation", "@CompareToMethod", "1 or 2", methElt, params); } else if (hasEqualsMethodAnno && !(params == 1 || params == 2)) { checker.reportError( node, "invalid.method.annotation", "@EqualsMethod", "1 or 2", methElt, params); } else if (hasInternMethodAnno && !(params == 0)) { checker.reportError(node, "invalid.method.annotation", "@InternMethod", "0", methElt, params); } return super.visitMethod(node, p); } /** * Method to implement the @UsesObjectEquals functionality. If a class is annotated * with @UsesObjectEquals, it must: * *
    *
  • not override .equals(Object) and be a subclass of a class annotated * with @UsesObjectEquals, or *
  • override equals(Object) with body "this == arg" *
* * If a class is not annotated with @UsesObjectEquals, it must: * *
    *
  • not have a superclass annotated with @UsesObjectEquals *
* * @see * org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree, * java.lang.Object) */ @Override public void processClassTree(ClassTree classTree) { TypeElement elt = TreeUtils.elementFromDeclaration(classTree); AnnotationMirror annotation = atypeFactory.getDeclAnnotation(elt, UsesObjectEquals.class); // If @UsesObjectEquals is present, check to make sure the class does not override equals // and its supertype is Object or is annotated with @UsesObjectEquals. if (annotation != null) { MethodTree equalsMethod = equalsImplementation(classTree); if (equalsMethod != null) { if (!isReferenceEqualityImplementation(equalsMethod)) { checker.reportError(classTree, "overrides.equals"); } } else { // Does not override equals() TypeMirror superClass = elt.getSuperclass(); if (superClass != null // The super class of an interface is "none" rather than null. && superClass.getKind() == TypeKind.DECLARED) { TypeElement superClassElement = TypesUtils.getTypeElement(superClass); if (superClassElement != null && !ElementUtils.isObject(superClassElement) && atypeFactory.getDeclAnnotation(superClassElement, UsesObjectEquals.class) == null) { checker.reportError(classTree, "superclass.notannotated"); } } } } super.processClassTree(classTree); } /** * Returns true if the given equals() method implements reference equality. * * @param equalsMethod an overriding implementation of Object.equals() * @return true if the given equals() method implements reference equality */ private boolean isReferenceEqualityImplementation(MethodTree equalsMethod) { BlockTree body = equalsMethod.getBody(); List bodyStatements = body.getStatements(); if (bodyStatements.size() == 1) { StatementTree bodyStatement = bodyStatements.get(0); if (bodyStatement.getKind() == Tree.Kind.RETURN) { ExpressionTree returnExpr = TreeUtils.withoutParens(((ReturnTree) bodyStatement).getExpression()); if (returnExpr.getKind() == Tree.Kind.EQUAL_TO) { BinaryTree bt = (BinaryTree) returnExpr; ExpressionTree lhsTree = bt.getLeftOperand(); ExpressionTree rhsTree = bt.getRightOperand(); if (lhsTree.getKind() == Tree.Kind.IDENTIFIER && rhsTree.getKind() == Tree.Kind.IDENTIFIER) { Name leftName = ((IdentifierTree) lhsTree).getName(); Name rightName = ((IdentifierTree) rhsTree).getName(); Name paramName = equalsMethod.getParameters().get(0).getName(); if ((leftName.contentEquals("this") && rightName == paramName) || (leftName == paramName && rightName.contentEquals("this"))) { return true; } } } } } return false; } @Override protected void checkConstructorResult( AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { if (constructorElement.getEnclosingElement().getKind() == ElementKind.ENUM) { // Enums constructor are only called once per enum constant. return; } super.checkConstructorResult(constructorType, constructorElement); } @Override public boolean validateTypeOf(Tree tree) { // Don't check the result type of a constructor, because it must be @UnknownInterned, even // if the type on the class declaration is @Interned. if (tree.getKind() == Tree.Kind.METHOD && TreeUtils.isConstructor((MethodTree) tree)) { return true; } else if (tree.getKind() == Tree.Kind.NEW_CLASS) { NewClassTree newClassTree = (NewClassTree) tree; TypeMirror typeMirror = TreeUtils.typeOf(newClassTree); Set bounds = atypeFactory.getTypeDeclarationBounds(typeMirror); // Don't issue an invalid type warning for creations of objects of interned classes; // instead, issue an interned.object.creation if required. if (atypeFactory.containsSameByClass(bounds, Interned.class)) { ParameterizedExecutableType fromUse = atypeFactory.constructorFromUse(newClassTree); AnnotatedExecutableType constructor = fromUse.executableType; if (!checkCreationOfInternedObject(newClassTree, constructor)) { return false; } } } return super.validateTypeOf(tree); } /** * Issue an error if {@code newInternedObject} is not immediately interned. * * @param newInternedObject call to a constructor of an interned class * @param constructor declared type of the constructor * @return false unless {@code newInternedObject} is immediately interned */ private boolean checkCreationOfInternedObject( NewClassTree newInternedObject, AnnotatedExecutableType constructor) { if (constructor.getReturnType().hasAnnotation(Interned.class)) { return true; } TreePath path = getCurrentPath(); if (path != null) { TreePath parentPath = path.getParentPath(); while (parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) { parentPath = parentPath.getParentPath(); } if (parentPath != null && parentPath.getParentPath() != null) { Tree parent = parentPath.getParentPath().getLeaf(); if (parent.getKind() == Tree.Kind.METHOD_INVOCATION) { // Allow new MyInternType().intern(), where "intern" is any method marked @InternMethod. ExecutableElement elt = TreeUtils.elementFromUse((MethodInvocationTree) parent); if (atypeFactory.getDeclAnnotation(elt, InternMethod.class) != null) { return true; } } } } checker.reportError(newInternedObject, "interned.object.creation"); return false; } // ********************************************************************** // Helper methods // ********************************************************************** /** * Returns the method that overrides Object.equals, or null. * * @param node a class * @return the class's implementation of equals, or null */ private MethodTree equalsImplementation(ClassTree node) { List members = node.getMembers(); for (Tree member : members) { if (member instanceof MethodTree) { MethodTree mTree = (MethodTree) member; ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); if (overrides(enclosing, Object.class, "equals")) { return mTree; } } } return null; } /** * Tests whether a method invocation is an invocation of {@link #equals} with one argument. * *

Returns true even if a method overloads {@link Object#equals(Object)}, because of the common * idiom of writing an equals method with a non-Object parameter, in addition to the equals method * that overrides {@link Object#equals(Object)}. * * @param node a method invocation node * @return true iff {@code node} is a invocation of {@code equals()} */ public static boolean isInvocationOfEquals(MethodInvocationTree node) { ExecutableElement method = TreeUtils.elementFromUse(node); return (method.getParameters().size() == 1 && method.getReturnType().getKind() == TypeKind.BOOLEAN // method symbols only have simple names && method.getSimpleName().contentEquals("equals")); } /** * Pattern matches particular comparisons to avoid common false positives in the {@link * Comparable#compareTo(Object)} and {@link Object#equals(Object)}. * *

Specifically, this method tests if: the comparison is a == comparison, it is the test of an * if statement that's the first statement in the method, and one of the following is true: * *

    *
  1. the method overrides {@link Comparator#compare}, the "then" branch of the if statement * returns zero, and the comparison tests equality of the method's two parameters *
  2. the method overrides {@link Object#equals(Object)} and the comparison tests "this" * against the method's parameter *
  3. the method overrides {@link Comparable#compareTo(Object)}, the "then" branch of the if * statement returns zero, and the comparison tests "this" against the method's parameter *
* * @param node the comparison to check * @return true if one of the supported heuristics is matched, false otherwise */ // TODO: handle != comparisons too! // TODO: handle more methods, such as early return from addAll when this == arg 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; } TreePath path = getCurrentPath(); TreePath parentPath = path.getParentPath(); Tree parent = parentPath.getLeaf(); // Ensure the == is in a return or in an if, and that enclosing statement is the first // statement in the method. if (parent.getKind() == Tree.Kind.RETURN) { // ensure the return statement is the first statement in the method if (parentPath.getParentPath().getParentPath().getLeaf().getKind() != Tree.Kind.METHOD) { return false; } // maybe set some variables?? } else if (Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) { // Ensure the if statement is the first statement in the method // Retrieve the enclosing if statement tree and method tree Tree ifStatementTree = null; MethodTree methodTree = null; // Set ifStatementTree and methodTree { TreePath ppath = parentPath; Tree tree; while ((tree = ppath.getLeaf()) != null) { if (tree.getKind() == Tree.Kind.IF) { ifStatementTree = tree; } else if (tree.getKind() == Tree.Kind.METHOD) { methodTree = (MethodTree) tree; break; } ppath = ppath.getParentPath(); } } assert ifStatementTree != null; assert methodTree != null; StatementTree firstStmnt = methodTree.getBody().getStatements().get(0); assert firstStmnt != null; @SuppressWarnings("interning:not.interned") // comparing AST nodes boolean notSameNode = firstStmnt != ifStatementTree; if (notSameNode) { return false; // The if statement is not the first statement in the method. } } else { return false; } ExecutableElement enclosingMethod = TreeUtils.elementFromDeclaration(methodTree); assert enclosingMethod != 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 matcherIfReturnsZero = 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().isEmpty()) { return false; } return visit(tree.getStatements().get(0), p); } @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)); } }; boolean hasCompareToMethodAnno = atypeFactory.getDeclAnnotation(enclosingMethod, CompareToMethod.class) != null; boolean hasEqualsMethodAnno = atypeFactory.getDeclAnnotation(enclosingMethod, EqualsMethod.class) != null; int params = enclosingMethod.getParameters().size(); // Determine whether or not the "then" statement of the if has a single // "return 0" statement (for the Comparator.compare heuristic). if (overrides(enclosingMethod, Comparator.class, "compare") || (hasCompareToMethodAnno && params == 2)) { final boolean returnsZero = new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) .match(getCurrentPath()); if (!returnsZero) { return false; } assert params == 2; Element p1 = enclosingMethod.getParameters().get(0); Element p2 = enclosingMethod.getParameters().get(1); return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); } else if (overrides(enclosingMethod, Object.class, "equals") || (hasEqualsMethodAnno && params == 1)) { assert params == 1; Element param = enclosingMethod.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (thisElt.equals(rhs) && param.equals(lhs)); } else if (hasEqualsMethodAnno && params == 2) { Element p1 = enclosingMethod.getParameters().get(0); Element p2 = enclosingMethod.getParameters().get(1); return (p1.equals(lhs) && p2.equals(rhs)) || (p1.equals(rhs) && p2.equals(lhs)); } else if (overrides(enclosingMethod, Comparable.class, "compareTo") || (hasCompareToMethodAnno && params == 1)) { final boolean returnsZero = new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.IF, matcherIfReturnsZero)) .match(getCurrentPath()); if (!returnsZero) { return false; } assert params == 1; Element param = enclosingMethod.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (thisElt.equals(rhs) && param.equals(lhs)); } return false; } /** * Pattern matches to prevent false positives of the forms: * *
{@code
   * (a == b) || a.equals(b)
   * (a == b) || (a != null ? a.equals(b) : false)
   * (a == b) || (a != null && a.equals(b))
   * }
* * Returns true iff the given node fits this pattern. * * @return true iff the node fits a pattern such as (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 = TreeUtils.withoutParens(node.getLeftOperand()); final ExpressionTree right = TreeUtils.withoutParens(node.getRightOperand()); // looking for ((a == b || a.equals(b)) Heuristics.Matcher matcherEqOrEquals = new Heuristics.Matcher() { /** Returns true if e is either "e1 != null" or "e2 != null". */ private boolean isNeqNull(ExpressionTree e, ExpressionTree e1, ExpressionTree e2) { e = TreeUtils.withoutParens(e); if (e.getKind() != Tree.Kind.NOT_EQUAL_TO) { return false; } ExpressionTree neqLeft = ((BinaryTree) e).getLeftOperand(); ExpressionTree neqRight = ((BinaryTree) e).getRightOperand(); return (((TreeUtils.sameTree(neqLeft, e1) || TreeUtils.sameTree(neqLeft, e2)) && neqRight.getKind() == Tree.Kind.NULL_LITERAL) // also check for "null != e1" and "null != e2" || ((TreeUtils.sameTree(neqRight, e1) || TreeUtils.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 (TreeUtils.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 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 (TreeUtils.sameTree(receiver, left) && TreeUtils.sameTree(arg, right)) { return true; } if (TreeUtils.sameTree(receiver, right) && TreeUtils.sameTree(arg, left)) { return true; } return false; } }; boolean okay = new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrEquals)) .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. * * @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 = TreeUtils.withoutParens(node.getLeftOperand()); Tree right = TreeUtils.withoutParens(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 matcherEqOrCompareTo = 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 @SuppressWarnings("interning:assignment" // AST node comparisons ) @InternedDistinct 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 (!TreeUtils.isMethodInvocation( tree, comparableCompareTo, checker.getProcessingEnvironment())) { return false; } List 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 = new Heuristics.Within(new Heuristics.OfKind(Tree.Kind.CONDITIONAL_OR, matcherEqOrCompareTo)) .match(getCurrentPath()); return okay; } /** * Given {@code a == b}, where a has type A and b has type B, don't issue a warning when either * the declaration of A or that of B is annotated with @Interned because {@code a == b} will be * true only if a's run-time type is B (or lower), in which case a is actually interned. */ private boolean suppressEqualsIfClassIsAnnotated( AnnotatedTypeMirror left, AnnotatedTypeMirror right) { // It would be better to just test their greatest lower bound. // That could permit some comparisons that this forbids. return classIsAnnotated(left) || classIsAnnotated(right); } /** Returns true if the type's declaration has an @Interned annotation. */ private boolean classIsAnnotated(AnnotatedTypeMirror type) { TypeMirror tm = type.getUnderlyingType(); if (tm == null) { // Maybe a type variable or wildcard had no upper bound return false; } tm = TypesUtils.findConcreteUpperBound(tm); if (tm == null || tm.getKind() == TypeKind.ARRAY) { // Bound of a wildcard might be null return false; } if (tm.getKind() != TypeKind.DECLARED) { checker.message( Kind.WARNING, "InterningVisitor.classIsAnnotated: tm = %s (%s)", tm, tm.getClass()); } Element classElt = ((DeclaredType) tm).asElement(); if (classElt == null) { checker.message( Kind.WARNING, "InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)", tm, tm.getClass()); } if (classElt != null) { Set bound = atypeFactory.getTypeDeclarationBounds(tm); return atypeFactory.containsSameByClass(bound, Interned.class); } return false; } /** * Determines the element corresponding to "this" inside a scope. Returns null within static * methods. * * @param scope the scope to search for the element corresponding to "this" in * @return the element corresponding to "this" in the given scope, or null if not found */ private Element getThis(Scope scope) { for (Element e : scope.getLocalElements()) { if (e.getSimpleName().contentEquals("this")) { return e; } } return null; } /** * Determines whether or not the given element overrides the named method in the named class. * * @param e an element for a method * @param clazz the class * @param method the name of a method * @return true if the method given by {@code e} overrides the named method in the named class; * false otherwise */ private boolean overrides(ExecutableElement e, Class clazz, String method) { // Get the element named by "clazz". TypeElement clazzElt = elements.getTypeElement(clazz.getCanonicalName()); assert clazzElt != null; // Check all of the methods in the class for name matches and overriding. for (ExecutableElement elt : ElementFilter.methodsIn(clazzElt.getEnclosedElements())) { if (elt.getSimpleName().contentEquals(method) && elements.overrides(e, elt, clazzElt)) { return true; } } return false; } /** * Returns the type to check. * * @return the type to check */ DeclaredType typeToCheck() { @SuppressWarnings("signature:assignment") // user input @CanonicalName String className = checker.getOption("checkclass"); if (className == null) { return null; } TypeElement classElt = elements.getTypeElement(className); if (classElt == null) { return null; } return types.getDeclaredType(classElt); } @Override protected boolean isTypeCastSafe(AnnotatedTypeMirror castType, AnnotatedTypeMirror exprType) { if (castType.getKind().isPrimitive()) { return true; } return super.isTypeCastSafe(castType, exprType); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy