Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ public double doRangeCheckNOK7(double num) {
double upper = 18.37;
double lower = -4.96;
double result = Math.min(lower, num);
return Math.max(result, upper); // Compliant - FN
return Math.max(result, upper); // Noncompliant
}

public double doRangeCheckNOK8(float num) {
float upper = 18.37f;
float lower = -4.96f;
float result = Math.min(lower, num);
return Math.max(result, upper); // Compliant - FN
return Math.max(result, upper); // Noncompliant
}

public double doRangeCheckNOK9(long num) {
Expand Down Expand Up @@ -98,11 +98,11 @@ public int doRangeCheckOK5(int num) { // using both time same range
return Math.max(LOWER_INT, result);
}

public int doRangeCheckOK6(int num) { // do not handle arithmetic
public int doRangeCheckOK6(int num) {
int upper = 1837 + 14;
int lower = -496 * 42;
int result = Math.min(lower, num);
return Math.max(result, upper);
return Math.max(result, upper); // Noncompliant
}

public float doRangeCheckIntFloatNOK(int num) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,16 @@
package org.sonar.java.model;

import java.util.Optional;
import java.util.function.BiFunction;
import javax.annotation.CheckForNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.plugins.java.api.semantic.Symbol;
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.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.ParenthesizedTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;

public final class SEExpressionUtils {

private static final Logger LOG = LoggerFactory.getLogger(SEExpressionUtils.class);

private SEExpressionUtils() {
}

Expand Down Expand Up @@ -125,71 +115,6 @@ public static boolean isNullLiteral(ExpressionTree tree) {
return skipParentheses(tree).is(Tree.Kind.NULL_LITERAL);
}

@CheckForNull
public static Object resolveAsConstant(ExpressionTree tree) {
ExpressionTree expression = tree;
while (expression.is(Tree.Kind.PARENTHESIZED_EXPRESSION)) {
expression = ((ParenthesizedTree) expression).expression();
}
if (expression.is(Tree.Kind.MEMBER_SELECT)) {
expression = ((MemberSelectExpressionTree) expression).identifier();
}
if (expression.is(Tree.Kind.IDENTIFIER)) {
return resolveIdentifier((IdentifierTree) expression);
}
if (expression.is(Tree.Kind.BOOLEAN_LITERAL)) {
return Boolean.parseBoolean(((LiteralTree) expression).value());
}
if (expression.is(Tree.Kind.STRING_LITERAL, Tree.Kind.TEXT_BLOCK)) {
return SELiteralUtils.getAsStringValue((LiteralTree) expression);
}
if (expression instanceof UnaryExpressionTree unaryExpressionTree) {
return resolveUnaryExpression(unaryExpressionTree);
}
if (expression.is(Tree.Kind.INT_LITERAL)) {
return SELiteralUtils.intLiteralValue(expression);
}
if (expression.is(Tree.Kind.LONG_LITERAL)) {
return SELiteralUtils.longLiteralValue(expression);
}
if (expression.is(Tree.Kind.PLUS)) {
return resolvePlus((BinaryExpressionTree) expression);
}
if (expression.is(Tree.Kind.OR)) {
return resolveOr((BinaryExpressionTree) expression);
}
if (expression.is(Tree.Kind.MINUS)) {
return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a - b, (a, b) -> a - b);
}
if (expression.is(Tree.Kind.MULTIPLY)) {
return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a * b, (a, b) -> a * b);
}
if (expression.is(Tree.Kind.DIVIDE)) {
return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a / b, (a, b) -> a / b);
}
if (expression.is(Tree.Kind.REMAINDER)) {
return resolveArithmeticOperation((BinaryExpressionTree) expression, (a, b) -> a % b, (a, b) -> a % b);
}
return null;
}

@CheckForNull
private static Object resolveIdentifier(IdentifierTree tree) {
Symbol symbol = tree.symbol();
if (!symbol.isVariableSymbol()) {
return null;
}
Symbol owner = symbol.owner();
if (owner.isTypeSymbol() && owner.type().is("java.lang.Boolean")) {
if ("TRUE".equals(symbol.name())) {
return Boolean.TRUE;
} else if ("FALSE".equals(symbol.name())) {
return Boolean.FALSE;
}
}
return ((Symbol.VariableSymbol) symbol).constantValue().orElse(null);
}

public static IdentifierTree extractIdentifier(AssignmentExpressionTree tree) {
Optional<IdentifierTree> identifier = extractIdentifier(tree.variable());

Expand All @@ -212,85 +137,4 @@ public static boolean isThis(ExpressionTree expression) {
return newExpression.is(Tree.Kind.IDENTIFIER) && "this".equals(((IdentifierTree) newExpression).name());
}

@CheckForNull
private static Object resolveArithmeticOperation(Object left, Object right, BiFunction<Long, Long, Object> longOperation, BiFunction<Integer, Integer, Object> intOperation) {
try {
if (left instanceof Integer leftInt && right instanceof Integer rightInt) {
return intOperation.apply(leftInt, rightInt);
} else if ((left instanceof Long || right instanceof Long) && (left instanceof Integer || right instanceof Integer)) {
return longOperation.apply(((Number) left).longValue(), ((Number) right).longValue());
}
} catch (ArithmeticException e) {
LOG.debug("Arithmetic exception while resolving arithmetic operation value", e);
}
return null;
}

@CheckForNull
private static Object resolveUnaryExpression(UnaryExpressionTree unaryExpression) {
Object value = resolveAsConstant(unaryExpression.expression());
if (unaryExpression.is(Tree.Kind.UNARY_PLUS)) {
return value;
} else if (unaryExpression.is(Tree.Kind.UNARY_MINUS)) {
if (value instanceof Long longValue) {
return -longValue;
} else if (value instanceof Integer intValue) {
return -intValue;
}
} else if (unaryExpression.is(Tree.Kind.BITWISE_COMPLEMENT)) {
if (value instanceof Long longValue) {
return ~longValue;
} else if (value instanceof Integer intValue) {
return ~intValue;
}
} else if (unaryExpression.is(Tree.Kind.LOGICAL_COMPLEMENT) && value instanceof Boolean bool) {
return !bool;
}
return null;
}

@CheckForNull
private static Object resolvePlus(BinaryExpressionTree binaryExpression) {
Object left = resolveAsConstant(binaryExpression.leftOperand());
Object right = resolveAsConstant(binaryExpression.rightOperand());
if (left == null || right == null) {
return null;
} else if (left instanceof String leftString) {
return leftString + right;
} else if (right instanceof String rightString) {
return left + rightString;
}
return resolveArithmeticOperation(left, right, Long::sum, Integer::sum);
}

@CheckForNull
private static Object resolveArithmeticOperation(BinaryExpressionTree binaryExpression,
BiFunction<Long, Long, Object> longOperation,
BiFunction<Integer, Integer, Object> intOperation) {
Object left = resolveAsConstant(binaryExpression.leftOperand());
Object right = resolveAsConstant(binaryExpression.rightOperand());
if (left == null || right == null) {
return null;
}
return resolveArithmeticOperation(left, right, longOperation, intOperation);
}

@CheckForNull
private static Object resolveOr(BinaryExpressionTree binaryExpression) {
Object left = resolveAsConstant(binaryExpression.leftOperand());
Object right = resolveAsConstant(binaryExpression.rightOperand());
if (left == null || right == null) {
return null;
} else if (left instanceof Long leftLong && right instanceof Long rightLong) {
return leftLong | rightLong;
} else if (left instanceof Long leftLong && right instanceof Integer rightInt) {
return leftLong | rightInt;
} else if (left instanceof Integer leftInt && right instanceof Long rightLong) {
return leftInt | rightLong;
} else if (left instanceof Integer leftInt && right instanceof Integer rightInt) {
return leftInt | rightInt;
}
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,63 +33,6 @@ private SELiteralUtils() {
// This class only contains static methods
}

@CheckForNull
public static Long longLiteralValue(ExpressionTree tree) {
ExpressionTree expression = tree;

int sign = tree.is(Kind.UNARY_MINUS) ? -1 : 1;
if (tree.is(Kind.UNARY_MINUS, Kind.UNARY_PLUS)) {
expression = ((UnaryExpressionTree) tree).expression();
}

if (expression.is(Kind.INT_LITERAL, Kind.LONG_LITERAL)) {
String value = trimLongSuffix(((LiteralTree) expression).value());
// long as hexadecimal can be written using underscore to separate groups
value = value.replace("_", "");
try {
if (value.startsWith("0b") || value.startsWith("0B")) {
return sign * Long.valueOf(value.substring(2), 2);
}
return sign * Long.decode(value);
} catch (NumberFormatException e) {
// Long.decode() may fail in case of very large long number written in hexadecimal. In such situation, we ignore the number.
// Note that Long.MAX_VALUE = "0x7FFF_FFFF_FFFF_FFFFL", but it is possible to write larger numbers in hexadecimal
// to be used as mask in bitwise operation. For instance:
// 0x8000_0000_0000_0000L (MAX_VALUE + 1),
// 0xFFFF_FFFF_FFFF_FFFFL (only ones),
// 0xFFFF_FFFF_FFFF_FFFEL (only ones except least significant bit), ...
}
}
return null;
}

private static Integer intLiteralValue(LiteralTree literal) {
String literalValue = literal.value().replace("_", "");
if (literalValue.startsWith("0b") || literalValue.startsWith("0B")) {
// assume it is used as bit mask
return Integer.parseUnsignedInt(literalValue.substring(2), 2);
}
return Long.decode(literalValue).intValue();
}

@CheckForNull
public static Integer intLiteralValue(ExpressionTree expression) {
if (expression.is(Kind.INT_LITERAL)) {
return intLiteralValue((LiteralTree) expression);
}
if (expression.is(Kind.UNARY_MINUS, Kind.UNARY_PLUS)) {
UnaryExpressionTree unaryExp = (UnaryExpressionTree) expression;
Integer subExpressionIntValue = intLiteralValue(unaryExp.expression());
return expression.is(Kind.UNARY_MINUS) ? minus(subExpressionIntValue) : subExpressionIntValue;
}
return null;
}

@CheckForNull
private static Integer minus(@Nullable Integer nullableInteger) {
return nullableInteger == null ? null : -nullableInteger;
}

public static boolean isTrue(Tree tree) {
return tree.is(Kind.BOOLEAN_LITERAL) && "true".equals(((LiteralTree) tree).value());
}
Expand All @@ -98,72 +41,4 @@ public static boolean isFalse(Tree tree) {
return tree.is(Kind.BOOLEAN_LITERAL) && "false".equals(((LiteralTree) tree).value());
}

public static String trimQuotes(String value) {
int delimiterLength = isTextBlock(value) ? 3 : 1;
return value.substring(delimiterLength, value.length() - delimiterLength);
}

public static boolean isTextBlock(String value) {
return value.startsWith("\"\"\"");
}

public static String trimLongSuffix(String longString) {
if (StringUtils.isBlank(longString)) {
return longString;
}
int lastCharPosition = longString.length() - 1;
char lastChar = longString.charAt(lastCharPosition);
String value = longString;
if (lastChar == 'L' || lastChar == 'l') {
value = longString.substring(0, lastCharPosition);
}
return value;
}

public static int indentationOfTextBlock(String[] lines) {
return Arrays.stream(lines).skip(1)
.filter(SELiteralUtils::isNonEmptyLine)
.mapToInt(SELiteralUtils::getIndentation)
.min().orElse(0);
}

private static String stripIndent(int indent, String s) {
return s.isEmpty() ? s : s.substring(indent);
}

public static String getAsStringValue(LiteralTree tree) {
if (!tree.is(Kind.TEXT_BLOCK)) {
return tree.is(Kind.STRING_LITERAL) ? trimQuotes(tree.value()) : tree.value();
}
String[] lines = tree.value().split("\r?\n");
int indent = indentationOfTextBlock(lines);

return Arrays.stream(lines)
.skip(1)
.map(String::stripTrailing)
.map(s -> stripIndent(indent, s))
.collect(Collectors.joining("\n"))
.replaceAll("\"\"\"$", "");
}

private static int getIndentation(String line) {
for (int i = 0; i < line.length(); ++i) {
if (isNotWhiteSpace(line.charAt(i))) {
return i;
}
}
return line.length();
}

/**
* @return Whether c is not a white space character according to the space-stripping rules of text blocks, i.e. whether
* it's a space, tab or form feed
*/
private static boolean isNotWhiteSpace(int c) {
return c != ' ' && c != '\t' && c != '\f';
}

private static boolean isNonEmptyLine(String line) {
return line.chars().anyMatch(SELiteralUtils::isNotWhiteSpace);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -395,28 +395,27 @@ public void visitIdentifier(IdentifierTree identifier) {
}
Type type = identifier.symbolType();
if (type.isPrimitive() || type.isPrimitiveWrapper()) {
((Symbol.VariableSymbol) symbol).constantValue()
.filter(Number.class::isInstance)
.map(Number.class::cast)
.ifPresent(num -> {
if (isZero(num, symbol.type().fullyQualifiedName())) {
addZeroConstraint(sv, ZeroConstraint.ZERO);
} else {
addZeroConstraint(sv, ZeroConstraint.NON_ZERO);
}
});
Object constantValue = ((Symbol.VariableSymbol) symbol).constantValue().orElse(null);
if (constantValue instanceof Character ch) {
constantValue = (int) ch.charValue();
}
if (constantValue instanceof Number num) {
if (isZero(num, symbol.type().fullyQualifiedName())) {
addZeroConstraint(sv, ZeroConstraint.ZERO);
} else {
addZeroConstraint(sv, ZeroConstraint.NON_ZERO);
}
}
}
}

private static boolean isZero(Number number, String type) {
switch (type) {
case "char":
return number.intValue() == 0;
case "byte":
return number.byteValue() == 0;
case "short":
return number.shortValue() == 0;
case "int":
case "int", "char":
return number.intValue() == 0;
case "long":
return number.longValue() == 0L;
Expand Down
Loading