From 01ee148ebb305143ff64623e93c00bba16d265cf Mon Sep 17 00:00:00 2001 From: David Schroeter Date: Mon, 2 Nov 2026 17:29:29 +0100 Subject: [PATCH 1/5] Add regression tests for redundant return inside lambda --- .../checks/RedundantJumpCheck.java | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java index 11c3a11fe9f..9f018ce411a 100644 --- a/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java @@ -1,3 +1,6 @@ +import java.util.function.IntSupplier; +import java.util.function.Supplier; + abstract class A { public void reproducer() { @@ -329,4 +332,102 @@ void continueInTryFinallyOutsideLoop() { foo(); } } + + Runnable emptyLambda = () -> {}; + + Runnable expressionLambda = () -> System.out.println("expression"); + + Supplier expressionReturningLambda = () -> () -> { + return; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^ + }; + + Runnable fieldInitializer = () -> { + System.out.println("field"); + return; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^ + }; + + void immediatelyInvokedLambda(int x) { + Runnable r = () -> { + System.out.println(x); + return; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^ + }; + r.run(); + System.out.println("after lambda"); + } + + Runnable conditionalReturns(int x) { + return () -> { + if (x < 0) { + return; + } + System.out.println(x); + if (x == 0) { + return; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^ + } + }; + } + + IntSupplier valueReturningLambda(int x) { + return () -> { + System.out.println(x); + return x; + }; + } + + Runnable nestedLambdas(int x) { + return () -> { + Runnable inner = () -> { + System.out.println(x); + return; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^ + }; + inner.run(); + System.out.println("after inner lambda"); + }; + } + + Runnable loopJumps(int x) { + return () -> { + for (int i = 0; i < x; i++) { + if (i == 1) { + continue; + } + if (i == 2) { + return; + } + System.out.println(i); + continue; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^^^ + } + }; + } + + Runnable returnThroughFinally(int x) { + return () -> { + try { + System.out.println(x); + return; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^ + } finally { + System.out.println("cleanup"); + } + }; + } + + Runnable earlyReturnThroughFinally(int x) { + return () -> { + try { + if (x < 0) { + return; + } + } finally { + System.out.println("cleanup"); + } + System.out.println(x); + }; + } } From c197555773313d7550cfd0e1dae410bb18d53778 Mon Sep 17 00:00:00 2001 From: David Schroeter Date: Mon, 2 Nov 2026 17:30:02 +0100 Subject: [PATCH 2/5] Add implementation for redundant return inside lambda --- .../sonar/java/checks/RedundantJumpCheck.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java index 46a7aaea4e7..66c84c309e3 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java @@ -24,6 +24,7 @@ import org.sonar.java.cfg.CFG.Block; import org.sonar.java.cfg.CFGUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; +import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; import org.sonar.plugins.java.api.tree.Tree; @@ -35,14 +36,21 @@ public class RedundantJumpCheck extends IssuableSubscriptionVisitor { public List nodesToVisit() { return Arrays.asList( Tree.Kind.METHOD, - Tree.Kind.CONSTRUCTOR); + Tree.Kind.CONSTRUCTOR, + Tree.Kind.LAMBDA_EXPRESSION); } @Override public void visitNode(Tree tree) { - MethodTree methodTree = (MethodTree) tree; - if (methodTree.block() != null) { - CFG cfg = (CFG) methodTree.cfg(); + Tree body; + if (tree instanceof MethodTree methodTree) { + body = methodTree.block(); + } else { + LambdaExpressionTree lambdaExpressionTree = (LambdaExpressionTree) tree; + body = lambdaExpressionTree.body(); + } + if (body != null) { + CFG cfg = tree instanceof MethodTree methodTree ? (CFG) methodTree.cfg() : (CFG) ((LambdaExpressionTree) tree).cfg(); cfg.blocks().forEach(this::checkBlock); } } From 3706e138d95b7a95c3cd32460ac0df158f4036e3 Mon Sep 17 00:00:00 2001 From: David Schroeter Date: Mon, 2 Nov 2026 18:12:53 +0100 Subject: [PATCH 3/5] Add comment and additional test to explain why gitar suggestion doesn't hold --- .../non-compiling/checks/RedundantJumpCheck.java | 14 ++++++++++++++ .../org/sonar/java/checks/RedundantJumpCheck.java | 13 +++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java index 9f018ce411a..805df248021 100644 --- a/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java @@ -1,4 +1,5 @@ import java.util.function.IntSupplier; +import java.util.function.IntUnaryOperator; import java.util.function.Supplier; abstract class A { @@ -430,4 +431,17 @@ Runnable earlyReturnThroughFinally(int x) { System.out.println(x); }; } + + IntUnaryOperator continueInSwitchExpressionLambda() { + return x -> switch (x) { + default -> { + for (int i = 0; i < x; i++) { + System.out.println(i); + continue; // Noncompliant {{Remove this redundant jump.}} +// ^^^^^^^^^ + } + yield x; + } + }; + } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java index 66c84c309e3..ac366a1fa0b 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java @@ -42,15 +42,16 @@ public List nodesToVisit() { @Override public void visitNode(Tree tree) { - Tree body; + CFG cfg = null; if (tree instanceof MethodTree methodTree) { - body = methodTree.block(); + if (methodTree.block() != null) { + cfg = (CFG) methodTree.cfg(); + } } else { - LambdaExpressionTree lambdaExpressionTree = (LambdaExpressionTree) tree; - body = lambdaExpressionTree.body(); + // Expression-bodied lambdas can contain redundant continue statements in loops inside switch expressions. + cfg = (CFG) ((LambdaExpressionTree) tree).cfg(); } - if (body != null) { - CFG cfg = tree instanceof MethodTree methodTree ? (CFG) methodTree.cfg() : (CFG) ((LambdaExpressionTree) tree).cfg(); + if (cfg != null) { cfg.blocks().forEach(this::checkBlock); } } From fee38c96c145932af30bfd2fecc0a97f4fc7b5f7 Mon Sep 17 00:00:00 2001 From: David Schroeter Date: Tue, 3 Nov 2026 17:31:26 +0100 Subject: [PATCH 4/5] Update issue message to be clearer --- .../non-compiling/checks/RedundantJumpCheck.java | 16 ++++++++-------- .../sonar/java/checks/RedundantJumpCheck.java | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java index 805df248021..e8d1c40dbd8 100644 --- a/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/RedundantJumpCheck.java @@ -339,20 +339,20 @@ void continueInTryFinallyOutsideLoop() { Runnable expressionLambda = () -> System.out.println("expression"); Supplier expressionReturningLambda = () -> () -> { - return; // Noncompliant {{Remove this redundant jump.}} + return; // Noncompliant {{Remove this redundant return.}} // ^^^^^^^ }; Runnable fieldInitializer = () -> { System.out.println("field"); - return; // Noncompliant {{Remove this redundant jump.}} + return; // Noncompliant {{Remove this redundant return.}} // ^^^^^^^ }; void immediatelyInvokedLambda(int x) { Runnable r = () -> { System.out.println(x); - return; // Noncompliant {{Remove this redundant jump.}} + return; // Noncompliant {{Remove this redundant return.}} // ^^^^^^^ }; r.run(); @@ -366,7 +366,7 @@ Runnable conditionalReturns(int x) { } System.out.println(x); if (x == 0) { - return; // Noncompliant {{Remove this redundant jump.}} + return; // Noncompliant {{Remove this redundant return.}} // ^^^^^^^ } }; @@ -383,7 +383,7 @@ Runnable nestedLambdas(int x) { return () -> { Runnable inner = () -> { System.out.println(x); - return; // Noncompliant {{Remove this redundant jump.}} + return; // Noncompliant {{Remove this redundant return.}} // ^^^^^^^ }; inner.run(); @@ -401,7 +401,7 @@ Runnable loopJumps(int x) { return; } System.out.println(i); - continue; // Noncompliant {{Remove this redundant jump.}} + continue; // Noncompliant {{Remove this redundant continue.}} // ^^^^^^^^^ } }; @@ -411,7 +411,7 @@ Runnable returnThroughFinally(int x) { return () -> { try { System.out.println(x); - return; // Noncompliant {{Remove this redundant jump.}} + return; // Noncompliant {{Remove this redundant return.}} // ^^^^^^^ } finally { System.out.println("cleanup"); @@ -437,7 +437,7 @@ IntUnaryOperator continueInSwitchExpressionLambda() { default -> { for (int i = 0; i < x; i++) { System.out.println(i); - continue; // Noncompliant {{Remove this redundant jump.}} + continue; // Noncompliant {{Remove this redundant continue.}} // ^^^^^^^^^ } yield x; diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java index ac366a1fa0b..0e2919e4d04 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java @@ -73,7 +73,8 @@ && hasValidContinueTarget(terminator)) { Block successor = CFGUtils.nonEmptySuccessor(successors.next()); if (successor.equals(successorWithoutJump) && !CFGUtils.isJumpThroughFinallyWithDistinctContinuation(terminator, successor)) { - reportIssue(terminator, "Remove this redundant jump."); + String jump = terminator.is(Tree.Kind.RETURN_STATEMENT) ? "return" : "continue"; + reportIssue(terminator, "Remove this redundant %s.".formatted(jump)); } } } From 7be949c1f5deda04340428ddb467f26bd883d87a Mon Sep 17 00:00:00 2001 From: David Schroeter Date: Wed, 4 Nov 2026 08:55:26 +0100 Subject: [PATCH 5/5] Apply reviewer suggestion --- .../main/java/org/sonar/java/checks/RedundantJumpCheck.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java index 0e2919e4d04..103982f96fc 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantJumpCheck.java @@ -27,6 +27,7 @@ import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.MethodTree; import org.sonar.plugins.java.api.tree.ReturnStatementTree; +import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.Tree; @Rule(key = "S3626") @@ -73,7 +74,8 @@ && hasValidContinueTarget(terminator)) { Block successor = CFGUtils.nonEmptySuccessor(successors.next()); if (successor.equals(successorWithoutJump) && !CFGUtils.isJumpThroughFinallyWithDistinctContinuation(terminator, successor)) { - String jump = terminator.is(Tree.Kind.RETURN_STATEMENT) ? "return" : "continue"; + SyntaxToken fstToken = terminator.firstToken(); + String jump = fstToken != null ? fstToken.text() : "jump"; reportIssue(terminator, "Remove this redundant %s.".formatted(jump)); } }