Control flow analysis: nested finally blocks now appear in correct order in CFG #KT-4764 Fixed
This commit is contained in:
+1
-1
@@ -204,7 +204,7 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() {
|
||||
val blockInfo = allBlocks[i]
|
||||
if (blockInfo is BreakableBlockInfo) {
|
||||
if (blockInfo.referablePoints.contains(jumpTarget) || jumpTarget === error) {
|
||||
for (j in finallyBlocks.indices.reversed()) {
|
||||
for (j in finallyBlocks.indices) {
|
||||
finallyBlocks[j].generateFinallyBlock()
|
||||
}
|
||||
break
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
== sample ==
|
||||
fun sample(): String? {
|
||||
try {
|
||||
if (false) {
|
||||
return "fail"
|
||||
} else {
|
||||
if (false) {
|
||||
if (false) {
|
||||
var foo: String? = null
|
||||
try {
|
||||
foo = "test"
|
||||
} catch (e: Exception) {
|
||||
return "fail"
|
||||
} finally {
|
||||
println(foo) // Variable 'foo' must be initialized
|
||||
}
|
||||
}
|
||||
return "fail"
|
||||
}
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
---------------------
|
||||
L0:
|
||||
1 <START> INIT: in: {} out: {}
|
||||
2 mark({ try { if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } finally {} return null })
|
||||
mark(try { if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } finally {})
|
||||
jmp?(L2)
|
||||
3 mark({ if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } })
|
||||
mark(if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } })
|
||||
r(false) -> <v0>
|
||||
jf(L3|<v0>)
|
||||
4 mark({ return "fail" })
|
||||
mark("fail")
|
||||
r("fail") -> <v1>
|
||||
L4 [start finally]:
|
||||
5 mark({})
|
||||
read (Unit)
|
||||
L5 [finish finally]:
|
||||
4 ret(*|<v1>) L1 USE: in: {} out: {}
|
||||
- 3 jmp(L6)
|
||||
L3 [else branch]:
|
||||
4 mark({ if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } }) INIT: in: {} out: {}
|
||||
mark(if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" })
|
||||
r(false) -> <v3>
|
||||
jf(L7|<v3>)
|
||||
5 mark({ if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" })
|
||||
mark(if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } })
|
||||
r(false) -> <v4>
|
||||
jf(L8|<v4>) USE: in: {} out: {}
|
||||
6 mark({ var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } })
|
||||
v(var foo: String? = null) INIT: in: {} out: {foo=D}
|
||||
r(null) -> <v5> INIT: in: {foo=D} out: {foo=D}
|
||||
w(foo|<v5>) INIT: in: {foo=D} out: {foo=ID}
|
||||
mark(try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized }) INIT: in: {foo=ID} out: {foo=ID}
|
||||
jmp?(L9)
|
||||
jmp?(L10) USE: in: {foo=READ} out: {foo=READ}
|
||||
7 mark({ foo = "test" })
|
||||
mark("test")
|
||||
r("test") -> <v6> USE: in: {foo=WRITTEN_AFTER_READ} out: {foo=WRITTEN_AFTER_READ}
|
||||
w(foo|<v6>) USE: in: {foo=READ} out: {foo=WRITTEN_AFTER_READ}
|
||||
6 jmp(L11)
|
||||
L9 [onException]:
|
||||
7 v(e: Exception) INIT: in: {foo=ID} out: {e=D, foo=ID}
|
||||
magic[FAKE_INITIALIZER](e: Exception) -> <v8> INIT: in: {e=D, foo=ID} out: {e=D, foo=ID}
|
||||
w(e|<v8>) INIT: in: {e=D, foo=ID} out: {e=ID, foo=ID}
|
||||
8 mark({ return "fail" }) INIT: in: {e=ID, foo=ID} out: {e=ID, foo=ID}
|
||||
mark("fail")
|
||||
r("fail") -> <v9>
|
||||
L12 [start finally]:
|
||||
9 mark({ println(foo) // Variable 'foo' must be initialized }) USE: in: {foo=READ} out: {foo=READ}
|
||||
r(foo) -> <v10> USE: in: {} out: {foo=READ}
|
||||
mark(println(foo))
|
||||
magic[UNRESOLVED_CALL](println(foo)|<v10>, !<v11>) -> <v12>
|
||||
L13 [finish finally]:
|
||||
5 mark({}) INIT: in: {} out: {}
|
||||
read (Unit)
|
||||
8 ret(*|<v9>) L1 USE: in: {} out: {}
|
||||
- 7 jmp(L11)
|
||||
L11 [afterCatches]:
|
||||
6 jmp(L14) INIT: in: {foo=ID} out: {foo=ID}
|
||||
L10 [onExceptionToFinallyBlock]:
|
||||
9 mark({ println(foo) // Variable 'foo' must be initialized }) USE: in: {foo=READ} out: {foo=READ}
|
||||
r(foo) -> <v10> USE: in: {} out: {foo=READ}
|
||||
mark(println(foo))
|
||||
magic[UNRESOLVED_CALL](println(foo)|<v10>, !<v11>) -> <v12>
|
||||
5 mark({}) INIT: in: {} out: {}
|
||||
read (Unit)
|
||||
6 jmp(error) USE: in: {} out: {}
|
||||
L14 [skipFinallyToErrorBlock]:
|
||||
9 mark({ println(foo) // Variable 'foo' must be initialized }) INIT: in: {foo=ID} out: {foo=ID} USE: in: {foo=READ} out: {foo=READ}
|
||||
r(foo) -> <v10> USE: in: {} out: {foo=READ}
|
||||
mark(println(foo))
|
||||
magic[UNRESOLVED_CALL](println(foo)|<v10>, !<v11>) -> <v12>
|
||||
6 merge(try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized }|!<v7>, !<v13>) -> <v14>
|
||||
5 jmp(L15) INIT: in: {} out: {}
|
||||
L8 [else branch]:
|
||||
read (Unit)
|
||||
L15 ['if' expression result]:
|
||||
merge(if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } }|<v14>) -> <v15>
|
||||
mark("fail")
|
||||
r("fail") -> <v16>
|
||||
mark({})
|
||||
read (Unit)
|
||||
ret(*|<v16>) L1 USE: in: {} out: {}
|
||||
- 4 jmp(L16)
|
||||
L7 [else branch]:
|
||||
read (Unit) INIT: in: {} out: {}
|
||||
L16 ['if' expression result]:
|
||||
merge(if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" }|!<v17>) -> <v18>
|
||||
L6 ['if' expression result]:
|
||||
3 merge(if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } }|!<v2>, <v18>) -> <v19>
|
||||
2 jmp(L17)
|
||||
L2 [onExceptionToFinallyBlock]:
|
||||
5 mark({})
|
||||
read (Unit)
|
||||
2 jmp(error)
|
||||
L17 [skipFinallyToErrorBlock]:
|
||||
5 mark({})
|
||||
read (Unit)
|
||||
2 merge(try { if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } finally {}|<v19>) -> <v20>
|
||||
r(null) -> <v21>
|
||||
ret(*|<v21>) L1
|
||||
L1:
|
||||
1 <END>
|
||||
error:
|
||||
<ERROR>
|
||||
sink:
|
||||
<SINK> USE: in: {} out: {}
|
||||
=====================
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
fun sample(): String? {
|
||||
try {
|
||||
if (false) {
|
||||
return "fail"
|
||||
} else {
|
||||
if (false) {
|
||||
if (false) {
|
||||
var foo: String? = null
|
||||
try {
|
||||
foo = "test"
|
||||
} catch (e: Exception) {
|
||||
return "fail"
|
||||
} finally {
|
||||
println(foo) // Variable 'foo' must be initialized
|
||||
}
|
||||
}
|
||||
return "fail"
|
||||
}
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
== sample ==
|
||||
fun sample(): String? {
|
||||
try {
|
||||
if (false) {
|
||||
return "fail"
|
||||
} else {
|
||||
if (false) {
|
||||
if (false) {
|
||||
var foo: String? = null
|
||||
try {
|
||||
foo = "test"
|
||||
} catch (e: Exception) {
|
||||
return "fail"
|
||||
} finally {
|
||||
println(foo) // Variable 'foo' must be initialized
|
||||
}
|
||||
}
|
||||
return "fail"
|
||||
}
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
---------------------
|
||||
<v8>: {<: Exception} NEW: magic[FAKE_INITIALIZER](e: Exception) -> <v8>
|
||||
false <v0>: Boolean NEW: r(false) -> <v0>
|
||||
"fail" <v1>: {<: String?} NEW: r("fail") -> <v1>
|
||||
return "fail" !<v2>: *
|
||||
{ return "fail" } !<v2>: * COPY
|
||||
false <v3>: Boolean NEW: r(false) -> <v3>
|
||||
false <v4>: Boolean NEW: r(false) -> <v4>
|
||||
null <v5>: {<: String?} NEW: r(null) -> <v5>
|
||||
"test" <v6>: {<: String?} NEW: r("test") -> <v6>
|
||||
foo = "test" !<v7>: *
|
||||
{ foo = "test" } !<v7>: * COPY
|
||||
"fail" <v9>: {<: String?} NEW: r("fail") -> <v9>
|
||||
return "fail" !<v13>: *
|
||||
{ return "fail" } !<v13>: * COPY
|
||||
println !<v11>: *
|
||||
foo <v10>: * NEW: r(foo) -> <v10>
|
||||
println(foo) <v12>: * NEW: magic[UNRESOLVED_CALL](println(foo)|<v10>, !<v11>) -> <v12>
|
||||
{ println(foo) // Variable 'foo' must be initialized } <v12>: * COPY
|
||||
try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } <v14>: * NEW: merge(try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized }|!<v7>, !<v13>) -> <v14>
|
||||
{ var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } <v14>: * COPY
|
||||
if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } <v15>: * NEW: merge(if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } }|<v14>) -> <v15>
|
||||
"fail" <v16>: {<: String?} NEW: r("fail") -> <v16>
|
||||
return "fail" !<v17>: *
|
||||
{ if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } !<v17>: * COPY
|
||||
if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } <v18>: * NEW: merge(if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" }|!<v17>) -> <v18>
|
||||
{ if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } <v18>: * COPY
|
||||
if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } <v19>: * NEW: merge(if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } }|!<v2>, <v18>) -> <v19>
|
||||
{ if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } <v19>: * COPY
|
||||
try { if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } finally {} <v20>: * NEW: merge(try { if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } finally {}|<v19>) -> <v20>
|
||||
null <v21>: {<: String?} NEW: r(null) -> <v21>
|
||||
return null !<v22>: *
|
||||
{ try { if (false) { return "fail" } else { if (false) { if (false) { var foo: String? = null try { foo = "test" } catch (e: Exception) { return "fail" } finally { println(foo) // Variable 'foo' must be initialized } } return "fail" } } } finally {} return null } !<v22>: * COPY
|
||||
=====================
|
||||
@@ -0,0 +1,24 @@
|
||||
fun use(arg: String?) = arg
|
||||
|
||||
fun sample(): String? {
|
||||
try {
|
||||
if (false) {
|
||||
return "fail"
|
||||
} else {
|
||||
if (false) {
|
||||
if (false) {
|
||||
var foo: String? = null
|
||||
try {
|
||||
foo = "test"
|
||||
} catch (e: Exception) {
|
||||
return "fail"
|
||||
} finally {
|
||||
use(foo) // 'foo' is initialized here
|
||||
}
|
||||
}
|
||||
return "fail"
|
||||
}
|
||||
}
|
||||
} finally {}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package
|
||||
|
||||
public fun sample(): kotlin.String?
|
||||
public fun use(/*0*/ arg: kotlin.String?): kotlin.String?
|
||||
@@ -106,6 +106,12 @@ public class DataFlowTestGenerated extends AbstractDataFlowTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt4764.kt")
|
||||
public void testKt4764() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/bugs/kt4764.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("referenceToPropertyInitializer.kt")
|
||||
public void testReferenceToPropertyInitializer() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/bugs/referenceToPropertyInitializer.kt");
|
||||
|
||||
@@ -864,6 +864,12 @@ public class PseudoValueTestGenerated extends AbstractPseudoValueTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt4764.kt")
|
||||
public void testKt4764() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/bugs/kt4764.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("referenceToPropertyInitializer.kt")
|
||||
public void testReferenceToPropertyInitializer() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cfg-variables/bugs/referenceToPropertyInitializer.kt");
|
||||
|
||||
@@ -3123,6 +3123,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nestedTryFinally.kt")
|
||||
public void testNestedTryFinally() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/nestedTryFinally.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nonLocalReturnUnreachable.kt")
|
||||
public void testNonLocalReturnUnreachable() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/controlFlowAnalysis/nonLocalReturnUnreachable.kt");
|
||||
|
||||
Reference in New Issue
Block a user