From a08b8f43b2b0f65dfe7220b302a9aa13a5239c72 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Tue, 2 Feb 2016 16:17:02 +0300 Subject: [PATCH] Control flow graph for safe calls corrected #KT-10913 Fixed Also #KT-10186 Fixed Also #KT-5198 Fixed --- .../kotlin/cfg/ControlFlowProcessor.kt | 11 +- .../cfg/deadCode/notLocalReturn.instructions | 206 ++++++++++++++++++ .../testData/cfg/deadCode/notLocalReturn.kt | 25 +++ .../cfg/deadCode/notLocalReturn.values | 90 ++++++++ .../cfg/deadCode/throwInLambda.instructions | 160 ++++++++++++++ .../testData/cfg/deadCode/throwInLambda.kt | 16 ++ .../cfg/deadCode/throwInLambda.values | 65 ++++++ .../expressions/propertySafeCall.instructions | 4 +- .../nonLocalReturnUnreachable.kt | 25 +++ .../nonLocalReturnUnreachable.txt | 7 + .../controlFlowAnalysis/throwInLambda.kt | 12 + .../controlFlowAnalysis/throwInLambda.txt | 5 + .../kotlin/cfg/ControlFlowTestGenerated.java | 12 + .../kotlin/cfg/PseudoValueTestGenerated.java | 12 + .../checkers/DiagnosticsTestGenerated.java | 12 + 15 files changed, 660 insertions(+), 2 deletions(-) create mode 100644 compiler/testData/cfg/deadCode/notLocalReturn.instructions create mode 100644 compiler/testData/cfg/deadCode/notLocalReturn.kt create mode 100644 compiler/testData/cfg/deadCode/notLocalReturn.values create mode 100644 compiler/testData/cfg/deadCode/throwInLambda.instructions create mode 100644 compiler/testData/cfg/deadCode/throwInLambda.kt create mode 100644 compiler/testData/cfg/deadCode/throwInLambda.values create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.kt create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.txt create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt create mode 100644 compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index 4cd259ba033..c6a601627e4 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -947,10 +947,19 @@ class ControlFlowProcessor(private val trace: BindingTrace) { mark(expression) val selectorExpression = expression.selectorExpression val receiverExpression = expression.receiverExpression + val safe = expression is KtSafeQualifiedExpression // todo: replace with selectorExpresion != null after parser is fixed if (selectorExpression is KtCallExpression || selectorExpression is KtSimpleNameExpression) { - generateInstructions(selectorExpression) + if (!safe) { + generateInstructions(selectorExpression) + } + else { + val resultLabel = builder.createUnboundLabel("result of call") + builder.jumpOnFalse(resultLabel, expression, null) + generateInstructions(selectorExpression) + builder.bindLabel(resultLabel) + } copyValue(selectorExpression, expression) } else { diff --git a/compiler/testData/cfg/deadCode/notLocalReturn.instructions b/compiler/testData/cfg/deadCode/notLocalReturn.instructions new file mode 100644 index 00000000000..90ac0be492b --- /dev/null +++ b/compiler/testData/cfg/deadCode/notLocalReturn.instructions @@ -0,0 +1,206 @@ +== doCall == +inline fun doCall(f: () -> Unit) = f() +--------------------- +L0: + 1 + v(f: () -> Unit) + magic[FAKE_INITIALIZER](f: () -> Unit) -> + w(f|) + r(f) -> + mark(f()) + call(f(), invoke|) -> + ret(*|) L1 +L1: + NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== test1 == +fun test1(nonLocal: String): String { + val localResult = doCall { + return nonLocal //unreachable + } + return "NON_LOCAL_FAILED $localResult" //unreachable +} +--------------------- +L0: + 1 + v(nonLocal: String) + magic[FAKE_INITIALIZER](nonLocal: String) -> + w(nonLocal|) + 2 mark({ val localResult = doCall { return nonLocal //unreachable } return "NON_LOCAL_FAILED $localResult" //unreachable }) + v(val localResult = doCall { return nonLocal //unreachable }) + mark({ return nonLocal //unreachable }) + jmp?(L2) NEXT:[r({ return nonLocal //unreachable }) -> , d({ return nonLocal //unreachable })] + d({ return nonLocal //unreachable }) NEXT:[] +L2 [after local declaration]: + r({ return nonLocal //unreachable }) -> PREV:[jmp?(L2)] + mark(doCall { return nonLocal //unreachable }) + call(doCall { return nonLocal //unreachable }, doCall|) -> + w(localResult|) + mark("NON_LOCAL_FAILED $localResult") + r(localResult) -> + magic[STRING_TEMPLATE]("NON_LOCAL_FAILED $localResult"|) -> + ret(*|) L1 +L1: + 1 NEXT:[] PREV:[ret(*|) L1, ret(*|) L1] +error: + PREV:[] +sink: + PREV:[, , d({ return nonLocal //unreachable })] +===================== +== anonymous_0 == +{ + return nonLocal //unreachable + } +--------------------- +L3: + 3 + 4 mark(return nonLocal) + r(nonLocal) -> + ret(*|) L1 NEXT:[] +L4: + 3 NEXT:[] PREV:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== doSomething == +fun doSomething() {} +--------------------- +L0: + 1 + 2 mark({}) + read (Unit) +L1: + 1 NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== test2 == +fun test2() { + fun f(x: Any?) = x + f(null?.let { return }) + + // false unreachable here + doSomething() +} +--------------------- +L0: + 1 + 2 mark({ fun f(x: Any?) = x f(null?.let { return }) // false unreachable here doSomething() }) + jmp?(L2) NEXT:[mark(null?.let { return }), d(fun f(x: Any?) = x)] + d(fun f(x: Any?) = x) NEXT:[] +L2 [after local declaration]: + mark(null?.let { return }) PREV:[jmp?(L2)] + jf(L5) NEXT:[mark(f(null?.let { return })), mark({ return })] + mark({ return }) + jmp?(L6) NEXT:[r({ return }) -> , d({ return })] + d({ return }) NEXT:[] +L6 [after local declaration]: + r({ return }) -> PREV:[jmp?(L6)] + r(null) -> + mark(let { return }) + magic[UNRESOLVED_CALL](let { return }|, !, ) -> +L5 [result of call]: + mark(f(null?.let { return })) PREV:[jf(L5), magic[UNRESOLVED_CALL](let { return }|, !, ) -> ] + call(f(null?.let { return }), f|) -> + mark(doSomething()) + call(doSomething(), doSomething) -> +L1: + 1 NEXT:[] PREV:[ret L1, call(doSomething(), doSomething) -> ] +error: + PREV:[] +sink: + PREV:[, , d(fun f(x: Any?) = x), d({ return })] +===================== +== f == +fun f(x: Any?) = x +--------------------- +L3: + 3 + v(x: Any?) + magic[FAKE_INITIALIZER](x: Any?) -> + w(x|) + r(x) -> + ret(*|) L4 +L4: + NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== anonymous_1 == +{ return } +--------------------- +L7: + 3 + 4 mark(return) + ret L1 NEXT:[] +- 3 ret(*|!) L8 PREV:[] +L8: + NEXT:[] PREV:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== test3 == +fun test3(x: Any?): Boolean = + x?.let { + return true + } ?: false +--------------------- +L0: + 1 + v(x: Any?) + magic[FAKE_INITIALIZER](x: Any?) -> + w(x|) + mark(x?.let { return true }) + jf(L2) NEXT:[mark(x?.let { return true } ?: false), mark({ return true })] + mark({ return true }) + jmp?(L3) NEXT:[r({ return true }) -> , d({ return true })] + d({ return true }) NEXT:[] +L3 [after local declaration]: + r({ return true }) -> PREV:[jmp?(L3)] + r(x) -> + mark(let { return true }) + magic[UNRESOLVED_CALL](let { return true }|, !, ) -> +L2 [result of call]: + mark(x?.let { return true } ?: false) PREV:[jf(L2), magic[UNRESOLVED_CALL](let { return true }|, !, ) -> ] + jt(L6|) NEXT:[r(false) -> , merge(x?.let { return true } ?: false|, ) -> ] + r(false) -> +L6 [after elvis operator]: + merge(x?.let { return true } ?: false|, ) -> PREV:[jt(L6|), r(false) -> ] + ret(*|) L1 +L1: + NEXT:[] PREV:[ret(*|) L1, ret(*|) L1] +error: + PREV:[] +sink: + PREV:[, , d({ return true })] +===================== +== anonymous_2 == +{ + return true + } +--------------------- +L4: + 2 + 3 mark(return true) + r(true) -> + ret(*|) L1 NEXT:[] +- 2 ret(*|!) L5 PREV:[] +L5: + NEXT:[] PREV:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/deadCode/notLocalReturn.kt b/compiler/testData/cfg/deadCode/notLocalReturn.kt new file mode 100644 index 00000000000..804e44b34ae --- /dev/null +++ b/compiler/testData/cfg/deadCode/notLocalReturn.kt @@ -0,0 +1,25 @@ +// See also KT-5198 / KT-10186 + +inline fun doCall(f: () -> Unit) = f() + +fun test1(nonLocal: String): String { + val localResult = doCall { + return nonLocal //unreachable + } + return "NON_LOCAL_FAILED $localResult" //unreachable +} + +fun doSomething() {} + +fun test2() { + fun f(x: Any?) = x + f(null?.let { return }) + + // false unreachable here + doSomething() +} + +fun test3(x: Any?): Boolean = + x?.let { + return true + } ?: false diff --git a/compiler/testData/cfg/deadCode/notLocalReturn.values b/compiler/testData/cfg/deadCode/notLocalReturn.values new file mode 100644 index 00000000000..51139292ee7 --- /dev/null +++ b/compiler/testData/cfg/deadCode/notLocalReturn.values @@ -0,0 +1,90 @@ +== doCall == +inline fun doCall(f: () -> Unit) = f() +--------------------- + : {<: () -> Unit} NEW: magic[FAKE_INITIALIZER](f: () -> Unit) -> +f : {<: () -> Unit} NEW: r(f) -> +f() : Unit NEW: call(f(), invoke|) -> +===================== +== test1 == +fun test1(nonLocal: String): String { + val localResult = doCall { + return nonLocal //unreachable + } + return "NON_LOCAL_FAILED $localResult" //unreachable +} +--------------------- + : String NEW: magic[FAKE_INITIALIZER](nonLocal: String) -> +{ return nonLocal //unreachable } : {<: () -> Unit} NEW: r({ return nonLocal //unreachable }) -> +doCall { return nonLocal //unreachable } : Unit NEW: call(doCall { return nonLocal //unreachable }, doCall|) -> +localResult : * NEW: r(localResult) -> +"NON_LOCAL_FAILED $localResult" : String NEW: magic[STRING_TEMPLATE]("NON_LOCAL_FAILED $localResult"|) -> +return "NON_LOCAL_FAILED $localResult" !: * +{ val localResult = doCall { return nonLocal //unreachable } return "NON_LOCAL_FAILED $localResult" //unreachable } !: * COPY +===================== +== anonymous_0 == +{ + return nonLocal //unreachable + } +--------------------- +nonLocal : String NEW: r(nonLocal) -> +return nonLocal !: * +return nonLocal !: * COPY +===================== +== doSomething == +fun doSomething() {} +--------------------- +===================== +== test2 == +fun test2() { + fun f(x: Any?) = x + f(null?.let { return }) + + // false unreachable here + doSomething() +} +--------------------- +null : * NEW: r(null) -> +let !: * +{ return } : * NEW: r({ return }) -> +let { return } : * NEW: magic[UNRESOLVED_CALL](let { return }|, !, ) -> +null?.let { return } : * COPY +f(null?.let { return }) : * NEW: call(f(null?.let { return }), f|) -> +doSomething() : * NEW: call(doSomething(), doSomething) -> +{ fun f(x: Any?) = x f(null?.let { return }) // false unreachable here doSomething() } : * COPY +===================== +== f == +fun f(x: Any?) = x +--------------------- + : * NEW: magic[FAKE_INITIALIZER](x: Any?) -> +x : * NEW: r(x) -> +===================== +== anonymous_1 == +{ return } +--------------------- +return !: * +return !: * COPY +===================== +== test3 == +fun test3(x: Any?): Boolean = + x?.let { + return true + } ?: false +--------------------- + : * NEW: magic[FAKE_INITIALIZER](x: Any?) -> +x : * NEW: r(x) -> +let !: * +{ return true } : * NEW: r({ return true }) -> +let { return true } : Boolean NEW: magic[UNRESOLVED_CALL](let { return true }|, !, ) -> +x?.let { return true } : Boolean COPY +false : Boolean NEW: r(false) -> +x?.let { return true } ?: false : Boolean NEW: merge(x?.let { return true } ?: false|, ) -> +===================== +== anonymous_2 == +{ + return true + } +--------------------- +true : Boolean NEW: r(true) -> +return true !: * +return true !: * COPY +===================== diff --git a/compiler/testData/cfg/deadCode/throwInLambda.instructions b/compiler/testData/cfg/deadCode/throwInLambda.instructions new file mode 100644 index 00000000000..1b82575ddcf --- /dev/null +++ b/compiler/testData/cfg/deadCode/throwInLambda.instructions @@ -0,0 +1,160 @@ +== fn == +fun fn() : String? = null +--------------------- +L0: + 1 + r(null) -> + ret(*|) L1 +L1: + NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== let == +inline fun T.let(f: (T) -> R): R = f(this) +--------------------- +L0: + 1 + v(f: (T) -> R) + magic[FAKE_INITIALIZER](f: (T) -> R) -> + w(f|) + r(f) -> + r(this, ) -> + mark(f(this)) + call(f(this), invoke|, ) -> + ret(*|) L1 +L1: + NEXT:[] +error: + PREV:[] +sink: + PREV:[, ] +===================== +== foo == +fun foo(): String { + val x = fn()?.let { throw Exception() } ?: "unreachable?" + return x +} +--------------------- +L0: + 1 + 2 mark({ val x = fn()?.let { throw Exception() } ?: "unreachable?" return x }) + v(val x = fn()?.let { throw Exception() } ?: "unreachable?") + mark(fn()?.let { throw Exception() }) + jf(L2) NEXT:[mark(fn()?.let { throw Exception() } ?: "unreachable?"), mark(fn())] + mark(fn()) + call(fn(), fn) -> + mark({ throw Exception() }) + jmp?(L3) NEXT:[r({ throw Exception() }) -> , d({ throw Exception() })] + d({ throw Exception() }) NEXT:[] +L3 [after local declaration]: + r({ throw Exception() }) -> PREV:[jmp?(L3)] + mark(let { throw Exception() }) + call(let { throw Exception() }, let|, ) + jmp(error) NEXT:[] +L2 [result of call]: + mark(fn()?.let { throw Exception() } ?: "unreachable?") PREV:[jf(L2)] + jt(L6|!) NEXT:[mark("unreachable?"), merge(fn()?.let { throw Exception() } ?: "unreachable?"|!, ) -> ] + mark("unreachable?") + r("unreachable?") -> +L6 [after elvis operator]: + merge(fn()?.let { throw Exception() } ?: "unreachable?"|!, ) -> PREV:[jt(L6|!), r("unreachable?") -> ] + w(x|) + r(x) -> + ret(*|) L1 +L1: + 1 NEXT:[] +error: + PREV:[jmp(error)] +sink: + PREV:[, , d({ throw Exception() })] +===================== +== anonymous_0 == +{ throw Exception() } +--------------------- +L4: + 3 + 4 mark(throw Exception()) + mark(throw Exception()) + mark(Exception()) + call(Exception(), ) -> + throw (throw Exception()|) NEXT:[] +- 3 ret(*|!) L5 PREV:[] +L5: + NEXT:[] PREV:[] +error: + PREV:[throw (throw Exception()|)] +sink: + PREV:[, ] +===================== +== bar == +fun bar(): String { + val x = fn() ?: return "" + val y = x?.let { throw Exception() } ?: "unreachable" + return y +} +--------------------- +L0: + 1 + 2 mark({ val x = fn() ?: return "" val y = x?.let { throw Exception() } ?: "unreachable" return y }) + v(val x = fn() ?: return "") + mark(fn()) + call(fn(), fn) -> + mark(fn() ?: return "") + jt(L2|) NEXT:[mark(""), merge(fn() ?: return ""|, !) -> ] + mark("") + r("") -> + ret(*|) L1 NEXT:[] +L2 [after elvis operator]: + merge(fn() ?: return ""|, !) -> PREV:[jt(L2|)] + w(x|) + v(val y = x?.let { throw Exception() } ?: "unreachable") + mark(x?.let { throw Exception() }) + jf(L3) NEXT:[jmp(error), r(x) -> ] + r(x) -> + mark({ throw Exception() }) + jmp?(L4) NEXT:[r({ throw Exception() }) -> , d({ throw Exception() })] + d({ throw Exception() }) NEXT:[] +L4 [after local declaration]: + r({ throw Exception() }) -> PREV:[jmp?(L4)] + mark(let { throw Exception() }) + call(let { throw Exception() }, let|, ) + jmp(error) NEXT:[] +L3 [result of call]: + jmp(error) NEXT:[] PREV:[jf(L3)] +- mark(x?.let { throw Exception() } ?: "unreachable") PREV:[] +- jt(L7|!) NEXT:[mark("unreachable"), merge(x?.let { throw Exception() } ?: "unreachable"|!, ) -> ] PREV:[] +- mark("unreachable") PREV:[] +- r("unreachable") -> PREV:[] +L7 [after elvis operator]: +- merge(x?.let { throw Exception() } ?: "unreachable"|!, ) -> PREV:[] +- w(y|) PREV:[] +- r(y) -> PREV:[] +- ret(*|) L1 PREV:[] +L1: + 1 NEXT:[] PREV:[ret(*|) L1] +error: + PREV:[jmp(error), jmp(error)] +sink: + PREV:[, , d({ throw Exception() })] +===================== +== anonymous_1 == +{ throw Exception() } +--------------------- +L5: + 3 + 4 mark(throw Exception()) + mark(throw Exception()) + mark(Exception()) + call(Exception(), ) -> + throw (throw Exception()|) NEXT:[] +- 3 ret(*|!) L6 PREV:[] +L6: + NEXT:[] PREV:[] +error: + PREV:[throw (throw Exception()|)] +sink: + PREV:[, ] +===================== diff --git a/compiler/testData/cfg/deadCode/throwInLambda.kt b/compiler/testData/cfg/deadCode/throwInLambda.kt new file mode 100644 index 00000000000..73d803617ea --- /dev/null +++ b/compiler/testData/cfg/deadCode/throwInLambda.kt @@ -0,0 +1,16 @@ +// See KT-10913 Bogus unreachable code warning + +fun fn() : String? = null + +inline fun T.let(f: (T) -> R): R = f(this) + +fun foo(): String { + val x = fn()?.let { throw Exception() } ?: "unreachable?" + return x +} + +fun bar(): String { + val x = fn() ?: return "" + val y = x?.let { throw Exception() } ?: "unreachable" + return y +} \ No newline at end of file diff --git a/compiler/testData/cfg/deadCode/throwInLambda.values b/compiler/testData/cfg/deadCode/throwInLambda.values new file mode 100644 index 00000000000..555e2bc01f7 --- /dev/null +++ b/compiler/testData/cfg/deadCode/throwInLambda.values @@ -0,0 +1,65 @@ +== fn == +fun fn() : String? = null +--------------------- +null : {<: String?} NEW: r(null) -> +===================== +== let == +inline fun T.let(f: (T) -> R): R = f(this) +--------------------- + : {<: (T) -> R} NEW: magic[FAKE_INITIALIZER](f: (T) -> R) -> +f : {<: (T) -> R} NEW: r(f) -> +this : {<: T} COPY +this : {<: T} NEW: r(this, ) -> +f(this) : {<: R} NEW: call(f(this), invoke|, ) -> +===================== +== foo == +fun foo(): String { + val x = fn()?.let { throw Exception() } ?: "unreachable?" + return x +} +--------------------- +fn() : String NEW: call(fn(), fn) -> +{ throw Exception() } : {<: (String) -> Nothing} NEW: r({ throw Exception() }) -> +let { throw Exception() } !: * +fn()?.let { throw Exception() } !: * COPY +"unreachable?" : String NEW: r("unreachable?") -> +fn()?.let { throw Exception() } ?: "unreachable?" : String NEW: merge(fn()?.let { throw Exception() } ?: "unreachable?"|!, ) -> +x : String NEW: r(x) -> +return x !: * +{ val x = fn()?.let { throw Exception() } ?: "unreachable?" return x } !: * COPY +===================== +== anonymous_0 == +{ throw Exception() } +--------------------- +Exception() : {<: Throwable} NEW: call(Exception(), ) -> +throw Exception() !: * +throw Exception() !: * COPY +===================== +== bar == +fun bar(): String { + val x = fn() ?: return "" + val y = x?.let { throw Exception() } ?: "unreachable" + return y +} +--------------------- +fn() : AND{Boolean, String} NEW: call(fn(), fn) -> +"" : String NEW: r("") -> +return "" !: * +fn() ?: return "" : String NEW: merge(fn() ?: return ""|, !) -> +x : String NEW: r(x) -> +{ throw Exception() } : {<: (String) -> Nothing} NEW: r({ throw Exception() }) -> +let { throw Exception() } !: * +x?.let { throw Exception() } !: * COPY +"unreachable" : String NEW: r("unreachable") -> +x?.let { throw Exception() } ?: "unreachable" : String NEW: merge(x?.let { throw Exception() } ?: "unreachable"|!, ) -> +y : String NEW: r(y) -> +return y !: * +{ val x = fn() ?: return "" val y = x?.let { throw Exception() } ?: "unreachable" return y } !: * COPY +===================== +== anonymous_1 == +{ throw Exception() } +--------------------- +Exception() : {<: Throwable} NEW: call(Exception(), ) -> +throw Exception() !: * +throw Exception() !: * COPY +===================== diff --git a/compiler/testData/cfg/expressions/propertySafeCall.instructions b/compiler/testData/cfg/expressions/propertySafeCall.instructions index 112dff4302e..7474a8b1048 100644 --- a/compiler/testData/cfg/expressions/propertySafeCall.instructions +++ b/compiler/testData/cfg/expressions/propertySafeCall.instructions @@ -10,10 +10,12 @@ L0: w(s|) 2 mark({ s?.first }) mark(s?.first) + jf(L2) NEXT:[, r(s) -> ] r(s) -> r(first|) -> L1: - 1 NEXT:[] +L2 [result of call]: + 1 NEXT:[] PREV:[jf(L2), r(first|) -> ] error: PREV:[] sink: diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.kt new file mode 100644 index 00000000000..804e44b34ae --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.kt @@ -0,0 +1,25 @@ +// See also KT-5198 / KT-10186 + +inline fun doCall(f: () -> Unit) = f() + +fun test1(nonLocal: String): String { + val localResult = doCall { + return nonLocal //unreachable + } + return "NON_LOCAL_FAILED $localResult" //unreachable +} + +fun doSomething() {} + +fun test2() { + fun f(x: Any?) = x + f(null?.let { return }) + + // false unreachable here + doSomething() +} + +fun test3(x: Any?): Boolean = + x?.let { + return true + } ?: false diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.txt new file mode 100644 index 00000000000..ae298e9aa3d --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.txt @@ -0,0 +1,7 @@ +package + +public inline fun doCall(/*0*/ f: () -> kotlin.Unit): kotlin.Unit +public fun doSomething(): kotlin.Unit +public fun test1(/*0*/ nonLocal: kotlin.String): kotlin.String +public fun test2(): kotlin.Unit +public fun test3(/*0*/ x: kotlin.Any?): kotlin.Boolean diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt new file mode 100644 index 00000000000..95b25e9059f --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt @@ -0,0 +1,12 @@ +// See KT-10913 Bogus unreachable code warning + +fun fn() : String? = null +fun foo(): String { + val x = fn()?.let { throw Exception() } ?: "unreachable?" + return x +} +fun bar(): String { + val x = fn() ?: return "" + val y = x?.let { throw Exception() } ?: "unreachable" + return y +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.txt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.txt new file mode 100644 index 00000000000..99e0a28c689 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.txt @@ -0,0 +1,5 @@ +package + +public fun bar(): kotlin.String +public fun fn(): kotlin.String? +public fun foo(): kotlin.String diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java index d8fddda8dbb..c40a124dcc2 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/ControlFlowTestGenerated.java @@ -322,6 +322,12 @@ public class ControlFlowTestGenerated extends AbstractControlFlowTest { doTest(fileName); } + @TestMetadata("notLocalReturn.kt") + public void testNotLocalReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/notLocalReturn.kt"); + doTest(fileName); + } + @TestMetadata("returnInElvis.kt") public void testReturnInElvis() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/returnInElvis.kt"); @@ -333,6 +339,12 @@ public class ControlFlowTestGenerated extends AbstractControlFlowTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/stringTemplate.kt"); doTest(fileName); } + + @TestMetadata("throwInLambda.kt") + public void testThrowInLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/throwInLambda.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/cfg/declarations") diff --git a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java index a884f1735c7..e7fe616188a 100644 --- a/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cfg/PseudoValueTestGenerated.java @@ -324,6 +324,12 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest { doTest(fileName); } + @TestMetadata("notLocalReturn.kt") + public void testNotLocalReturn() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/notLocalReturn.kt"); + doTest(fileName); + } + @TestMetadata("returnInElvis.kt") public void testReturnInElvis() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/returnInElvis.kt"); @@ -335,6 +341,12 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/stringTemplate.kt"); doTest(fileName); } + + @TestMetadata("throwInLambda.kt") + public void testThrowInLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg/deadCode/throwInLambda.kt"); + doTest(fileName); + } } @TestMetadata("compiler/testData/cfg/declarations") diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 04cee18c03b..e9350e00b24 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3069,6 +3069,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("nonLocalReturnUnreachable.kt") + public void testNonLocalReturnUnreachable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.kt"); + doTest(fileName); + } + @TestMetadata("propertiesInitWithOtherInstance.kt") public void testPropertiesInitWithOtherInstance() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/propertiesInitWithOtherInstance.kt"); @@ -3111,6 +3117,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("throwInLambda.kt") + public void testThrowInLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/throwInLambda.kt"); + doTest(fileName); + } + @TestMetadata("uninitializedInLocalDeclarations.kt") public void testUninitializedInLocalDeclarations() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/uninitializedInLocalDeclarations.kt");