diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt index c86e7e777c7..6a3985594f7 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt @@ -417,6 +417,7 @@ fun MethodNode.textifyMethodNode(): String { val text = Textifier() val tmv = TraceMethodVisitor(text) this.instructions.asSequence().forEach { it.accept(tmv) } + localVariables.forEach { text.visitLocalVariable(it.name, it.desc, it.signature, it.start.label, it.end.label, it.index) } val sw = StringWriter() text.print(PrintWriter(sw)) return "$sw" diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index 4d634d7e22e..d8782483a7e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.TransformationMethodVisitor import org.jetbrains.kotlin.codegen.inline.* import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.boxing.isPrimitiveUnboxing import org.jetbrains.kotlin.codegen.optimization.common.* import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer import org.jetbrains.kotlin.codegen.optimization.fixStack.top @@ -20,6 +21,8 @@ import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.isReleaseCoroutines import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.kotlin.utils.addToStdlib.cast +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.sure import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.MethodVisitor @@ -532,6 +535,7 @@ class CoroutineTransformerMethodVisitor( } remove(possibleTryCatchBlockStart.previous) + val afterSuspensionPointLineNumber = nextLineNumberNode?.line ?: suspendElementLineNumber insert(possibleTryCatchBlockStart, withInstructionAdapter { generateResumeWithExceptionCheck(languageVersionSettings.isReleaseCoroutines(), dataIndex, exceptionIndex) @@ -542,10 +546,34 @@ class CoroutineTransformerMethodVisitor( // Extend next instruction linenumber. Can't use line number of suspension point here because both non-suspended execution // and re-entering after suspension passes this label. - val afterSuspensionPointLineNumber = nextLineNumberNode?.line ?: suspendElementLineNumber - visitLineNumber(afterSuspensionPointLineNumber, continuationLabelAfterLoadedResult.label) + + // However, for primitives we generate it separately + if (possibleTryCatchBlockStart.next?.isUnboxingSequence() != true) { + visitLineNumber(afterSuspensionPointLineNumber, continuationLabelAfterLoadedResult.label) + } }) + // In code like val a = suspendReturnsInt() + // `a` is coerced from Object to int, and coercion happens before scopeStart's mark: + // LL + // CHECKCAST java/lang/Number + // INVOKEVIRTUAL java/lang/Number.intValue ()I + // ISTORE N + // LM + // /* put lineNumber here */ + // ... + // LOCALVARIABLE name LM LK N + if (continuationLabelAfterLoadedResult.label.info.safeAs()?.next?.isUnboxingSequence() == true) { + // Find next label after unboxing and put linenumber there + var current = (continuationLabelAfterLoadedResult.label.info as AbstractInsnNode).next + while (current != null && current !is LabelNode) { + current = current.next + } + if (current != null) { + insert(current, LineNumberNode(afterSuspensionPointLineNumber, current.cast())) + } + } + if (nextLineNumberNode != null) { // Remove the line number instruction as it now covered with line number on continuation label. // If both linenumber are present in bytecode, debugger will trigger line specific events twice. @@ -556,6 +584,10 @@ class CoroutineTransformerMethodVisitor( return continuationLabel } + private fun AbstractInsnNode.isUnboxingSequence(): Boolean { + return opcode == Opcodes.CHECKCAST && next?.isPrimitiveUnboxing() == true + } + // It's necessary to preserve some sensible invariants like there should be no jump in the middle of try-catch-block // Also it's important that spilled variables are being restored outside of TCB, // otherwise they would be treated as uninitialized within catch-block while they can be used there diff --git a/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt b/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt new file mode 100644 index 00000000000..1ed0b1ab69d --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt @@ -0,0 +1,18 @@ +// LANGUAGE_VERSION: 1.3 + +import kotlin.coroutines.* +import kotlin.sequences.* + +fun main(args: Array) { + val s = buildSequence { + yield(1) + val a = awaitSeq() + println(a) // (1) + } + println(s.toList()) +} + +suspend fun SequenceBuilder.awaitSeq(): Int = 42 + +// 1 LOCALVARIABLE a I L18 L22 3 +// 1 LINENUMBER 10 L18 \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 0060254d2b8..385d0e43f34 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -1119,6 +1119,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { runTest("compiler/testData/codegen/bytecodeText/coroutines/debug/continuationInLvt.kt"); } + @TestMetadata("localVariableCorrectLabel.kt") + public void testLocalVariableCorrectLabel() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/coroutines/debug/localVariableCorrectLabel.kt"); + } + @TestMetadata("probeCoroutineSuspended.kt") public void testProbeCoroutineSuspended() throws Exception { runTest("compiler/testData/codegen/bytecodeText/coroutines/debug/probeCoroutineSuspended.kt"); diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.kt new file mode 100644 index 00000000000..eeec6575172 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.kt @@ -0,0 +1,18 @@ +package primitivesCoertion + +import kotlin.coroutines.experimental.* + +fun main(args: Array) { + val a = buildSequence { + yield(1) + val a = awaitSeq() + //Breakpoint! + println(a) // (1) + } + println(a.toList()) +} + +suspend fun SequenceBuilder.awaitSeq(): Int = 42 + +// EXPRESSION: a +// RESULT: 42: I diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.out new file mode 100644 index 00000000000..11b7f6460d5 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.out @@ -0,0 +1,10 @@ +LineBreakpoint created at primitivesCoertion.kt:10 +Run Java +Connected to the target VM +primitivesCoertion.kt:10 +Compile bytecode for a +Disconnected from the target VM + +Process finished with exit code 0 +42 +[1] diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index dbcf87e5038..f35d247e1a2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -409,6 +409,24 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat } } + @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Coroutines extends AbstractKotlinEvaluateExpressionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doSingleBreakpointTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInCoroutines() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("primitivesCoertion.kt") + public void testPrimitivesCoertion() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/coroutines/primitivesCoertion.kt"); + } + } + @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/createExpression") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)