This is an automated email from the ASF dual-hosted git repository. sunlan pushed a commit to branch GROOVY_4_0_X in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push: new e73492692d GROOVY-11739: `continue` jumps to condition of `do`/`while` loop e73492692d is described below commit e73492692deff0dddf27f96e7d0eea0edbc3a4e8 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Sun Aug 24 08:05:52 2025 -0500 GROOVY-11739: `continue` jumps to condition of `do`/`while` loop (cherry picked from commit 8328a76c5a42ea16f8b40f449e35ff748f0f161d) --- .../groovy/classgen/asm/StatementWriter.java | 27 +++--- .../groovy/groovy/BreakContinueLabelTest.groovy | 101 +++++++++++++-------- 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java index d6f0bbde9e..9c53893348 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/StatementWriter.java @@ -262,12 +262,12 @@ public class StatementWriter { controller.getAcg().onLineNumber(statement, "visitWhileLoop"); writeStatementLabel(statement); - MethodVisitor mv = controller.getMethodVisitor(); - - controller.getCompileStack().pushLoop(statement.getStatementLabels()); - Label continueLabel = controller.getCompileStack().getContinueLabel(); - Label breakLabel = controller.getCompileStack().getBreakLabel(); + CompileStack compileStack = controller.getCompileStack(); + compileStack.pushLoop(statement.getStatementLabels()); + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + MethodVisitor mv = controller.getMethodVisitor(); mv.visitLabel(continueLabel); visitConditionOfLoopingStatement(statement.getBooleanExpression(), breakLabel, mv); @@ -276,26 +276,29 @@ public class StatementWriter { mv.visitJumpInsn(GOTO, continueLabel); mv.visitLabel(breakLabel); - controller.getCompileStack().pop(); + compileStack.pop(); } public void writeDoWhileLoop(final DoWhileStatement statement) { writeStatementLabel(statement); - controller.getCompileStack().pushLoop(statement.getStatementLabels()); - Label continueLabel = controller.getCompileStack().getContinueLabel(); - Label breakLabel = controller.getCompileStack().getBreakLabel(); + CompileStack compileStack = controller.getCompileStack(); + compileStack.pushLoop(statement.getStatementLabels()); + Label continueLabel = compileStack.getContinueLabel(); + Label breakLabel = compileStack.getBreakLabel(); + Label blockLabel = new Label(); MethodVisitor mv = controller.getMethodVisitor(); - mv.visitLabel(continueLabel); + mv.visitLabel(blockLabel); statement.getLoopBlock().visit(controller.getAcg()); + mv.visitLabel(continueLabel); // GROOVY-11739: continue jumps to condition visitConditionOfLoopingStatement(statement.getBooleanExpression(), breakLabel, mv); - mv.visitJumpInsn(GOTO, continueLabel); + mv.visitJumpInsn(GOTO, blockLabel); mv.visitLabel(breakLabel); - controller.getCompileStack().pop(); + compileStack.pop(); } public void writeIfElse(final IfStatement statement) { diff --git a/src/test/groovy/groovy/BreakContinueLabelTest.groovy b/src/test/groovy/groovy/BreakContinueLabelTest.groovy index a993f20382..703fb7ffdb 100644 --- a/src/test/groovy/groovy/BreakContinueLabelTest.groovy +++ b/src/test/groovy/groovy/BreakContinueLabelTest.groovy @@ -18,18 +18,20 @@ */ package groovy -import gls.CompilableTestSupport +import org.junit.jupiter.api.Test -/** - * todo: add BreakContinueLabelWithClosureTest (when break is used to return from a Closure) - */ -class BreakContinueLabelTest extends CompilableTestSupport { +import static org.junit.jupiter.api.Assertions.fail + +final class BreakContinueLabelTest { + @Test void testDeclareSimpleLabel() { label_1: assert true label_2: assert true } + + @Test void testBreakLabelInSimpleForLoop() { label_1: for (i in [1]) { break label_1 @@ -37,28 +39,31 @@ class BreakContinueLabelTest extends CompilableTestSupport { } } + @Test void testBreakLabelInNestedForLoop() { label: for (i in [1]) { for (j in [1]){ break label - assert false, 'did not break inner loop' + assert false : 'did not break inner loop' } - assert false, 'did not break outer loop' + assert false : 'did not break outer loop' } } + @Test void testUnlabelledBreakInNestedForLoop() { def reached = false for (i in [1]) { - for (j in [1]){ + for (j in [1]) { break - assert false, 'did not break inner loop' + assert false : 'did not break inner loop' } reached = true } - assert reached, 'must not break outer loop' + assert reached : 'must not break outer loop' } + @Test void testBreakLabelInSimpleWhileLoop() { label_1: while (true) { break label_1 @@ -66,62 +71,80 @@ class BreakContinueLabelTest extends CompilableTestSupport { } } + @Test void testBreakLabelInNestedWhileLoop() { def count = 0 label: while (count < 1) { count++ - while (true){ + while (true) { break label - assert false, 'did not break inner loop' + assert false : 'did not break inner loop' } - assert false, 'did not break outer loop' + assert false : 'did not break outer loop' } } + @Test void testBreakLabelInNestedMixedForAndWhileLoop() { def count = 0 label_1: while (count < 1) { count++ - for (i in [1]){ + for (i in [1]) { break label_1 - assert false, 'did not break inner loop' + assert false : 'did not break inner loop' } - assert false, 'did not break outer loop' + assert false : 'did not break outer loop' } label_2: for (i in [1]) { - while (true){ + while (true) { break label_2 - assert false, 'did not break inner loop' + assert false : 'did not break inner loop' } - assert false, 'did not break outer loop' + assert false : 'did not break outer loop' } } + // GROOVY-11739 + @Test + void testUnlabelledContinueWithinDoWhileLoop() { + int i = 0; + do { + i += 1 + if (i > 1000) break // prevent infinite loop + continue // control should pass to condition + } while (i < 100) + + assert i == 100 + } + + @Test void testUnlabelledContinueInNestedForLoop() { def log = '' for (i in [1,2]) { log += i - for (j in [3,4]){ + for (j in [3,4]) { if (j==3) continue log += j } } - assertEquals '1424',log + assert log == '1424' } + @Test void testContinueLabelInNestedForLoop() { def log = '' label: for (i in [1,2]) { log += i - for (j in [3,4]){ + for (j in [3,4]) { if (j==4) continue label log += j } log += 'never reached' } - assertEquals '1323',log + assert log == '1323' } + @Test void testBreakToLastLabelSucceeds() { one: two: @@ -132,25 +155,25 @@ class BreakContinueLabelTest extends CompilableTestSupport { } } + @Test void testMultipleLabelSupport() { - assertScript """ - def visited = [] - label1: - label2: - label3: - for (int i = 0; i < 9; i++) { - visited << i - if (i == 1) continue label1 - visited << 10 + i - if (i == 3) continue label2 - visited << 100 + i - if (i == 5) break label3 - } - assert visited == [0, 10, 100, 1, 2, 12, 102, 3, 13, 4, 14, 104, 5, 15, 105] - """ + def visited = [] + label1: + label2: + label3: + for (int i = 0; i < 9; i++) { + visited << i + if (i == 1) continue label1 + visited << 10 + i + if (i == 3) continue label2 + visited << 100 + i + if (i == 5) break label3 + } + assert visited == [0, 10, 100, 1, 2, 12, 102, 3, 13, 4, 14, 104, 5, 15, 105] } // this is in accordance with Java; Spock Framework relies on this + @Test void testLabelCanOccurMultipleTimesInSameScope() { one: for (i in 1..2) { @@ -163,4 +186,4 @@ class BreakContinueLabelTest extends CompilableTestSupport { fail() } } -} \ No newline at end of file +}