diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index 894410563bb..0dd43c51741 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -25,6 +25,14 @@ import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.KtFunction import java.util.* +public fun isInsideInlineFunctionBody(visibleVariables: List): Boolean { + return visibleVariables.any { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } +} + +public fun numberOfInlinedFunctions(visibleVariables: List): Int { + return visibleVariables.count { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } +} + fun isInsideInlineArgument(inlineArgument: KtFunction, location: Location, debugProcess: DebugProcessImpl): Boolean { val visibleVariables = location.visibleVariables(debugProcess) val lambdaOrdinalIndex = runReadAction { lambdaOrdinalIndex(inlineArgument) } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt index 7f414df9d10..e040b4a232f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameVisitor.kt @@ -27,6 +27,8 @@ import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.eval4j.obj import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil +import org.jetbrains.kotlin.idea.debugger.isInsideInlineFunctionBody +import org.jetbrains.kotlin.idea.debugger.numberOfInlinedFunctions import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.jvm.AsmTypes @@ -48,6 +50,14 @@ class FrameVisitor(context: EvaluationContextImpl) { } } else -> { + if (isInsideInlineFunctionBody(frame.visibleVariables())) { + val number = numberOfInlinedFunctions(frame.visibleVariables()) + val inlineFunVar = findLocalVariableForInlineArgument(name, number, asmType, true) + if (inlineFunVar != null) { + return inlineFunVar + } + } + val localVariable = if (isFunctionType(asmType)) findLocalVariableForLocalFun(name, asmType, checkType) else @@ -77,7 +87,15 @@ class FrameVisitor(context: EvaluationContextImpl) { } private fun findThis(asmType: Type?): Value? { - val thisObject = frame!!.thisObject() + if (isInsideInlineFunctionBody(frame!!.visibleVariables())) { + val number = numberOfInlinedFunctions(frame.visibleVariables()) + val inlineFunVar = findLocalVariableForInlineArgument("this_", number, asmType, true) + if (inlineFunVar != null) { + return inlineFunVar + } + } + + val thisObject = frame.thisObject() if (thisObject != null) { val eval4jValue = thisObject.asValue() if (isValueOfCorrectType(eval4jValue, asmType, true)) return eval4jValue @@ -99,6 +117,10 @@ class FrameVisitor(context: EvaluationContextImpl) { return findLocalVariable(name + "$", asmType, checkType) } + private fun findLocalVariableForInlineArgument(name: String, number: Int, asmType: Type?, checkType: Boolean): Value? { + return findLocalVariable(name + InlineCodegenUtil.INLINE_FUN_VAR_SUFFIX.repeat(number), asmType, checkType) + } + private fun isFunctionType(type: Type?): Boolean { return type?.sort == Type.OBJECT && type!!.internalName.startsWith(InlineCodegenUtil.NUMBERED_FUNCTION_PREFIX) diff --git a/idea/testData/debugger/tinyApp/outs/frameInlineArgument.out b/idea/testData/debugger/tinyApp/outs/frameInlineArgument.out new file mode 100644 index 00000000000..f5502f33109 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/frameInlineArgument.out @@ -0,0 +1,39 @@ +LineBreakpoint created at frameInlineArgument.kt:7 +!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! frameInlineArgument.FrameInlineArgumentKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +frameInlineArgument.kt:7 +Compile bytecode for element +package frameInlineArgument + +fun main(args: Array) { + val element = 1 + A().inlineFun { + //Breakpoint! + element + } +} + +class A { + inline fun inlineFun(s: () -> Unit) { + val element = 1.0 + s() + } + + val prop = 1 +} + +// PRINT_FRAME + +// EXPRESSION: element +// RESULT: 1: I + + + frame = main():7, FrameInlineArgumentKt {frameInlineArgument} + local = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = frameInlineArgument.kt, 3) + local = element: int = 1 (sp = frameInlineArgument.kt, 4) + local = this_$iv: frameInlineArgument.A = {frameInlineArgument.A@uniqueID} (sp = null) + field = prop: int = 1 (sp = frameInlineArgument.kt, 17) + local = element$iv: double = 1.0 (sp = frameInlineArgument.kt, 4) +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/frameInlineFun.out b/idea/testData/debugger/tinyApp/outs/frameInlineFun.out new file mode 100644 index 00000000000..2c2184a2e9d --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/frameInlineFun.out @@ -0,0 +1,43 @@ +LineBreakpoint created at frameInlineFun.kt:14 +!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! frameInlineFun.FrameInlineFunKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +frameInlineFun.kt:14 +Compile bytecode for element +Compile bytecode for this.prop +package frameInlineFun + +fun main(args: Array) { + val element = 1 + A().inlineFun { + element + } +} + +class A { + inline fun inlineFun(s: (Int) -> Unit) { + val element = 1.0 + //Breakpoint! + s(1) + } + + val prop = 1 +} + +// PRINT_FRAME + +// EXPRESSION: element +// RESULT: 1.0: D + +// EXPRESSION: this.prop +// RESULT: 1: I + + + frame = main():14, FrameInlineFunKt {frameInlineFun} + local = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = null) + local = element: int = 1 (sp = frameInlineFun.kt, 12) + local = this_$iv: frameInlineFun.A = {frameInlineFun.A@uniqueID} (sp = null) + field = prop: int = 1 (sp = frameInlineFun.kt, 17) + local = element$iv: double = 1.0 (sp = frameInlineFun.kt, 12) +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/frameInlineFunCallInsideInlineFun.out b/idea/testData/debugger/tinyApp/outs/frameInlineFunCallInsideInlineFun.out new file mode 100644 index 00000000000..3d20f8289e4 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/frameInlineFunCallInsideInlineFun.out @@ -0,0 +1,71 @@ +LineBreakpoint created at frameInlineFunCallInsideInlineFun.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! frameInlineFunCallInsideInlineFun.FrameInlineFunCallInsideInlineFunKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +frameInlineFunCallInsideInlineFun.kt:20 +frameInlineFunCallInsideInlineFun.kt:5 +frameInlineFunCallInsideInlineFun.kt:7 +Compile bytecode for element +Compile bytecode for this.prop +package frameInlineFunCallInsideInlineFun + +class A { + inline fun inlineFun(s: (Int) -> Unit) { + val element = 1.0 + //TODO breakpoint here doesn't work (not only in tests) + s(1) + } + + val prop = 1 +} + +class B { + inline fun foo(s: (Int) -> Unit) { + val element = 2 + val a = A() + // STEP_INTO: 1 + // STEP_OVER: 1 + //Breakpoint! + a.inlineFun { + val e = element + } + s(1) + } +} + +class C { + fun bar() { + val element = 1f + B().foo { + val e = element + } + } +} + +fun main(args: Array) { + C().bar() +} + +// PRINT_FRAME + +// EXPRESSION: element +// RESULT: 1.0: D + +// EXPRESSION: this.prop +// RESULT: 1: I + + + frame = bar():7, C {frameInlineFunCallInsideInlineFun} + this = this = {frameInlineFunCallInsideInlineFun.C@uniqueID} + - Class has no fields + local = element: float = 1.0 (sp = frameInlineFunCallInsideInlineFun.kt, 5) + local = this_$iv: frameInlineFunCallInsideInlineFun.B = {frameInlineFunCallInsideInlineFun.B@uniqueID} (sp = null) + - Class has no fields + local = element$iv: int = 2 (sp = frameInlineFunCallInsideInlineFun.kt, 5) + local = a$iv: frameInlineFunCallInsideInlineFun.A = {frameInlineFunCallInsideInlineFun.A@uniqueID} (sp = null) + field = prop: int = 1 (sp = frameInlineFunCallInsideInlineFun.kt, 10) + local = this_$iv$iv: frameInlineFunCallInsideInlineFun.A = {frameInlineFunCallInsideInlineFun.A@uniqueID} (sp = null) + field = prop: int = 1 (sp = frameInlineFunCallInsideInlineFun.kt, 10) + local = element$iv$iv: double = 1.0 (sp = frameInlineFunCallInsideInlineFun.kt, 5) +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineArgument.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineArgument.kt new file mode 100644 index 00000000000..a5538b66a28 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineArgument.kt @@ -0,0 +1,24 @@ +package frameInlineArgument + +fun main(args: Array) { + val element = 1 + A().inlineFun { + //Breakpoint! + element + } +} + +class A { + inline fun inlineFun(s: () -> Unit) { + val element = 1.0 + s() + } + + val prop = 1 +} + +// PRINT_FRAME + +// EXPRESSION: element +// RESULT: 1: I + diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFun.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFun.kt new file mode 100644 index 00000000000..45fab8e909f --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFun.kt @@ -0,0 +1,27 @@ +package frameInlineFun + +fun main(args: Array) { + val element = 1 + A().inlineFun { + element + } +} + +class A { + inline fun inlineFun(s: (Int) -> Unit) { + val element = 1.0 + //Breakpoint! + s(1) + } + + val prop = 1 +} + +// PRINT_FRAME + +// EXPRESSION: element +// RESULT: 1.0: D + +// EXPRESSION: this.prop +// RESULT: 1: I + diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFunCallInsideInlineFun.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFunCallInsideInlineFun.kt new file mode 100644 index 00000000000..1417fdd687a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFunCallInsideInlineFun.kt @@ -0,0 +1,47 @@ +package frameInlineFunCallInsideInlineFun + +class A { + inline fun inlineFun(s: (Int) -> Unit) { + val element = 1.0 + //TODO breakpoint here doesn't work (not only in tests) + s(1) + } + + val prop = 1 +} + +class B { + inline fun foo(s: (Int) -> Unit) { + val element = 2 + val a = A() + // STEP_INTO: 1 + // STEP_OVER: 1 + //Breakpoint! + a.inlineFun { + val e = element + } + s(1) + } +} + +class C { + fun bar() { + val element = 1f + B().foo { + val e = element + } + } +} + +fun main(args: Array) { + C().bar() +} + +// PRINT_FRAME + +// EXPRESSION: element +// RESULT: 1.0: D + +// EXPRESSION: this.prop +// RESULT: 1: I + 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 9606aa5abb3..97a8aa1ce91 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -489,6 +489,24 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doSingleBreakpointTest(fileName); } + @TestMetadata("frameInlineArgument.kt") + public void testFrameInlineArgument() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineArgument.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("frameInlineFun.kt") + public void testFrameInlineFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFun.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("frameInlineFunCallInsideInlineFun.kt") + public void testFrameInlineFunCallInsideInlineFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInlineFunCallInsideInlineFun.kt"); + doSingleBreakpointTest(fileName); + } + @TestMetadata("frameInnerClass.kt") public void testFrameInnerClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameInnerClass.kt");