Control flow graph for safe calls corrected #KT-10913 Fixed
Also #KT-10186 Fixed Also #KT-5198 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
5ae394fec0
commit
a08b8f43b2
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
== doCall ==
|
||||
inline fun doCall(f: () -> Unit) = f()
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
v(f: () -> Unit)
|
||||
magic[FAKE_INITIALIZER](f: () -> Unit) -> <v0>
|
||||
w(f|<v0>)
|
||||
r(f) -> <v1>
|
||||
mark(f())
|
||||
call(f(), invoke|<v1>) -> <v2>
|
||||
ret(*|<v2>) L1
|
||||
L1:
|
||||
<END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== test1 ==
|
||||
fun test1(nonLocal: String): String {
|
||||
val localResult = doCall {
|
||||
return nonLocal //unreachable
|
||||
}
|
||||
return "NON_LOCAL_FAILED $localResult" //unreachable
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
v(nonLocal: String)
|
||||
magic[FAKE_INITIALIZER](nonLocal: String) -> <v0>
|
||||
w(nonLocal|<v0>)
|
||||
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 }) -> <v1>, d({ return nonLocal //unreachable })]
|
||||
d({ return nonLocal //unreachable }) NEXT:[<SINK>]
|
||||
L2 [after local declaration]:
|
||||
r({ return nonLocal //unreachable }) -> <v1> PREV:[jmp?(L2)]
|
||||
mark(doCall { return nonLocal //unreachable })
|
||||
call(doCall { return nonLocal //unreachable }, doCall|<v1>) -> <v2>
|
||||
w(localResult|<v2>)
|
||||
mark("NON_LOCAL_FAILED $localResult")
|
||||
r(localResult) -> <v3>
|
||||
magic[STRING_TEMPLATE]("NON_LOCAL_FAILED $localResult"|<v3>) -> <v4>
|
||||
ret(*|<v4>) L1
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>] PREV:[ret(*|<v0>) L1, ret(*|<v4>) L1]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>, d({ return nonLocal //unreachable })]
|
||||
=====================
|
||||
== anonymous_0 ==
|
||||
{
|
||||
return nonLocal //unreachable
|
||||
}
|
||||
---------------------
|
||||
L3:
|
||||
3 <START>
|
||||
4 mark(return nonLocal)
|
||||
r(nonLocal) -> <v0>
|
||||
ret(*|<v0>) L1 NEXT:[<END>]
|
||||
L4:
|
||||
3 <END> NEXT:[<SINK>] PREV:[]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== doSomething ==
|
||||
fun doSomething() {}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
2 mark({})
|
||||
read (Unit)
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== test2 ==
|
||||
fun test2() {
|
||||
fun f(x: Any?) = x
|
||||
f(null?.let { return })
|
||||
|
||||
// false unreachable here
|
||||
doSomething()
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
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:[<SINK>]
|
||||
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 }) -> <v0>, d({ return })]
|
||||
d({ return }) NEXT:[<SINK>]
|
||||
L6 [after local declaration]:
|
||||
r({ return }) -> <v0> PREV:[jmp?(L6)]
|
||||
r(null) -> <v1>
|
||||
mark(let { return })
|
||||
magic[UNRESOLVED_CALL](let { return }|<v0>, !<v2>, <v1>) -> <v3>
|
||||
L5 [result of call]:
|
||||
mark(f(null?.let { return })) PREV:[jf(L5), magic[UNRESOLVED_CALL](let { return }|<v0>, !<v2>, <v1>) -> <v3>]
|
||||
call(f(null?.let { return }), f|<v3>) -> <v4>
|
||||
mark(doSomething())
|
||||
call(doSomething(), doSomething) -> <v5>
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>] PREV:[ret L1, call(doSomething(), doSomething) -> <v5>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>, d(fun f(x: Any?) = x), d({ return })]
|
||||
=====================
|
||||
== f ==
|
||||
fun f(x: Any?) = x
|
||||
---------------------
|
||||
L3:
|
||||
3 <START>
|
||||
v(x: Any?)
|
||||
magic[FAKE_INITIALIZER](x: Any?) -> <v0>
|
||||
w(x|<v0>)
|
||||
r(x) -> <v1>
|
||||
ret(*|<v1>) L4
|
||||
L4:
|
||||
<END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== anonymous_1 ==
|
||||
{ return }
|
||||
---------------------
|
||||
L7:
|
||||
3 <START>
|
||||
4 mark(return)
|
||||
ret L1 NEXT:[<END>]
|
||||
- 3 ret(*|!<v0>) L8 PREV:[]
|
||||
L8:
|
||||
<END> NEXT:[<SINK>] PREV:[]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== test3 ==
|
||||
fun test3(x: Any?): Boolean =
|
||||
x?.let {
|
||||
return true
|
||||
} ?: false
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
v(x: Any?)
|
||||
magic[FAKE_INITIALIZER](x: Any?) -> <v0>
|
||||
w(x|<v0>)
|
||||
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 }) -> <v1>, d({ return true })]
|
||||
d({ return true }) NEXT:[<SINK>]
|
||||
L3 [after local declaration]:
|
||||
r({ return true }) -> <v1> PREV:[jmp?(L3)]
|
||||
r(x) -> <v2>
|
||||
mark(let { return true })
|
||||
magic[UNRESOLVED_CALL](let { return true }|<v1>, !<v3>, <v2>) -> <v4>
|
||||
L2 [result of call]:
|
||||
mark(x?.let { return true } ?: false) PREV:[jf(L2), magic[UNRESOLVED_CALL](let { return true }|<v1>, !<v3>, <v2>) -> <v4>]
|
||||
jt(L6|<v4>) NEXT:[r(false) -> <v5>, merge(x?.let { return true } ?: false|<v4>, <v5>) -> <v6>]
|
||||
r(false) -> <v5>
|
||||
L6 [after elvis operator]:
|
||||
merge(x?.let { return true } ?: false|<v4>, <v5>) -> <v6> PREV:[jt(L6|<v4>), r(false) -> <v5>]
|
||||
ret(*|<v6>) L1
|
||||
L1:
|
||||
<END> NEXT:[<SINK>] PREV:[ret(*|<v0>) L1, ret(*|<v6>) L1]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>, d({ return true })]
|
||||
=====================
|
||||
== anonymous_2 ==
|
||||
{
|
||||
return true
|
||||
}
|
||||
---------------------
|
||||
L4:
|
||||
2 <START>
|
||||
3 mark(return true)
|
||||
r(true) -> <v0>
|
||||
ret(*|<v0>) L1 NEXT:[<END>]
|
||||
- 2 ret(*|!<v1>) L5 PREV:[]
|
||||
L5:
|
||||
<END> NEXT:[<SINK>] PREV:[]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
@@ -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
|
||||
@@ -0,0 +1,90 @@
|
||||
== doCall ==
|
||||
inline fun doCall(f: () -> Unit) = f()
|
||||
---------------------
|
||||
<v0>: {<: () -> Unit} NEW: magic[FAKE_INITIALIZER](f: () -> Unit) -> <v0>
|
||||
f <v1>: {<: () -> Unit} NEW: r(f) -> <v1>
|
||||
f() <v2>: Unit NEW: call(f(), invoke|<v1>) -> <v2>
|
||||
=====================
|
||||
== test1 ==
|
||||
fun test1(nonLocal: String): String {
|
||||
val localResult = doCall {
|
||||
return nonLocal //unreachable
|
||||
}
|
||||
return "NON_LOCAL_FAILED $localResult" //unreachable
|
||||
}
|
||||
---------------------
|
||||
<v0>: String NEW: magic[FAKE_INITIALIZER](nonLocal: String) -> <v0>
|
||||
{ return nonLocal //unreachable } <v1>: {<: () -> Unit} NEW: r({ return nonLocal //unreachable }) -> <v1>
|
||||
doCall { return nonLocal //unreachable } <v2>: Unit NEW: call(doCall { return nonLocal //unreachable }, doCall|<v1>) -> <v2>
|
||||
localResult <v3>: * NEW: r(localResult) -> <v3>
|
||||
"NON_LOCAL_FAILED $localResult" <v4>: String NEW: magic[STRING_TEMPLATE]("NON_LOCAL_FAILED $localResult"|<v3>) -> <v4>
|
||||
return "NON_LOCAL_FAILED $localResult" !<v5>: *
|
||||
{ val localResult = doCall { return nonLocal //unreachable } return "NON_LOCAL_FAILED $localResult" //unreachable } !<v5>: * COPY
|
||||
=====================
|
||||
== anonymous_0 ==
|
||||
{
|
||||
return nonLocal //unreachable
|
||||
}
|
||||
---------------------
|
||||
nonLocal <v0>: String NEW: r(nonLocal) -> <v0>
|
||||
return nonLocal !<v1>: *
|
||||
return nonLocal !<v1>: * COPY
|
||||
=====================
|
||||
== doSomething ==
|
||||
fun doSomething() {}
|
||||
---------------------
|
||||
=====================
|
||||
== test2 ==
|
||||
fun test2() {
|
||||
fun f(x: Any?) = x
|
||||
f(null?.let { return })
|
||||
|
||||
// false unreachable here
|
||||
doSomething()
|
||||
}
|
||||
---------------------
|
||||
null <v1>: * NEW: r(null) -> <v1>
|
||||
let !<v2>: *
|
||||
{ return } <v0>: * NEW: r({ return }) -> <v0>
|
||||
let { return } <v3>: * NEW: magic[UNRESOLVED_CALL](let { return }|<v0>, !<v2>, <v1>) -> <v3>
|
||||
null?.let { return } <v3>: * COPY
|
||||
f(null?.let { return }) <v4>: * NEW: call(f(null?.let { return }), f|<v3>) -> <v4>
|
||||
doSomething() <v5>: * NEW: call(doSomething(), doSomething) -> <v5>
|
||||
{ fun f(x: Any?) = x f(null?.let { return }) // false unreachable here doSomething() } <v5>: * COPY
|
||||
=====================
|
||||
== f ==
|
||||
fun f(x: Any?) = x
|
||||
---------------------
|
||||
<v0>: * NEW: magic[FAKE_INITIALIZER](x: Any?) -> <v0>
|
||||
x <v1>: * NEW: r(x) -> <v1>
|
||||
=====================
|
||||
== anonymous_1 ==
|
||||
{ return }
|
||||
---------------------
|
||||
return !<v0>: *
|
||||
return !<v0>: * COPY
|
||||
=====================
|
||||
== test3 ==
|
||||
fun test3(x: Any?): Boolean =
|
||||
x?.let {
|
||||
return true
|
||||
} ?: false
|
||||
---------------------
|
||||
<v0>: * NEW: magic[FAKE_INITIALIZER](x: Any?) -> <v0>
|
||||
x <v2>: * NEW: r(x) -> <v2>
|
||||
let !<v3>: *
|
||||
{ return true } <v1>: * NEW: r({ return true }) -> <v1>
|
||||
let { return true } <v4>: Boolean NEW: magic[UNRESOLVED_CALL](let { return true }|<v1>, !<v3>, <v2>) -> <v4>
|
||||
x?.let { return true } <v4>: Boolean COPY
|
||||
false <v5>: Boolean NEW: r(false) -> <v5>
|
||||
x?.let { return true } ?: false <v6>: Boolean NEW: merge(x?.let { return true } ?: false|<v4>, <v5>) -> <v6>
|
||||
=====================
|
||||
== anonymous_2 ==
|
||||
{
|
||||
return true
|
||||
}
|
||||
---------------------
|
||||
true <v0>: Boolean NEW: r(true) -> <v0>
|
||||
return true !<v1>: *
|
||||
return true !<v1>: * COPY
|
||||
=====================
|
||||
@@ -0,0 +1,160 @@
|
||||
== fn ==
|
||||
fun fn() : String? = null
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
r(null) -> <v0>
|
||||
ret(*|<v0>) L1
|
||||
L1:
|
||||
<END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== let ==
|
||||
inline fun <T, R> T.let(f: (T) -> R): R = f(this)
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
v(f: (T) -> R)
|
||||
magic[FAKE_INITIALIZER](f: (T) -> R) -> <v0>
|
||||
w(f|<v0>)
|
||||
r(f) -> <v1>
|
||||
r(this, <this>) -> <v2>
|
||||
mark(f(this))
|
||||
call(f(this), invoke|<v1>, <v2>) -> <v3>
|
||||
ret(*|<v3>) L1
|
||||
L1:
|
||||
<END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== foo ==
|
||||
fun foo(): String {
|
||||
val x = fn()?.let { throw Exception() } ?: "unreachable?"
|
||||
return x
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
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) -> <v0>
|
||||
mark({ throw Exception() })
|
||||
jmp?(L3) NEXT:[r({ throw Exception() }) -> <v1>, d({ throw Exception() })]
|
||||
d({ throw Exception() }) NEXT:[<SINK>]
|
||||
L3 [after local declaration]:
|
||||
r({ throw Exception() }) -> <v1> PREV:[jmp?(L3)]
|
||||
mark(let { throw Exception() })
|
||||
call(let { throw Exception() }, let|<v0>, <v1>)
|
||||
jmp(error) NEXT:[<ERROR>]
|
||||
L2 [result of call]:
|
||||
mark(fn()?.let { throw Exception() } ?: "unreachable?") PREV:[jf(L2)]
|
||||
jt(L6|!<v2>) NEXT:[mark("unreachable?"), merge(fn()?.let { throw Exception() } ?: "unreachable?"|!<v2>, <v3>) -> <v4>]
|
||||
mark("unreachable?")
|
||||
r("unreachable?") -> <v3>
|
||||
L6 [after elvis operator]:
|
||||
merge(fn()?.let { throw Exception() } ?: "unreachable?"|!<v2>, <v3>) -> <v4> PREV:[jt(L6|!<v2>), r("unreachable?") -> <v3>]
|
||||
w(x|<v4>)
|
||||
r(x) -> <v5>
|
||||
ret(*|<v5>) L1
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>]
|
||||
error:
|
||||
<ERROR> PREV:[jmp(error)]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>, d({ throw Exception() })]
|
||||
=====================
|
||||
== anonymous_0 ==
|
||||
{ throw Exception() }
|
||||
---------------------
|
||||
L4:
|
||||
3 <START>
|
||||
4 mark(throw Exception())
|
||||
mark(throw Exception())
|
||||
mark(Exception())
|
||||
call(Exception(), <init>) -> <v0>
|
||||
throw (throw Exception()|<v0>) NEXT:[<ERROR>]
|
||||
- 3 ret(*|!<v1>) L5 PREV:[]
|
||||
L5:
|
||||
<END> NEXT:[<SINK>] PREV:[]
|
||||
error:
|
||||
<ERROR> PREV:[throw (throw Exception()|<v0>)]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
== bar ==
|
||||
fun bar(): String {
|
||||
val x = fn() ?: return ""
|
||||
val y = x?.let { throw Exception() } ?: "unreachable"
|
||||
return y
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START>
|
||||
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) -> <v0>
|
||||
mark(fn() ?: return "")
|
||||
jt(L2|<v0>) NEXT:[mark(""), merge(fn() ?: return ""|<v0>, !<v2>) -> <v3>]
|
||||
mark("")
|
||||
r("") -> <v1>
|
||||
ret(*|<v1>) L1 NEXT:[<END>]
|
||||
L2 [after elvis operator]:
|
||||
merge(fn() ?: return ""|<v0>, !<v2>) -> <v3> PREV:[jt(L2|<v0>)]
|
||||
w(x|<v3>)
|
||||
v(val y = x?.let { throw Exception() } ?: "unreachable")
|
||||
mark(x?.let { throw Exception() })
|
||||
jf(L3) NEXT:[jmp(error), r(x) -> <v4>]
|
||||
r(x) -> <v4>
|
||||
mark({ throw Exception() })
|
||||
jmp?(L4) NEXT:[r({ throw Exception() }) -> <v5>, d({ throw Exception() })]
|
||||
d({ throw Exception() }) NEXT:[<SINK>]
|
||||
L4 [after local declaration]:
|
||||
r({ throw Exception() }) -> <v5> PREV:[jmp?(L4)]
|
||||
mark(let { throw Exception() })
|
||||
call(let { throw Exception() }, let|<v4>, <v5>)
|
||||
jmp(error) NEXT:[<ERROR>]
|
||||
L3 [result of call]:
|
||||
jmp(error) NEXT:[<ERROR>] PREV:[jf(L3)]
|
||||
- mark(x?.let { throw Exception() } ?: "unreachable") PREV:[]
|
||||
- jt(L7|!<v6>) NEXT:[mark("unreachable"), merge(x?.let { throw Exception() } ?: "unreachable"|!<v6>, <v7>) -> <v8>] PREV:[]
|
||||
- mark("unreachable") PREV:[]
|
||||
- r("unreachable") -> <v7> PREV:[]
|
||||
L7 [after elvis operator]:
|
||||
- merge(x?.let { throw Exception() } ?: "unreachable"|!<v6>, <v7>) -> <v8> PREV:[]
|
||||
- w(y|<v8>) PREV:[]
|
||||
- r(y) -> <v9> PREV:[]
|
||||
- ret(*|<v9>) L1 PREV:[]
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>] PREV:[ret(*|<v1>) L1]
|
||||
error:
|
||||
<ERROR> PREV:[jmp(error), jmp(error)]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>, d({ throw Exception() })]
|
||||
=====================
|
||||
== anonymous_1 ==
|
||||
{ throw Exception() }
|
||||
---------------------
|
||||
L5:
|
||||
3 <START>
|
||||
4 mark(throw Exception())
|
||||
mark(throw Exception())
|
||||
mark(Exception())
|
||||
call(Exception(), <init>) -> <v0>
|
||||
throw (throw Exception()|<v0>) NEXT:[<ERROR>]
|
||||
- 3 ret(*|!<v1>) L6 PREV:[]
|
||||
L6:
|
||||
<END> NEXT:[<SINK>] PREV:[]
|
||||
error:
|
||||
<ERROR> PREV:[throw (throw Exception()|<v0>)]
|
||||
sink:
|
||||
<SINK> PREV:[<ERROR>, <END>]
|
||||
=====================
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// See KT-10913 Bogus unreachable code warning
|
||||
|
||||
fun fn() : String? = null
|
||||
|
||||
inline fun <T, R> 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
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
== fn ==
|
||||
fun fn() : String? = null
|
||||
---------------------
|
||||
null <v0>: {<: String?} NEW: r(null) -> <v0>
|
||||
=====================
|
||||
== let ==
|
||||
inline fun <T, R> T.let(f: (T) -> R): R = f(this)
|
||||
---------------------
|
||||
<v0>: {<: (T) -> R} NEW: magic[FAKE_INITIALIZER](f: (T) -> R) -> <v0>
|
||||
f <v1>: {<: (T) -> R} NEW: r(f) -> <v1>
|
||||
this <v2>: {<: T} COPY
|
||||
this <v2>: {<: T} NEW: r(this, <this>) -> <v2>
|
||||
f(this) <v3>: {<: R} NEW: call(f(this), invoke|<v1>, <v2>) -> <v3>
|
||||
=====================
|
||||
== foo ==
|
||||
fun foo(): String {
|
||||
val x = fn()?.let { throw Exception() } ?: "unreachable?"
|
||||
return x
|
||||
}
|
||||
---------------------
|
||||
fn() <v0>: String NEW: call(fn(), fn) -> <v0>
|
||||
{ throw Exception() } <v1>: {<: (String) -> Nothing} NEW: r({ throw Exception() }) -> <v1>
|
||||
let { throw Exception() } !<v2>: *
|
||||
fn()?.let { throw Exception() } !<v2>: * COPY
|
||||
"unreachable?" <v3>: String NEW: r("unreachable?") -> <v3>
|
||||
fn()?.let { throw Exception() } ?: "unreachable?" <v4>: String NEW: merge(fn()?.let { throw Exception() } ?: "unreachable?"|!<v2>, <v3>) -> <v4>
|
||||
x <v5>: String NEW: r(x) -> <v5>
|
||||
return x !<v6>: *
|
||||
{ val x = fn()?.let { throw Exception() } ?: "unreachable?" return x } !<v6>: * COPY
|
||||
=====================
|
||||
== anonymous_0 ==
|
||||
{ throw Exception() }
|
||||
---------------------
|
||||
Exception() <v0>: {<: Throwable} NEW: call(Exception(), <init>) -> <v0>
|
||||
throw Exception() !<v1>: *
|
||||
throw Exception() !<v1>: * COPY
|
||||
=====================
|
||||
== bar ==
|
||||
fun bar(): String {
|
||||
val x = fn() ?: return ""
|
||||
val y = x?.let { throw Exception() } ?: "unreachable"
|
||||
return y
|
||||
}
|
||||
---------------------
|
||||
fn() <v0>: AND{Boolean, String} NEW: call(fn(), fn) -> <v0>
|
||||
"" <v1>: String NEW: r("") -> <v1>
|
||||
return "" !<v2>: *
|
||||
fn() ?: return "" <v3>: String NEW: merge(fn() ?: return ""|<v0>, !<v2>) -> <v3>
|
||||
x <v4>: String NEW: r(x) -> <v4>
|
||||
{ throw Exception() } <v5>: {<: (String) -> Nothing} NEW: r({ throw Exception() }) -> <v5>
|
||||
let { throw Exception() } !<v6>: *
|
||||
x?.let { throw Exception() } !<v6>: * COPY
|
||||
"unreachable" <v7>: String NEW: r("unreachable") -> <v7>
|
||||
x?.let { throw Exception() } ?: "unreachable" <v8>: String NEW: merge(x?.let { throw Exception() } ?: "unreachable"|!<v6>, <v7>) -> <v8>
|
||||
y <v9>: String NEW: r(y) -> <v9>
|
||||
return y !<v10>: *
|
||||
{ val x = fn() ?: return "" val y = x?.let { throw Exception() } ?: "unreachable" return y } !<v10>: * COPY
|
||||
=====================
|
||||
== anonymous_1 ==
|
||||
{ throw Exception() }
|
||||
---------------------
|
||||
Exception() <v0>: {<: Throwable} NEW: call(Exception(), <init>) -> <v0>
|
||||
throw Exception() !<v1>: *
|
||||
throw Exception() !<v1>: * COPY
|
||||
=====================
|
||||
@@ -10,10 +10,12 @@ L0:
|
||||
w(s|<v0>)
|
||||
2 mark({ s?.first })
|
||||
mark(s?.first)
|
||||
jf(L2) NEXT:[<END>, r(s) -> <v1>]
|
||||
r(s) -> <v1>
|
||||
r(first|<v1>) -> <v2>
|
||||
L1:
|
||||
1 <END> NEXT:[<SINK>]
|
||||
L2 [result of call]:
|
||||
1 <END> NEXT:[<SINK>] PREV:[jf(L2), r(first|<v1>) -> <v2>]
|
||||
error:
|
||||
<ERROR> PREV:[]
|
||||
sink:
|
||||
|
||||
+25
@@ -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
|
||||
+7
@@ -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
|
||||
@@ -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 ""
|
||||
<!UNREACHABLE_CODE!>val <!UNUSED_VARIABLE!>y<!> =<!> x<!UNNECESSARY_SAFE_CALL!>?.<!>let { throw Exception() } <!USELESS_ELVIS, UNREACHABLE_CODE!>?: "unreachable"<!>
|
||||
<!UNREACHABLE_CODE!>return y<!>
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package
|
||||
|
||||
public fun bar(): kotlin.String
|
||||
public fun fn(): kotlin.String?
|
||||
public fun foo(): kotlin.String
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user