/** * Fixes the error by assigning the result of the call to the receiver reference, or deleting the * method call. */ public Description describe(MethodInvocationTree methodInvocationTree, VisitorState state) { // Find the root of the field access chain, i.e. a.intern().trim() ==> a. ExpressionTree identifierExpr = ASTHelpers.getRootAssignable(methodInvocationTree); String identifierStr = null; Type identifierType = null; if (identifierExpr != null) { identifierStr = identifierExpr.toString(); if (identifierExpr instanceof JCIdent) { identifierType = ((JCIdent) identifierExpr).sym.type; } else if (identifierExpr instanceof JCFieldAccess) { identifierType = ((JCFieldAccess) identifierExpr).sym.type; } else { throw new IllegalStateException("Expected a JCIdent or a JCFieldAccess"); } } Type returnType = ASTHelpers.getReturnType(((JCMethodInvocation) methodInvocationTree).getMethodSelect()); Fix fix; if (identifierStr != null && !"this".equals(identifierStr) && returnType != null && state.getTypes().isAssignable(returnType, identifierType)) { // Fix by assigning the assigning the result of the call to the root receiver reference. fix = SuggestedFix.prefixWith(methodInvocationTree, identifierStr + " = "); } else { // Unclear what the programmer intended. Delete since we don't know what else to do. Tree parent = state.getPath().getParentPath().getLeaf(); fix = SuggestedFix.delete(parent); } return describeMatch(methodInvocationTree, fix); }
@Override protected boolean matchArgument(ExpressionTree tree, VisitorState state) { Type type = ASTHelpers.getType(tree); if (!type.isReference()) { return false; } ClassTree classTree = ASTHelpers.findEnclosingNode(state.getPath(), ClassTree.class); if (classTree == null) { return false; } Type classType = ASTHelpers.getType(classTree); if (classType == null) { return false; } if (inEqualsOrCompareTo(classType, type, state)) { return false; } if (ASTHelpers.isSubtype(type, state.getSymtab().enumSym.type, state)) { return false; } if (ASTHelpers.isSubtype(type, state.getSymtab().classType, state)) { return false; } if (!implementsEquals(type, state)) { return false; } return true; }
@Override public Description matchClass(ClassTree classTree, VisitorState state) { TypeSymbol symbol = ASTHelpers.getSymbol(classTree); if (symbol.getKind() != ElementKind.CLASS) { return Description.NO_MATCH; } MethodTree equals = null; for (Tree member : classTree.getMembers()) { if (!(member instanceof MethodTree)) { continue; } MethodTree methodTree = (MethodTree) member; if (EQUALS_MATCHER.matches(methodTree, state)) { equals = methodTree; } } if (equals == null) { return Description.NO_MATCH; } MethodSymbol hashCodeSym = ASTHelpers.resolveExistingMethod( state, symbol, state.getName("hashCode"), ImmutableList.<Type>of(), ImmutableList.<Type>of()); if (hashCodeSym.owner.equals(state.getSymtab().objectType.tsym)) { return describeMatch(equals); } return Description.NO_MATCH; }
@Override public boolean matches(TryTree tryTree, VisitorState state) { return ASTHelpers.findEnclosingNode(state.getPath(), DoWhileLoopTree.class) != null || ASTHelpers.findEnclosingNode(state.getPath(), EnhancedForLoopTree.class) != null || ASTHelpers.findEnclosingNode(state.getPath(), WhileLoopTree.class) != null || ASTHelpers.findEnclosingNode(state.getPath(), ForLoopTree.class) != null; }
@Override public boolean matches(ExpressionTree expressionTree, VisitorState state) { Symbol sym = ASTHelpers.getSymbol(expressionTree); if (sym == null) { return false; } if (!(sym instanceof MethodSymbol)) { throw new IllegalArgumentException( "DescendantOf matcher expects a method call but found " + sym.getClass() + ". Expression: " + expressionTree); } if (sym.isStatic()) { return false; } if (methodName.equals(sym.toString())) { Type accessedReferenceType = sym.owner.type; Type collectionType = state.getTypeFromString(fullClassName); if (collectionType != null) { return state .getTypes() .isSubtype(accessedReferenceType, state.getTypes().erasure(collectionType)); } } return false; }
/** Return true if the given type is 'void' or 'Void'. */ public static boolean isVoidType(Type type, VisitorState state) { if (type == null) { return false; } return type.getKind() == TypeKind.VOID || state.getTypes().isSameType(Suppliers.JAVA_LANG_VOID_TYPE.get(state), type); }
/** Returns true if {@code erasure(s) == erasure(t)}. */ public static boolean isSameType(Type s, Type t, VisitorState state) { if (s == null || t == null) { return false; } Types types = state.getTypes(); return types.isSameType(types.erasure(s), types.erasure(t)); }
@Override public Description matchMethod(MethodTree methodTree, VisitorState state) { // if method is itself annotated with @Inject or it has no ancestor methods, return NO_MATCH; if (hasInjectAnnotation().matches(methodTree, state)) { return Description.NO_MATCH; } boolean foundJavaxInject = false; for (MethodSymbol superMethod : ASTHelpers.findSuperMethods(ASTHelpers.getSymbol(methodTree), state.getTypes())) { // With a Guice annotation, Guice will still inject the subclass-overridden method. if (ASTHelpers.hasAnnotation(superMethod, GUICE_INJECT_ANNOTATION, state)) { return Description.NO_MATCH; } // is not necessarily a match even if we find javax Inject on an ancestor // since a higher up ancestor may have @com.google.inject.Inject foundJavaxInject |= ASTHelpers.hasAnnotation(superMethod, JAVAX_INJECT_ANNOTATION, state); } if (foundJavaxInject) { return describeMatch( methodTree, SuggestedFix.builder() .addImport(JAVAX_INJECT_ANNOTATION) .prefixWith(methodTree, "@Inject\n") .build()); } return Description.NO_MATCH; }
/** * Determines whether a symbol has an annotation of the given type. This includes annotations * inherited from superclasses due to @Inherited. * * @param annotationType The type of the annotation to look for (e.g, "javax.annotation.Nullable") */ public static boolean hasAnnotation(Symbol sym, String annotationType, VisitorState state) { Symbol annotationSym = state.getSymbolFromString(annotationType); Symbol inheritedSym = state.getSymtab().inheritedType.tsym; if ((sym == null) || (annotationSym == null)) { return false; } if ((sym instanceof ClassSymbol) && (annotationSym.attribute(inheritedSym) != null)) { while (sym != null) { if (sym.attribute(annotationSym) != null) { return true; } sym = ((ClassSymbol) sym).getSuperclass().tsym; } return false; } else { return sym.attribute(annotationSym) != null; } }
private boolean inEqualsOrCompareTo(Type classType, Type type, VisitorState state) { MethodTree methodTree = ASTHelpers.findEnclosingNode(state.getPath(), MethodTree.class); if (methodTree == null) { return false; } MethodSymbol sym = ASTHelpers.getSymbol(methodTree); if (sym == null || sym.isStatic()) { return false; } Symbol compareTo = getOnlyMember(state, state.getSymtab().comparableType, "compareTo"); Symbol equals = getOnlyMember(state, state.getSymtab().objectType, "equals"); if (!sym.overrides(compareTo, classType.tsym, state.getTypes(), false) && !sym.overrides(equals, classType.tsym, state.getTypes(), false)) { return false; } if (!ASTHelpers.isSameType(type, classType, state)) { return false; } return true; }
/** Check if the method declares or inherits an implementation of .equals() */ public static boolean implementsEquals(Type type, VisitorState state) { Name equalsName = state.getName("equals"); Symbol objectEquals = getOnlyMember(state, state.getSymtab().objectType, "equals"); for (Type sup : state.getTypes().closure(type)) { if (sup.tsym.isInterface()) { continue; } if (ASTHelpers.isSameType(sup, state.getSymtab().objectType, state)) { return false; } WriteableScope scope = sup.tsym.members(); if (scope == null) { continue; } for (Symbol sym : scope.getSymbolsByName(equalsName)) { if (sym.overrides(objectEquals, type.tsym, state.getTypes(), false)) { return true; } } } return false; }
@Override public boolean matches(TryTree tryTree, VisitorState state) { MethodTree enclosingMethodTree = ASTHelpers.findEnclosingNode(state.getPath(), MethodTree.class); Name name = enclosingMethodTree.getName(); return JUnitMatchers.looksLikeJUnit3SetUp.matches(enclosingMethodTree, state) || JUnitMatchers.looksLikeJUnit3TearDown.matches(enclosingMethodTree, state) || name.contentEquals("main") // TODO(schmitt): Move to JUnitMatchers? || name.contentEquals("suite") || Matchers.hasAnnotation(JUNIT_BEFORE_ANNOTATION).matches(enclosingMethodTree, state) || Matchers.hasAnnotation(JUNIT_AFTER_ANNOTATION).matches(enclosingMethodTree, state); }
private static Symbol getOnlyMember(VisitorState state, Type type, String name) { return getOnlyElement(type.tsym.members().getSymbolsByName(state.getName(name))); }
@Override public Type get(VisitorState state) { return ((JCTree) state.findEnclosing(ClassTree.class)).type; }
@Override public Type get(VisitorState state) { return state.getSymtab().annotationType; }
@Override public Type get(VisitorState state) { return state.getSymtab().objectType; }
@Override public Type get(VisitorState state) { return state.getSymtab().booleanType; }
@Override public Type get(VisitorState state) { return state.getTypeFromString("java.lang.Boolean"); }
/** * Returns the {@link Nullness} for an expression as determined by the nullness dataflow analysis. */ public static Nullness getNullnessValue( ExpressionTree expr, VisitorState state, NullnessAnalysis nullnessAnalysis) { TreePath pathToExpr = new TreePath(state.getPath(), expr); return nullnessAnalysis.getNullness(pathToExpr, state.context); }