示例#1
0
  @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 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;
  }
 @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;
 }
示例#4
0
 @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;
 }
  /**
   * 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
 public Description matchMethodInvocation(
     MethodInvocationTree methodInvocationTree, VisitorState state) {
   if (methodInvocationTree.getArguments().isEmpty()) {
     return Description.NO_MATCH;
   }
   if (!TRUTH_SUBJECT_CALL.matches(methodInvocationTree, state)) {
     return Description.NO_MATCH;
   }
   ExpressionTree rec = ASTHelpers.getReceiver(methodInvocationTree);
   if (rec == null) {
     return Description.NO_MATCH;
   }
   if (!ASSERT_THAT.matches(rec, state)) {
     return Description.NO_MATCH;
   }
   ExpressionTree expr = getOnlyElement(((MethodInvocationTree) rec).getArguments());
   if (expr == null) {
     return Description.NO_MATCH;
   }
   // check that argument of assertThat is a constant
   if (ASTHelpers.constValue(expr) == null) {
     return Description.NO_MATCH;
   }
   // check that expectation isn't a constant
   ExpressionTree expectation = getOnlyElement(methodInvocationTree.getArguments());
   if (ASTHelpers.constValue(expectation) != null) {
     return Description.NO_MATCH;
   }
   SuggestedFix fix = SuggestedFix.swap(expr, expectation);
   return buildDescription(methodInvocationTree).addFix(fix).build();
 }
示例#7
0
 @Override
 public Optional<MatchState> matchResult(ExpressionTree tree, VisitorState state) {
   Symbol sym = ASTHelpers.getSymbol(tree);
   if (!(sym instanceof MethodSymbol)) {
     return Optional.absent();
   }
   if (tree instanceof NewClassTree) {
     // Don't match constructors as they are neither static nor instance methods.
     return Optional.absent();
   }
   if (tree instanceof MethodInvocationTree) {
     tree = ((MethodInvocationTree) tree).getMethodSelect();
   }
   return Optional.of(
       MatchState.create(ASTHelpers.getReceiverType(tree), (MethodSymbol) sym));
 }
示例#8
0
  @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;
  }
示例#9
0
 // TODO(eaftan): refactor other code that accesses symbols to use this method
 public static Symbol getSymbol(Tree tree) {
   if (tree instanceof ClassTree) {
     return getSymbol((ClassTree) tree);
   }
   if (tree instanceof MethodTree) {
     return getSymbol((MethodTree) tree);
   }
   if (tree instanceof VariableTree) {
     return getSymbol((VariableTree) tree);
   }
   if (tree instanceof JCFieldAccess) {
     return ((JCFieldAccess) tree).sym;
   }
   if (tree instanceof JCIdent) {
     return ((JCIdent) tree).sym;
   }
   if (tree instanceof JCMethodInvocation) {
     return ASTHelpers.getSymbol((MethodInvocationTree) tree);
   }
   if (tree instanceof JCNewClass) {
     return ((JCNewClass) tree).constructor;
   }
   if (tree instanceof AnnotationTree) {
     return getSymbol(((AnnotationTree) tree).getAnnotationType());
   }
   if (tree instanceof PackageTree) {
     return getSymbol((PackageTree) tree);
   }
   return null;
 }
  /**
   * Extend suppression sets for both {@code @SuppressWarnings} and custom suppression annotations.
   * When we explore a new node, we have to extend the suppression sets with any new suppressed
   * warnings or custom suppression annotations. We also have to retain the previous suppression set
   * so that we can reinstate it when we move up the tree.
   *
   * <p>We do not modify the existing suppression sets, so they can be restored when moving up the
   * tree. We also avoid copying the suppression sets if the next node to explore does not have any
   * suppressed warnings or custom suppression annotations. This is the common case.
   *
   * @param sym The {@code Symbol} for the AST node currently being scanned
   * @param suppressWarningsType The {@code Type} for {@code @SuppressWarnings}, as given by javac's
   *     symbol table
   * @param suppressionsOnCurrentPath The set of strings in all {@code @SuppressWarnings}
   *     annotations on the current path through the AST
   * @param customSuppressionsOnCurrentPath The set of all custom suppression annotations
   */
  public NewSuppressions extendSuppressionSets(
      Symbol sym,
      Type suppressWarningsType,
      Set<String> suppressionsOnCurrentPath,
      Set<Class<? extends Annotation>> customSuppressionsOnCurrentPath,
      boolean inGeneratedCode) {

    boolean newInGeneratedCode = inGeneratedCode || ASTHelpers.hasAnnotation(sym, Generated.class);

    /** Handle custom suppression annotations. */
    Set<Class<? extends Annotation>> newCustomSuppressions = null;
    for (Class<? extends Annotation> annotationType : customSuppressionAnnotations) {
      if (ASTHelpers.hasAnnotation(sym, annotationType)) {
        if (newCustomSuppressions == null) {
          newCustomSuppressions = new HashSet<>(customSuppressionsOnCurrentPath);
        }
        newCustomSuppressions.add(annotationType);
      }
    }

    /** Handle @SuppressWarnings. */
    Set<String> newSuppressions = null;
    // Iterate over annotations on this symbol, looking for SuppressWarnings
    for (Attribute.Compound attr : sym.getAnnotationMirrors()) {
      // TODO(eaftan): use JavacElements.getAnnotation instead
      if (attr.type.tsym == suppressWarningsType.tsym) {
        for (List<Pair<MethodSymbol, Attribute>> v = attr.values; v.nonEmpty(); v = v.tail) {
          Pair<MethodSymbol, Attribute> value = v.head;
          if (value.fst.name.toString().equals("value"))
            if (value.snd instanceof Attribute.Array) { // SuppressWarnings takes an array
              for (Attribute suppress : ((Attribute.Array) value.snd).values) {
                if (newSuppressions == null) {
                  newSuppressions = new HashSet<>(suppressionsOnCurrentPath);
                }
                // TODO(eaftan): check return value to see if this was a new warning?
                newSuppressions.add((String) suppress.getValue());
              }
            } else {
              throw new RuntimeException("Expected SuppressWarnings annotation to take array type");
            }
        }
      }
    }

    return new NewSuppressions(newSuppressions, newCustomSuppressions, newInGeneratedCode);
  }
示例#11
0
 /** Gets the symbol for a method invocation. */
 public static MethodSymbol getSymbol(MethodInvocationTree tree) {
   Symbol sym = ASTHelpers.getSymbol(tree.getMethodSelect());
   if (!(sym instanceof MethodSymbol)) {
     // Defensive. Would only occur if there are errors in the AST.
     return null;
   }
   return (MethodSymbol) sym;
 }
示例#12
0
 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;
 }
示例#13
0
    @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);
    }
示例#14
0
 @Override
 @Nullable
 protected Unifier defaultAction(Tree node, @Nullable Unifier unifier) {
   Symbol symbol = ASTHelpers.getSymbol(node);
   if (symbol != null
       && symbol.getEnclosingElement() != null
       && symbol
           .getEnclosingElement()
           .getQualifiedName()
           .contentEquals(classIdent().getQualifiedName())
       && symbol.getSimpleName().contentEquals(member())) {
     return memberType().unify(symbol.asType(), unifier);
   }
   return null;
 }
 @Override
 public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
   if (tree.getTypeDecls().size() <= 1) {
     // package-info.java files have zero top-level declarations, everything
     // else should have exactly one.
     return Description.NO_MATCH;
   }
   if (tree.getPackageName() == null) {
     // Real code doesn't use the default package.
     return Description.NO_MATCH;
   }
   List<String> names = new ArrayList<>();
   for (Tree member : tree.getTypeDecls()) {
     if (member instanceof ClassTree) {
       ClassTree classMember = (ClassTree) member;
       switch (classMember.getKind()) {
         case CLASS:
         case INTERFACE:
         case ANNOTATION_TYPE:
         case ENUM:
           SuppressWarnings suppression =
               ASTHelpers.getAnnotation(classMember, SuppressWarnings.class);
           if (suppression != null
               && !Collections.disjoint(Arrays.asList(suppression.value()), allNames())) {
             // If any top-level classes have @SuppressWarnings("TopLevel"), ignore
             // this compilation unit. We can't rely on the normal suppression
             // mechanism because the only enclosing element is the package declaration,
             // and @SuppressWarnings can't be applied to packages.
             return Description.NO_MATCH;
           }
           names.add(classMember.getSimpleName().toString());
           break;
         default:
           break;
       }
     }
   }
   if (names.size() <= 1) {
     // this can happen with multiple type declarations if some of them are
     // empty (e.g. ";" at the top level counts as an empty type decl)
     return Description.NO_MATCH;
   }
   String message =
       String.format(
           "Expected at most one top-level class declaration, instead found: %s",
           Joiner.on(", ").join(names));
   return buildDescription(tree.getPackageName()).setMessage(message).build();
 }
示例#16
0
    @Override
    public boolean matches(ExpressionTree expressionTree, VisitorState state) {
      Symbol sym = ASTHelpers.getSymbol(expressionTree);
      if (sym != null && sym.getSimpleName().toString().startsWith("log")) {
        return true;
      }

      if (sym != null && sym.isStatic()) {
        if (sym.owner.getQualifiedName().toString().contains("Logger")) {
          return true;
        }
      } else if (expressionTree instanceof MemberSelectTree) {
        if (((MemberSelectTree) expressionTree).getExpression().toString().startsWith("log")) {
          return true;
        }
      }

      return false;
    }
示例#17
0
 /** 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;
 }
示例#18
0
 @Override
 public boolean matches(ExpressionTree expressionTree, VisitorState state) {
   Symbol sym = ASTHelpers.getSymbol(expressionTree);
   return sym != null && sym.getSimpleName().toString().startsWith("assert");
 }