From a8d1327da62a332c40f69b3c14929df95ea177ec Mon Sep 17 00:00:00 2001 From: Romain Brenguier Date: Fri, 19 Jun 2026 15:40:22 +0200 Subject: [PATCH] Implement S8795: Code after unconditional control flow statements should be removed Detects unreachable code after return, throw, break, and continue statements. Related to RIS-493 --- .../checks/UnreachableCodeCheckSample.java | 177 ++++++++++++++++++ .../java/checks/UnreachableCodeCheck.java | 63 +++++++ .../java/checks/UnreachableCodeCheckTest.java | 33 ++++ .../org/sonar/l10n/java/rules/java/S8795.html | 41 ++++ .../org/sonar/l10n/java/rules/java/S8795.json | 24 +++ 5 files changed, 338 insertions(+) create mode 100644 java-checks-test-sources/default/src/main/java/checks/UnreachableCodeCheckSample.java create mode 100644 java-checks/src/main/java/org/sonar/java/checks/UnreachableCodeCheck.java create mode 100644 java-checks/src/test/java/org/sonar/java/checks/UnreachableCodeCheckTest.java create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.html create mode 100644 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.json diff --git a/java-checks-test-sources/default/src/main/java/checks/UnreachableCodeCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/UnreachableCodeCheckSample.java new file mode 100644 index 00000000000..8d29bd30163 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/UnreachableCodeCheckSample.java @@ -0,0 +1,177 @@ +package checks; + +class UnreachableCodeCheckSample { + + int codeAfterReturn(int a) { + int i = 10; + return i + a; + i++; // Noncompliant {{Remove this unreachable code.}} + } + + int multipleStatementsAfterReturn() { + return 1; + int x = 2; // Noncompliant + int y = 3; + } + + void voidReturn() { + System.out.println("Start"); + return; + System.out.println("End"); // Noncompliant + } + + void codeAfterThrow(String input) { + if (input == null) { + throw new IllegalArgumentException(); + System.out.println("Valid"); // Noncompliant + } + } + + void throwInMethod() { + throw new RuntimeException(); + System.out.println("After throw"); // Noncompliant + } + + void breakInLoop() { + while (true) { + System.out.println("Loop"); + break; + System.out.println("After break"); // Noncompliant + } + } + + void breakInSwitch(int val) { + switch (val) { + case 1: + System.out.println("Case 1"); + break; + System.out.println("After break"); // Noncompliant + case 2: + break; + } + } + + void labeledBreak() { + outer: for (int i = 0; i < 10; i++) { + break outer; + System.out.println(i); // Noncompliant + } + } + + void codeAfterContinue() { + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) { + continue; + System.out.println(i); // Noncompliant + } + } + } + + void labeledContinue() { + outer: for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + continue outer; + System.out.println(j); // Noncompliant + } + } + } + + public UnreachableCodeCheckSample(boolean flag) { + if (!flag) { + return; + System.out.println("Init"); // Noncompliant + } + } + + int lastStatement() { + int x = 10; + return x; + } + + int conditionalReturn(boolean flag) { + if (flag) { + return 1; + } + return 0; + } + + void breakInConditional() { + for (int i = 0; i < 10; i++) { + if (i == 5) { + break; + } + System.out.println(i); + } + } + + void continueInConditional() { + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) { + continue; + } + System.out.println(i); + } + } + + void emptyAfterReturn() { + return; + } + + void nestedBlocks() { + { + System.out.println("Block 1"); + } + System.out.println("Block 2"); + } + + void switchCases(int val) { + switch (val) { + case 1: + System.out.println("Case 1"); + break; + case 2: + System.out.println("Case 2"); + break; + default: + System.out.println("Default"); + } + } + + void tryCatch() { + try { + System.out.println("Try"); + return; + } catch (Exception e) { + System.out.println("Catch"); + } finally { + System.out.println("Finally"); + } + } + + int ifElseReturns(int x) { + if (x > 0) { + return 1; + } else { + return -1; + } + } + + void forLoop() { + for (int i = 0; i < 10; i++) { + if (i == 5) { + continue; + } + System.out.println(i); + } + } + + void whileLoop(boolean cond) { + while (cond) { + System.out.println("Loop"); + if (cond) { + break; + } + System.out.println("After check"); + } + } +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/UnreachableCodeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCodeCheck.java new file mode 100644 index 00000000000..527e109e31d --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/UnreachableCodeCheck.java @@ -0,0 +1,63 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import java.util.Arrays; +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.BlockTree; +import org.sonar.plugins.java.api.tree.CaseGroupTree; +import org.sonar.plugins.java.api.tree.StatementTree; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S8795") +public class UnreachableCodeCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return Arrays.asList(Tree.Kind.BLOCK, Tree.Kind.CASE_GROUP); + } + + @Override + public void visitNode(Tree tree) { + List statements; + if (tree.is(Tree.Kind.BLOCK)) { + statements = ((BlockTree) tree).body(); + } else { + statements = ((CaseGroupTree) tree).body(); + } + + for (int i = 0; i < statements.size() - 1; i++) { + StatementTree statement = statements.get(i); + if (isUnconditionalJump(statement)) { + StatementTree unreachableStatement = statements.get(i + 1); + reportIssue(unreachableStatement, "Remove this unreachable code."); + break; + } + } + } + + private static boolean isUnconditionalJump(StatementTree statement) { + return statement.is( + Tree.Kind.RETURN_STATEMENT, + Tree.Kind.THROW_STATEMENT, + Tree.Kind.BREAK_STATEMENT, + Tree.Kind.CONTINUE_STATEMENT + ); + } +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/UnreachableCodeCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/UnreachableCodeCheckTest.java new file mode 100644 index 00000000000..e2189d3cb2c --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/UnreachableCodeCheckTest.java @@ -0,0 +1,33 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * 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 Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.checks; + +import org.junit.jupiter.api.Test; +import org.sonar.java.checks.verifier.CheckVerifier; + +import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath; + +class UnreachableCodeCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/UnreachableCodeCheckSample.java")) + .withCheck(new UnreachableCodeCheck()) + .verifyIssues(); + } +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.html new file mode 100644 index 00000000000..3dca6e18dbd --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.html @@ -0,0 +1,41 @@ +

Why is this an issue?

+

Unreachable code is a sign of a logical error in your program's control flow. When a statement appears after an unconditional jump (like return statements or exception throws), it can never be executed because the jump statement always transfers control away from that point.

+

This creates several problems:

+
    +
  • Misleading intent: Developers reading the code may think the unreachable statements have some effect, leading to incorrect assumptions about program behavior.
  • +
  • Wasted effort: Time spent understanding, debugging, or modifying unreachable code is entirely wasted.
  • +
  • Hidden bugs: The unreachable code might have been intended to execute, indicating a logic error where the jump statement was placed incorrectly or should have been conditional.
  • +
  • Code bloat: Unreachable code increases the codebase size without providing any value.
  • +
+

While compilers often warn about unreachable code, such warnings may be overlooked or disabled. Static analysis helps catch these issues early in the development process.

+

Common scenarios where unreachable code appears:

+
    +
  • Statements after a return that were meant to execute before returning
  • +
  • Code after an exception throw that was intended for cleanup (which should instead go in a cleanup or finalization block)
  • +
  • Statements after loop control transfers that were meant to be outside the conditional block
  • +
  • Dead code left over from refactoring that was never removed
  • +
+

How to fix it

+

Remove the unreachable code if it serves no purpose. If the statements were intended to execute, restructure the control flow to ensure they run before the jump statement.

+

Code examples

+

Noncompliant code example

+
+public int calculateValue(int a) {
+    int i = 10;
+    return i + a;
+    i++;  // Noncompliant
+}
+
+

Compliant solution

+
+public int calculateValue(int a) {
+    int i = 10;
+    i++;
+    return i + a;
+}
+
+

Resources

+

Documentation

+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.json new file mode 100644 index 00000000000..2b9a63cdf7e --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S8795.json @@ -0,0 +1,24 @@ +{ + "title": "Code after unconditional control flow statements should be removed", + "type": "CODE_SMELL", + "code": { + "impacts": { + "MAINTAINABILITY": "HIGH" + }, + "attribute": "LOGICAL" + }, + "status": "ready", + "remediation": { + "func": "Constant/Issue", + "constantCost": "5min" + }, + "tags": [ + "confusing", + "unused" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-8795", + "sqKey": "S8795", + "scope": "All", + "quickfix": "unknown" +}