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:
+98
-21
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user