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:
Dmitry Petrov
2017-01-24 16:46:52 +03:00
parent 3be1174824
commit e05f2eaff6
9 changed files with 137 additions and 22 deletions
@@ -83,10 +83,7 @@ class CoroutineTransformerMethodVisitor(
// 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(classBuilder.thisName, methodNode)
suspensionPoints.removeAll {
it.suspensionCallBegin.next == null && it.suspensionCallBegin.previous == null
}
removeUnreachableSuspensionPointsAndExitPoints(methodNode, suspensionPoints)
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> {
val suspensionPoints = mutableListOf<SuspensionPoint>()
val beforeSuspensionPointMarkerStack = Stack<MethodInsnNode>()
@@ -149,12 +162,7 @@ class CoroutineTransformerMethodVisitor(
}
AFTER_SUSPENSION_POINT_MARKER_NAME -> {
suspensionPoints.add(
SuspensionPoint(
suspensionCallBegin = beforeSuspensionPointMarkerStack.pop(),
suspensionCallEnd = methodInsn
)
)
suspensionPoints.add(SuspensionPoint(beforeSuspensionPointMarkerStack.pop(), methodInsn))
}
}
}
@@ -186,8 +194,8 @@ class CoroutineTransformerMethodVisitor(
for (suspension in suspensionPoints) {
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"
}
@@ -430,9 +438,3 @@ private class SuspensionPoint(
) {
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)
@@ -16,15 +16,22 @@
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.isMeaningful
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() {
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 insnsArray = insnList.toArray()
@@ -32,9 +39,19 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() {
// by try/catch blocks or local variables table.
insnsArray.zip(frames).filter {
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.
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
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.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.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"
+25
View File
@@ -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
}
@@ -4751,6 +4751,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
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")
public void testLastExpressionIsLoop() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
@@ -4751,6 +4751,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
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")
public void testLastExpressionIsLoop() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
@@ -4751,6 +4751,12 @@ public class LightAnalysisModeCodegenTestGenerated extends AbstractLightAnalysis
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")
public void testLastExpressionIsLoop() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");
@@ -5436,6 +5436,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
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")
public void testLastExpressionIsLoop() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/lastExpressionIsLoop.kt");