Package com.google.errorprone.bugpatterns

Source Code of com.google.errorprone.bugpatterns.PreconditionsCheckNotNullPrimitive

/*
* Copyright 2011 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.GUAVA;
import static com.google.errorprone.BugPattern.MaturityLevel.MATURE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.methodSelect;
import static com.google.errorprone.matchers.Matchers.staticMethod;

import com.google.common.base.Joiner;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matchers;
import com.google.errorprone.util.ASTHelpers;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCIdent;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Checks that the 1st argument to Preconditions.checkNotNull() isn't a primitive.
* The primitive would be autoboxed to a non-null boxed type, and the check would trivially
* pass.
*
* In our experience, most of these errors are from copied-and-pasted code and should
* simply be removed.
*
* @author sjnickerson@google.com (Simon Nickerson)
* @author eaftan@google.com (Eddie Aftandilian)
*/
@BugPattern(name = "PreconditionsCheckNotNullPrimitive",
    summary = "First argument to Preconditions.checkNotNull() is a primitive rather " +
        "than an object reference",
    explanation =
        "Preconditions.checkNotNull() takes as an argument a reference that should be " +
        "non-null. Often a primitive is passed as the argument to check. The primitive " +
        "will be [http://docs.oracle.com/javase/7/docs/technotes/guides/language/autoboxing.html " +
        "autoboxed] into a boxed object, which is non-null, causing the check to " +
        "always pass without the condition being evaluated.\n" +
        "If the intent was to ensure that the primitive met some criterion (e.g., a boolean " +
        "that should be non-null), please use Precondtions.checkState() or " +
        "Preconditions.checkArgument() instead.",
    category = GUAVA, severity = ERROR, maturity = MATURE)
public class PreconditionsCheckNotNullPrimitive
    extends BugChecker implements MethodInvocationTreeMatcher {

  @Override
  public Description matchMethodInvocation(MethodInvocationTree methodInvocationTree, VisitorState state) {
    if (allOf(
            methodSelect(staticMethod(
                "com.google.common.base.Preconditions", "checkNotNull")),
            argument(0, Matchers.<ExpressionTree>isPrimitiveType()))
            .matches(methodInvocationTree, state)) {
      return describe(methodInvocationTree, state);
    }
    return Description.NO_MATCH;
  }

  /**
   * If the call to Preconditions.checkNotNull is part of an expression (assignment, return, etc.),
   * we substitute the argument for the method call. E.g.:
   *   bar = Preconditions.checkNotNull(foo); ==> bar = foo;
   * If the argument to Preconditions.checkNotNull is a comparison using == or != and one of the
   * operands is null, we call checkNotNull on the non-null operand. E.g.:
   *   checkNotNull(a == null); ==> checkNotNull(a);
   * If the argument is a method call or binary tree and its return type is boolean, change it to a
   * checkArgument/checkState. E.g.:
   *   Preconditions.checkNotNull(foo.hasFoo()) ==> Preconditions.checkArgument(foo.hasFoo())
   * Otherwise, delete the checkNotNull call. E.g.:
   *   Preconditions.checkNotNull(foo); ==> [delete the line]
   */
  public Description describe(MethodInvocationTree methodInvocationTree, VisitorState state) {
    ExpressionTree arg1 = methodInvocationTree.getArguments().get(0);
    Tree parent = state.getPath().getParentPath().getLeaf();

    // Assignment, return, etc.
    if (parent.getKind() != Kind.EXPRESSION_STATEMENT) {
      return describeMatch(arg1,
          SuggestedFix.replace(methodInvocationTree, arg1.toString()));
    }

    // Comparison to null
    if (arg1.getKind() == Kind.EQUAL_TO || arg1.getKind() == Kind.NOT_EQUAL_TO) {
      BinaryTree binaryExpr = (BinaryTree) arg1;
      if (binaryExpr.getLeftOperand().getKind() == Kind.NULL_LITERAL) {
        return describeMatch(arg1,
            SuggestedFix.replace(arg1, binaryExpr.getRightOperand().toString()));
      }
      if (binaryExpr.getRightOperand().getKind() == Kind.NULL_LITERAL) {
        return describeMatch(arg1,
            SuggestedFix.replace(arg1, binaryExpr.getLeftOperand().toString()));
      }
    }

    if ((arg1 instanceof BinaryTree || arg1.getKind() == Kind.METHOD_INVOCATION ||
         arg1.getKind() == Kind.LOGICAL_COMPLEMENT) &&
        ((JCExpression) arg1).type == state.getSymtab().booleanType) {
      return describeMatch(arg1,
          createCheckArgumentOrStateCall(methodInvocationTree, state, arg1));
    }

    return describeMatch(arg1, SuggestedFix.delete(parent));
  }

  /**
   * Creates a SuggestedFix that replaces the checkNotNull call with a checkArgument or checkState
   * call.
   */
  private Fix createCheckArgumentOrStateCall(MethodInvocationTree methodInvocationTree,
      VisitorState state, ExpressionTree arg1) {
    SuggestedFix.Builder fix = SuggestedFix.builder();
    String replacementMethod = "checkState";
    if (hasMethodParameter(state.getPath(), arg1)) {
      replacementMethod = "checkArgument";
    }

    StringBuilder replacement = new StringBuilder();

    // Was the original call to Preconditions.checkNotNull a static import or not?
    if (methodInvocationTree.getMethodSelect().getKind() == Kind.IDENTIFIER) {
      replacement.append(replacementMethod + "(");
      fix.addStaticImport("com.google.common.base.Preconditions." + replacementMethod);
    } else {
      replacement.append("Preconditions." + replacementMethod + "(");
    }

    Joiner.on(", ").appendTo(replacement, methodInvocationTree.getArguments());

    replacement.append(")");
    fix.replace(methodInvocationTree, replacement.toString());
    return fix.build();
  }

  /**
   * Determines whether the expression contains a reference to one of the
   * enclosing method's parameters.
   *
   * TODO(user): Extract this to ASTHelpers.
   *
   * @param path the path to the current tree node
   * @param tree the node to compare against the parameters
   * @return whether the argument is a parameter to the enclosing method
   */
  private static boolean hasMethodParameter(TreePath path, ExpressionTree tree) {
    Set<Symbol> symbols = new HashSet<>();
    for (IdentifierTree ident : getVariableUses(tree)) {
      Symbol sym = ASTHelpers.getSymbol(ident);
      if (sym.isLocal()) {
        symbols.add(sym);
      }
    }

    // Find enclosing method declaration.
    while (path != null && !(path.getLeaf() instanceof MethodTree)) {
      path = path.getParentPath();
    }
    if (path == null) {
      throw new IllegalStateException("Should have an enclosing method declaration");
    }
    MethodTree methodDecl = (MethodTree) path.getLeaf();
    for (VariableTree param : methodDecl.getParameters()) {
      if (symbols.contains(ASTHelpers.getSymbol(param))) {
        return true;
      }
    }

    return false;
  }

  /**
   * Find the root variable identifiers from an arbitrary expression.
   *
   * Examples:
   *    a.trim().intern() ==> {a}
   *    a.b.trim().intern() ==> {a}
   *    this.intValue.foo() ==> {this}
   *    this.foo() ==> {this}
   *    intern() ==> {}
   *    String.format() ==> {}
   *    java.lang.String.format() ==> {}
   *    x.y.z(s.t) ==> {x,s}
   */
  static List<IdentifierTree> getVariableUses(ExpressionTree tree) {
    final List<IdentifierTree> freeVars = new ArrayList<>();

    new TreeScanner<Void, Void>() {
      @Override
      public Void visitIdentifier(IdentifierTree node, Void v) {
        if (((JCIdent) node).sym instanceof VarSymbol) {
          freeVars.add(node);
        }
        return super.visitIdentifier(node, v);
      }
    }.scan(tree, null);

    return freeVars;
  }
}
TOP

Related Classes of com.google.errorprone.bugpatterns.PreconditionsCheckNotNullPrimitive

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.