/* * Copyright 2016 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.Description.NO_MATCH; import static com.google.errorprone.util.ASTHelpers.getType; import static com.google.errorprone.util.ASTHelpers.isSameType; import com.google.common.math.IntMath; import com.google.common.math.LongMath; 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.util.ASTHelpers; import com.sun.source.tree.BinaryTree; import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.LiteralTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.ParenthesizedTree; import com.sun.source.tree.PrimitiveTypeTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.TypeCastTree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.SimpleTreeVisitor; import com.sun.source.util.TreePath; import com.sun.tools.javac.code.Type; import javax.lang.model.type.TypeKind; /** @author cushon@google.com (Liam Miller-Cushon) */ @BugPattern( name = "ConstantOverflow", summary = "Compile-time constant expression overflows", category = JDK, severity = ERROR ) public class ConstantOverflow extends BugChecker implements BinaryTreeMatcher { @Override public Description matchBinary(BinaryTree tree, VisitorState state) { TreePath path = state.getPath().getParentPath(); while (path != null && path.getLeaf() instanceof ExpressionTree) { if (path.getLeaf() instanceof BinaryTree) { // only match on the outermost nested binary expression return NO_MATCH; } path = path.getParentPath(); } try { tree.accept(CONSTANT_VISITOR, null); return NO_MATCH; } catch (ArithmeticException e) { Description.Builder description = buildDescription(tree); Fix longFix = longFix(tree, state); if (longFix != null) { description.addFix(longFix); } return description.build(); } } /** * If the left operand of an int binary expression is an int literal, suggest making it a long. */ private Fix longFix(ExpressionTree expr, VisitorState state) { BinaryTree binExpr = null; while (expr instanceof BinaryTree) { binExpr = (BinaryTree) expr; expr = binExpr.getLeftOperand(); } if (!(expr instanceof LiteralTree) || expr.getKind() != Kind.INT_LITERAL) { return null; } Type intType = state.getSymtab().intType; if (!isSameType(getType(binExpr), intType, state)) { return null; } SuggestedFix.Builder fix = SuggestedFix.builder().postfixWith(expr, "L"); Tree parent = state.getPath().getParentPath().getLeaf(); if (parent instanceof VariableTree && isSameType(getType(parent), intType, state)) { fix.replace(((VariableTree) parent).getType(), "long"); } return fix.build(); } /** A compile-time constant expression evaluator that checks for overflow. */ private static final SimpleTreeVisitor<Number, Void> CONSTANT_VISITOR = new SimpleTreeVisitor<Number, Void>() { @Override public Number visitConditionalExpression(ConditionalExpressionTree node, Void p) { Number ifTrue = node.getTrueExpression().accept(this, null); Number ifFalse = node.getFalseExpression().accept(this, null); Boolean condition = ASTHelpers.constValue(node.getCondition(), Boolean.class); if (condition == null) { return null; } return condition ? ifTrue : ifFalse; } @Override public Number visitParenthesized(ParenthesizedTree node, Void p) { return node.getExpression().accept(this, null); } @Override public Number visitUnary(UnaryTree node, Void p) { Number value = node.getExpression().accept(this, null); if (value == null) { return value; } if (value instanceof Long) { return unop(node.getKind(), value.longValue()); } else { return unop(node.getKind(), value.intValue()); } } @Override public Number visitBinary(BinaryTree node, Void p) { Number lhs = node.getLeftOperand().accept(this, null); Number rhs = node.getRightOperand().accept(this, null); if (lhs == null || rhs == null) { return null; } // assume that e.g. `Integer.MIN_VALUE - 1` is intentional switch (node.getKind()) { case MINUS: if ((lhs instanceof Long && lhs.longValue() == Long.MIN_VALUE) || (lhs instanceof Integer && lhs.intValue() == Integer.MIN_VALUE)) { return null; } break; case PLUS: if ((lhs instanceof Long && lhs.longValue() == Long.MAX_VALUE) || (lhs instanceof Integer && lhs.intValue() == Integer.MAX_VALUE)) { return null; } break; default: break; } if (lhs instanceof Long || rhs instanceof Long) { return binop(node.getKind(), lhs.longValue(), rhs.longValue()); } else { return binop(node.getKind(), lhs.intValue(), rhs.intValue()); } } @Override public Number visitTypeCast(TypeCastTree node, Void p) { Number value = node.getExpression().accept(this, null); if (value == null) { return null; } if (!(node.getType() instanceof PrimitiveTypeTree)) { return null; } TypeKind kind = ((PrimitiveTypeTree) node.getType()).getPrimitiveTypeKind(); return cast(kind, value); } @Override public Number visitMemberSelect(MemberSelectTree node, Void p) { return getIntegralConstant(node); } @Override public Number visitIdentifier(IdentifierTree node, Void p) { return getIntegralConstant(node); } @Override public Number visitLiteral(LiteralTree node, Void unused) { return getIntegralConstant(node); } }; private static Long unop(Kind kind, long value) { switch (kind) { case UNARY_PLUS: return +value; case UNARY_MINUS: return -value; case BITWISE_COMPLEMENT: return ~value; default: return null; } } private static Integer unop(Kind kind, int value) { switch (kind) { case UNARY_PLUS: return +value; case UNARY_MINUS: return -value; case BITWISE_COMPLEMENT: return ~value; default: return null; } } static Long binop(Kind kind, long lhs, long rhs) { switch (kind) { case MULTIPLY: return LongMath.checkedMultiply(lhs, rhs); case DIVIDE: return lhs / rhs; case REMAINDER: return lhs % rhs; case PLUS: return LongMath.checkedAdd(lhs, rhs); case MINUS: return LongMath.checkedSubtract(lhs, rhs); case LEFT_SHIFT: return lhs << rhs; case RIGHT_SHIFT: return lhs >> rhs; case UNSIGNED_RIGHT_SHIFT: return lhs >>> rhs; case AND: return lhs & rhs; case XOR: return lhs ^ rhs; case OR: return lhs | rhs; default: return null; } } static Integer binop(Kind kind, int lhs, int rhs) { switch (kind) { case MULTIPLY: return IntMath.checkedMultiply(lhs, rhs); case DIVIDE: return lhs / rhs; case REMAINDER: return lhs % rhs; case PLUS: return IntMath.checkedAdd(lhs, rhs); case MINUS: return IntMath.checkedSubtract(lhs, rhs); case LEFT_SHIFT: return lhs << rhs; case RIGHT_SHIFT: return lhs >> rhs; case UNSIGNED_RIGHT_SHIFT: return lhs >>> rhs; case AND: return lhs & rhs; case XOR: return lhs ^ rhs; case OR: return lhs | rhs; default: return null; } } private static Number cast(TypeKind kind, Number value) { switch (kind) { case SHORT: return value.shortValue(); case INT: return value.intValue(); case LONG: return value.longValue(); case BYTE: return value.byteValue(); case CHAR: return (int) (char) value.intValue(); default: return null; } } private static Number getIntegralConstant(Tree node) { Number number = ASTHelpers.constValue(node, Number.class); if (number instanceof Integer || number instanceof Long) { return number; } return null; } }