Maintain evaluation order for suspend calls in constructor arguments
Insert 'Class.forName(...)' in place of NEW instruction, so that the corresponding class will be initialized if required.
This commit is contained in:
+24
-22
@@ -70,10 +70,16 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
|
|||||||
* - restore constructor arguments
|
* - restore constructor arguments
|
||||||
*/
|
*/
|
||||||
internal fun processUninitializedStores(methodNode: MethodNode) {
|
internal fun processUninitializedStores(methodNode: MethodNode) {
|
||||||
UninitializedStoresProcessor(methodNode, forCoroutines = true).run()
|
UninitializedStoresProcessor(methodNode).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
class UninitializedStoresProcessor(private val methodNode: MethodNode, private val forCoroutines: Boolean) {
|
class UninitializedStoresProcessor(private val methodNode: MethodNode) {
|
||||||
|
// <init> method is "special", because it will invoke <init> from this class or from a base class for #0
|
||||||
|
//
|
||||||
|
// <clinit> method is "special", because <clinit> for singleton objects is generated as:
|
||||||
|
// NEW <obj>
|
||||||
|
// INVOKESPECIAL <obj>.<init> ()V
|
||||||
|
// and the newly created value is dropped.
|
||||||
private val isInSpecialMethod = methodNode.name == "<init>" || methodNode.name == "<clinit>"
|
private val isInSpecialMethod = methodNode.name == "<init>" || methodNode.name == "<clinit>"
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
@@ -97,26 +103,22 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode, private v
|
|||||||
|
|
||||||
methodNode.instructions.run {
|
methodNode.instructions.run {
|
||||||
removeAll(copyUsages)
|
removeAll(copyUsages)
|
||||||
if (forCoroutines) {
|
|
||||||
remove(newInsn)
|
// Replace 'NEW C' instruction with "manual" initialization of class 'C':
|
||||||
}
|
// LDC [typeName for C]
|
||||||
else {
|
// INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class;
|
||||||
// Replace 'NEW C' instruction with "manual" initialization of class 'C':
|
// POP
|
||||||
// LDC [typeName for C]
|
val typeNameForClass = newInsn.desc.replace('/', '.')
|
||||||
// INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class;
|
insertBefore(newInsn, LdcInsnNode(typeNameForClass))
|
||||||
// POP
|
insertBefore(
|
||||||
val typeNameForClass = newInsn.desc.replace('/', '.')
|
newInsn,
|
||||||
insertBefore(newInsn, LdcInsnNode(typeNameForClass))
|
MethodInsnNode(
|
||||||
insertBefore(
|
Opcodes.INVOKESTATIC,
|
||||||
newInsn,
|
"java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;",
|
||||||
MethodInsnNode(
|
false
|
||||||
Opcodes.INVOKESTATIC,
|
)
|
||||||
"java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;",
|
)
|
||||||
false
|
set(newInsn, InsnNode(Opcodes.POP))
|
||||||
)
|
|
||||||
)
|
|
||||||
set(newInsn, InsnNode(Opcodes.POP))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size
|
val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size
|
||||||
|
|||||||
+1
-1
@@ -22,6 +22,6 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
|||||||
|
|
||||||
class UninitializedStoresMethodTransformer : MethodTransformer() {
|
class UninitializedStoresMethodTransformer : MethodTransformer() {
|
||||||
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
UninitializedStoresProcessor(methodNode, forCoroutines = false).run()
|
UninitializedStoresProcessor(methodNode).run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+56
@@ -0,0 +1,56 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// WITH_COROUTINES
|
||||||
|
import helpers.*
|
||||||
|
import kotlin.coroutines.experimental.*
|
||||||
|
import kotlin.coroutines.experimental.intrinsics.*
|
||||||
|
|
||||||
|
class Controller {
|
||||||
|
suspend fun suspendHere(): String = suspendCoroutineOrReturn { x ->
|
||||||
|
x.resume("K")
|
||||||
|
COROUTINE_SUSPENDED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun builder(c: suspend Controller.() -> Unit) {
|
||||||
|
c.startCoroutine(Controller(), EmptyContinuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
val logger = StringBuilder()
|
||||||
|
|
||||||
|
class A(val first: String, val second: String) {
|
||||||
|
init {
|
||||||
|
logger.append("A.<init>;")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString() = "$first$second"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
logger.append("A.<clinit>;")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> logged(message: String, result: () -> T): T {
|
||||||
|
logger.append(message)
|
||||||
|
return result()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var result = "OK"
|
||||||
|
|
||||||
|
builder {
|
||||||
|
var local: Any = A(logged("args;") { "O" }, suspendHere())
|
||||||
|
|
||||||
|
if (local.toString() != "OK") {
|
||||||
|
result = "fail 1: $local"
|
||||||
|
return@builder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logger.toString() != "A.<clinit>;args;A.<init>;") {
|
||||||
|
return "Fail: '$logger'"
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
+6
@@ -5498,6 +5498,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt")
|
||||||
|
public void testSuspendInTheMiddleOfObjectConstructionEvaluationOrder() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("suspensionInsideSafeCall.kt")
|
@TestMetadata("suspensionInsideSafeCall.kt")
|
||||||
public void testSuspensionInsideSafeCall() throws Exception {
|
public void testSuspensionInsideSafeCall() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
||||||
|
|||||||
@@ -5498,6 +5498,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt")
|
||||||
|
public void testSuspendInTheMiddleOfObjectConstructionEvaluationOrder() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("suspensionInsideSafeCall.kt")
|
@TestMetadata("suspensionInsideSafeCall.kt")
|
||||||
public void testSuspensionInsideSafeCall() throws Exception {
|
public void testSuspensionInsideSafeCall() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
||||||
|
|||||||
@@ -5498,6 +5498,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt")
|
||||||
|
public void testSuspendInTheMiddleOfObjectConstructionEvaluationOrder() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("suspensionInsideSafeCall.kt")
|
@TestMetadata("suspensionInsideSafeCall.kt")
|
||||||
public void testSuspensionInsideSafeCall() throws Exception {
|
public void testSuspensionInsideSafeCall() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
||||||
|
|||||||
@@ -6146,6 +6146,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt")
|
||||||
|
public void testSuspendInTheMiddleOfObjectConstructionEvaluationOrder() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("suspensionInsideSafeCall.kt")
|
@TestMetadata("suspensionInsideSafeCall.kt")
|
||||||
public void testSuspensionInsideSafeCall() throws Exception {
|
public void testSuspensionInsideSafeCall() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspensionInsideSafeCall.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user