Fix some try-catch block related problems in coroutine transformer

Split all try-catch blocks when they intersect suspension points
This commit is contained in:
Denis Zharkov
2016-06-08 13:37:55 +03:00
parent e802049fd0
commit 771cca9c23
8 changed files with 812 additions and 26 deletions
@@ -17,10 +17,12 @@
package org.jetbrains.kotlin.codegen.coroutines
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.insnListOf
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
@@ -64,18 +66,27 @@ class CoroutineTransformerMethodVisitor(
if (methodNode.visibleAnnotations?.none { it.desc == CONTINUATION_METHOD_ANNOTATION_DESC } != false) return
methodNode.visibleAnnotations.removeAll { it.desc == CONTINUATION_METHOD_ANNOTATION_DESC }
// Spill stack to variables before suspension points
MandatoryMethodTransformer().transform("fake", methodNode)
processUninitializedStores(methodNode)
val suspensionPoints = collectSuspensionPoints(methodNode)
if (suspensionPoints.isEmpty()) return
for (suspensionPoint in suspensionPoints) {
splitTryCatchBlocksContainingSuspensionPoint(methodNode, suspensionPoint)
}
// Spill stack to variables before suspension points, try/catch blocks
MandatoryMethodTransformer().transform("fake", methodNode)
// Remove unreachable suspension points
// If we don't do this, then relevant frames will not be analyzed, that is unexpected from point of view of next steps (e.g. variable spilling)
DeadCodeEliminationMethodTransformer().transform("fake", methodNode)
suspensionPoints.removeAll { it.suspensionCall.next == null && it.suspensionCall.previous == null }
processUninitializedStores(methodNode)
spillVariables(suspensionPoints, methodNode)
val suspensionPointLabels = suspensionPoints.map {
transformCallAndReturnContinuationLabel(it, methodNode)
val suspensionPointLabels = suspensionPoints.withIndex().map {
transformCallAndReturnContinuationLabel(it.index + 1, it.value, methodNode)
}
methodNode.instructions.apply {
@@ -88,7 +99,7 @@ class CoroutineTransformerMethodVisitor(
FieldInsnNode(
Opcodes.GETFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor),
TableSwitchInsnNode(0,
suspensionPoints.map { it.id }.max()!!,
suspensionPoints.size,
defaultLabel,
*(arrayOf(startLabel) + suspensionPointLabels)),
startLabel))
@@ -101,9 +112,11 @@ class CoroutineTransformerMethodVisitor(
})
}
methodNode.removeEmptyCatchBlocks()
}
private fun collectSuspensionPoints(methodNode: MethodNode): List<SuspensionPoint> {
private fun collectSuspensionPoints(methodNode: MethodNode): MutableList<SuspensionPoint> {
val suspensionPoints = mutableListOf<SuspensionPoint>()
for (methodInsn in methodNode.instructions.asSequence().filterIsInstance<MethodInsnNode>()) {
@@ -115,7 +128,7 @@ class CoroutineTransformerMethodVisitor(
"Expected method call instruction after suspension point, but ${methodInsn.next} found"
}
suspensionPoints.add(SuspensionPoint(suspensionPoints.size + 1, methodInsn.next as MethodInsnNode))
suspensionPoints.add(SuspensionPoint(methodInsn.next as MethodInsnNode))
}
else -> error("Unexpected suspension point marker kind '${methodInsn.name}'")
@@ -170,7 +183,7 @@ class CoroutineTransformerMethodVisitor(
insertBefore(call, FieldInsnNode(Opcodes.PUTFIELD, classBuilder.thisName, fieldName, normalizedType.descriptor))
// restore variable after suspension call
val nextInsnAfterCall = call.next
val nextInsnAfterCall = call.tryCatchBlockEndLabelAfterSuspensionCall.next
insertBefore(nextInsnAfterCall, VarInsnNode(Opcodes.ALOAD, 0))
insertBefore(nextInsnAfterCall,
FieldInsnNode(Opcodes.GETFIELD, classBuilder.thisName, fieldName, normalizedType.descriptor))
@@ -197,7 +210,16 @@ class CoroutineTransformerMethodVisitor(
}
}
private fun transformCallAndReturnContinuationLabel(suspension: SuspensionPoint, methodNode: MethodNode): LabelNode {
private val MethodInsnNode.tryCatchBlockEndLabelAfterSuspensionCall: LabelNode
get() {
assert(next is LabelNode) {
"Next instruction after ${this} should be a label, but ${next.javaClass}/${next.opcode} was found"
}
return next as LabelNode
}
private fun transformCallAndReturnContinuationLabel(id: Int, suspension: SuspensionPoint, methodNode: MethodNode): LabelNode {
val call = suspension.suspensionCall
val method = Method(call.name, call.desc)
call.desc = Method(method.name, Type.VOID_TYPE, method.argumentTypes).descriptor
@@ -209,11 +231,11 @@ class CoroutineTransformerMethodVisitor(
insertBefore(call,
insnListOf(
VarInsnNode(Opcodes.ALOAD, 0),
*withInstructionAdapter { iconst(suspension.id) }.toArray(),
*withInstructionAdapter { iconst(id) }.toArray(),
FieldInsnNode(
Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor)))
val nextInsnAfterCall = call.next
val nextInsnAfterCall = call.tryCatchBlockEndLabelAfterSuspensionCall.next
// Exit
insertBefore(nextInsnAfterCall, InsnNode(Opcodes.RETURN))
@@ -221,8 +243,20 @@ class CoroutineTransformerMethodVisitor(
// Mark place for continuation
insertBefore(nextInsnAfterCall, continuationLabel)
// Check if resumeWithException has been called
insertBefore(nextInsnAfterCall, withInstructionAdapter {
// After suspension point there is always three nodes: L1, NOP, L2
// And if there are relevant exception handlers, they always start at L2
// See 'splitTryCatchBlocksContainingSuspensionPoint'
val possibleTryCatchBlockStart = suspension.tryCatchBlocksContinuationLabel
// Remove NOP as it's unnecessary anymore
assert(possibleTryCatchBlockStart.previous.opcode == Opcodes.NOP) {
"NOP expected but ${possibleTryCatchBlockStart.previous.opcode} was found"
}
remove(possibleTryCatchBlockStart.previous)
insert(possibleTryCatchBlockStart, withInstructionAdapter {
// Check if resumeWithException has been called
load(2, AsmTypes.OBJECT_TYPE)
dup()
val noExceptionLabel = Label()
@@ -231,15 +265,56 @@ class CoroutineTransformerMethodVisitor(
mark(noExceptionLabel)
pop()
})
// Load continuation argument just like suspending function returns it
insertBefore(nextInsnAfterCall, VarInsnNode(Opcodes.ALOAD, 1))
insertBefore(nextInsnAfterCall, coercionInsns(AsmTypes.OBJECT_TYPE, method.returnType))
// Load continuation argument just like suspending function returns it
load(1, AsmTypes.OBJECT_TYPE)
StackValue.coerce(AsmTypes.OBJECT_TYPE, method.returnType, this)
})
}
return continuationLabel
}
// It's necessary to preserve some sensible invariants like there should be no jump in the middle of try-catch-block
// Also it's important that spilled variables are being restored outside of TCB,
// otherwise they would be treated as uninitialized within catch-block while they can be used there
// How suspension point area will look like after all transformations:
// <spill variables>
// INVOKEVIRTUAL suspensionMethod()
// L1: -- end of all TCB's that are containing the suspension point (inserted by this method)
// RETURN
// L2: -- continuation label (used for the TABLESWITCH)
// <restore variables> (no try-catch blocks here)
// L3: begin/continuation of all TCB's that are containing the suspension point (inserted by this method)
// ...
private fun splitTryCatchBlocksContainingSuspensionPoint(methodNode: MethodNode, suspensionPoint: SuspensionPoint) {
val instructions = methodNode.instructions
val indexOfSuspension = instructions.indexOf(suspensionPoint.suspensionCall)
val firstLabel = LabelNode()
val secondLabel = LabelNode()
instructions.insert(suspensionPoint.suspensionCall, firstLabel)
// NOP is needed to preventing these label merge
// Here between these labels additional instructions are supposed to be inserted (variables spilling, etc.)
instructions.insert(firstLabel, InsnNode(Opcodes.NOP))
instructions.insert(firstLabel.next, secondLabel)
methodNode.tryCatchBlocks =
methodNode.tryCatchBlocks.flatMap {
val isContainingSuspensionPoint =
instructions.indexOf(it.start) < indexOfSuspension && indexOfSuspension < instructions.indexOf(it.end)
if (isContainingSuspensionPoint)
listOf(TryCatchBlockNode(it.start, firstLabel, it.handler, it.type),
TryCatchBlockNode(secondLabel, it.end, it.handler, it.type))
else
listOf(it)
}
suspensionPoint.tryCatchBlocksContinuationLabel = secondLabel
return
}
}
private fun Type.fieldNameForVar(index: Int) = descriptor.first() + "$" + index
@@ -260,4 +335,6 @@ private fun Type.normalize() =
else -> this
}
private class SuspensionPoint(val id: Int, val suspensionCall: MethodInsnNode)
private class SuspensionPoint(val suspensionCall: MethodInsnNode) {
lateinit var tryCatchBlocksContinuationLabel: LabelNode
}
@@ -45,11 +45,7 @@ class InsnSequence(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Sequ
fun InsnList.asSequence() = InsnSequence(this)
fun MethodNode.prepareForEmitting() {
tryCatchBlocks = tryCatchBlocks.filter { tcb ->
InsnSequence(tcb.start, tcb.end).any { insn ->
insn.isMeaningful
}
}
removeEmptyCatchBlocks()
// local variables with live ranges starting after last meaningful instruction lead to VerifyError
localVariables = localVariables.filter { lv ->
@@ -72,6 +68,14 @@ fun MethodNode.prepareForEmitting() {
}
}
fun MethodNode.removeEmptyCatchBlocks() {
tryCatchBlocks = tryCatchBlocks.filter { tcb ->
InsnSequence(tcb.start, tcb.end).any { insn ->
insn.isMeaningful
}
}
}
abstract class BasicValueWrapper(val wrappedValue: BasicValue?) : BasicValue(wrappedValue?.type) {
val basicValue: BasicValue? get() = (wrappedValue as? BasicValueWrapper)?.basicValue ?: wrappedValue
@@ -0,0 +1,155 @@
// WITH_RUNTIME
var globalResult = ""
var wasCalled = false
class Controller {
val postponedActions = java.util.ArrayList<() -> Unit>()
suspend fun suspendWithValue(v: String, x: Continuation<String>) {
postponedActions.add {
x.resume(v)
}
}
suspend fun suspendWithException(e: Exception, x: Continuation<String>) {
postponedActions.add {
x.resumeWithException(e)
}
}
operator fun handleResult(x: String, c: Continuation<Nothing>) {
globalResult = x
}
fun run(c: Controller.() -> Continuation<Unit>) {
c(this).resume(Unit)
while (postponedActions.isNotEmpty()) {
postponedActions[0]()
postponedActions.removeAt(0)
}
}
}
fun builder(expectException: Boolean = false, coroutine c: Controller.() -> Continuation<Unit>) {
val controller = Controller()
globalResult = "#"
wasCalled = false
if (!expectException) {
controller.run(c)
}
else {
try {
controller.run(c)
globalResult = "fail: exception was not thrown"
} catch (e: Exception) {
globalResult = e.message!!
}
}
if (!wasCalled) {
throw RuntimeException("fail wasCalled")
}
if (globalResult != "OK") {
throw RuntimeException("fail $globalResult")
}
}
fun commonThrow(t: Throwable) {
throw t
}
inline fun tryCatch(t: () -> String, onException: (Exception) -> String) =
try {
t()
} catch (e: java.lang.RuntimeException) {
onException(e)
}
inline fun tryCatchFinally(t: () -> String, onException: (Exception) -> String, f: () -> Unit) =
try {
t()
} catch (e: java.lang.RuntimeException) {
onException(e)
} finally {
f()
}
fun box(): String {
builder {
tryCatch(
{
suspendWithValue("<ignored>")
wasCalled = true
suspendWithValue("OK")
},
{ e ->
suspendWithValue("fail 1")
}
)
}
builder {
tryCatch(
{
suspendWithException(RuntimeException("M"))
},
{ e ->
if (e.message != "M") throw RuntimeException("fail 2")
wasCalled = true
suspendWithValue("OK")
}
)
}
builder {
tryCatchFinally(
{
suspendWithValue("<none>")
wasCalled = true
suspendWithValue("OK")
},
{
suspendWithValue("fail 1")
},
{
suspendWithValue("ignored 1")
wasCalled = true
}
)
}
builder {
tryCatchFinally(
{
suspendWithException(RuntimeException("M"))
},
{ e ->
if (e.message != "M") throw RuntimeException("fail 2")
suspendWithValue("OK")
},
{
suspendWithValue("ignored 2")
wasCalled = true
}
)
}
builder {
tryCatchFinally(
{
if (suspendWithValue("56") == "56") return@tryCatchFinally "OK"
suspendWithValue("fail 4")
},
{
suspendWithValue("fail 5")
},
{
suspendWithValue("ignored 3")
wasCalled = true
}
)
}
return globalResult
}
@@ -0,0 +1,109 @@
// WITH_RUNTIME
var globalResult = ""
var wasCalled = false
class Controller {
val postponedActions = java.util.ArrayList<() -> Unit>()
suspend fun suspendWithValue(v: String, x: Continuation<String>) {
postponedActions.add {
x.resume(v)
}
}
suspend fun suspendWithException(e: Exception, x: Continuation<String>) {
postponedActions.add {
x.resumeWithException(e)
}
}
operator fun handleResult(x: String, c: Continuation<Nothing>) {
globalResult = x
}
fun run(c: Controller.() -> Continuation<Unit>) {
c(this).resume(Unit)
while (postponedActions.isNotEmpty()) {
postponedActions[0]()
postponedActions.removeAt(0)
}
}
}
fun builder(expectException: Boolean = false, coroutine c: Controller.() -> Continuation<Unit>) {
val controller = Controller()
globalResult = "#"
wasCalled = false
if (!expectException) {
controller.run(c)
}
else {
try {
controller.run(c)
globalResult = "fail: exception was not thrown"
} catch (e: Exception) {
globalResult = e.message!!
}
}
if (!wasCalled) {
throw RuntimeException("fail wasCalled")
}
if (globalResult != "OK") {
throw RuntimeException("fail $globalResult")
}
}
fun commonThrow(t: Throwable) {
throw t
}
fun box(): String {
builder {
try {
try {
suspendWithValue("<ignored>")
suspendWithValue("OK")
}
catch (e: RuntimeException) {
suspendWithValue("fail 1")
}
} finally {
wasCalled = true
}
}
builder {
try {
try {
suspendWithException(RuntimeException("M1"))
}
catch (e: RuntimeException) {
if (e.message != "M1") throw RuntimeException("fail 2")
wasCalled = true
suspendWithValue("OK")
}
} catch (e: Exception) {
suspendWithValue("fail 3")
}
}
builder {
try {
try {
suspendWithException(Exception("M2"))
}
catch (e: RuntimeException) {
suspendWithValue("fail 4")
} finally {
wasCalled = true
}
} catch (e: Exception) {
if (e.message != "M2") throw RuntimeException("fail 5")
suspendWithValue("OK")
}
}
return globalResult
}
@@ -0,0 +1,172 @@
// WITH_RUNTIME
var globalResult = ""
var wasCalled = false
class Controller {
val postponedActions = java.util.ArrayList<() -> Unit>()
suspend fun suspendWithValue(v: String, x: Continuation<String>) {
postponedActions.add {
x.resume(v)
}
}
suspend fun suspendWithException(e: Exception, x: Continuation<String>) {
postponedActions.add {
x.resumeWithException(e)
}
}
operator fun handleResult(x: String, c: Continuation<Nothing>) {
globalResult = x
}
fun run(c: Controller.() -> Continuation<Unit>) {
c(this).resume(Unit)
while (postponedActions.isNotEmpty()) {
postponedActions[0]()
postponedActions.removeAt(0)
}
}
}
fun builder(expectException: Boolean = false, coroutine c: Controller.() -> Continuation<Unit>) {
val controller = Controller()
globalResult = "#"
wasCalled = false
if (!expectException) {
controller.run(c)
}
else {
try {
controller.run(c)
globalResult = "fail: exception was not thrown"
} catch (e: Exception) {
globalResult = e.message!!
}
}
if (!wasCalled) {
throw RuntimeException("fail wasCalled")
}
if (globalResult != "OK") {
throw RuntimeException("fail $globalResult")
}
}
fun commonThrow(t: Throwable) {
throw t
}
fun box(): String {
builder {
try {
suspendWithValue("<none>")
suspendWithValue("OK")
} catch (e: RuntimeException) {
suspendWithValue("fail 1")
} finally {
suspendWithValue("ignored 1")
wasCalled = true
}
}
builder {
try {
suspendWithException(RuntimeException("M"))
} catch (e: RuntimeException) {
if (e.message != "M") throw RuntimeException("fail 2")
suspendWithValue("OK")
} finally {
suspendWithValue("ignored 2")
wasCalled = true
}
}
builder(expectException = true) {
try {
suspendWithException(Exception("OK"))
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 3")
} finally {
suspendWithValue("ignored 3")
wasCalled = true
}
}
builder(expectException = true) {
try {
suspendWithException(Exception("OK"))
} catch (e: RuntimeException) {
suspendWithValue("fail")
return@builder "xyz"
} finally {
suspendWithValue("ignored 4")
wasCalled = true
}
}
builder {
try {
suspendWithException(Exception("M2"))
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 4")
} catch (e: Exception) {
if (e.message != "M2") throw Exception("fail 5: ${e.message}")
suspendWithValue("OK")
} finally {
suspendWithValue("ignored 4")
wasCalled = true
}
}
builder {
try {
suspendWithValue("123")
commonThrow(RuntimeException("M3"))
suspendWithValue("456")
} catch (e: RuntimeException) {
if (e.message != "M3") throw Exception("fail 6: ${e.message}")
suspendWithValue("OK")
} finally {
suspendWithValue("ignored 5")
wasCalled = true
}
}
builder(expectException = true) {
try {
suspendWithValue("123")
commonThrow(Exception("OK"))
suspendWithValue("456")
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 7")
} finally {
suspendWithValue("ignored 6")
wasCalled = true
}
}
builder {
try {
suspendWithValue("123")
commonThrow(Exception("M3"))
suspendWithValue("456")
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 8")
} catch (e: Exception) {
if (e.message != "M3") throw Exception("fail 9: ${e.message}")
suspendWithValue("OK")
} finally {
suspendWithValue("ignored 7")
wasCalled = true
}
}
return globalResult
}
@@ -0,0 +1,146 @@
// WITH_RUNTIME
var globalResult = ""
var wasCalled = false
class Controller {
val postponedActions = java.util.ArrayList<() -> Unit>()
suspend fun suspendWithValue(v: String, x: Continuation<String>) {
postponedActions.add {
x.resume(v)
}
}
suspend fun suspendWithException(e: Exception, x: Continuation<String>) {
postponedActions.add {
x.resumeWithException(e)
}
}
operator fun handleResult(x: String, c: Continuation<Nothing>) {
globalResult = x
}
fun run(c: Controller.() -> Continuation<Unit>) {
c(this).resume(Unit)
while (postponedActions.isNotEmpty()) {
postponedActions[0]()
postponedActions.removeAt(0)
}
}
}
fun builder(expectException: Boolean = false, coroutine c: Controller.() -> Continuation<Unit>) {
val controller = Controller()
globalResult = "#"
wasCalled = false
if (!expectException) {
controller.run(c)
}
else {
try {
controller.run(c)
globalResult = "fail: exception was not thrown"
} catch (e: Exception) {
globalResult = e.message!!
}
}
if (!wasCalled) {
throw RuntimeException("fail wasCalled")
}
if (globalResult != "OK") {
throw RuntimeException("fail $globalResult")
}
}
fun commonThrow(t: Throwable) {
throw t
}
fun box(): String {
builder {
try {
suspendWithValue("<ignored>")
wasCalled = true
suspendWithValue("OK")
} catch (e: RuntimeException) {
suspendWithValue("fail 1")
}
}
builder {
try {
suspendWithException(RuntimeException("M"))
} catch (e: RuntimeException) {
if (e.message != "M") throw RuntimeException("fail 2")
wasCalled = true
suspendWithValue("OK")
}
}
builder(expectException = true) {
try {
wasCalled = true
suspendWithException(Exception("OK"))
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 3")
}
}
builder {
try {
suspendWithException(Exception("M2"))
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 4")
} catch (e: Exception) {
if (e.message != "M2") throw Exception("fail 5: ${e.message}")
wasCalled = true
suspendWithValue("OK")
}
}
builder {
try {
suspendWithValue("123")
commonThrow(RuntimeException("M3"))
suspendWithValue("456")
} catch (e: RuntimeException) {
if (e.message != "M3") throw Exception("fail 6: ${e.message}")
wasCalled = true
suspendWithValue("OK")
}
}
builder(expectException = true) {
try {
suspendWithValue("123")
wasCalled = true
commonThrow(Exception("OK"))
suspendWithValue("456")
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 7")
}
}
builder {
try {
suspendWithValue("123")
commonThrow(Exception("M3"))
suspendWithValue("456")
} catch (e: RuntimeException) {
suspendWithValue("fail")
throw RuntimeException("fail 8")
} catch (e: Exception) {
if (e.message != "M3") throw Exception("fail 9: ${e.message}")
wasCalled = true
suspendWithValue("OK")
}
}
return globalResult
}
@@ -0,0 +1,93 @@
// WITH_RUNTIME
var globalResult = ""
var wasCalled = false
class Controller {
val postponedActions = java.util.ArrayList<() -> Unit>()
suspend fun suspendWithValue(v: String, x: Continuation<String>) {
postponedActions.add {
x.resume(v)
}
}
suspend fun suspendWithException(e: Exception, x: Continuation<String>) {
postponedActions.add {
x.resumeWithException(e)
}
}
operator fun handleResult(x: String, c: Continuation<Nothing>) {
globalResult = x
}
fun run(c: Controller.() -> Continuation<Unit>) {
c(this).resume(Unit)
while (postponedActions.isNotEmpty()) {
postponedActions[0]()
postponedActions.removeAt(0)
}
}
}
fun builder(expectException: Boolean = false, coroutine c: Controller.() -> Continuation<Unit>) {
val controller = Controller()
globalResult = "#"
wasCalled = false
if (!expectException) {
controller.run(c)
}
else {
try {
controller.run(c)
globalResult = "fail: exception was not thrown"
} catch (e: Exception) {
globalResult = e.message!!
}
}
if (!wasCalled) {
throw RuntimeException("fail wasCalled")
}
if (globalResult != "OK") {
throw RuntimeException("fail $globalResult")
}
}
fun commonThrow() {
throw RuntimeException("OK")
}
fun box(): String {
builder {
try {
suspendWithValue("OK")
} finally {
if (suspendWithValue("G") != "G") throw RuntimeException("fail 1")
wasCalled = true
}
}
builder(expectException = true) {
try {
suspendWithException(java.lang.RuntimeException("OK"))
} finally {
if (suspendWithValue("G") != "G") throw RuntimeException("fail 2")
wasCalled = true
}
}
builder(expectException = true) {
try {
suspendWithValue("OK")
commonThrow()
suspendWithValue("OK")
} finally {
if (suspendWithValue("G") != "G") throw RuntimeException("fail 3")
wasCalled = true
}
}
return globalResult
}
@@ -4129,6 +4129,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("inlinedTryCatchFinally.kt")
public void testInlinedTryCatchFinally() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/inlinedTryCatchFinally.kt");
doTest(fileName);
}
@TestMetadata("innerSuspensionCalls.kt")
public void testInnerSuspensionCalls() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/innerSuspensionCalls.kt");
@@ -4141,6 +4147,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("nestedTryCatch.kt")
public void testNestedTryCatch() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/nestedTryCatch.kt");
doTest(fileName);
}
@TestMetadata("nonLocalReturnFromInlineLambda.kt")
public void testNonLocalReturnFromInlineLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/nonLocalReturnFromInlineLambda.kt");
@@ -4207,11 +4219,29 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("tryCatchFinallyWithHandleResult.kt")
public void testTryCatchFinallyWithHandleResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tryCatchFinallyWithHandleResult.kt");
doTest(fileName);
}
@TestMetadata("tryCatchWithHandleResult.kt")
public void testTryCatchWithHandleResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tryCatchWithHandleResult.kt");
doTest(fileName);
}
@TestMetadata("tryFinallyInsideInlineLambda.kt")
public void testTryFinallyInsideInlineLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tryFinallyInsideInlineLambda.kt");
doTest(fileName);
}
@TestMetadata("tryFinallyWithHandleResult.kt")
public void testTryFinallyWithHandleResult() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tryFinallyWithHandleResult.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/box/dataClasses")