Keep $continuation in LVT
in case of suspend functions and `this` is case of lambdas, since they are used in async stack trace generation. Update tests. #KT-40661 Open
This commit is contained in:
+71
-1
@@ -132,7 +132,7 @@ class CoroutineTransformerMethodVisitor(
|
||||
|
||||
UninitializedStoresProcessor(methodNode, shouldPreserveClassInitialization).run()
|
||||
|
||||
updateLvtAccordingToLiveness(methodNode)
|
||||
updateLvtAccordingToLiveness(methodNode, isForNamedFunction)
|
||||
|
||||
val spilledToVariableMapping = spillVariables(suspensionPoints, methodNode)
|
||||
|
||||
@@ -1000,3 +1000,73 @@ internal fun replaceFakeContinuationsWithRealOnes(methodNode: MethodNode, contin
|
||||
methodNode.instructions.set(fakeContinuation, VarInsnNode(Opcodes.ALOAD, continuationIndex))
|
||||
}
|
||||
}
|
||||
|
||||
/* We do not want to spill dead variables, thus, we shrink its LVT record to region, where the variable is alive,
|
||||
* so, the variable will not be visible in debugger. User can still prolong life span of the variable by using it.
|
||||
*
|
||||
* This means, that function parameters do not longer span the whole function, including `this`.
|
||||
* This might and will break some bytecode processors, including old versions of R8. See KT-24510.
|
||||
*/
|
||||
private fun updateLvtAccordingToLiveness(method: MethodNode, isForNamedFunction: Boolean) {
|
||||
val liveness = analyzeLiveness(method)
|
||||
|
||||
fun List<LocalVariableNode>.findRecord(insnIndex: Int, variableIndex: Int): LocalVariableNode? {
|
||||
for (variable in this) {
|
||||
if (variable.index == variableIndex &&
|
||||
method.instructions.indexOf(variable.start) <= insnIndex &&
|
||||
insnIndex < method.instructions.indexOf(variable.end)
|
||||
) return variable
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun isAlive(insnIndex: Int, variableIndex: Int): Boolean =
|
||||
liveness[insnIndex].isAlive(variableIndex)
|
||||
|
||||
val oldLvt = arrayListOf<LocalVariableNode>()
|
||||
for (record in method.localVariables) {
|
||||
oldLvt += record
|
||||
}
|
||||
method.localVariables.clear()
|
||||
// Skip `this` for suspend lamdba
|
||||
val start = if (isForNamedFunction) 0 else 1
|
||||
for (variableIndex in start until method.maxLocals) {
|
||||
if (oldLvt.none { it.index == variableIndex }) continue
|
||||
var startLabel: LabelNode? = null
|
||||
for (insnIndex in 0 until (method.instructions.size() - 1)) {
|
||||
val insn = method.instructions[insnIndex]
|
||||
if (!isAlive(insnIndex, variableIndex) && isAlive(insnIndex + 1, variableIndex)) {
|
||||
startLabel = insn as? LabelNode ?: insn.findNextOrNull { it is LabelNode } as? LabelNode
|
||||
}
|
||||
if (isAlive(insnIndex, variableIndex) && !isAlive(insnIndex + 1, variableIndex)) {
|
||||
// No variable in LVT -> do not add one
|
||||
val lvtRecord = oldLvt.findRecord(insnIndex, variableIndex) ?: continue
|
||||
if (lvtRecord.name == CONTINUATION_VARIABLE_NAME) continue
|
||||
val endLabel = insn as? LabelNode ?: insn.findNextOrNull { it is LabelNode } as? LabelNode ?: continue
|
||||
// startLabel can be null in case of parameters
|
||||
@Suppress("NAME_SHADOWING") val startLabel = startLabel ?: lvtRecord.start
|
||||
// No LINENUMBER in range -> no way to put a breakpoint -> do not bother adding a record
|
||||
if (InsnSequence(startLabel, endLabel).none { it is LineNumberNode }) continue
|
||||
method.localVariables.add(
|
||||
LocalVariableNode(lvtRecord.name, lvtRecord.desc, lvtRecord.signature, startLabel, endLabel, lvtRecord.index)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (variable in oldLvt) {
|
||||
// $completion, $continuation and $result are dead, but they are used by debugger, as well as fake inliner variables
|
||||
// For example, $continuation is used to create async stack trace
|
||||
if (variable.name == SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME ||
|
||||
variable.name == CONTINUATION_VARIABLE_NAME ||
|
||||
variable.name == SUSPEND_CALL_RESULT_NAME ||
|
||||
isFakeLocalVariableForInline(variable.name)
|
||||
) {
|
||||
method.localVariables.add(variable)
|
||||
}
|
||||
// this acts like $continuation for lambdas. For example, it is used by debugger to create async stack trace. Keep it.
|
||||
if (variable.name == "this" && !isForNamedFunction) {
|
||||
method.localVariables.add(variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-66
@@ -5,10 +5,10 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.optimization.common
|
||||
|
||||
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_CALL_RESULT_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME
|
||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.IincInsnNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.VarInsnNode
|
||||
import java.util.*
|
||||
|
||||
|
||||
@@ -59,65 +59,4 @@ private fun useVar(frame: VariableLivenessFrame, insn: AbstractInsnNode) {
|
||||
} else if (insn is IincInsnNode) {
|
||||
frame.markAlive(insn.`var`)
|
||||
}
|
||||
}
|
||||
|
||||
/* We do not want to spill dead variables, thus, we shrink its LVT record to region, where the variable is alive,
|
||||
* so, the variable will not be visible in debugger. User can still prolong life span of the variable by using it.
|
||||
*
|
||||
* This means, that function parameters do not longer span the whole function, including `this`.
|
||||
* This might and will break some bytecode processors, including old versions of R8. See KT-24510.
|
||||
*/
|
||||
fun updateLvtAccordingToLiveness(method: MethodNode) {
|
||||
val liveness = analyzeLiveness(method)
|
||||
|
||||
fun List<LocalVariableNode>.findRecord(insnIndex: Int, variableIndex: Int): LocalVariableNode? {
|
||||
for (variable in this) {
|
||||
if (variable.index == variableIndex &&
|
||||
method.instructions.indexOf(variable.start) <= insnIndex &&
|
||||
insnIndex < method.instructions.indexOf(variable.end)
|
||||
) return variable
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun isAlive(insnIndex: Int, variableIndex: Int): Boolean =
|
||||
liveness[insnIndex].isAlive(variableIndex)
|
||||
|
||||
val oldLvt = arrayListOf<LocalVariableNode>()
|
||||
for (record in method.localVariables) {
|
||||
oldLvt += record
|
||||
}
|
||||
method.localVariables.clear()
|
||||
for (variableIndex in 0 until method.maxLocals) {
|
||||
if (oldLvt.none { it.index == variableIndex }) continue
|
||||
var startLabel: LabelNode? = null
|
||||
for (insnIndex in 0 until (method.instructions.size() - 1)) {
|
||||
val insn = method.instructions[insnIndex]
|
||||
if (!isAlive(insnIndex, variableIndex) && isAlive(insnIndex + 1, variableIndex)) {
|
||||
startLabel = insn as? LabelNode ?: insn.findNextOrNull { it is LabelNode } as? LabelNode
|
||||
}
|
||||
if (isAlive(insnIndex, variableIndex) && !isAlive(insnIndex + 1, variableIndex)) {
|
||||
// No variable in LVT -> do not add one
|
||||
val lvtRecord = oldLvt.findRecord(insnIndex, variableIndex) ?: continue
|
||||
val endLabel = insn as? LabelNode ?: insn.findNextOrNull { it is LabelNode } as? LabelNode ?: continue
|
||||
// startLabel can be null in case of parameters
|
||||
@Suppress("NAME_SHADOWING") val startLabel = startLabel ?: lvtRecord.start
|
||||
// No LINENUMBER in range -> no way to put a breakpoint -> do not bother adding a record
|
||||
if (InsnSequence(startLabel, endLabel).none { it is LineNumberNode }) continue
|
||||
method.localVariables.add(
|
||||
LocalVariableNode(lvtRecord.name, lvtRecord.desc, lvtRecord.signature, startLabel, endLabel, lvtRecord.index)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (variable in oldLvt) {
|
||||
// $completion and $result are dead, but they are used by debugger, as well as fake inliner variables
|
||||
if (variable.name == SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME ||
|
||||
variable.name == SUSPEND_CALL_RESULT_NAME ||
|
||||
isFakeLocalVariableForInline(variable.name)
|
||||
) {
|
||||
method.localVariables.add(variable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+3
-2
@@ -5,6 +5,7 @@ class A {
|
||||
suspend fun foo1(l: Long) {
|
||||
foo()
|
||||
foo()
|
||||
val dead = l
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +13,5 @@ class A {
|
||||
// VARIABLE : NAME=this TYPE=LA; INDEX=0
|
||||
// VARIABLE : NAME=l TYPE=J INDEX=1
|
||||
// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=3
|
||||
// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=5
|
||||
// VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=4
|
||||
// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=7
|
||||
// VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=6
|
||||
Vendored
+3
-2
@@ -4,10 +4,11 @@ suspend fun foo() {}
|
||||
suspend fun foo1(l: Long) {
|
||||
foo()
|
||||
foo()
|
||||
val dead = l
|
||||
}
|
||||
|
||||
// METHOD : StaticStateMachineKt.foo1(JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
// VARIABLE : NAME=l TYPE=J INDEX=0
|
||||
// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=2
|
||||
// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=4
|
||||
// VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=3
|
||||
// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=6
|
||||
// VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=5
|
||||
compiler/testData/checkLocalVariablesTable/completionInSuspendFunction/staticStateMachineReceiver.kt
Vendored
+3
-2
@@ -6,11 +6,12 @@ suspend fun A.foo() {}
|
||||
suspend fun A.foo1(l: Long) {
|
||||
foo()
|
||||
foo()
|
||||
val dead = l
|
||||
}
|
||||
|
||||
// METHOD : StaticStateMachineReceiverKt.foo1(LA;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
// VARIABLE : NAME=$this$foo1 TYPE=LA; INDEX=0
|
||||
// VARIABLE : NAME=l TYPE=J INDEX=1
|
||||
// VARIABLE : NAME=$completion TYPE=Lkotlin/coroutines/Continuation; INDEX=3
|
||||
// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=5
|
||||
// VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=4
|
||||
// VARIABLE : NAME=$continuation TYPE=Lkotlin/coroutines/Continuation; INDEX=7
|
||||
// VARIABLE : NAME=$result TYPE=Ljava/lang/Object; INDEX=6
|
||||
-1
@@ -18,7 +18,6 @@ suspend fun foo(data: Data, body: suspend (Data) -> Unit) {
|
||||
// METHOD : DataClassKt$test$2.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// VARIABLE : NAME=$dstr$x_param$y_param TYPE=LData; INDEX=2
|
||||
// VARIABLE : NAME=x_param TYPE=Ljava/lang/String; INDEX=3
|
||||
// VARIABLE : NAME=y_param TYPE=I INDEX=4
|
||||
// VARIABLE : NAME=this TYPE=LDataClassKt$test$2; INDEX=0
|
||||
|
||||
Vendored
+3
-2
@@ -12,7 +12,9 @@ object B {
|
||||
suspend fun B.bar(): String {
|
||||
operator fun <R> A<R>.component3() = z
|
||||
|
||||
return foo(A("O", "K", 123)) { (x_param, y_param, z_param) -> x_param + y_param + z_param.toString() }
|
||||
return foo(A("O", "K", 123)) { (x_param, y_param, z_param) ->
|
||||
x_param + y_param + z_param.toString()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun test() = B.bar()
|
||||
@@ -26,7 +28,6 @@ suspend fun test() = B.bar()
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// METHOD : ExtensionComponentsKt$bar$3.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
// VARIABLE : NAME=$dstr$x_param$y_param$z_param TYPE=LA; INDEX=2
|
||||
// VARIABLE : NAME=x_param TYPE=Ljava/lang/String; INDEX=3
|
||||
// VARIABLE : NAME=y_param TYPE=Ljava/lang/String; INDEX=4
|
||||
// VARIABLE : NAME=z_param TYPE=I INDEX=5
|
||||
|
||||
+3
-2
@@ -4,7 +4,9 @@ data class A<T, F>(val x: T, val y: F)
|
||||
|
||||
suspend fun <X, Y> foo(a: A<X, Y>, block: suspend (A<X, Y>) -> String) = block(a)
|
||||
|
||||
suspend fun test() = foo(A("OK", 1)) { (x_param, y_param) -> x_param + (y_param.toString()) }
|
||||
suspend fun test() = foo(A("OK", 1)) { (x_param, y_param) ->
|
||||
x_param + (y_param.toString())
|
||||
}
|
||||
|
||||
// Parameters (including anonymous destructuring parameters) are moved to fields in the Continuation class for the suspend lambda class.
|
||||
// However, in non-IR, the fields are first stored in local variables, and they are not read directly (even for destructuring components).
|
||||
@@ -13,7 +15,6 @@ suspend fun test() = foo(A("OK", 1)) { (x_param, y_param) -> x_param + (y_param.
|
||||
// METHOD : GenericKt$test$2.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// VARIABLE : NAME=$dstr$x_param$y_param TYPE=LA; INDEX=2
|
||||
// VARIABLE : NAME=x_param TYPE=Ljava/lang/String; INDEX=3
|
||||
// VARIABLE : NAME=y_param TYPE=I INDEX=4
|
||||
// VARIABLE : NAME=this TYPE=LGenericKt$test$2; INDEX=0
|
||||
|
||||
+3
-2
@@ -3,7 +3,9 @@ data class A(val x: String, val y: String)
|
||||
|
||||
suspend fun foo(a: A, block: suspend (Int, A, String) -> String): String = block(1, a, "#")
|
||||
|
||||
suspend fun test() = foo(A("O", "K")) { i_param, (x_param, y_param), v_param -> i_param.toString() + x_param + y_param + v_param }
|
||||
suspend fun test() = foo(A("O", "K")) { i_param, (x_param, y_param), v_param ->
|
||||
i_param.toString() + x_param + y_param + v_param
|
||||
}
|
||||
|
||||
// Parameters (including anonymous destructuring parameters) are moved to fields in the Continuation class for the suspend lambda class.
|
||||
// However, in non-IR, the fields are first stored in local variables, and they are not read directly (even for destructuring components).
|
||||
@@ -13,7 +15,6 @@ suspend fun test() = foo(A("O", "K")) { i_param, (x_param, y_param), v_param ->
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// VARIABLE : NAME=i_param TYPE=I INDEX=2
|
||||
// VARIABLE : NAME=$dstr$x_param$y_param TYPE=LA; INDEX=3
|
||||
// VARIABLE : NAME=v_param TYPE=Ljava/lang/String; INDEX=4
|
||||
// VARIABLE : NAME=x_param TYPE=Ljava/lang/String; INDEX=5
|
||||
// VARIABLE : NAME=y_param TYPE=Ljava/lang/String; INDEX=6
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ suspend fun foo(data: Data, body: suspend Long.(String, Data, Int) -> Unit) {
|
||||
// JVM_TEMPLATES
|
||||
// VARIABLE : NAME=$this$foo TYPE=J INDEX=2
|
||||
// VARIABLE : NAME=str TYPE=Ljava/lang/String; INDEX=4
|
||||
// VARIABLE : NAME=$dstr$x$_u24__u24$z TYPE=LData; INDEX=5
|
||||
// VARIABLE : NAME=i TYPE=I INDEX=6
|
||||
// VARIABLE : NAME=x TYPE=Ljava/lang/String; INDEX=7
|
||||
// VARIABLE : NAME=z TYPE=I INDEX=8
|
||||
|
||||
+3
-2
@@ -7,7 +7,9 @@ class A {
|
||||
|
||||
suspend fun foo(a: A, block: suspend (A) -> String): String = block(a)
|
||||
|
||||
suspend fun test() = foo(A()) { (x_param, _, y_param) -> x_param + y_param }
|
||||
suspend fun test() = foo(A()) { (x_param, _, y_param) ->
|
||||
x_param + y_param
|
||||
}
|
||||
|
||||
// Parameters (including anonymous destructuring parameters) are moved to fields in the Continuation class for the suspend lambda class.
|
||||
// However, in non-IR, the fields are first stored in local variables, and they are not read directly (even for destructuring components).
|
||||
@@ -16,7 +18,6 @@ suspend fun test() = foo(A()) { (x_param, _, y_param) -> x_param + y_param }
|
||||
// METHOD : UnderscoreNamesKt$test$2.invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
|
||||
// JVM_TEMPLATES
|
||||
// VARIABLE : NAME=$dstr$x_param$_u24__u24$y_param TYPE=LA; INDEX=2
|
||||
// VARIABLE : NAME=x_param TYPE=Ljava/lang/String; INDEX=3
|
||||
// VARIABLE : NAME=y_param TYPE=Ljava/lang/String; INDEX=4
|
||||
// VARIABLE : NAME=this TYPE=LUnderscoreNamesKt$test$2; INDEX=0
|
||||
|
||||
+3
-1
@@ -22,6 +22,7 @@ fun box(): String {
|
||||
try {
|
||||
var i: String = "abc"
|
||||
i = "123"
|
||||
println(i)
|
||||
} finally { }
|
||||
|
||||
// This variable should take the same slot as 'i' had
|
||||
@@ -46,4 +47,5 @@ fun box(): String {
|
||||
// 0 PUTFIELD VarValueConflictsWithTableSameSort_1_2Kt\$box\$1.L\$0 : Ljava/lang/Object;
|
||||
/* 1 load in try/finally */
|
||||
/* 1 load in result = s */
|
||||
// 2 ALOAD 3
|
||||
/* 1 load in println */
|
||||
// 3 ALOAD 3
|
||||
|
||||
+4
@@ -5,12 +5,15 @@ suspend fun main() {
|
||||
val b = 7
|
||||
none()
|
||||
foo()
|
||||
val dead1 = a
|
||||
val dead2 = b
|
||||
}
|
||||
|
||||
suspend fun foo() {
|
||||
val x = "foo"
|
||||
none()
|
||||
bar()
|
||||
val dead = x
|
||||
}
|
||||
|
||||
suspend fun bar() {
|
||||
@@ -19,6 +22,7 @@ suspend fun bar() {
|
||||
val z = "bar"
|
||||
//Breakpoint!
|
||||
val a = 5
|
||||
val dead = y
|
||||
}
|
||||
|
||||
suspend fun none() {}
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
LineBreakpoint created at asyncFunctions.kt:21
|
||||
LineBreakpoint created at asyncFunctions.kt:24
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
asyncFunctions.kt:21
|
||||
asyncFunctions.kt:24
|
||||
Async stack trace:
|
||||
asyncFunctions.AsyncFunctionsKt:18
|
||||
asyncFunctions.AsyncFunctionsKt:21
|
||||
y = "zoo"
|
||||
asyncFunctions.AsyncFunctionsKt:13
|
||||
asyncFunctions.AsyncFunctionsKt:15
|
||||
x = "foo"
|
||||
asyncFunctions.AsyncFunctionsKt:7
|
||||
a = 5
|
||||
|
||||
@@ -19,7 +19,7 @@ suspend fun test1(i: Int): Int {
|
||||
suspend fun a(aParam: String) {
|
||||
val a = "a"
|
||||
b(a)
|
||||
a + 1
|
||||
a + aParam
|
||||
}
|
||||
|
||||
suspend fun b(bParam: String) {
|
||||
|
||||
+4
-4
@@ -9,7 +9,7 @@ fun main() {
|
||||
runBlocking {
|
||||
val rootCoroutineVal = mainTestVal
|
||||
TestSuspendA().a()
|
||||
print("")
|
||||
print(rootCoroutineVal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class TestSuspendA {
|
||||
suspend fun a() {
|
||||
val methodVal = ""
|
||||
InClassB().b()
|
||||
print("")
|
||||
print(methodVal)
|
||||
}
|
||||
|
||||
class InClassB {
|
||||
@@ -27,7 +27,7 @@ class TestSuspendA {
|
||||
suspend fun b() {
|
||||
val inClassBMethodVal = ""
|
||||
InClassC().c()
|
||||
print("")
|
||||
print(inClassBMethodVal)
|
||||
}
|
||||
|
||||
inner class InClassC {
|
||||
@@ -37,7 +37,7 @@ class TestSuspendA {
|
||||
yield()
|
||||
val inClassCMethodVal = ""
|
||||
//Breakpoint!
|
||||
print("")
|
||||
print(inClassCMethodVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,12 +4,12 @@ Connected to the target VM
|
||||
suspendFunWithInner.kt:40
|
||||
Thread stack trace:
|
||||
CoroutinePreflightFrame c:!LINE_NUMBER!, TestSuspendA$InClassB$InClassC (continuation)
|
||||
($completion, $continuation, $result, inClassCMethodVal, this)
|
||||
($completion, $continuation, $result, inClassCMethodVal)
|
||||
CoroutineInfo: 1 coroutine RUNNING
|
||||
CoroutineStackFrame b:!LINE_NUMBER!, TestSuspendA$InClassB (continuation)
|
||||
(inClassBMethodVal, this)
|
||||
(inClassBMethodVal)
|
||||
CoroutineStackFrame a:!LINE_NUMBER!, TestSuspendA (continuation)
|
||||
(methodVal, this)
|
||||
(methodVal)
|
||||
CoroutineStackFrame invokeSuspend:!LINE_NUMBER!, SuspendFunWithInnerKt$main$1 (continuation)
|
||||
(rootCoroutineVal)
|
||||
CoroutineStackFrame resumeWith:!LINE_NUMBER!, BaseContinuationImpl (kotlin.coroutines.jvm.internal)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ suspend fun main() {
|
||||
val a = 5
|
||||
foo()
|
||||
//Breakpoint!
|
||||
val b = 4
|
||||
val b = a
|
||||
}
|
||||
|
||||
suspend fun foo() {}
|
||||
|
||||
+3
-1
@@ -14,16 +14,18 @@ fun main() {
|
||||
suspend fun a() {
|
||||
val a = "a"
|
||||
b(a)
|
||||
val aLate = "a" // to prevent stackFrame to collapse
|
||||
val aLate = a // to prevent stackFrame to collapse
|
||||
}
|
||||
|
||||
suspend fun b(paramA: String) {
|
||||
yield()
|
||||
val b = "b"
|
||||
c(b)
|
||||
val dead = paramA
|
||||
}
|
||||
|
||||
suspend fun c(paramB: String) {
|
||||
val c = "c"
|
||||
//Breakpoint!
|
||||
val dead = paramB
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
LineBreakpoint created at coroutineSuspendFun.kt:29
|
||||
LineBreakpoint created at coroutineSuspendFun.kt:30
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
coroutineSuspendFun.kt:29
|
||||
coroutineSuspendFun.kt:30
|
||||
Thread stack trace:
|
||||
KotlinStackFrame c:!LINE_NUMBER!, CoroutineSuspendFunKt (continuation)
|
||||
($completion, c, paramB)
|
||||
|
||||
+3
-1
@@ -14,16 +14,18 @@ fun main() {
|
||||
suspend fun a() {
|
||||
val a = "a"
|
||||
b(a)
|
||||
val aLate = "a" // to prevent stackFrame to collapse
|
||||
val aLate = a // to prevent stackFrame to collapse
|
||||
}
|
||||
|
||||
suspend fun b(paramA: String) {
|
||||
yield()
|
||||
val b = "b"
|
||||
c(b)
|
||||
val dead = paramA
|
||||
}
|
||||
|
||||
suspend fun c(paramB: String) {
|
||||
val c = "c"
|
||||
//Breakpoint!
|
||||
val dead = paramB
|
||||
}
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
LineBreakpoint created at coroutineSuspendFun136.kt:29
|
||||
LineBreakpoint created at coroutineSuspendFun136.kt:30
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
coroutineSuspendFun136.kt:29
|
||||
coroutineSuspendFun136.kt:30
|
||||
Thread stack trace:
|
||||
KotlinStackFrame c:!LINE_NUMBER!, CoroutineSuspendFun136Kt (continuation)
|
||||
($completion, c, paramB)
|
||||
|
||||
@@ -4,7 +4,7 @@ Connected to the target VM
|
||||
suspendMain.kt:15
|
||||
Thread stack trace:
|
||||
CoroutinePreflightFrame invokeSuspend:!LINE_NUMBER!, SuspendMainKt$main$2 (continuation)
|
||||
($result, $this$coroutineScope, this)
|
||||
($result, this)
|
||||
CoroutineInfo: -1 coroutine UNKNOWN
|
||||
CoroutineStackFrame startUndispatchedOrReturn:!LINE_NUMBER!, UndispatchedKt (kotlinx.coroutines.intrinsics)
|
||||
($i$a$-undispatchedResult-UndispatchedKt$startUndispatchedOrReturn$2, $i$f$undispatchedResult, $this$startUndispatchedOrReturn, $this$undispatchedResult$iv, block, receiver)
|
||||
|
||||
Reference in New Issue
Block a user