diff --git a/its/ruling/src/test/resources/commons-beanutils/java-S9356.json b/its/ruling/src/test/resources/commons-beanutils/java-S9356.json new file mode 100644 index 00000000000..e4b4c4c1fca --- /dev/null +++ b/its/ruling/src/test/resources/commons-beanutils/java-S9356.json @@ -0,0 +1,23 @@ +{ +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java": [ +276, +819 +], +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java": [ +346, +352 +], +"commons-beanutils:commons-beanutils:src/main/java/org/apache/commons/beanutils2/MethodUtils.java": [ +880 +], +"commons-beanutils:commons-beanutils:src/test/java/org/apache/commons/beanutils2/PropertyUtilsTestCase.java": [ +3998, +4001, +4004, +4056, +4059, +4063, +4066, +4069 +] +} diff --git a/its/ruling/src/test/resources/eclipse-jetty/java-S9356.json b/its/ruling/src/test/resources/eclipse-jetty/java-S9356.json new file mode 100644 index 00000000000..95e0022ff53 --- /dev/null +++ b/its/ruling/src/test/resources/eclipse-jetty/java-S9356.json @@ -0,0 +1,373 @@ +{ +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/CookieCutter.java": [ +88, +102, +109, +113, +139, +144, +238, +252, +263, +275, +298, +312 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/GZIPContentDecoder.java": [ +196, +252 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java": [ +168 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java": [ +1325 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java": [ +846 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java": [ +872, +894 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java": [ +841, +888, +923 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java": [ +433, +439, +602, +609 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java": [ +129, +134, +143, +153, +166, +182, +210, +227, +234, +241, +255, +264, +273 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java": [ +184 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java": [ +225, +229, +238, +245 +], +"org.eclipse.jetty:jetty-project:jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java": [ +153, +157, +163, +167, +171, +192 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java": [ +74, +99, +105, +131, +152, +158, +197, +204 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java": [ +178 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ +1005 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java": [ +335 +], +"org.eclipse.jetty:jetty-project:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ +628, +638, +721, +747, +1001, +1011, +1089, +1196 +], +"org.eclipse.jetty:jetty-project:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java": [ +201, +461, +466 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +723, +728 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java": [ +496, +498, +500, +502, +504, +506, +508, +510, +512 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java": [ +783, +791, +796, +802, +863 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java": [ +1056, +1078 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/InclusiveByteRange.java": [ +160, +185, +195 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java": [ +202, +208, +323, +332 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/Response.java": [ +284, +289, +292, +297, +300, +1183, +1202 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [ +344 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java": [ +68 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +359, +753, +793, +811, +1132, +2106, +2160 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java": [ +144 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java": [ +339 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java": [ +593 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java": [ +801, +939, +984 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/AsyncCompletionTest.java": [ +245, +252 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java": [ +979 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ +1151, +1155 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/MultiPartCaptureTest.java": [ +217 +], +"org.eclipse.jetty:jetty-project:jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java": [ +289 +], +"org.eclipse.jetty:jetty-project:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java": [ +92 +], +"org.eclipse.jetty:jetty-project:jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/Slf4jEffort.java": [ +137, +143 +], +"org.eclipse.jetty:jetty-project:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java": [ +706 +], +"org.eclipse.jetty:jetty-project:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java": [ +88 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java": [ +854, +894, +934 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/IO.java": [ +264, +465 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java": [ +262, +298 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ +1191, +1201, +1284 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java": [ +777, +780 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java": [ +900, +904, +910, +914, +922, +930, +933, +939, +948, +954, +960, +967, +970, +975, +981, +983 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java": [ +145, +148, +151, +154, +157, +160, +163, +166, +169, +172, +175, +178, +181, +184, +187, +190, +193, +214, +217, +220, +223, +226, +229, +232, +235, +238, +241, +244, +247, +250, +253, +256, +259, +262 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java": [ +832, +850 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java": [ +194 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java": [ +336, +345, +354 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java": [ +210, +219 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ +659 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java": [ +69, +212 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateValidator.java": [ +207 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/security/UnixCrypt.java": [ +215, +241, +247, +477 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ +318 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java": [ +539, +654, +904 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java": [ +184, +350 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/EatWhatYouKill.java": [ +174 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ExecuteProduceConsume.java": [ +162 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceConsume.java": [ +87 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/ProduceExecuteConsume.java": [ +90 +], +"org.eclipse.jetty:jetty-project:jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java": [ +249 +], +"org.eclipse.jetty:jetty-project:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +435, +618, +910, +913, +915, +917, +1093, +1196, +1488, +1530, +1566, +1725 +], +"org.eclipse.jetty:jetty-project:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java": [ +753 +], +"org.eclipse.jetty:jetty-project:tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/HttpTester.java": [ +459, +465, +469, +473 +] +} diff --git a/its/ruling/src/test/resources/guava/java-S9356.json b/its/ruling/src/test/resources/guava/java-S9356.json new file mode 100644 index 00000000000..555e008edb8 --- /dev/null +++ b/its/ruling/src/test/resources/guava/java-S9356.json @@ -0,0 +1,82 @@ +{ +"com.google.guava:guava:src/com/google/common/base/Ascii.java": [ +647, +653 +], +"com.google.guava:guava:src/com/google/common/base/Splitter.java": [ +191, +606, +619 +], +"com.google.guava:guava:src/com/google/common/cache/LocalCache.java": [ +2713, +2719, +2819 +], +"com.google.guava:guava:src/com/google/common/cache/Striped64.java": [ +224, +249 +], +"com.google.guava:guava:src/com/google/common/collect/Collections2.java": [ +644, +652 +], +"com.google.guava:guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ +165 +], +"com.google.guava:guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +2406, +2412, +2484 +], +"com.google.guava:guava:src/com/google/common/collect/Ordering.java": [ +738 +], +"com.google.guava:guava:src/com/google/common/escape/CharEscaper.java": [ +98 +], +"com.google.guava:guava:src/com/google/common/io/Files.java": [ +742 +], +"com.google.guava:guava:src/com/google/common/primitives/Booleans.java": [ +156 +], +"com.google.guava:guava:src/com/google/common/primitives/Bytes.java": [ +132 +], +"com.google.guava:guava:src/com/google/common/primitives/Chars.java": [ +189 +], +"com.google.guava:guava:src/com/google/common/primitives/Doubles.java": [ +181 +], +"com.google.guava:guava:src/com/google/common/primitives/Floats.java": [ +177 +], +"com.google.guava:guava:src/com/google/common/primitives/Ints.java": [ +196 +], +"com.google.guava:guava:src/com/google/common/primitives/Longs.java": [ +165 +], +"com.google.guava:guava:src/com/google/common/primitives/Shorts.java": [ +195 +], +"com.google.guava:guava:src/com/google/common/reflect/ClassPath.java": [ +372, +433 +], +"com.google.guava:guava:src/com/google/common/reflect/TypeVisitor.java": [ +73 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ +202, +205 +], +"com.google.guava:guava:src/com/google/common/util/concurrent/AtomicLongMap.java": [ +124, +173, +208, +385 +] +} diff --git a/its/ruling/src/test/resources/sonar-server/java-S9356.json b/its/ruling/src/test/resources/sonar-server/java-S9356.json new file mode 100644 index 00000000000..c781183fd96 --- /dev/null +++ b/its/ruling/src/test/resources/sonar-server/java-S9356.json @@ -0,0 +1,28 @@ +{ +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStep.java": [ +208 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackerRawInputFactory.java": [ +103 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistMeasuresStep.java": [ +112 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java": [ +179 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/rule/RegisterRules.java": [ +117, +354, +363 +], +"org.sonarsource.sonarqube:sonar-server:src/main/java/org/sonar/server/user/UserUpdater.java": [ +344 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MapBasedRawMeasureRepositoryTest.java": [ +184 +], +"org.sonarsource.sonarqube:sonar-server:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryImplTest.java": [ +241 +] +} diff --git a/java-checks-test-sources/default/src/main/java/checks/ContinueInLoopCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ContinueInLoopCheckSample.java new file mode 100644 index 00000000000..e4eb20a9184 --- /dev/null +++ b/java-checks-test-sources/default/src/main/java/checks/ContinueInLoopCheckSample.java @@ -0,0 +1,121 @@ +package checks; + +import java.util.List; + +public class ContinueInLoopCheckSample { + + void unlabeledContinueInForLoop(String[] items) { + for (int i = 0; i < items.length; i++) { + if (items[i] == null) { + continue; // Noncompliant {{Remove this "continue" statement.}} + } + process(items[i]); + } + } + + void unlabeledContinueInForEachLoop(List items) { + for (String item : items) { + if (item.isEmpty()) { + continue; // Noncompliant + } + process(item); + } + } + + void unlabeledContinueInWhileLoop(int[] data) { + int i = 0; + while (i < data.length) { + i++; + if (data[i - 1] < 0) { + continue; // Noncompliant + } + process(String.valueOf(data[i - 1])); + } + } + + void unlabeledContinueInDoWhileLoop(int[] data) { + int i = 0; + do { + if (data[i] == 0) { + continue; // Noncompliant + } + process(String.valueOf(data[i])); + } while (++i < data.length); + } + + void labeledContinueInNestedLoop(int[][] matrix) { + outer: + for (int[] row : matrix) { + for (int value : row) { + if (value < 0) { + continue outer; // Noncompliant + } + process(String.valueOf(value)); + } + } + } + + void multipleContinuesInOneLoop(List items) { + for (String item : items) { + if (item == null) { + continue; // Noncompliant + } + if (item.startsWith("#")) { + continue; // Noncompliant + } + process(item); + } + } + + void continueInsideNestedIf(List items, boolean flag) { + for (String item : items) { + if (flag) { + if (item == null) { + continue; // Noncompliant + } + } + process(item); + } + } + + void compliantInvertedCondition(String[] items) { + for (int i = 0; i < items.length; i++) { + if (items[i] != null) { // compliant + process(items[i]); + } + } + } + + void compliantBreakOnly(List items) { + for (String item : items) { + if ("STOP".equals(item)) { + break; // compliant + } + process(item); + } + } + + void compliantReturnOnly(List items) { + for (String item : items) { + if ("target".equals(item)) { + return; // compliant + } + } + } + + void compliantEmptyLoop(int count) { + for (int i = 0; i < count; i++) { + // compliant - no jump statement + } + } + + void compliantSimpleLoop(List items) { + for (String item : items) { + process(item); + } + } + + private void process(String value) { + } + +} diff --git a/java-checks/src/main/java/org/sonar/java/checks/ContinueInLoopCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ContinueInLoopCheck.java new file mode 100644 index 00000000000..61771dd1b0a --- /dev/null +++ b/java-checks/src/main/java/org/sonar/java/checks/ContinueInLoopCheck.java @@ -0,0 +1,38 @@ +/* + * 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.Collections; +import java.util.List; +import org.sonar.check.Rule; +import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.Tree; + +@Rule(key = "S9356") +public class ContinueInLoopCheck extends IssuableSubscriptionVisitor { + + @Override + public List nodesToVisit() { + return Collections.singletonList(Tree.Kind.CONTINUE_STATEMENT); + } + + @Override + public void visitNode(Tree tree) { + reportIssue(tree, "Remove this \"continue\" statement."); + } + +} diff --git a/java-checks/src/test/java/org/sonar/java/checks/ContinueInLoopCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ContinueInLoopCheckTest.java new file mode 100644 index 00000000000..899e36ac200 --- /dev/null +++ b/java-checks/src/test/java/org/sonar/java/checks/ContinueInLoopCheckTest.java @@ -0,0 +1,34 @@ +/* + * 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 ContinueInLoopCheckTest { + + @Test + void test() { + CheckVerifier.newVerifier() + .onFile(mainCodeSourcesPath("checks/ContinueInLoopCheckSample.java")) + .withCheck(new ContinueInLoopCheck()) + .verifyIssues(); + } + +} diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9356.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9356.html new file mode 100644 index 00000000000..e2a2c8f971a --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9356.html @@ -0,0 +1,67 @@ +

This rule raises an issue when a statement that skips the rest of the current loop iteration and proceeds to the next iteration is used in a loop, +including both simple and labeled forms.

+

In Java, this specifically refers to the continue statement.

+

Why is this an issue?

+

Loop continuation statements transfer control to the next iteration of a loop, skipping the remaining code in the current iteration. While this is +a valid control flow mechanism in many languages, it can make loop logic harder to understand and maintain.

+

When a developer encounters a loop continuation statement, they must mentally trace backwards to understand which loop will be affected and +forwards to understand which code will be skipped. This cognitive overhead increases with labeled continuation statements, which can jump to any +enclosing loop rather than just the innermost one.

+

The presence of loop continuation statements often indicates that the loop body could be restructured for better clarity. In most cases, the same +logic can be expressed using positive conditional logic (if-then) instead of negative conditional logic (if-continue), making the code’s intent more +explicit.

+

Labeled continuation statements are particularly problematic because they:

+
    +
  • Create non-local control flow that is difficult to trace
  • +
  • Often indicate overly complex nested loops that should be decomposed
  • +
  • Can be easily confused with labeled early exit statements
  • +
  • Make it harder to reason about loop invariants and termination conditions
  • +
+

In Java, this refers to the continue statement.

+

What is the potential impact?

+

Using statements that skip the remainder of the current loop iteration and jump to the next iteration reduces code readability and maintainability. +The impact includes:

+
    +
  • Increased cognitive load: Developers must mentally trace control flow to understand which code executes and when
  • +
  • Higher maintenance cost: Future modifications become more error-prone when control flow is non-obvious
  • +
  • Reduced testability: Complex control flow with iteration-skipping statements is harder to test comprehensively
  • +
  • Team comprehension: Code reviews and knowledge transfer become more difficult
  • +
+

For statements that skip to outer loop iterations (non-local jumps), these impacts are amplified because the control flow becomes even less local +and more difficult to follow.

+

How to fix it

+

Replace the continue statement with inverted conditional logic. Instead of using continue to skip certain iterations, use +a positive if statement to explicitly process the desired iterations. This makes the loop’s intent clearer by showing what should happen +rather than what should be skipped.

+

Code examples

+

Noncompliant code example

+
+for (int i = 0; i < items.length; i++) {
+    if (items[i] == null) {
+        continue; // Noncompliant
+    }
+    processItem(items[i]);
+}
+
+

Compliant solution

+
+for (int i = 0; i < items.length; i++) {
+    if (items[i] != null) {
+        processItem(items[i]);
+    }
+}
+
+

Resources

+

Documentation

+ +

Related rules

+
    +
  • {rule:java:S135} - Loops should not contain more than a single 'break' or 'continue' statement
  • +
  • {rule:java:S1119} - Labels should not be used
  • +
  • {rule:java:S3626} - Jump statements should not be redundant
  • +
+ diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9356.json b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9356.json new file mode 100644 index 00000000000..7d5ad5c8841 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9356.json @@ -0,0 +1,25 @@ +{ + "title": "\"continue\" should not be used in loops", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5 min" + }, + "tags": [ + "loop", + "confusing", + "brain-overload" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-9356", + "sqKey": "S9356", + "scope": "All", + "quickfix": "unknown", + "code": { + "impacts": { + "MAINTAINABILITY": "MEDIUM" + }, + "attribute": "CLEAR" + } +} diff --git a/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9356 b/sonar-java-plugin/src/main/resources/profiles/Sonar_way/S9356 new file mode 100644 index 00000000000..e69de29bb2d