Make breakpoints work in suspend functions

This commit is contained in:
Nikolay Krasko
2017-01-24 13:28:42 +03:00
parent 649ec9a73d
commit c525a59df8
10 changed files with 226 additions and 20 deletions
@@ -30,11 +30,11 @@ import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.xdebugger.impl.XDebugSessionImpl
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.containsNonTailSuspensionCalls
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
@@ -145,27 +145,35 @@ class DebuggerClassNameProvider(val myDebugProcess: DebugProcess, val scopes: Li
}
}
element is KtProperty && (!element.readAction { it.isTopLevel } || !isInLibrary) -> {
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
if (descriptor !is PropertyDescriptor) {
return CachedClassNames(classNamesForPosition(elementOfClassName, withInlines))
}
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element) as? PropertyDescriptor ?:
return CachedClassNames(classNamesForPosition(elementOfClassName, withInlines))
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
}
element is KtNamedFunction -> {
val parentInternalName = if (elementOfClassName is KtClassOrObject) {
getClassNameForClass(elementOfClassName, typeMapper)
}
else if (elementOfClassName != null) {
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
asmType.internalName
}
else {
getClassNameForFile(file)
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
val parentInternalName = when {
isSuspendDescriptor(descriptor, typeMapper.bindingContext) -> {
CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName
}
elementOfClassName is KtClassOrObject -> getClassNameForClass(elementOfClassName, typeMapper)
elementOfClassName != null -> {
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
asmType.internalName
}
else -> {
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
if (isSuspendDescriptor(descriptor, typeMapper.bindingContext)) {
CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName
}
else {
getClassNameForFile(file)
}
}
}
if (!withInlines) return NonCachedClassNames(parentInternalName)
val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext)
if (parentInternalName == null) return CachedClassNames(inlinedCalls)
@@ -178,6 +186,10 @@ class DebuggerClassNameProvider(val myDebugProcess: DebugProcess, val scopes: Li
return CachedClassNames(getClassNameForFile(file))
}
private fun isSuspendDescriptor(descriptor: DeclarationDescriptor?, bindingContext: BindingContext): Boolean {
return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext)
}
private fun inlineCallClassPatterns(typeMapper: KotlinTypeMapper, element: KtElement): List<String> {
val context = typeMapper.bindingContext
@@ -215,10 +227,20 @@ class DebuggerClassNameProvider(val myDebugProcess: DebugProcess, val scopes: Li
val originalInternalClassName = CodegenBinding.asmTypeForAnonymousClass(
typeMapper.bindingContext, ktAnonymousClassElementProducer).internalName
val ownerDescriptorName = lexicalScope.ownerDescriptor.name
val ownerDescriptor = lexicalScope.ownerDescriptor
val mangledInternalClassName = originalInternalClassName.funPrefix() + (if (ownerDescriptorName.isSpecial) "\$\$special\$" else "$") +
InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName
val className = if (isSuspendDescriptor(ownerDescriptor, typeMapper.bindingContext)) {
originalInternalClassName.replaceAfterLast("$", DO_RESUME_METHOD_NAME)
}
else {
originalInternalClassName.funPrefix()
}
val mangledInternalClassName =
className +
(if (ownerDescriptor.name.isSpecial) "\$\$special\$" else "$") +
InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" +
inlineFunctionName
return listOf("$mangledInternalClassName*")
}
@@ -0,0 +1,8 @@
LineBreakpoint created at stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral.kt:26
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral.StopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteralKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral.kt:26
stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral.kt:27
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at stopInSuspendFunctionWithSuspendPoints.kt:16
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stopInSuspendFunctionWithSuspendPoints.StopInSuspendFunctionWithSuspendPointsKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stopInSuspendFunctionWithSuspendPoints.kt:16
stopInSuspendFunctionWithSuspendPoints.kt:17
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at stopInSuspendFunctionWithSuspendPointsInAnonymousObject.kt:20
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stopInSuspendFunctionWithSuspendPointsInAnonymousObject.StopInSuspendFunctionWithSuspendPointsInAnonymousObjectKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stopInSuspendFunctionWithSuspendPointsInAnonymousObject.kt:20
stopInSuspendFunctionWithSuspendPointsInAnonymousObject.kt:21
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure.kt:28
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure.StopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosureKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure.kt:28
stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure.kt:29
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,40 @@
package stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral
import forTests.builder
fun foo(a: Any) {}
suspend fun second() { }
interface Bar {
suspend fun first()
}
inline fun call(f: () -> Unit) {
f()
}
val bar = object : Bar {
val t = 121
override suspend fun first() {
foo("first")
call {
{
//Breakpoint!
foo(t)
}()
}
second()
foo("second")
}
}
fun main(args: Array<String>) {
builder {
bar.first()
}
}
@@ -0,0 +1,23 @@
package stopInSuspendFunctionWithSuspendPoints
import forTests.builder
fun foo(a: Any) {}
suspend fun second() {
}
suspend fun first() {
foo("first")
second()
//Breakpoint!
foo("second")
}
fun main(args: Array<String>) {
builder {
first()
}
}
@@ -0,0 +1,28 @@
package stopInSuspendFunctionWithSuspendPointsInAnonymousObject
import forTests.builder
fun foo(a: Any) {}
suspend fun second() { }
interface Bar {
suspend fun first()
}
val bar = object : Bar {
override suspend fun first() {
foo("first")
second()
//Breakpoint!
foo("second")
}
}
fun main(args: Array<String>) {
builder {
bar.first()
}
}
@@ -0,0 +1,37 @@
package stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure
import forTests.builder
fun foo(a: Any) {}
suspend fun second() {
foo("call")
}
interface F {
suspend fun test() {}
}
inline fun call(f: () -> Unit) {
f()
}
fun main(args: Array<String>) {
val inClosure = "first"
call {
{
val some = object : F {
override suspend fun test() {
foo(inClosure)
second()
//Breakpoint!
foo("other")
}
}
builder {
some.test()
}
}()
}
}
@@ -752,6 +752,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepOverTest(fileName);
}
@TestMetadata("stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral.kt")
public void testStopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInInlinedLambdaInSuspendFunctionWithSuspendPointsInObjectLiteral.kt");
doStepOverTest(fileName);
}
@TestMetadata("stopInLabdaOfCrossinlineCalledInAnonymous.kt")
public void testStopInLabdaOfCrossinlineCalledInAnonymous() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInLabdaOfCrossinlineCalledInAnonymous.kt");
@@ -818,6 +824,24 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepOverTest(fileName);
}
@TestMetadata("stopInSuspendFunctionWithSuspendPoints.kt")
public void testStopInSuspendFunctionWithSuspendPoints() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInSuspendFunctionWithSuspendPoints.kt");
doStepOverTest(fileName);
}
@TestMetadata("stopInSuspendFunctionWithSuspendPointsInAnonymousObject.kt")
public void testStopInSuspendFunctionWithSuspendPointsInAnonymousObject() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInSuspendFunctionWithSuspendPointsInAnonymousObject.kt");
doStepOverTest(fileName);
}
@TestMetadata("stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure.kt")
public void testStopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInSuspendFunctionWithSuspendPointsInObjectLiteralInInlineCallWithClosure.kt");
doStepOverTest(fileName);
}
@TestMetadata("stopInSuspendFunctionWithoutSuspendPoints.kt")
public void testStopInSuspendFunctionWithoutSuspendPoints() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInSuspendFunctionWithoutSuspendPoints.kt");