Fix line numbers generation for coerced primitives
in coroutines #KT-25076: Fixed
This commit is contained in:
@@ -417,6 +417,7 @@ fun MethodNode.textifyMethodNode(): String {
|
|||||||
val text = Textifier()
|
val text = Textifier()
|
||||||
val tmv = TraceMethodVisitor(text)
|
val tmv = TraceMethodVisitor(text)
|
||||||
this.instructions.asSequence().forEach { it.accept(tmv) }
|
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()
|
val sw = StringWriter()
|
||||||
text.print(PrintWriter(sw))
|
text.print(PrintWriter(sw))
|
||||||
return "$sw"
|
return "$sw"
|
||||||
|
|||||||
+34
-2
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.StackValue
|
|||||||
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
|
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
|
||||||
import org.jetbrains.kotlin.codegen.inline.*
|
import org.jetbrains.kotlin.codegen.inline.*
|
||||||
import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer
|
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.common.*
|
||||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
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.config.isReleaseCoroutines
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
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.kotlin.utils.sure
|
||||||
import org.jetbrains.org.objectweb.asm.Label
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||||
@@ -532,6 +535,7 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
}
|
}
|
||||||
remove(possibleTryCatchBlockStart.previous)
|
remove(possibleTryCatchBlockStart.previous)
|
||||||
|
|
||||||
|
val afterSuspensionPointLineNumber = nextLineNumberNode?.line ?: suspendElementLineNumber
|
||||||
insert(possibleTryCatchBlockStart, withInstructionAdapter {
|
insert(possibleTryCatchBlockStart, withInstructionAdapter {
|
||||||
generateResumeWithExceptionCheck(languageVersionSettings.isReleaseCoroutines(), dataIndex, exceptionIndex)
|
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
|
// 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.
|
// 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<AbstractInsnNode>()?.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) {
|
if (nextLineNumberNode != null) {
|
||||||
// Remove the line number instruction as it now covered with line number on continuation label.
|
// 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.
|
// If both linenumber are present in bytecode, debugger will trigger line specific events twice.
|
||||||
@@ -556,6 +584,10 @@ class CoroutineTransformerMethodVisitor(
|
|||||||
return continuationLabel
|
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
|
// 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,
|
// 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
|
// otherwise they would be treated as uninitialized within catch-block while they can be used there
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
// LANGUAGE_VERSION: 1.3
|
||||||
|
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
import kotlin.sequences.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val s = buildSequence {
|
||||||
|
yield(1)
|
||||||
|
val a = awaitSeq()
|
||||||
|
println(a) // (1)
|
||||||
|
}
|
||||||
|
println(s.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun SequenceBuilder<Int>.awaitSeq(): Int = 42
|
||||||
|
|
||||||
|
// 1 LOCALVARIABLE a I L18 L22 3
|
||||||
|
// 1 LINENUMBER 10 L18
|
||||||
@@ -1119,6 +1119,11 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
runTest("compiler/testData/codegen/bytecodeText/coroutines/debug/continuationInLvt.kt");
|
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")
|
@TestMetadata("probeCoroutineSuspended.kt")
|
||||||
public void testProbeCoroutineSuspended() throws Exception {
|
public void testProbeCoroutineSuspended() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/coroutines/debug/probeCoroutineSuspended.kt");
|
runTest("compiler/testData/codegen/bytecodeText/coroutines/debug/probeCoroutineSuspended.kt");
|
||||||
|
|||||||
Vendored
+18
@@ -0,0 +1,18 @@
|
|||||||
|
package primitivesCoertion
|
||||||
|
|
||||||
|
import kotlin.coroutines.experimental.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val a = buildSequence {
|
||||||
|
yield(1)
|
||||||
|
val a = awaitSeq()
|
||||||
|
//Breakpoint!
|
||||||
|
println(a) // (1)
|
||||||
|
}
|
||||||
|
println(a.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun SequenceBuilder<Int>.awaitSeq(): Int = 42
|
||||||
|
|
||||||
|
// EXPRESSION: a
|
||||||
|
// RESULT: 42: I
|
||||||
Vendored
+10
@@ -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]
|
||||||
Generated
+18
@@ -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")
|
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/createExpression")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user