checker.src.org.checkerframework.checker.interning.InterningVisitor Maven / Gradle / Ivy
Show all versions of checker Show documentation
package org.checkerframework.checker.interning;
import org.checkerframework.checker.interning.qual.Interned;
import org.checkerframework.checker.interning.qual.UsesObjectEquals;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.util.Heuristics;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import java.util.Comparator;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
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 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.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;
/**
* Typechecks source code for interning violations. A type is considered interned
* if its primary annotation is {@link Interned}.
* This visitor reports errors or warnings for violations for the following cases:
*
*
* - either argument to a "==" or "!=" comparison is not Interned (error
* "not.interned")
*
- 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;
/** See method typeToCheck() */
private final DeclaredType typeToCheck;
public InterningVisitor(BaseTypeChecker checker) {
super(checker);
this.INTERNED = AnnotationUtils.fromClass(elements, Interned.class);
typeToCheck = typeToCheck();
}
/**
* @return true if interning should be verified for the input expression. By default, all classes are checked
* for interning unless -Acheckclass is specified.
* @see What the Interning Checker checks
*/
private boolean shouldCheckExpression(ExpressionTree tree) {
if (typeToCheck == null) return true;
TypeMirror type = InternalUtils.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 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 = null;
Element rightElt = null;
if (left instanceof AnnotatedTypeMirror.AnnotatedDeclaredType) {
leftElt = ((DeclaredType)left.getUnderlyingType()).asElement();
}
if (right instanceof AnnotatedTypeMirror.AnnotatedDeclaredType) {
rightElt = ((DeclaredType)right.getUnderlyingType()).asElement();
}
//TODO: CODE REVIEW
//TODO: WOULD IT BE CLEARER TO USE A METHOD usesReferenceEquality(AnnotatedTypeMirror type)
//TODO: RATHER THAN leftElt.getAnnotation(UsesObjectEquals.class) != null)
// 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);
}
/**
* 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 recv = atypeFactory.getReceiverType(node);
AnnotatedTypeMirror comp = atypeFactory.getAnnotatedType(node.getArguments().get(0));
if (this.checker.getLintOption("dotequals", true)
&& recv.hasEffectiveAnnotation(INTERNED)
&& comp.hasEffectiveAnnotation(INTERNED))
checker.report(Result.warning("unnecessary.equals"), node);
}
return super.visitMethodInvocation(node, p);
}
/**
* Method to implement the @UsesObjectEquals functionality.
* If a class is annotated with @UsesObjectEquals, it must:
*
* -not override .equals(Object)
* -be a subclass of Object or another class annotated with @UsesObjectEquals
*
* 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 visitClass(ClassTree node, Void p) {
// TODO: Should this method use the Javac types or some other utility to get
// all direct supertypes instead, and should it verify that each does not
// override .equals and that at least one of them is annotated with @UsesObjectEquals?
// Looking for an @UsesObjectEquals class declaration
TypeElement elt = TreeUtils.elementFromDeclaration(node);
UsesObjectEquals annotation = elt.getAnnotation(UsesObjectEquals.class);
Tree superClass = node.getExtendsClause();
Element elmt = null;
if (superClass != null && (superClass instanceof IdentifierTree || superClass instanceof MemberSelectTree)) {
elmt = TreeUtils.elementFromUse((ExpressionTree)superClass);
}
// 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) {
// Check methods to ensure no .equals
if (overridesEquals(node)) {
checker.report(Result.failure("overrides.equals"), node);
}
if (!(superClass == null || (elmt != null && elmt.getAnnotation(UsesObjectEquals.class) != null))) {
checker.report(Result.failure("superclass.notannotated"), node);
}
} else {
// The class is not annotated with @UsesObjectEquals -> make sure its superclass isn't either.
// TODO: is this impossible after the design change making @UsesObjectEquals inherited?
// This check is left behind in case of a future design change back to non-inherited.
if (superClass != null && (elmt != null && elmt.getAnnotation(UsesObjectEquals.class) != null)) {
checker.report(Result.failure("superclass.annotated"), node);
}
}
return super.visitClass(node, p);
}
// **********************************************************************
// Helper methods
// **********************************************************************
/**
* Returns true if a class overrides Object.equals
*/
private boolean overridesEquals(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 true;
}
}
}
return false;
}
/**
* Tests whether a method invocation is an invocation of
* {@link #equals} that overrides or hides {@link Object#equals(Object)}.
*
*
* Returns true even if a method does not override {@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()}
*/
private boolean isInvocationOfEquals(MethodInvocationTree node) {
ExecutableElement method = TreeUtils.elementFromUse(node);
//TODO: CODE REVIEW NEITHER OF THE TWO
return (method.getParameters().size() == 1
&& method.getReturnType().getKind() == TypeKind.BOOLEAN
// method symbols only have simple names
&& method.getSimpleName().contentEquals("equals"));
}
/**
* Tests whether a method invocation is an invocation of
* {@link Comparable#compareTo}.
*
*
* @param node a method invocation node
* @return true iff {@code node} is a invocation of {@code compareTo()}
*/
private boolean isInvocationOfCompareTo(MethodInvocationTree node) {
ExecutableElement method = TreeUtils.elementFromUse(node);
return (method.getParameters().size() == 1
&& method.getReturnType().getKind() == TypeKind.INT
// method symbols only have simple names
&& method.getSimpleName().contentEquals("compareTo"));
}
/**
* 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:
*
* - 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
*
* - the method overrides {@link Object#equals(Object)} and the
* comparison tests "this" against the method's parameter
*
* - 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;
// If we're not directly in an if statement in a method (ignoring
// parens and blocks), terminate.
if (!Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) {
return false;
}
// Ensure the if statement is the first statement in the method
TreePath parentPath = getCurrentPath().getParentPath();
// Retrieve the enclosing if statement tree and method tree
Tree tree, ifStatementTree = null;
MethodTree methodTree = null;
while ((tree = parentPath.getLeaf()) != null) {
if (tree.getKind() == Tree.Kind.IF) {
ifStatementTree = tree;
} else if (tree.getKind() == Tree.Kind.METHOD) {
methodTree = (MethodTree) tree;
break;
}
parentPath = parentPath.getParentPath();
}
assert ifStatementTree != null; // The call to Heuristics.matchParents already ensured there is an enclosing if statement
assert methodTree != null; // The call to Heuristics.matchParents already ensured there is an enclosing method
StatementTree stmnt = methodTree.getBody().getStatements().get(0);
assert stmnt != null; // The call to Heuristics.matchParents already ensured the enclosing method has at least one statement (an if statement) in the body
if (stmnt != ifStatementTree) {
return false; // The if statement is not the first statement in the method.
}
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 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().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, matcherIfReturnsZero)).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, matcherIfReturnsZero)).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;
}
/**
* Returns true if two expressions originating from the same scope are identical,
* i.e. they are syntactically represented in the same way (modulo parentheses) and
* represent the same value.
*
* For example, given an expression
* (a == b) || a.equals(b)
* sameTree can be called to determine that the first 'a' and second 'a' refer
* to the same variable, which is the case since both expressions 'a' originate from
* the same scope.
*
* If the expression includes one or more method calls, assumes the method calls
* are deterministic.
*
* @param expr1 the first expression to compare
* @param expr2 the second expression to compare - expr2 must originate from the same scope as expr1
* @return true if the expressions expr1 and expr2 are identical
*/
private static boolean sameTree(ExpressionTree expr1, ExpressionTree expr2) {
return TreeUtils.skipParens(expr1).toString().equals(TreeUtils.skipParens(expr2).toString());
}
/**
* Pattern matches to prevent false positives of the forms:
*
* (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.skipParens(node.getLeftOperand());
final ExpressionTree right = TreeUtils.skipParens(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.skipParens(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 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, 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.skipParens(node.getLeftOperand());
Tree right = TreeUtils.skipParens(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
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 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, matcherEqOrCompareTo)).match(getCurrentPath());
return okay;
}
/**
* Given 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 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)%n", tm, tm.getClass());
}
Element classElt = ((DeclaredType) tm).asElement();
if (classElt == null) {
checker.message(Kind.WARNING,
"InterningVisitor.classIsAnnotated: classElt = null for tm = %s (%s)%n", tm, tm.getClass());
}
if (classElt != null) {
AnnotatedTypeMirror classType = atypeFactory.fromElement(classElt);
assert classType != null;
for (AnnotationMirror anno : classType.getAnnotations()) {
if (INTERNED.equals(anno)) {
return true;
}
}
}
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 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.
*
* If no class is specified, or the class specified isn't in the
* classpath, it returns null.
*
*/
DeclaredType typeToCheck() {
String className = checker.getOption("checkclass");
if (className == null) return null;
TypeElement classElt = elements.getTypeElement(className);
if (classElt == null) return null;
return types.getDeclaredType(classElt);
}
}