Control flow analysis: nested finally blocks now appear in correct order in CFG #KT-4764 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-04-19 18:01:08 +03:00
parent a5b428d9ce
commit d24528f6bb
9 changed files with 257 additions and 1 deletions
@@ -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
+131
View File
@@ -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
View File
@@ -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
}
+57
View File
@@ -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");