From 54ef591487d541903f471d565c1a5dd23e956b96 Mon Sep 17 00:00:00 2001 From: Leonardo Pilastri Date: Thu, 30 May 2024 17:13:53 +0200 Subject: [PATCH] Decrease duplication --- .../java/collections/CollectionUtils.java | 18 +- .../main/java/org/sonar/java/cfg/CFGLoop.java | 50 +++--- .../org/sonar/java/cfg/SELiveVariables.java | 90 +++++----- .../java/org/sonar/java/model/CFGUtils.java | 4 +- .../sonar/java/model/SEExpressionUtils.java | 168 +++++++++--------- .../org/sonar/java/model/SELiteralUtils.java | 92 +++++----- .../java/org/sonar/java/model/SESymbols.java | 126 ++++++------- .../sonar/java/se/NullabilityDataUtils.java | 74 -------- .../java/se/checks/NonNullSetToNullCheck.java | 44 ++++- .../java/se/NullabilityDataUtilsTest.java | 61 ------- 10 files changed, 317 insertions(+), 410 deletions(-) delete mode 100644 java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/NullabilityDataUtils.java delete mode 100644 java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/NullabilityDataUtilsTest.java diff --git a/java-frontend/src/main/java/org/sonar/java/collections/CollectionUtils.java b/java-frontend/src/main/java/org/sonar/java/collections/CollectionUtils.java index 1b674311584..b65b540801d 100644 --- a/java-frontend/src/main/java/org/sonar/java/collections/CollectionUtils.java +++ b/java-frontend/src/main/java/org/sonar/java/collections/CollectionUtils.java @@ -28,6 +28,15 @@ public final class CollectionUtils { private CollectionUtils() { } + private static int size(Iterator iterator) { + int count; + for(count = 0; iterator.hasNext(); ++count) { + iterator.next(); + } + + return count; + } + @Nullable public static T getFirst(Iterable iterable, @Nullable T defaultValue) { Iterator iterator = iterable.iterator(); @@ -38,13 +47,4 @@ public static int size(Iterable iterable) { return iterable instanceof Collection collection ? collection.size() : size(iterable.iterator()); } - private static int size(Iterator iterator) { - int count; - for(count = 0; iterator.hasNext(); ++count) { - iterator.next(); - } - - return count; - } - } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/CFGLoop.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/CFGLoop.java index 6027654d17d..bee7fce62e0 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/CFGLoop.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/CFGLoop.java @@ -52,16 +52,12 @@ private void initialize(Block block, Map container) { collectWaysOut(container); } - Collection successors() { - return new ArrayList<>(successors); - } - public boolean hasNoWayOut() { return successors.isEmpty(); } - private void collectBlocks(Block block, Map container) { - collectBlocks(block, container, new HashSet<>()); + Collection successors() { + return new ArrayList<>(successors); } private boolean collectBlocks(Block block, Map container, Set visitedBlocks) { @@ -78,20 +74,8 @@ private boolean collectBlocks(Block block, Map container, Set container, Set visitedBlocks) { - Set localSuccessors = localSuccessors(block, container); - if (localSuccessors == null) { - return true; - } - boolean answer = false; - for (Block successor : localSuccessors) { - if (startingBlock.id() == successor.id()) { - answer = true; - } else { - answer |= collectBlocks(successor, container, visitedBlocks); - } - } - return answer; + private void collectBlocks(Block block, Map container) { + collectBlocks(block, container, new HashSet<>()); } @CheckForNull @@ -113,9 +97,20 @@ private static Set localSuccessors(Block block, Map container, Set visitedBlocks) { + Set localSuccessors = localSuccessors(block, container); + if (localSuccessors == null) { + return true; + } + boolean answer = false; + for (Block successor : localSuccessors) { + if (startingBlock.id() == successor.id()) { + answer = true; + } else { + answer |= collectBlocks(successor, container, visitedBlocks); + } + } + return answer; } private void collectWaysOut(Map container) { @@ -131,9 +126,9 @@ private void collectWaysOut(Map container) { successors.remove(startingBlock); } - private static boolean isStarting(Block block) { + private static boolean isBreak(Block block) { Tree terminator = block.terminator(); - return terminator != null && terminator.is(Tree.Kind.FOR_STATEMENT, Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT); + return terminator != null && terminator.is(Tree.Kind.BREAK_STATEMENT); } public static Map getCFGLoops(ControlFlowGraph cfg) { @@ -155,4 +150,9 @@ private static CFGLoop create(Block block, Map container) { loop.initialize(block, container); return loop; } + + private static boolean isStarting(Block block) { + Tree terminator = block.terminator(); + return terminator != null && terminator.is(Tree.Kind.FOR_STATEMENT, Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT); + } } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/SELiveVariables.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/SELiveVariables.java index d87cc33969e..ecf4b2facb8 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/SELiveVariables.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/cfg/SELiveVariables.java @@ -56,8 +56,25 @@ private SELiveVariables(ControlFlowGraph cfg, boolean includeFields) { this.includeFields = includeFields; } - public Set getOut(ControlFlowGraph.Block block) { - return out.get(block); + private void analyzeControlFlowGraph(Map> in, Map> kill, Map> gen) { + Deque workList = new LinkedList<>(); + workList.addAll(cfg.reversedBlocks()); + while (!workList.isEmpty()) { + ControlFlowGraph.Block block = workList.removeFirst(); + + Set blockOut = out.computeIfAbsent(block, k -> new HashSet<>()); + block.successors().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll); + block.exceptions().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll); + // in = gen and (out - kill) + Set newIn = new HashSet<>(gen.get(block)); + newIn.addAll(SetUtils.difference(blockOut, kill.get(block))); + + if (newIn.equals(in.get(block))) { + continue; + } + in.put(block, newIn); + block.predecessors().forEach(workList::addLast); + } } /** @@ -93,24 +110,34 @@ private static SELiveVariables analyze(ControlFlowGraph cfg, boolean includeFiel return liveVariables; } - private void analyzeControlFlowGraph(Map> in, Map> kill, Map> gen) { - Deque workList = new LinkedList<>(); - workList.addAll(cfg.reversedBlocks()); - while (!workList.isEmpty()) { - ControlFlowGraph.Block block = workList.removeFirst(); - - Set blockOut = out.computeIfAbsent(block, k -> new HashSet<>()); - block.successors().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll); - block.exceptions().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll); - // in = gen and (out - kill) - Set newIn = new HashSet<>(gen.get(block)); - newIn.addAll(SetUtils.difference(blockOut, kill.get(block))); + public Set getOut(ControlFlowGraph.Block block) { + return out.get(block); + } - if (newIn.equals(in.get(block))) { - continue; + private void processMemberSelect(MemberSelectExpressionTree element, Set assignmentLHS, Set blockGen) { + Symbol symbol; + if (!assignmentLHS.contains(element) && includeFields) { + symbol = getField(element); + if (symbol != null) { + blockGen.add(symbol); } - in.put(block, newIn); - block.predecessors().forEach(workList::addLast); + } + } + + private void processAssignment(AssignmentExpressionTree element, Set blockKill, Set blockGen, Set assignmentLHS) { + Symbol symbol = null; + ExpressionTree lhs = element.variable(); + if (lhs.is(Kind.IDENTIFIER)) { + symbol = ((IdentifierTree) lhs).symbol(); + + } else if (includeFields && lhs.is(Kind.MEMBER_SELECT)) { + symbol = getField((MemberSelectExpressionTree) lhs); + } + + if (symbol != null && includeSymbol(symbol)) { + assignmentLHS.add(lhs); + blockGen.remove(symbol); + blockKill.add(symbol); } } @@ -154,33 +181,6 @@ private void processIdentifier(IdentifierTree element, Set blockGen, Set } } - private void processMemberSelect(MemberSelectExpressionTree element, Set assignmentLHS, Set blockGen) { - Symbol symbol; - if (!assignmentLHS.contains(element) && includeFields) { - symbol = getField(element); - if (symbol != null) { - blockGen.add(symbol); - } - } - } - - private void processAssignment(AssignmentExpressionTree element, Set blockKill, Set blockGen, Set assignmentLHS) { - Symbol symbol = null; - ExpressionTree lhs = element.variable(); - if (lhs.is(Kind.IDENTIFIER)) { - symbol = ((IdentifierTree) lhs).symbol(); - - } else if (includeFields && lhs.is(Kind.MEMBER_SELECT)) { - symbol = getField((MemberSelectExpressionTree) lhs); - } - - if (symbol != null && includeSymbol(symbol)) { - assignmentLHS.add(lhs); - blockGen.remove(symbol); - blockKill.add(symbol); - } - } - private boolean includeSymbol(Symbol symbol) { return symbol.isLocalVariable() || (includeFields && isField(symbol)); } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/CFGUtils.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/CFGUtils.java index 31a18941ed6..b69a8aab9c9 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/CFGUtils.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/CFGUtils.java @@ -28,10 +28,10 @@ private CFGUtils(){ // utility class } - public static final Predicate IS_CATCH_BLOCK = ControlFlowGraph.Block::isCatchBlock; - public static boolean isMethodExitBlock(ControlFlowGraph.Block block) { return block.successors().isEmpty(); } + public static final Predicate IS_CATCH_BLOCK = ControlFlowGraph.Block::isCatchBlock; + } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SEExpressionUtils.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SEExpressionUtils.java index 0893f2f0cc6..c6a6ae7e829 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SEExpressionUtils.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SEExpressionUtils.java @@ -44,22 +44,19 @@ private SEExpressionUtils() { } /** - * In case of simple assignments, only the expression is evaluated, as we only use the reference to the variable to store the result. - * For SE-Based checks, only a single value should be unstacked if its the case. For other cases, two values should be unstacked. - * See JLS8-15.26 - * - * @param tree The assignment tree - * @return true if the tree is a simple assignment - * @see #extractIdentifier(AssignmentExpressionTree) + * Checks of is the given tree is selecting with this or super + * @param tree The tree to check. + * @return true when the tree is a select on this or super + * @see #isSelectOnThisOrSuper(AssignmentExpressionTree) */ - public static boolean isSimpleAssignment(AssignmentExpressionTree tree) { - if (!tree.is(Tree.Kind.ASSIGNMENT)) { - // This can't possibly be a simple assignment. + public static boolean isSelectOnThisOrSuper(MemberSelectExpressionTree tree) { + if (!tree.expression().is(Tree.Kind.IDENTIFIER)) { + // This is no longer simple. return false; } - ExpressionTree variable = SEExpressionUtils.skipParentheses(tree.variable()); - return variable.is(Tree.Kind.IDENTIFIER) || isSelectOnThisOrSuper(tree); + String selectSourceName = ((IdentifierTree) tree.expression()).name(); + return "this".equalsIgnoreCase(selectSourceName) || "super".equalsIgnoreCase(selectSourceName); } /** @@ -73,32 +70,12 @@ public static boolean isSelectOnThisOrSuper(AssignmentExpressionTree tree) { return variable.is(Tree.Kind.MEMBER_SELECT) && isSelectOnThisOrSuper((MemberSelectExpressionTree) variable); } - /** - * Checks of is the given tree is selecting with this or super - * @param tree The tree to check. - * @return true when the tree is a select on this or super - * @see #isSelectOnThisOrSuper(AssignmentExpressionTree) - */ - public static boolean isSelectOnThisOrSuper(MemberSelectExpressionTree tree) { - if (!tree.expression().is(Tree.Kind.IDENTIFIER)) { - // This is no longer simple. - return false; - } - - String selectSourceName = ((IdentifierTree) tree.expression()).name(); - return "this".equalsIgnoreCase(selectSourceName) || "super".equalsIgnoreCase(selectSourceName); - } - - public static IdentifierTree extractIdentifier(AssignmentExpressionTree tree) { - Optional identifier = extractIdentifier(tree.variable()); - - if (identifier.isPresent()) { - return identifier.get(); + public static ExpressionTree skipParentheses(ExpressionTree tree) { + ExpressionTree result = tree; + while (result.is(Tree.Kind.PARENTHESIZED_EXPRESSION)) { + result = ((ParenthesizedTree) result).expression(); } - - // This should not be possible. - // If it happens anyway, you should make sure the assignment is simple (by calling isSimpleAssignment) before. - throw new IllegalArgumentException("Can not extract identifier."); + return result; } private static Optional extractIdentifier(ExpressionTree tree) { @@ -114,16 +91,23 @@ private static Optional extractIdentifier(ExpressionTree tree) { return Optional.empty(); } - public static ExpressionTree skipParentheses(ExpressionTree tree) { - ExpressionTree result = tree; - while (result.is(Tree.Kind.PARENTHESIZED_EXPRESSION)) { - result = ((ParenthesizedTree) result).expression(); + /** + * In case of simple assignments, only the expression is evaluated, as we only use the reference to the variable to store the result. + * For SE-Based checks, only a single value should be unstacked if its the case. For other cases, two values should be unstacked. + * See JLS8-15.26 + * + * @param tree The assignment tree + * @return true if the tree is a simple assignment + * @see #extractIdentifier(AssignmentExpressionTree) + */ + public static boolean isSimpleAssignment(AssignmentExpressionTree tree) { + if (!tree.is(Tree.Kind.ASSIGNMENT)) { + // This can't possibly be a simple assignment. + return false; } - return result; - } - public static boolean isNullLiteral(ExpressionTree tree) { - return skipParentheses(tree).is(Tree.Kind.NULL_LITERAL); + ExpressionTree variable = SEExpressionUtils.skipParentheses(tree.variable()); + return variable.is(Tree.Kind.IDENTIFIER) || isSelectOnThisOrSuper(tree); } /** @@ -140,14 +124,8 @@ public static IdentifierTree methodName(MethodInvocationTree mit) { return id; } - /** - * Checks if the given expression refers to "this" - * @param expression the expression to check - * @return true if this expression refers to "this" - */ - public static boolean isThis(ExpressionTree expression) { - ExpressionTree newExpression = SEExpressionUtils.skipParentheses(expression); - return newExpression.is(Tree.Kind.IDENTIFIER) && "this".equals(((IdentifierTree) newExpression).name()); + public static boolean isNullLiteral(ExpressionTree tree) { + return skipParentheses(tree).is(Tree.Kind.NULL_LITERAL); } @CheckForNull @@ -198,6 +176,59 @@ public static Object resolveAsConstant(ExpressionTree tree) { 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 identifier = extractIdentifier(tree.variable()); + + if (identifier.isPresent()) { + return identifier.get(); + } + + // This should not be possible. + // If it happens anyway, you should make sure the assignment is simple (by calling isSimpleAssignment) before. + throw new IllegalArgumentException("Can not extract identifier."); + } + + /** + * Checks if the given expression refers to "this" + * @param expression the expression to check + * @return true if this expression refers to "this" + */ + public static boolean isThis(ExpressionTree expression) { + ExpressionTree newExpression = SEExpressionUtils.skipParentheses(expression); + return newExpression.is(Tree.Kind.IDENTIFIER) && "this".equals(((IdentifierTree) newExpression).name()); + } + + @CheckForNull + private static Object resolveArithmeticOperation(Object left, Object right, BiFunction longOperation, BiFunction 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()); @@ -221,23 +252,6 @@ private static Object resolveUnaryExpression(UnaryExpressionTree unaryExpression 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); - } - @CheckForNull private static Object resolvePlus(BinaryExpressionTree binaryExpression) { Object left = resolveAsConstant(binaryExpression.leftOperand()); @@ -264,20 +278,6 @@ private static Object resolveArithmeticOperation(BinaryExpressionTree binaryExpr return resolveArithmeticOperation(left, right, longOperation, intOperation); } - @CheckForNull - private static Object resolveArithmeticOperation(Object left, Object right, BiFunction longOperation, BiFunction 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 resolveOr(BinaryExpressionTree binaryExpression) { Object left = resolveAsConstant(binaryExpression.leftOperand()); diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SELiteralUtils.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SELiteralUtils.java index 79957caf8f5..ca26f8c6053 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SELiteralUtils.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SELiteralUtils.java @@ -36,28 +36,6 @@ private SELiteralUtils() { // This class only contains static methods } - @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; - } - - 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 Long longLiteralValue(ExpressionTree tree) { ExpressionTree expression = tree; @@ -88,11 +66,41 @@ public static Long longLiteralValue(ExpressionTree tree) { 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()); + } + + 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); @@ -115,12 +123,15 @@ public static String trimLongSuffix(String longString) { return value; } - public static boolean isTrue(Tree tree) { - return tree.is(Kind.BOOLEAN_LITERAL) && "true".equals(((LiteralTree) tree).value()); + public static int indentationOfTextBlock(String[] lines) { + return Arrays.stream(lines).skip(1) + .filter(SELiteralUtils::isNonEmptyLine) + .mapToInt(SELiteralUtils::getIndentation) + .min().orElse(0); } - public static boolean isFalse(Tree tree) { - return tree.is(Kind.BOOLEAN_LITERAL) && "false".equals(((LiteralTree) tree).value()); + private static String stripIndent(int indent, String s) { + return s.isEmpty() ? s : s.substring(indent); } public static String getAsStringValue(LiteralTree tree) { @@ -138,19 +149,13 @@ public static String getAsStringValue(LiteralTree tree) { .replaceAll("\"\"\"$", ""); } - private static String stripIndent(int indent, String s) { - return s.isEmpty() ? s : s.substring(indent); - } - - public static int indentationOfTextBlock(String[] lines) { - return Arrays.stream(lines).skip(1) - .filter(SELiteralUtils::isNonEmptyLine) - .mapToInt(SELiteralUtils::getIndentation) - .min().orElse(0); - } - - private static boolean isNonEmptyLine(String line) { - return line.chars().anyMatch(SELiteralUtils::isNotWhiteSpace); + private static int getIndentation(String line) { + for (int i = 0; i < line.length(); ++i) { + if (isNotWhiteSpace(line.charAt(i))) { + return i; + } + } + return line.length(); } /** @@ -161,12 +166,7 @@ private static boolean isNotWhiteSpace(int c) { return c != ' ' && c != '\t' && c != '\f'; } - private static int getIndentation(String line) { - for (int i = 0; i < line.length(); ++i) { - if (isNotWhiteSpace(line.charAt(i))) { - return i; - } - } - return line.length(); + private static boolean isNonEmptyLine(String line) { + return line.chars().anyMatch(SELiteralUtils::isNotWhiteSpace); } } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SESymbols.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SESymbols.java index 8ea11328c77..1398b98ae4d 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SESymbols.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/model/SESymbols.java @@ -47,41 +47,6 @@ private SESymbols() { public static final Symbol rootPackage = new RootPackageSymbol(); public static final Symbol.TypeSymbol unknownTypeSymbol = new UnkownTypeSymbol(); - public static final SymbolMetadata EMPTY_METADATA = new SymbolMetadata() { - - @Override - public boolean isAnnotatedWith(String fullyQualifiedName) { - return false; - } - - @Override - @CheckForNull - public List valuesForAnnotation(String fullyQualifiedNameOfAnnotation) { - return null; - } - - @Override - public List annotations() { - return Collections.emptyList(); - } - - @Override - public NullabilityData nullabilityData() { - return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN); - } - - @Override - public NullabilityData nullabilityData(NullabilityTarget level) { - return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN); - } - - @Nullable - @Override - public AnnotationTree findAnnotationTree(AnnotationInstance annotationInstance) { - return null; - } - }; - public abstract static class DefaultSymbol implements Symbol { @Override @@ -165,25 +130,45 @@ public SymbolMetadata metadata() { } } - private static class UnknownSymbol extends DefaultSymbol { + public static final SymbolMetadata EMPTY_METADATA = new SymbolMetadata() { + @Override - public boolean isUnknown() { - return true; + public boolean isAnnotatedWith(String fullyQualifiedName) { + return false; } @Override - public String name() { - return "!unknown!"; + @CheckForNull + public List valuesForAnnotation(String fullyQualifiedNameOfAnnotation) { + return null; } @Override - public Symbol owner() { - return rootPackage; + public List annotations() { + return Collections.emptyList(); } @Override - public final Type type() { - return unknownType; + public NullabilityData nullabilityData() { + return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN); + } + + @Override + public NullabilityData nullabilityData(NullabilityTarget level) { + return JSymbolMetadata.unknownNullabilityAt(NullabilityLevel.UNKNOWN); + } + + @Nullable + @Override + public AnnotationTree findAnnotationTree(AnnotationInstance annotationInstance) { + return null; + } + }; + + private static class UnknownSymbol extends DefaultSymbol { + @Override + public boolean isUnknown() { + return true; } @Override @@ -191,6 +176,11 @@ public final TypeSymbol enclosingClass() { return unknownTypeSymbol; } + @Override + public String name() { + return "!unknown!"; + } + @Override public Tree declaration() { return null; @@ -200,6 +190,16 @@ public Tree declaration() { public final List usages() { return Collections.emptyList(); } + + @Override + public final Type type() { + return unknownType; + } + + @Override + public Symbol owner() { + return rootPackage; + } } private static final class UnkownTypeSymbol extends UnknownSymbol implements Symbol.TypeSymbol { @@ -313,14 +313,13 @@ public Type primitiveWrapperType() { return null; } - @Nullable @Override - public Type primitiveType() { - return null; + public boolean isUnknown() { + return true; } @Override - public boolean isNullType() { + public boolean isNumerical() { return false; } @@ -339,44 +338,45 @@ public Type declaringType() { return this; } + @Nullable @Override - public boolean isUnknown() { - return true; + public Type primitiveType() { + return null; } @Override - public boolean isNumerical() { + public boolean isNullType() { return false; } @Override - public String fullyQualifiedName() { - return "!Unknown!"; + public Type erasure() { + return unknownType; } @Override - public String name() { - return "!Unknown!"; + public boolean isParameterized() { + return false; } @Override - public Symbol.TypeSymbol symbol() { - return unknownTypeSymbol; + public List typeArguments() { + return Collections.emptyList(); } @Override - public Type erasure() { - return unknownType; + public String fullyQualifiedName() { + return "!Unknown!"; } @Override - public boolean isParameterized() { - return false; + public String name() { + return "!Unknown!"; } @Override - public List typeArguments() { - return Collections.emptyList(); + public Symbol.TypeSymbol symbol() { + return unknownTypeSymbol; } } } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/NullabilityDataUtils.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/NullabilityDataUtils.java deleted file mode 100644 index 0551ae644f9..00000000000 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/NullabilityDataUtils.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) 2012-2024 SonarSource SA - * mailto:info 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; - -import java.util.List; -import java.util.Locale; -import java.util.Optional; -import org.sonar.plugins.java.api.semantic.Symbol; -import org.sonar.plugins.java.api.semantic.SymbolMetadata; - -public class NullabilityDataUtils { - private NullabilityDataUtils() { - // Utility class - } - - public static Optional nullabilityAsString(SymbolMetadata.NullabilityData nullabilityData) { - SymbolMetadata.AnnotationInstance annotation = nullabilityData.annotation(); - if (annotation == null) { - return Optional.empty(); - } - String name = getAnnotationName(annotation); - if (nullabilityData.metaAnnotation()) { - name += " via meta-annotation"; - } - String level = levelToString(nullabilityData.level()); - return Optional.of(String.format("@%s%s", name, level)); - } - - private static String getAnnotationName(SymbolMetadata.AnnotationInstance annotation) { - String name = annotation.symbol().name(); - if ("Nonnull".equals(name)) { - return name + annotationArguments(annotation.values()); - } - return name; - } - - private static String annotationArguments(List valuesForAnnotation) { - return valuesForAnnotation.stream() - .filter(annotationValue -> "when".equals(annotationValue.name())) - .map(SymbolMetadata.AnnotationValue::value) - .filter(Symbol.class::isInstance) - .map(symbol -> String.format("(when=%s)", ((Symbol) symbol).name())) - .findFirst().orElse(""); - } - - private static String levelToString(SymbolMetadata.NullabilityLevel level) { - switch (level) { - case PACKAGE: - case CLASS: - return String.format(" at %s level", level.toString().toLowerCase(Locale.ROOT)); - case METHOD: - case VARIABLE: - default: - return ""; - } - } -} diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java index d99608704ea..ad5e002fb89 100644 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java +++ b/java-symbolic-execution/java-symbolic-execution-plugin/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java @@ -23,6 +23,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.stream.Stream; import javax.annotation.Nullable; @@ -54,7 +55,6 @@ import org.sonar.plugins.java.api.tree.VariableTree; import org.sonarsource.analyzer.commons.collections.ListUtils; -import static org.sonar.java.se.NullabilityDataUtils.nullabilityAsString; import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel.PACKAGE; import static org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel.VARIABLE; @@ -322,4 +322,46 @@ private static Optional getNonnullAnnotationAsString(Symbol symbol, Null return Optional.empty(); } + private static Optional nullabilityAsString(SymbolMetadata.NullabilityData nullabilityData) { + SymbolMetadata.AnnotationInstance annotation = nullabilityData.annotation(); + if (annotation == null) { + return Optional.empty(); + } + String name = getAnnotationName(annotation); + if (nullabilityData.metaAnnotation()) { + name += " via meta-annotation"; + } + String level = levelToString(nullabilityData.level()); + return Optional.of(String.format("@%s%s", name, level)); + } + + private static String getAnnotationName(SymbolMetadata.AnnotationInstance annotation) { + String name = annotation.symbol().name(); + if ("Nonnull".equals(name)) { + return name + annotationArguments(annotation.values()); + } + return name; + } + + private static String annotationArguments(List valuesForAnnotation) { + return valuesForAnnotation.stream() + .filter(annotationValue -> "when".equals(annotationValue.name())) + .map(SymbolMetadata.AnnotationValue::value) + .filter(Symbol.class::isInstance) + .map(symbol -> String.format("(when=%s)", ((Symbol) symbol).name())) + .findFirst().orElse(""); + } + + private static String levelToString(SymbolMetadata.NullabilityLevel level) { + switch (level) { + case PACKAGE: + case CLASS: + return String.format(" at %s level", level.toString().toLowerCase(Locale.ROOT)); + case METHOD: + case VARIABLE: + default: + return ""; + } + } + } diff --git a/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/NullabilityDataUtilsTest.java b/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/NullabilityDataUtilsTest.java deleted file mode 100644 index 8513b4de6fd..00000000000 --- a/java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/NullabilityDataUtilsTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) 2012-2024 SonarSource SA - * mailto:info 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; - - -import org.junit.jupiter.api.Test; -import org.sonar.java.model.JavaTree; -import org.sonar.java.se.utils.JParserTestUtils; -import org.sonar.plugins.java.api.semantic.SymbolMetadata; -import org.sonar.plugins.java.api.tree.ClassTree; -import org.sonar.plugins.java.api.tree.VariableTree; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.java.se.NullabilityDataUtils.nullabilityAsString; - -class NullabilityDataUtilsTest { - - @Test - void test_no_annotation_nullability_data() { - JavaTree.CompilationUnitTreeImpl cut = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse("" + - "class A {" + - " Object o;" + - "}"); - SymbolMetadata.NullabilityData nullabilityData = getNullabilityDataOfFirstMember(cut); - assertThat(nullabilityAsString(nullabilityData)).isEmpty(); - } - - @Test - void test_unknown_nullability_data() { - JavaTree.CompilationUnitTreeImpl cut = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse("" + - "class A {" + - " @Unknown" + - " Object o;" + - "}"); - SymbolMetadata.NullabilityData nullabilityData = getNullabilityDataOfFirstMember(cut); - assertThat(nullabilityAsString(nullabilityData)).isEmpty(); - } - - private static SymbolMetadata.NullabilityData getNullabilityDataOfFirstMember(JavaTree.CompilationUnitTreeImpl cut) { - VariableTree o = (VariableTree) ((ClassTree) cut.types().get(0)).members().get(0); - return o.symbol().metadata().nullabilityData(); - } - -}