/* * Copyright 2014 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.errorprone.bugpatterns; import static com.google.errorprone.BugPattern.Category.JDK; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.google.errorprone.matchers.Matchers.allOf; import static com.google.errorprone.matchers.Matchers.anyOf; import static com.google.errorprone.matchers.Matchers.isSubtypeOf; import static com.google.errorprone.matchers.Matchers.kindIs; import static com.google.errorprone.suppliers.Suppliers.ANNOTATION_TYPE; import static com.sun.source.tree.Tree.Kind.CLASS; import static com.sun.source.tree.Tree.Kind.ENUM; import com.google.common.base.Predicate; import com.google.common.base.Verify; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; import com.google.errorprone.matchers.Description; import com.google.errorprone.matchers.Matcher; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.ClassTree; import com.sun.source.tree.Tree.Kind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.code.Types; import com.sun.tools.javac.util.Name; import java.lang.annotation.Annotation; /** * Checker that ensures implementations of {@link Annotation} override equals and hashCode. * Otherwise, the implementation inherits equals and hashCode from {@link Object}, and those do not * meet the contract specified by the {@link Annotation} interface. */ @BugPattern( name = "BadAnnotationImplementation", summary = "Classes that implement Annotation must override equals and hashCode. Consider " + "using AutoAnnotation instead of implementing Annotation by hand.", category = JDK, severity = WARNING ) public class BadAnnotationImplementation extends BugChecker implements ClassTreeMatcher { private static final Matcher<ClassTree> CLASS_TREE_MATCHER = allOf( anyOf(kindIs(CLASS), kindIs(ENUM)), isSubtypeOf(ANNOTATION_TYPE)); @Override public Description matchClass(ClassTree classTree, final VisitorState state) { if (!CLASS_TREE_MATCHER.matches(classTree, state)) { return Description.NO_MATCH; } // If this is an enum that is trying to implement Annotation, give a special error message. if (classTree.getKind() == Kind.ENUM) { return buildDescription(classTree) .setMessage( "Enums cannot correctly implement Annotation because their equals and hashCode " + "methods are final. Consider using AutoAnnotation instead of implementing " + "Annotation by hand.") .build(); } // Otherwise walk up type hierarchy looking for equals and hashcode methods MethodSymbol equals = null; MethodSymbol hashCode = null; final Types types = state.getTypes(); Name equalsName = state.getName("equals"); Predicate<MethodSymbol> equalsPredicate = new Predicate<MethodSymbol>() { @Override public boolean apply(MethodSymbol methodSymbol) { return !methodSymbol.isStatic() && ((methodSymbol.flags() & Flags.SYNTHETIC) == 0) && ((methodSymbol.flags() & Flags.ABSTRACT) == 0) && methodSymbol.getParameters().size() == 1 && types.isSameType( methodSymbol.getParameters().get(0).type, state.getSymtab().objectType); } }; Name hashCodeName = state.getName("hashCode"); Predicate<MethodSymbol> hashCodePredicate = new Predicate<MethodSymbol>() { @Override public boolean apply(MethodSymbol methodSymbol) { return !methodSymbol.isStatic() && ((methodSymbol.flags() & Flags.SYNTHETIC) == 0) && ((methodSymbol.flags() & Flags.ABSTRACT) == 0) && methodSymbol.getParameters().isEmpty(); } }; for (Type sup : types.closure(ASTHelpers.getSymbol(classTree).type)) { if (equals == null) { equals = getMatchingMethod(sup, equalsName, equalsPredicate); } if (hashCode == null) { hashCode = getMatchingMethod(sup, hashCodeName, hashCodePredicate); } } Verify.verifyNotNull(equals); Verify.verifyNotNull(hashCode); Symbol objectSymbol = state.getSymtab().objectType.tsym; if (equals.owner.equals(objectSymbol) || hashCode.owner.equals(objectSymbol)) { return describeMatch(classTree); } return Description.NO_MATCH; } private static MethodSymbol getMatchingMethod( Type type, Name name, Predicate<MethodSymbol> predicate) { Scope scope = type.tsym.members(); for (Symbol sym : scope.getSymbolsByName(name)) { if (!(sym instanceof MethodSymbol)) { continue; } MethodSymbol methodSymbol = (MethodSymbol) sym; if (predicate.apply(methodSymbol)) { return methodSymbol; } } return null; } }