/*
* SonarQube Java
* Copyright (C) 2012-2016 SonarSource SA
* mailto:contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.java.se.checks;
import com.google.common.collect.ImmutableSet;
import org.sonar.check.Rule;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.se.CheckerContext;
import org.sonar.java.se.ProgramState;
import org.sonar.java.se.constraint.Constraint;
import org.sonar.java.se.constraint.ConstraintManager;
import org.sonar.java.se.constraint.ObjectConstraint;
import org.sonar.java.se.symbolicvalues.SymbolicValue;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeCastTree;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import javax.annotation.Nullable;
import java.util.List;
@Rule(key = "S3518")
public class DivisionByZeroCheck extends SECheck {
private enum DivByZeroStatus implements ObjectConstraint.Status {
ZERO {
@Override
public String valueAsString() {
return "zero";
}
},
NON_ZERO,
UNDETERMINED
}
/**
* This SV is only used to hold the Status to set alongside an initial object constraint in the PostStatementVisitor
*/
private static class DeferredStatusHolderSV extends SymbolicValue {
private final DivByZeroStatus deferredStatus;
public DeferredStatusHolderSV(int id, DivByZeroStatus deferredStatus) {
super(id);
this.deferredStatus = deferredStatus;
}
}
private static class ZeroConstraint extends ObjectConstraint<DivByZeroStatus> {
private ZeroConstraint(DivByZeroStatus status) {
super(false, false, status);
}
@Override
public boolean isInvalidWith(@Nullable Constraint constraint) {
return hasStatus(DivByZeroStatus.ZERO) && constraint instanceof ObjectConstraint && ((ObjectConstraint) constraint).hasStatus(DivByZeroStatus.ZERO);
}
}
@Override
public ProgramState checkPreStatement(CheckerContext context, Tree syntaxNode) {
PreStatementVisitor visitor = new PreStatementVisitor(context);
syntaxNode.accept(visitor);
return visitor.programState;
}
private class PreStatementVisitor extends CheckerTreeNodeVisitor {
private final ConstraintManager constraintManager;
private final CheckerContext context;
PreStatementVisitor(CheckerContext context) {
super(context.getState());
this.context = context;
this.constraintManager = context.getConstraintManager();
}
@Override
public void visitAssignmentExpression(AssignmentExpressionTree tree) {
List<SymbolicValue> symbolicValues;
SymbolicValue var;
SymbolicValue expr;
if (ExpressionUtils.isSimpleAssignment(tree)) {
var = programState.getValue(((IdentifierTree) ExpressionUtils.skipParentheses(tree.variable())).symbol());
expr = programState.peekValue();
} else {
symbolicValues = programState.peekValues(2);
var = symbolicValues.get(1);
expr = symbolicValues.get(0);
}
checkExpression(tree, var, expr);
}
@Override
public void visitBinaryExpression(BinaryExpressionTree tree) {
List<SymbolicValue> symbolicValues;
switch (tree.kind()) {
case MULTIPLY:
case PLUS:
case MINUS:
case DIVIDE:
case REMAINDER:
symbolicValues = programState.peekValues(2);
checkExpression(tree, symbolicValues.get(1), symbolicValues.get(0));
break;
case GREATER_THAN:
case GREATER_THAN_OR_EQUAL_TO:
case LESS_THAN:
case LESS_THAN_OR_EQUAL_TO:
symbolicValues = programState.peekValues(2);
setAsUndetermined(symbolicValues.get(1));
setAsUndetermined(symbolicValues.get(0));
break;
default:
// do nothing
}
}
private void setAsUndetermined(SymbolicValue sv) {
programState = programState.addConstraint(sv, new ZeroConstraint(DivByZeroStatus.UNDETERMINED));
}
private void checkExpression(Tree tree, SymbolicValue leftOp, SymbolicValue rightOp) {
switch (tree.kind()) {
case MULTIPLY:
case MULTIPLY_ASSIGNMENT:
handleMultiply(leftOp, rightOp);
break;
case PLUS:
case PLUS_ASSIGNMENT:
case MINUS:
case MINUS_ASSIGNMENT:
handlePlusMinus(leftOp, rightOp);
break;
case DIVIDE:
case DIVIDE_ASSIGNMENT:
case REMAINDER:
case REMAINDER_ASSIGNMENT:
handleDivide(tree, leftOp, rightOp);
break;
default:
// can not be reached
}
}
private boolean isZero(SymbolicValue symbolicValue) {
return hasStatus(symbolicValue, DivByZeroStatus.ZERO);
}
private boolean isNonZero(SymbolicValue symbolicValue) {
return hasStatus(symbolicValue, DivByZeroStatus.NON_ZERO);
}
private boolean hasStatus(SymbolicValue symbolicValue, DivByZeroStatus status) {
return programState.getConstraintWithStatus(symbolicValue, status) != null;
}
private void handleMultiply(SymbolicValue left, SymbolicValue right) {
boolean leftIsZero = isZero(left);
if (leftIsZero || isZero(right)) {
reuseSymbolicValue(leftIsZero ? left : right);
} else if (isNonZero(left) && isNonZero(right)) {
deferConstraint(DivByZeroStatus.NON_ZERO);
}
}
private void handlePlusMinus(SymbolicValue left, SymbolicValue right) {
boolean leftIsZero = isZero(left);
if (leftIsZero || isZero(right)) {
reuseSymbolicValue(leftIsZero ? right : left);
}
}
private void handleDivide(Tree tree, SymbolicValue leftOp, SymbolicValue rightOp) {
if (isZero(rightOp)) {
reportIssue(tree, rightOp);
} else if (isZero(leftOp)) {
reuseSymbolicValue(leftOp);
} else if (isNonZero(leftOp) && isNonZero(rightOp)) {
deferConstraint(tree.is(Tree.Kind.DIVIDE, Tree.Kind.DIVIDE_ASSIGNMENT) ? DivByZeroStatus.NON_ZERO : DivByZeroStatus.UNDETERMINED);
}
}
private void deferConstraint(DivByZeroStatus status) {
constraintManager.setValueFactory(id -> new DeferredStatusHolderSV(id, status));
}
private void reuseSymbolicValue(SymbolicValue sv) {
constraintManager.setValueFactory(id -> new DeferredStatusHolderSV(id, statusFromSV(sv)) {
@Override
public SymbolicValue wrappedValue() {
return sv.wrappedValue();
}
});
}
private DivisionByZeroCheck.DivByZeroStatus statusFromSV(SymbolicValue sv) {
return isZero(sv) ? DivByZeroStatus.ZERO : (isNonZero(sv) ? DivByZeroStatus.NON_ZERO : DivByZeroStatus.UNDETERMINED);
}
private void reportIssue(Tree tree, SymbolicValue denominator) {
ExpressionTree expression = getDenominator(tree);
String operation = tree.is(Tree.Kind.REMAINDER, Tree.Kind.REMAINDER_ASSIGNMENT) ? "modulation" : "division";
String expressionName;
String flowMessage;
if (expression.is(Tree.Kind.IDENTIFIER)) {
String name = ((IdentifierTree) expression).name();
expressionName = "'" + name + "'";
flowMessage = name + " is divided by zero";
} else {
expressionName = "this expression";
flowMessage = "this expression contains division by zero";
}
List<JavaFileScannerContext.Location> flow = FlowComputation.flow(context.getNode(), denominator);
flow.add(0, new JavaFileScannerContext.Location(flowMessage, tree));
context.reportIssue(expression, DivisionByZeroCheck.this, "Make sure " + expressionName + " can't be zero before doing this " + operation + ".",
ImmutableSet.of(flow));
// interrupt exploration
programState = null;
}
private ExpressionTree getDenominator(Tree tree) {
return tree.is(Tree.Kind.DIVIDE, Tree.Kind.REMAINDER) ? ((BinaryExpressionTree) tree).rightOperand() : ((AssignmentExpressionTree) tree).expression();
}
@Override
public void visitTypeCast(TypeCastTree tree) {
Type type = tree.type().symbolType();
if (type.isPrimitive()) {
SymbolicValue sv = programState.peekValue();
if (isZero(sv)) {
reuseSymbolicValue(sv);
} else if (isNonZero(sv)) {
deferConstraint(DivByZeroStatus.NON_ZERO);
}
}
}
@Override
public void visitUnaryExpression(UnaryExpressionTree tree) {
if (!tree.is(Tree.Kind.LOGICAL_COMPLEMENT)) {
SymbolicValue sv = programState.peekValue();
if (isZero(sv)) {
if (tree.is(Tree.Kind.UNARY_MINUS, Tree.Kind.UNARY_PLUS)) {
reuseSymbolicValue(sv);
} else {
deferConstraint(DivByZeroStatus.NON_ZERO);
}
} else {
deferConstraint(DivByZeroStatus.UNDETERMINED);
}
}
}
}
@Override
public ProgramState checkPostStatement(CheckerContext context, Tree syntaxNode) {
PostStatementVisitor visitor = new PostStatementVisitor(context);
syntaxNode.accept(visitor);
return visitor.programState;
}
private static class PostStatementVisitor extends CheckerTreeNodeVisitor {
PostStatementVisitor(CheckerContext context) {
super(context.getState());
}
@Override
public void visitLiteral(LiteralTree tree) {
String value = tree.value();
SymbolicValue sv = programState.peekValue();
if (tree.is(Tree.Kind.CHAR_LITERAL) && isNullCharacter(value)) {
addZeroConstraint(sv, DivByZeroStatus.ZERO);
} else if (tree.is(Tree.Kind.INT_LITERAL, Tree.Kind.LONG_LITERAL, Tree.Kind.DOUBLE_LITERAL, Tree.Kind.FLOAT_LITERAL)) {
addZeroConstraint(sv, isNumberZero(value) ? DivByZeroStatus.ZERO : DivByZeroStatus.NON_ZERO);
}
}
private static boolean isNumberZero(String literalValue) {
return !(literalValue.matches("(.)*[1-9]+(.)*") || literalValue.matches("(0x|0X){1}(.)*[1-9a-fA-F]+(.)*") || literalValue.matches("(0b|0B){1}(.)*[1]+(.)*"));
}
private static boolean isNullCharacter(String literalValue) {
return "'\\0'".equals(literalValue) || "'\\u0000'".equals(literalValue);
}
@Override
public void visitBinaryExpression(BinaryExpressionTree tree) {
checkDeferredConstraint();
}
@Override
public void visitAssignmentExpression(AssignmentExpressionTree tree) {
checkDeferredConstraint();
}
@Override
public void visitUnaryExpression(UnaryExpressionTree tree) {
checkDeferredConstraint();
}
@Override
public void visitTypeCast(TypeCastTree tree) {
checkDeferredConstraint();
}
private void checkDeferredConstraint() {
SymbolicValue sv = programState.peekValue();
if (sv instanceof DeferredStatusHolderSV) {
addZeroConstraint(sv, ((DeferredStatusHolderSV) sv).deferredStatus);
}
}
private void addZeroConstraint(SymbolicValue sv, DivByZeroStatus status) {
programState = programState.addConstraint(sv, new ZeroConstraint(status));
}
}
}