diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinStackFrame.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinStackFrame.kt index 4d3b620a6d7..fbcdc569c97 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinStackFrame.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinStackFrame.kt @@ -16,16 +16,27 @@ package org.jetbrains.kotlin.idea.debugger +import com.intellij.debugger.DebuggerContext import com.intellij.debugger.engine.JavaStackFrame import com.intellij.debugger.engine.JavaValue +import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluationContextImpl +import com.intellij.debugger.impl.descriptors.data.DescriptorData +import com.intellij.debugger.impl.descriptors.data.DisplayKey +import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey import com.intellij.debugger.jdi.LocalVariableProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.ui.impl.watch.MethodsTracker import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl +import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiExpression +import com.intellij.util.IncorrectOperationException import com.intellij.xdebugger.frame.XValue import com.intellij.xdebugger.frame.XValueChildrenList +import com.sun.jdi.ObjectReference import com.sun.jdi.ReferenceType import com.sun.jdi.Type import com.sun.jdi.Value @@ -49,9 +60,13 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe return super.superBuildVariables(evaluationContext, children) } - val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager + val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager fun addItem(variable: LocalVariableProxyImpl) { + if (nodeManager == null) { + return + } + val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable) children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false)) } @@ -59,7 +74,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe val (thisReferences, otherVariables) = visibleVariables .partition { it.name() == THIS || it is ThisLocalVariable } - if (!removeSyntheticThisObject(evaluationContext, children) && thisReferences.isNotEmpty()) { + if (!removeSyntheticThisObject(evaluationContext, children, thisReferences) && thisReferences.isNotEmpty()) { val thisLabels = thisReferences.asSequence() .filterIsInstance() .mapNotNullTo(hashSetOf()) { it.label } @@ -71,10 +86,13 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe otherVariables.forEach(::addItem) } - private fun removeSyntheticThisObject(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean { + private fun removeSyntheticThisObject( + evaluationContext: EvaluationContextImpl, + children: XValueChildrenList, + thisReferences: List + ): Boolean { val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false - if (thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) { ExistingInstanceThis.find(children)?.remove() return true @@ -82,13 +100,57 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe val thisObjectType = thisObject.type() if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) { - ExistingInstanceThis.find(children)?.remove() + val existingThis = ExistingInstanceThis.find(children) + if (existingThis != null) { + existingThis.remove() + val javaValue = existingThis.value as? JavaValue + if (javaValue != null) { + attachCapturedThisFromLambda(evaluationContext, children, javaValue, thisReferences) + } + } return true } return false } + private fun attachCapturedThisFromLambda( + evaluationContext: EvaluationContextImpl, + children: XValueChildrenList, + javaValue: JavaValue, + thisReferences: List + ) { + try { + val value = javaValue.descriptor.calcValue(evaluationContext) as? ObjectReference ?: return + val thisField = value.referenceType().fieldByName(AsmUtil.CAPTURED_THIS_FIELD) ?: return + val thisValue = value.getValue(thisField) as? ObjectReference ?: return + val thisType = thisValue.referenceType() + val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return + val label = checkLabel(unsafeLabel) + + if (label != null) { + val thisName = getThisName(label) + + if (thisReferences.any { it.name() == thisName }) { + // Avoid label duplication + return + } + } + + val thisName = when { + thisReferences.isEmpty() -> THIS + label != null -> getThisName(label) + else -> "$THIS (anonymous fun)" + } + + val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return + val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(thisName, thisValue)) + children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false)) + } catch (e: EvaluateException) { + // do nothing + } + } + private fun remapThisObjectForOuterThis( evaluationContext: EvaluationContextImpl, children: XValueChildrenList, @@ -112,7 +174,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe private class ExistingInstanceThis( private val children: XValueChildrenList, private val index: Int, - private val value: XValue, + val value: XValue, private val size: Int ) { companion object { @@ -251,10 +313,16 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe } private fun generateThisLabel(type: Type?): String? { - val referenceType = type as? ReferenceType ?: return null - val label = referenceType.name().substringAfterLast('.').substringAfterLast('$') + return checkLabel(generateThisLabelUnsafe(type) ?: return null) + } - if (label.isEmpty() || label.any { it.isDigit() }) { + private fun generateThisLabelUnsafe(type: Type?): String? { + val referenceType = type as? ReferenceType ?: return null + return referenceType.name().substringAfterLast('.').substringAfterLast('$') + } + + private fun checkLabel(label: String): String? { + if (label.isEmpty() || label.all { it.isDigit() }) { return null } @@ -270,10 +338,6 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe return dropLast(depth * INLINE_FUN_VAR_SUFFIX.length) } - private fun getThisName(label: String): String { - return "$THIS (@$label)" - } - private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl { return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable { override fun name() = name @@ -298,4 +362,32 @@ private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableP } } return LocalVariableProxyImpl(proxyWrapper, variable) +} + +private fun getThisName(label: String): String { + return "$THIS (@$label)" +} + +private class LabeledThisData(val name: String, val value: ObjectReference) : DescriptorData() { + override fun createDescriptorImpl(project: Project): ValueDescriptorImpl { + return object : ValueDescriptorImpl(project, value) { + override fun getName() = this@LabeledThisData.name + override fun calcValue(evaluationContext: EvaluationContextImpl?) = value + override fun canSetValue() = false + + override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression { + // TODO change to labeled this + val elementFactory = JavaPsiFacade.getElementFactory(myProject) + try { + return elementFactory.createExpressionFromText("this", null) + } catch (e: IncorrectOperationException) { + throw EvaluateException(e.message, e) + } + } + } + } + + override fun getDisplayKey(): DisplayKey = SimpleDisplayKey(this) + override fun equals(other: Any?) = other is LabeledThisData && other.name == name + override fun hashCode() = name.hashCode() } \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.kt new file mode 100644 index 00000000000..17b37134b83 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.kt @@ -0,0 +1,22 @@ +package lambdaFun1 + +fun foo() { + block { + //Breakpoint! + val a = 5 + } +} + +fun block(block: () -> T): T { + return block() +} + +fun main() { + foo() +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES + +// EXPRESSION: this +// RESULT: 'this' is not defined in this context \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.out new file mode 100644 index 00000000000..0fd52b3179f --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.out @@ -0,0 +1,8 @@ +LineBreakpoint created at lambdaFun1.kt:6 +Run Java +Connected to the target VM +lambdaFun1.kt:6 + frame = invoke:6, LambdaFun1Kt$foo$1 {lambdaFun1} +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.kt new file mode 100644 index 00000000000..652d0f34c90 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.kt @@ -0,0 +1,24 @@ +package lambdaFun2 + +class Foo { + fun foo() { + block { + //Breakpoint! + val a = this@Foo + } + } +} + +fun block(block: () -> T): T { + return block() +} + +fun main() { + Foo().foo() +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES + +// EXPRESSION: this +// RESULT: instance of lambdaFun2.Foo(id=ID): LlambdaFun2/Foo; \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.out new file mode 100644 index 00000000000..2c0a87a2557 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.out @@ -0,0 +1,11 @@ +LineBreakpoint created at lambdaFun2.kt:7 +Run Java +Connected to the target VM +lambdaFun2.kt:7 +Compile bytecode for this + frame = invoke:7, Foo$foo$1 {lambdaFun2} + unknown = this = {lambdaFun2.Foo@uniqueID} + - Class has no fields +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.kt new file mode 100644 index 00000000000..759fa9971d0 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.kt @@ -0,0 +1,24 @@ +package lambdaFun3 + +object Foo { + fun foo() { + block { + //Breakpoint! + val a = this@Foo + } + } +} + +fun block(block: () -> T): T { + return block() +} + +fun main() { + Foo.foo() +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES + +// EXPRESSION: this +// RESULT: instance of lambdaFun3.Foo(id=ID): LlambdaFun3/Foo; \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.out new file mode 100644 index 00000000000..74084e9a863 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.out @@ -0,0 +1,9 @@ +LineBreakpoint created at lambdaFun3.kt:7 +Run Java +Connected to the target VM +lambdaFun3.kt:7 +Compile bytecode for this + frame = invoke:7, Foo$foo$1 {lambdaFun3} +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.kt new file mode 100644 index 00000000000..916d7d07b9d --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.kt @@ -0,0 +1,29 @@ +package lambdaFun4 + +interface Foo { + fun foo() { + block { + with ("abc") { + //Breakpoint! + val a = this@Foo + } + } + } +} + +fun block(block: () -> T): T { + return block() +} + +fun main() { + (object : Foo {}).foo() +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES + +// EXPRESSION: this +// RESULT: "abc": Ljava/lang/String; + +// EXPRESSION: this@Foo +// RESULT: instance of lambdaFun4.LambdaFun4Kt$main$1(id=ID): LlambdaFun4/LambdaFun4Kt$main$1; \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.out new file mode 100644 index 00000000000..2da633aaf9b --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.out @@ -0,0 +1,18 @@ +LineBreakpoint created at lambdaFun4.kt:8 +Run Java +Connected to the target VM +lambdaFun4.kt:8 +Compile bytecode for this +Compile bytecode for this@Foo + frame = invoke:8, Foo$foo$1 {lambdaFun4} + unknown = this (anonymous fun) = {lambdaFun4.LambdaFun4Kt$main$1@uniqueID} + - Class has no fields + local = this: java.lang.String = abc (sp = null) + field = value: char[] = {char[3]@uniqueID} (sp = String.!EXT!) + element = 0 = 'a' 97 + element = 1 = 'b' 98 + element = 2 = 'c' 99 + field = hash: int = 0 (sp = String.!EXT!) +Disconnected from the target VM + +Process finished with exit code 0 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 b07a9f67794..cc451521b2b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -795,6 +795,26 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt"); } + @TestMetadata("lambdaFun1.kt") + public void testLambdaFun1() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.kt"); + } + + @TestMetadata("lambdaFun2.kt") + public void testLambdaFun2() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.kt"); + } + + @TestMetadata("lambdaFun3.kt") + public void testLambdaFun3() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.kt"); + } + + @TestMetadata("lambdaFun4.kt") + public void testLambdaFun4() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.kt"); + } + @TestMetadata("lambdaParameterMangling.kt") public void testLambdaParameterMangling() throws Exception { runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaParameterMangling.kt");