KT-15017 Throwing exception in the end of inline suspend-functions lead to internal compiler error
Suspend function call with a reachable (alive) begin marker and unreachable (dead) end marker is an exit point for the corresponding coroutine. It isn't a suspension point, and doesn't introduce a new state in the coroutine FSM.
This commit is contained in:
+20
-18
@@ -83,10 +83,7 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
|
|
||||||
// Remove unreachable suspension points
|
// 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)
|
// 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(classBuilder.thisName, methodNode)
|
removeUnreachableSuspensionPointsAndExitPoints(methodNode, suspensionPoints)
|
||||||
suspensionPoints.removeAll {
|
|
||||||
it.suspensionCallBegin.next == null && it.suspensionCallBegin.previous == null
|
|
||||||
}
|
|
||||||
|
|
||||||
processUninitializedStores(methodNode)
|
processUninitializedStores(methodNode)
|
||||||
|
|
||||||
@@ -136,6 +133,22 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeUnreachableSuspensionPointsAndExitPoints(methodNode: MethodNode, suspensionPoints: MutableList<SuspensionPoint>) {
|
||||||
|
val dceResult = DeadCodeEliminationMethodTransformer().transformWithResult(classBuilder.thisName, methodNode)
|
||||||
|
|
||||||
|
// If the suspension call begin is alive and suspension call end is dead
|
||||||
|
// (e.g., an inlined suspend function call ends with throwing a exception -- see KT-15017),
|
||||||
|
// this is an exit point for the corresponding coroutine.
|
||||||
|
// It doesn't introduce an additional state to the corresponding coroutine's FSM.
|
||||||
|
suspensionPoints.forEach {
|
||||||
|
if (dceResult.isAlive(it.suspensionCallBegin) && dceResult.isRemoved(it.suspensionCallEnd)) {
|
||||||
|
methodNode.instructions.remove(it.suspensionCallBegin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspensionPoints.removeAll { dceResult.isRemoved(it.suspensionCallBegin) || dceResult.isRemoved(it.suspensionCallEnd) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun collectSuspensionPoints(methodNode: MethodNode): MutableList<SuspensionPoint> {
|
private fun collectSuspensionPoints(methodNode: MethodNode): MutableList<SuspensionPoint> {
|
||||||
val suspensionPoints = mutableListOf<SuspensionPoint>()
|
val suspensionPoints = mutableListOf<SuspensionPoint>()
|
||||||
val beforeSuspensionPointMarkerStack = Stack<MethodInsnNode>()
|
val beforeSuspensionPointMarkerStack = Stack<MethodInsnNode>()
|
||||||
@@ -149,12 +162,7 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
AFTER_SUSPENSION_POINT_MARKER_NAME -> {
|
AFTER_SUSPENSION_POINT_MARKER_NAME -> {
|
||||||
suspensionPoints.add(
|
suspensionPoints.add(SuspensionPoint(beforeSuspensionPointMarkerStack.pop(), methodInsn))
|
||||||
SuspensionPoint(
|
|
||||||
suspensionCallBegin = beforeSuspensionPointMarkerStack.pop(),
|
|
||||||
suspensionCallEnd = methodInsn
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,8 +194,8 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
|
|
||||||
for (suspension in suspensionPoints) {
|
for (suspension in suspensionPoints) {
|
||||||
val suspensionCallBegin = suspension.suspensionCallBegin
|
val suspensionCallBegin = suspension.suspensionCallBegin
|
||||||
val suspensionCallEnd = suspension.suspensionCallEnd
|
|
||||||
assert(frames[suspensionCallEnd.next.index()]?.stackSize == 1) {
|
assert(frames[suspension.suspensionCallEnd.next.index()]?.stackSize == 1) {
|
||||||
"Stack should be spilled before suspension call"
|
"Stack should be spilled before suspension call"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,9 +438,3 @@ private class SuspensionPoint(
|
|||||||
) {
|
) {
|
||||||
lateinit var tryCatchBlocksContinuationLabel: LabelNode
|
lateinit var tryCatchBlocksContinuationLabel: LabelNode
|
||||||
}
|
}
|
||||||
|
|
||||||
private val DEFAULT_VALUE_OPCODES =
|
|
||||||
setOf(Opcodes.ICONST_0, Opcodes.LCONST_0, Opcodes.FCONST_0, Opcodes.DCONST_0, Opcodes.ACONST_NULL,
|
|
||||||
// GETSTATIC Unit.Instance
|
|
||||||
Opcodes.GETSTATIC)
|
|
||||||
|
|
||||||
|
|||||||
+21
-4
@@ -16,15 +16,22 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization
|
package org.jetbrains.kotlin.codegen.optimization
|
||||||
|
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
|
||||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
|
import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
|
|
||||||
class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
||||||
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
val frames = MethodTransformer.analyze(internalClassName, methodNode, OptimizationBasicInterpreter())
|
transformWithResult(internalClassName, methodNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun transformWithResult(internalClassName: String, methodNode: MethodNode): Result {
|
||||||
|
val removedNodes = HashSet<AbstractInsnNode>()
|
||||||
|
|
||||||
|
val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter())
|
||||||
val insnList = methodNode.instructions
|
val insnList = methodNode.instructions
|
||||||
val insnsArray = insnList.toArray()
|
val insnsArray = insnList.toArray()
|
||||||
|
|
||||||
@@ -32,9 +39,19 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
|||||||
// by try/catch blocks or local variables table.
|
// by try/catch blocks or local variables table.
|
||||||
insnsArray.zip(frames).filter {
|
insnsArray.zip(frames).filter {
|
||||||
it.second == null && it.first.isMeaningful
|
it.second == null && it.first.isMeaningful
|
||||||
}.forEach { insnList.remove(it.first) }
|
}.forEach {
|
||||||
|
insnList.remove(it.first)
|
||||||
|
removedNodes.add(it.first)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove empty try-catch blocks to make sure we don't break data flow analysis invariants by dead code elimination.
|
// Remove empty try-catch blocks to make sure we don't break data flow analysis invariants by dead code elimination.
|
||||||
methodNode.removeEmptyCatchBlocks()
|
methodNode.removeEmptyCatchBlocks()
|
||||||
|
|
||||||
|
return Result(removedNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Result(val removedNodes: Set<AbstractInsnNode>) {
|
||||||
|
fun isRemoved(node: AbstractInsnNode) = removedNodes.contains(node)
|
||||||
|
fun isAlive(node: AbstractInsnNode) = !isRemoved(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization.common
|
package org.jetbrains.kotlin.codegen.optimization.common
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
||||||
import org.jetbrains.org.objectweb.asm.tree.*
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
@@ -120,3 +121,7 @@ fun insnListOf(vararg insns: AbstractInsnNode) = InsnList().apply { insns.forEac
|
|||||||
|
|
||||||
fun AbstractInsnNode.isStoreOperation(): Boolean = getOpcode() in Opcodes.ISTORE..Opcodes.ASTORE
|
fun AbstractInsnNode.isStoreOperation(): Boolean = getOpcode() in Opcodes.ISTORE..Opcodes.ASTORE
|
||||||
fun AbstractInsnNode.isLoadOperation(): Boolean = getOpcode() in Opcodes.ILOAD..Opcodes.ALOAD
|
fun AbstractInsnNode.isLoadOperation(): Boolean = getOpcode() in Opcodes.ILOAD..Opcodes.ALOAD
|
||||||
|
|
||||||
|
val AbstractInsnNode?.insnText get() = InlineCodegenUtil.getInsnText(this)
|
||||||
|
val AbstractInsnNode?.debugText get() =
|
||||||
|
if (this == null) "<null>" else "${this.javaClass.simpleName}: $insnText"
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// WITH_COROUTINES
|
||||||
|
import kotlin.coroutines.startCoroutine
|
||||||
|
|
||||||
|
class Controller {
|
||||||
|
suspend inline fun suspendInlineThrow(v: String): String = throw RuntimeException(v)
|
||||||
|
suspend inline fun suspendInline(v: String) = v
|
||||||
|
}
|
||||||
|
|
||||||
|
fun builder(c: suspend Controller.() -> Unit) {
|
||||||
|
c.startCoroutine(Controller(), EmptyContinuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
class OK
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var result = ""
|
||||||
|
|
||||||
|
builder {
|
||||||
|
result = try { suspendInlineThrow("OK") } catch (e: RuntimeException) { e.message!! }
|
||||||
|
// result = suspendInline("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
@kotlin.Metadata
|
||||||
|
public final class Controller {
|
||||||
|
public method <init>(): void
|
||||||
|
public final @org.jetbrains.annotations.Nullable method suspendInline(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
|
||||||
|
public final @org.jetbrains.annotations.Nullable method suspendInlineThrow(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.Metadata
|
||||||
|
public final class CoroutineUtilKt {
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method handleExceptionContinuation(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): kotlin.coroutines.Continuation
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method handleResultContinuation(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): kotlin.coroutines.Continuation
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.Metadata
|
||||||
|
public class EmptyContinuation {
|
||||||
|
public final static field Companion: EmptyContinuation.Companion
|
||||||
|
private final @org.jetbrains.annotations.NotNull field context: kotlin.coroutines.CoroutineContext
|
||||||
|
inner class EmptyContinuation/Companion
|
||||||
|
public @synthetic.kotlin.jvm.GeneratedByJvmOverloads method <init>(): void
|
||||||
|
public method <init>(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.CoroutineContext): void
|
||||||
|
public synthetic method <init>(p0: kotlin.coroutines.CoroutineContext, p1: int, p2: kotlin.jvm.internal.DefaultConstructorMarker): void
|
||||||
|
public @org.jetbrains.annotations.NotNull method getContext(): kotlin.coroutines.CoroutineContext
|
||||||
|
public method resume(@org.jetbrains.annotations.Nullable p0: java.lang.Object): void
|
||||||
|
public method resumeWithException(@org.jetbrains.annotations.NotNull p0: java.lang.Throwable): void
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.Metadata
|
||||||
|
public final static class EmptyContinuation/Companion {
|
||||||
|
inner class EmptyContinuation/Companion
|
||||||
|
private method <init>(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.Metadata
|
||||||
|
public final class Kt15017Kt {
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||||
|
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function2): void
|
||||||
|
}
|
||||||
|
|
||||||
|
@kotlin.Metadata
|
||||||
|
public final class OK {
|
||||||
|
public method <init>(): void
|
||||||
|
}
|
||||||
+6
@@ -4751,6 +4751,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15017.kt")
|
||||||
|
public void testKt15017() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/kt15017.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("lastExpressionIsLoop.kt")
|
@TestMetadata("lastExpressionIsLoop.kt")
|
||||||
public void testLastExpressionIsLoop() throws Exception {
|
public void testLastExpressionIsLoop() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
||||||
|
|||||||
@@ -4751,6 +4751,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15017.kt")
|
||||||
|
public void testKt15017() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/kt15017.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("lastExpressionIsLoop.kt")
|
@TestMetadata("lastExpressionIsLoop.kt")
|
||||||
public void testLastExpressionIsLoop() throws Exception {
|
public void testLastExpressionIsLoop() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
||||||
|
|||||||
@@ -4751,6 +4751,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15017.kt")
|
||||||
|
public void testKt15017() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/kt15017.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("lastExpressionIsLoop.kt")
|
@TestMetadata("lastExpressionIsLoop.kt")
|
||||||
public void testLastExpressionIsLoop() throws Exception {
|
public void testLastExpressionIsLoop() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
||||||
|
|||||||
@@ -5436,6 +5436,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt15017.kt")
|
||||||
|
public void testKt15017() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/kt15017.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("lastExpressionIsLoop.kt")
|
@TestMetadata("lastExpressionIsLoop.kt")
|
||||||
public void testLastExpressionIsLoop() throws Exception {
|
public void testLastExpressionIsLoop() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user