/*
* 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.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.kindIs;
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.sun.source.tree.BinaryTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
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.code.Types;
import com.sun.tools.javac.tree.JCTree;
/**
* @author bill.pugh@gmail.com (Bill Pugh)
* @author eaftan@google.com (Eddie Aftandilian)
*/
@BugPattern(
name = "BadShiftAmount",
summary = "Shift by an amount that is out of range",
category = JDK,
severity = ERROR
)
public class BadShiftAmount extends BugChecker implements BinaryTreeMatcher {
/**
* Matches if the left operand is an int, byte, short, or char, and the right operand is a
* literal that is not in the range 0-31 inclusive.
*
* <p>In a shift expression, byte, short, and char undergo unary numeric promotion and are
* promoted to int. See JLS 5.6.1.
*/
private static final Matcher<BinaryTree> BAD_SHIFT_AMOUNT_INT = new Matcher<BinaryTree>() {
@Override
public boolean matches(BinaryTree tree, VisitorState state) {
Type leftType = ((JCTree) tree.getLeftOperand()).type;
Types types = state.getTypes();
Symtab symtab = state.getSymtab();
if (!(types.isSameType(leftType, symtab.intType)) &&
!(types.isSameType(leftType, symtab.byteType)) &&
!(types.isSameType(leftType, symtab.shortType)) &&
!(types.isSameType(leftType, symtab.charType))) {
return false;
}
ExpressionTree rightOperand = tree.getRightOperand();
if (rightOperand instanceof LiteralTree) {
Object rightValue = ((LiteralTree) rightOperand).getValue();
if (rightValue instanceof Number) {
int intValue = ((Number) rightValue).intValue();
return intValue < 0 || intValue > 31;
}
}
return false;
}
};
public static final Matcher<BinaryTree> BINARY_TREE_MATCHER = allOf(
anyOf(
kindIs(Kind.LEFT_SHIFT),
kindIs(Kind.RIGHT_SHIFT),
kindIs(Kind.UNSIGNED_RIGHT_SHIFT)),
BAD_SHIFT_AMOUNT_INT
);
@Override
public Description matchBinary(BinaryTree tree, VisitorState state) {
if (!BINARY_TREE_MATCHER.matches(tree, state)) {
return Description.NO_MATCH;
}
/*
* For shift amounts in [32, 63], cast the left operand to long. Otherwise change the shift
* amount to whatever would actually be used.
*/
int intValue = ((Number) ((LiteralTree) tree.getRightOperand()).getValue()).intValue();
Fix fix;
if (intValue >= 32 && intValue <= 63) {
if (tree.getLeftOperand().getKind() == Kind.INT_LITERAL) {
fix = SuggestedFix.postfixWith(tree.getLeftOperand(), "L");
} else {
fix = SuggestedFix.prefixWith(tree, "(long) ");
}
} else {
// This is the equivalent shift distance according to JLS 15.19.
String actualShiftDistance = Integer.toString(intValue & 0x1f);
fix = SuggestedFix.replace(tree.getRightOperand(), actualShiftDistance);
}
return describeMatch(tree, fix);
}
}