From 569a5888ff8ad82f8810c394c6972bae87139e9e Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Fri, 11 Mar 2016 19:27:08 +0300 Subject: [PATCH] do...while (true) is now considered infinite loop in CFA #KT-3896 Fixed Also #KT-3883 Fixed Also #KT-4986 Fixed --- .../kotlin/cfg/ControlFlowProcessor.kt | 12 ++- .../InfiniteLoops.instructions | 76 +++++++++++++++++++ .../cfg/controlStructures/InfiniteLoops.kt | 13 ++++ .../controlStructures/InfiniteLoops.values | 28 +++++++ .../continueInDoWhile.instructions | 9 ++- .../continueInDoWhile.values | 9 ++- .../controlFlowAnalysis/infiniteLoops.kt | 55 ++++++++++++++ .../controlFlowAnalysis/infiniteLoops.txt | 11 +++ .../kotlin/cfg/ControlFlowTestGenerated.java | 6 ++ .../kotlin/cfg/PseudoValueTestGenerated.java | 6 ++ .../checkers/DiagnosticsTestGenerated.java | 6 ++ 11 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 compiler/testData/cfg/controlStructures/InfiniteLoops.instructions create mode 100644 compiler/testData/cfg/controlStructures/InfiniteLoops.kt create mode 100644 compiler/testData/cfg/controlStructures/InfiniteLoops.values create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index c5d385a7dd3..2dfff4186d6 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -716,8 +716,16 @@ class ControlFlowProcessor(private val trace: BindingTrace) { generateInstructions(expression.body) builder.exitLoopBody(expression) builder.bindLabel(loopInfo.conditionEntryPoint) - generateInstructions(expression.condition) - builder.jumpOnTrue(loopInfo.entryPoint, expression, builder.getBoundValue(expression.condition)) + val condition = expression.condition + generateInstructions(condition) + if (!CompileTimeConstantUtils.canBeReducedToBooleanConstant(condition, trace.bindingContext, true)) { + builder.jumpOnTrue(loopInfo.entryPoint, expression, builder.getBoundValue(expression.condition)) + } + else { + assert(condition != null) { "Invalid do / while condition: " + expression.text } + createSyntheticValue(condition!!, MagicKind.VALUE_CONSUMER, condition) + builder.jump(loopInfo.entryPoint, expression) + } builder.bindLabel(loopInfo.exitPoint) builder.loadUnit(expression) builder.exitLexicalScope(expression) diff --git a/compiler/testData/cfg/controlStructures/InfiniteLoops.instructions b/compiler/testData/cfg/controlStructures/InfiniteLoops.instructions new file mode 100644 index 00000000000..899bfa4c7e1 --- /dev/null +++ b/compiler/testData/cfg/controlStructures/InfiniteLoops.instructions @@ -0,0 +1,76 @@ +== unreachable == +fun unreachable() {} +--------------------- +L0: + 1 + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== a == +fun a() { + do { + } while (true) + unreachable() +} +--------------------- +L0: + 1 + 2 mark({ do { } while (true) unreachable() }) + 3 mark(do { } while (true)) +L2 [loop entry point]: +L4 [body entry point]: + mark({ }) PREV:[mark(do { } while (true)), jmp(L2)] + read (Unit) +L5 [body exit point]: +L6 [condition entry point]: + r(true) -> + magic[VALUE_CONSUMER](true|) -> + jmp(L2) NEXT:[mark({ })] +L3 [loop exit point]: +- read (Unit) PREV:[] +- 2 mark(unreachable()) PREV:[] +- call(unreachable(), unreachable) -> PREV:[] +L1: + 1 NEXT:[] PREV:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== b == +fun b() { + while (true) { + } + unreachable() +} +--------------------- +L0: + 1 + 2 mark({ while (true) { } unreachable() }) +L2 [loop entry point]: +L6 [condition entry point]: + r(true) -> PREV:[mark({ while (true) { } unreachable() }), jmp(L2)] + mark(while (true) { }) + magic[VALUE_CONSUMER](true|) -> +L4 [body entry point]: + 3 mark({ }) + read (Unit) + 2 jmp(L2) NEXT:[r(true) -> ] +L3 [loop exit point]: +L5 [body exit point]: +- read (Unit) PREV:[] +- mark(unreachable()) PREV:[] +- call(unreachable(), unreachable) -> PREV:[] +L1: + 1 NEXT:[] PREV:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/controlStructures/InfiniteLoops.kt b/compiler/testData/cfg/controlStructures/InfiniteLoops.kt new file mode 100644 index 00000000000..641ea356b72 --- /dev/null +++ b/compiler/testData/cfg/controlStructures/InfiniteLoops.kt @@ -0,0 +1,13 @@ +fun unreachable() {} + +fun a() { + do { + } while (true) + unreachable() +} + +fun b() { + while (true) { + } + unreachable() +} \ No newline at end of file diff --git a/compiler/testData/cfg/controlStructures/InfiniteLoops.values b/compiler/testData/cfg/controlStructures/InfiniteLoops.values new file mode 100644 index 00000000000..45c4cb0bb7c --- /dev/null +++ b/compiler/testData/cfg/controlStructures/InfiniteLoops.values @@ -0,0 +1,28 @@ +== unreachable == +fun unreachable() {} +--------------------- +===================== +== a == +fun a() { + do { + } while (true) + unreachable() +} +--------------------- + : * NEW: magic[VALUE_CONSUMER](true|) -> +true : * NEW: r(true) -> +unreachable() : * NEW: call(unreachable(), unreachable) -> +{ do { } while (true) unreachable() } : * COPY +===================== +== b == +fun b() { + while (true) { + } + unreachable() +} +--------------------- + : * NEW: magic[VALUE_CONSUMER](true|) -> +true : Boolean NEW: r(true) -> +unreachable() : * NEW: call(unreachable(), unreachable) -> +{ while (true) { } unreachable() } : * COPY +===================== diff --git a/compiler/testData/cfg/controlStructures/continueInDoWhile.instructions b/compiler/testData/cfg/controlStructures/continueInDoWhile.instructions index abf81b4f3d5..c6b9b7bf667 100644 --- a/compiler/testData/cfg/controlStructures/continueInDoWhile.instructions +++ b/compiler/testData/cfg/controlStructures/continueInDoWhile.instructions @@ -15,7 +15,7 @@ L0: 3 mark(do { if (b) break; continue; } while (true)) L2 [loop entry point]: L4 [body entry point]: - mark({ if (b) break; continue; }) PREV:[mark(do { if (b) break; continue; } while (true)), jt(L2|)] + mark({ if (b) break; continue; }) PREV:[mark(do { if (b) break; continue; } while (true)), jmp(L2)] mark(if (b) break) r(b) -> jf(L7|) NEXT:[read (Unit), jmp(L3)] @@ -29,13 +29,14 @@ L8 ['if' expression result]: L5 [body exit point]: L6 [condition entry point]: r(true) -> - jt(L2|) NEXT:[read (Unit), mark({ if (b) break; continue; })] + magic[VALUE_CONSUMER](true|) -> + jmp(L2) NEXT:[mark({ if (b) break; continue; })] L3 [loop exit point]: - read (Unit) PREV:[jmp(L3), jt(L2|)] + read (Unit) PREV:[jmp(L3)] L1: 1 NEXT:[] error: PREV:[] sink: PREV:[, ] -===================== \ No newline at end of file +===================== diff --git a/compiler/testData/cfg/controlStructures/continueInDoWhile.values b/compiler/testData/cfg/controlStructures/continueInDoWhile.values index e280b22ea74..3866967cfe9 100644 --- a/compiler/testData/cfg/controlStructures/continueInDoWhile.values +++ b/compiler/testData/cfg/controlStructures/continueInDoWhile.values @@ -7,12 +7,13 @@ fun test(b: Boolean) { } --------------------- : Boolean NEW: magic[FAKE_INITIALIZER](b: Boolean) -> + : * NEW: magic[VALUE_CONSUMER](true|) -> b : Boolean NEW: r(b) -> break !: * if (b) break : * NEW: merge(if (b) break|!) -> continue !: * { if (b) break; continue; } !: * COPY -true : Boolean NEW: r(true) -> -do { if (b) break; continue; } while (true) !: * -{ do { if (b) break; continue; } while (true); } !: * COPY -===================== \ No newline at end of file +true : * NEW: r(true) -> +do { if (b) break; continue; } while (true) !: * +{ do { if (b) break; continue; } while (true); } !: * COPY +===================== diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt new file mode 100644 index 00000000000..7e6d421d766 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt @@ -0,0 +1,55 @@ +fun unreachable() {} + +fun a() { + do { + } while (true) + unreachable() +} + +fun b() { + while (true) { + } + unreachable() +} + +fun c() { + do {} while (1 == 1) +} + +fun d() { + while (2 == 2) {} +} + +fun use(arg: Any) = arg + +fun f(cond: Boolean) { + val bar: Any + do { + if (cond) { + bar = "value" + break + } + } while (true) + use(bar) // should work + + val foo: Any + while (true) { + if (cond) { + foo = "value" + break + } + } + use(foo) // should work +} + +fun g(): Int { + do { + if (true) return 12 + } while (true) +} // should work + +fun h(): Int { + while (true) { + if (true) return 12 + } +} // should work \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.txt new file mode 100644 index 00000000000..9907fb57304 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.txt @@ -0,0 +1,11 @@ +package + +public fun a(): kotlin.Unit +public fun b(): kotlin.Unit +public fun c(): kotlin.Unit +public fun d(): kotlin.Unit +public fun f(/*0*/ cond: kotlin.Boolean): kotlin.Unit +public fun g(): kotlin.Int +public fun h(): kotlin.Int +public fun unreachable(): kotlin.Unit +public fun use(/*0*/ arg: kotlin.Any): kotlin.Any diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java index 7820c7b562e..59a8c715438 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java @@ -208,6 +208,12 @@ public class ControlFlowTestGenerated extends AbstractControlFlowTest { doTest(fileName); } + @TestMetadata("InfiniteLoops.kt") + public void testInfiniteLoops() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/InfiniteLoops.kt"); + doTest(fileName); + } + @TestMetadata("localAndNonlocalReturnsWithFinally.kt") public void testLocalAndNonlocalReturnsWithFinally() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/localAndNonlocalReturnsWithFinally.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java index 30b52a25474..fa84fac429f 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java @@ -210,6 +210,12 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest { doTest(fileName); } + @TestMetadata("InfiniteLoops.kt") + public void testInfiniteLoops() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/InfiniteLoops.kt"); + doTest(fileName); + } + @TestMetadata("localAndNonlocalReturnsWithFinally.kt") public void testLocalAndNonlocalReturnsWithFinally() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/controlStructures/localAndNonlocalReturnsWithFinally.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 77de74927f1..28a8e2f34c5 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -2877,6 +2877,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("infiniteLoops.kt") + public void testInfiniteLoops() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt"); + doTest(fileName); + } + @TestMetadata("initializationInLambda.kt") public void testInitializationInLambda() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLambda.kt");