Package com.google.errorprone.bugpatterns

Source Code of com.google.errorprone.bugpatterns.ComparisonOutOfRange$BadComparisonMatcher

/*
* Copyright 2013 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.MaturityLevel.MATURE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Matchers.anyOf;

import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.BinaryTreeMatcher;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
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.Tree.Kind;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCLiteral;

import java.util.Arrays;
import java.util.List;

/**
* @author bill.pugh@gmail.com (Bill Pugh)
* @author eaftan@google.com (Eddie Aftandilian)
*
* TODO(user): Support other types of comparisons?  Are there likely to be errors in those?
*/
@BugPattern(name = "ComparisonOutOfRange",
    summary = "Comparison to value that is out of range for the compared type",
    explanation = "This checker looks for equality comparisons to values that are out of " +
        "range for the compared type.  For example, bytes may have a value in the range " +
        Byte.MIN_VALUE + " to " + Byte.MAX_VALUE + ". Comparing a byte for equality with a value " +
        "outside that range will always evaluate to false and usually indicates an error in the " +
        "code.\n\n" +
        "This checker currently supports checking for bad byte and character comparisons.",
    category = JDK, severity = ERROR, maturity = MATURE)
public class ComparisonOutOfRange extends BugChecker implements BinaryTreeMatcher {

  private static final String MESSAGE_TEMPLATE = "%ss may have a value in the range %d to %d; "
      + "therefore, this comparison to %s will always evaluate to %s";

  /**
   * Matches comparisons that are out of range for the given type.  Parameterized based on the
   * type of comparison (byte or char).
   */
  private static class BadComparisonMatcher implements Matcher<BinaryTree> {
    /**
     * The type of bad comparison matcher to create. Must be either Byte.TYPE or Character.TYPE.
     */
    private Class<?> type;

    private boolean initialized = false;
    private Type comparisonType;
    private int maxValue;
    private int minValue;

    public BadComparisonMatcher(Class<?> type) {
      if (type != Byte.TYPE && type != Character.TYPE) {
        throw new IllegalArgumentException("type must be either byte or char, but was "
            + type.getName());
      }
      this.type = type;
    }

    /**
     * Initialize matcher for the specific type.  We can't do this in the constructor because
     * we need the symbol table, which isn't available at that time.
     *
     * @param symbolTable The compiler's symbol table
     */
    private void init(Symtab symbolTable) {
      if (initialized) {
        throw new IllegalStateException("Do not try to initialize twice!");
      }

      // Specialize matcher based on type.
      if (type == Byte.TYPE) {
        comparisonType = symbolTable.byteType;
        maxValue = Byte.MAX_VALUE;
        minValue = Byte.MIN_VALUE;
      } else {
        comparisonType = symbolTable.charType;
        maxValue = Character.MAX_VALUE;
        minValue = Character.MIN_VALUE;
      }
      initialized = true;
    }

    @Override
    public boolean matches(BinaryTree tree, VisitorState state) {
      if (!initialized) {
        init(state.getSymtab());
      }

      // Must be an == or != comparison.
      if (tree.getKind() != Kind.EQUAL_TO && tree.getKind() != Kind.NOT_EQUAL_TO) {
        return false;
      }

      // Match trees that have one literal operand and another of the specified type.
      List<ExpressionTree> binaryTreeMatches = ASTHelpers.matchBinaryTree(tree,
          Arrays.asList(Matchers.<ExpressionTree>isInstance(JCLiteral.class),
              Matchers.<ExpressionTree>isSameType(comparisonType)),
          state);
      if (binaryTreeMatches == null) {
        return false;
      }
      JCLiteral literal = (JCLiteral) binaryTreeMatches.get(0);

      // Check whether literal is out of range for the specified type.  Logic is based on
      // JLS 5.6.2 - Binary Numeric Promotion:
      // If either is double, other is converted to double.
      // If either is float, other is converted to float.
      // If either is long, other is converted to long.
      // Otherwise, both are converted to int.
      Object literalValue = literal.getValue();
      switch (literal.getKind()) {
        case DOUBLE_LITERAL:
          double doubleValue = ((Double) literalValue).doubleValue();
          return doubleValue < minValue || doubleValue > maxValue;
        case FLOAT_LITERAL:
          float floatValue = ((Float) literalValue).floatValue();
          return floatValue < minValue || floatValue > maxValue;
        case LONG_LITERAL:
          long longValue = ((Long) literalValue).longValue();
          return longValue < minValue || longValue > maxValue;
        default:
          // JCLiteral.getValue() can return Integer, Character, or Boolean.
          int intValue;
          if (literalValue instanceof Integer) {
            intValue = ((Integer) literalValue).intValue();
          } else if (literalValue instanceof Character) {
            intValue = ((Character) literalValue).charValue();
          } else if (literalValue instanceof Boolean) {
            throw new IllegalStateException("Cannot compare " + comparisonType
                + " to boolean literal");
          } else {
            throw new IllegalStateException("Unexpected literal type: " + literal);
          }
          return intValue < minValue || intValue > maxValue;
      }
    }
  }

  private static final Matcher<BinaryTree> BYTE_MATCHER = new BadComparisonMatcher(Byte.TYPE);
  private static final Matcher<BinaryTree> CHAR_MATCHER = new BadComparisonMatcher(Character.TYPE);

  @Override
  public Description matchBinary(BinaryTree tree, VisitorState state) {
    if(anyOf(BYTE_MATCHER, CHAR_MATCHER).matches(tree, state)) {
      return describe(tree, state);
    }
    return Description.NO_MATCH;
  }

  /**
   * Suggested fixes are as follows.  For the byte case, convert the literal to its byte
   * representation. For example, "255" becomes "-1.  For the character case, replace the
   * comparison with "true"/"false" since it's not clear what was intended and that is
   * semantically equivalent.
   *
   * TODO(user): Suggested fixes don't handle side-effecting expressions, such as
   * (d = reader.read()) == -1.  Maybe add special case handling for assignments.
   */
  public Description describe(BinaryTree tree, VisitorState state) {
    List<ExpressionTree> binaryTreeMatches = ASTHelpers.matchBinaryTree(tree,
        Arrays.asList(Matchers.<ExpressionTree>isInstance(JCLiteral.class),
            Matchers.<ExpressionTree>anything()),
        state);
    if (binaryTreeMatches == null) {
      throw new IllegalStateException("Expected one of the operands to be a literal");
    }
    JCLiteral literal = (JCLiteral) binaryTreeMatches.get(0);
    JCTree nonLiteralOperand = (JCTree) binaryTreeMatches.get(1);
    boolean byteMatch = state.getTypes().isSameType(nonLiteralOperand.type,
        state.getSymtab().byteType);

    boolean willEvaluateTo = (tree.getKind() != Kind.EQUAL_TO);
    Fix fix;
    String customDiagnosticMessage;
    if (byteMatch) {
      String replacement = Byte.toString(((Number) literal.getValue()).byteValue());

      // Correct for poor javac 6 literal parsing.
      int actualStart = ASTHelpers.getActualStartPosition(literal, state.getSourceCode());
      if (actualStart != literal.getStartPosition()) {
        fix =
            SuggestedFix.replace(literal, replacement, actualStart - literal.getStartPosition(), 0);
      } else {
        fix = SuggestedFix.replace(literal, replacement);
      }
      customDiagnosticMessage = String.format(MESSAGE_TEMPLATE, "byte", (int) Byte.MIN_VALUE,
          (int) Byte.MAX_VALUE, literal.toString(), Boolean.toString(willEvaluateTo));
    } else {
      fix = SuggestedFix.replace(tree, Boolean.toString(willEvaluateTo));
      customDiagnosticMessage = String.format(MESSAGE_TEMPLATE, "char", (int) Character.MIN_VALUE,
          (int) Character.MAX_VALUE, literal.toString(), Boolean.toString(willEvaluateTo));
    }
    return Description.builder(tree, pattern)
        .addFix(fix)
        .setMessage(customDiagnosticMessage)
        .build();
  }
}
TOP

Related Classes of com.google.errorprone.bugpatterns.ComparisonOutOfRange$BadComparisonMatcher

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.