Support open suspend members and super-calls

The problem was that the resume call (from doResume) for open members
was based on common INVOKEVIRTUAL to the original function
that lead to the invocation of the override when it was expected
to be the overridden (after super-call being suspended)

The solution is to generate method bodies for open members into
the special $suspendImpl synthetic function that may be called
from the doResume implementation

 #KT-17587 Fixed
This commit is contained in:
Denis Zharkov
2017-05-15 10:43:31 +03:00
parent e75b6c8404
commit d24d3a73d7
10 changed files with 185 additions and 2 deletions
@@ -201,6 +201,46 @@ public class FunctionCodegen {
// Native methods are only defined in facades and do not need package part implementations // Native methods are only defined in facades and do not need package part implementations
return; return;
} }
boolean isOpenSuspendInClass =
functionDescriptor.isSuspend() && DescriptorUtilKt.isEffectivelyOpen(functionDescriptor) &&
!isInterface(functionDescriptor.getContainingDeclaration()) &&
origin.getOriginKind() != JvmDeclarationOriginKind.CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL;
if (isOpenSuspendInClass) {
MethodVisitor mv =
v.newMethod(origin,
flags,
asmMethod.getName(),
asmMethod.getDescriptor(),
jvmSignature.getGenericsSignature(),
getThrownExceptions(functionDescriptor, typeMapper)
);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
int index = 1;
for (Type type : asmMethod.getArgumentTypes()) {
mv.visitVarInsn(type.getOpcode(Opcodes.ILOAD), index);
index += type.getSize();
}
asmMethod = CoroutineCodegenUtilKt.getImplForOpenMethod(asmMethod, v.getThisName());
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
v.getThisName(), asmMethod.getName(), asmMethod.getDescriptor(),
false
);
mv.visitInsn(Opcodes.ARETURN);
mv.visitEnd();
flags |= Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
flags &= ~getVisibilityAccessFlag(functionDescriptor);
flags |= AsmUtil.NO_FLAG_PACKAGE_PRIVATE;
}
MethodVisitor mv = MethodVisitor mv =
strategy.wrapMethodVisitor( strategy.wrapMethodVisitor(
v.newMethod(origin, v.newMethod(origin,
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtFunctionLiteral import org.jetbrains.kotlin.psi.KtFunctionLiteral
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes
@@ -400,14 +401,28 @@ class CoroutineCodegenForNamedFunction private constructor(
).put(captureThisType, codegen.v) ).put(captureThisType, codegen.v)
} }
val callableMethod = typeMapper.mapToCallableMethod(suspendFunctionJvmView, false) val isInterfaceMethod = DescriptorUtils.isInterface(suspendFunctionJvmView.containingDeclaration)
val callableMethod =
typeMapper.mapToCallableMethod(
suspendFunctionJvmView,
// Obtain default impls method for interfaces
isInterfaceMethod
)
for (argumentType in callableMethod.getAsmMethod().argumentTypes.dropLast(1)) { for (argumentType in callableMethod.getAsmMethod().argumentTypes.dropLast(1)) {
AsmUtil.pushDefaultValueOnStack(argumentType, codegen.v) AsmUtil.pushDefaultValueOnStack(argumentType, codegen.v)
} }
codegen.v.load(0, AsmTypes.OBJECT_TYPE) codegen.v.load(0, AsmTypes.OBJECT_TYPE)
callableMethod.genInvokeInstruction(codegen.v)
if (suspendFunctionJvmView.isEffectivelyOpen() && !isInterfaceMethod && captureThisType != null) {
val owner = captureThisType.internalName
val impl = callableMethod.getAsmMethod().getImplForOpenMethod(owner)
codegen.v.invokestatic(owner, impl.name, impl.descriptor, false)
}
else {
callableMethod.genInvokeInstruction(codegen.v)
}
codegen.v.visitInsn(Opcodes.ARETURN) codegen.v.visitInsn(Opcodes.ARETURN)
} }
@@ -49,6 +49,7 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.MethodNode import org.jetbrains.org.objectweb.asm.tree.MethodNode
// These classes do not actually exist at runtime // These classes do not actually exist at runtime
@@ -300,3 +301,6 @@ fun InstructionAdapter.invokeDoResumeWithUnit(thisName: String) {
false false
) )
} }
fun Method.getImplForOpenMethod(ownerInternalName: String) =
Method("$name\$suspendImpl", returnType, arrayOf(Type.getObjectType(ownerInternalName)) + argumentTypes)
@@ -0,0 +1,34 @@
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.experimental.*
import kotlin.coroutines.experimental.intrinsics.*
open class A(val v: String) {
suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
x.resume(v)
COROUTINE_SUSPENDED
}
open suspend fun suspendHere(): String = suspendThere("O") + suspendThere(v)
}
class B(v: String) : A(v) {
override suspend fun suspendHere(): String = super.suspendHere() + suspendThere("56")
}
fun builder(c: suspend A.() -> Unit) {
c.startCoroutine(B("K"), EmptyContinuation)
}
fun box(): String {
var result = ""
builder {
result = suspendHere()
}
if (result != "OK56") return "fail 1: $result"
return "OK"
}
@@ -0,0 +1,38 @@
// WITH_RUNTIME
// WITH_COROUTINES
import helpers.*
import kotlin.coroutines.experimental.*
import kotlin.coroutines.experimental.intrinsics.*
interface A {
val v: String
suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x ->
x.resume(v)
COROUTINE_SUSPENDED
}
suspend fun suspendHere(): String = suspendThere("O") + suspendThere(v)
}
interface A2 : A {
override suspend fun suspendHere(): String = super.suspendHere() + suspendThere("56")
}
class B(override val v: String) : A2
fun builder(c: suspend A.() -> Unit) {
c.startCoroutine(B("K"), EmptyContinuation)
}
fun box(): String {
var result = ""
builder {
result = suspendHere()
}
if (result != "OK56") return "fail 1: $result"
return "OK"
}
@@ -5680,6 +5680,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName); doTest(fileName);
} }
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
doTest(fileName);
}
@TestMetadata("superCallInterface.kt")
public void testSuperCallInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
doTest(fileName);
}
@TestMetadata("withVariables.kt") @TestMetadata("withVariables.kt")
public void testWithVariables() throws Exception { public void testWithVariables() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
@@ -5680,6 +5680,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
doTest(fileName);
}
@TestMetadata("superCallInterface.kt")
public void testSuperCallInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
doTest(fileName);
}
@TestMetadata("withVariables.kt") @TestMetadata("withVariables.kt")
public void testWithVariables() throws Exception { public void testWithVariables() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
@@ -5680,6 +5680,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName); doTest(fileName);
} }
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
doTest(fileName);
}
@TestMetadata("superCallInterface.kt")
public void testSuperCallInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
doTest(fileName);
}
@TestMetadata("withVariables.kt") @TestMetadata("withVariables.kt")
public void testWithVariables() throws Exception { public void testWithVariables() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.descriptors
import org.jetbrains.kotlin.incremental.components.LookupLocation import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.sure import org.jetbrains.kotlin.utils.sure
fun ModuleDescriptor.resolveClassByFqName(fqName: FqName, lookupLocation: LookupLocation): ClassDescriptor? { fun ModuleDescriptor.resolveClassByFqName(fqName: FqName, lookupLocation: LookupLocation): ClassDescriptor? {
@@ -37,3 +38,6 @@ fun ModuleDescriptor.findContinuationClassDescriptorOrNull(lookupLocation: Looku
fun ModuleDescriptor.findContinuationClassDescriptor(lookupLocation: LookupLocation) = fun ModuleDescriptor.findContinuationClassDescriptor(lookupLocation: LookupLocation) =
findContinuationClassDescriptorOrNull(lookupLocation).sure { "Continuation interface is not found" } findContinuationClassDescriptorOrNull(lookupLocation).sure { "Continuation interface is not found" }
fun FunctionDescriptor.isEffectivelyOpen() =
modality == Modality.OPEN && containingDeclaration.safeAs<ClassDescriptor>()?.modality == Modality.OPEN
@@ -6371,6 +6371,18 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("superCall.kt")
public void testSuperCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCall.kt");
doTest(fileName);
}
@TestMetadata("superCallInterface.kt")
public void testSuperCallInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/superCallInterface.kt");
doTest(fileName);
}
@TestMetadata("withVariables.kt") @TestMetadata("withVariables.kt")
public void testWithVariables() throws Exception { public void testWithVariables() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/withVariables.kt");