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 5aebe6cca58..fe8558ec4d3 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 @@ -23,6 +23,7 @@ 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.xdebugger.frame.XValue import com.intellij.xdebugger.frame.XValueChildrenList import com.sun.jdi.Value @@ -47,50 +48,75 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager - removeThisObjectIfNeeded(evaluationContext, children) - fun addItem(variable: LocalVariableProxyImpl) { val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable) children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false)) } val (thisReferences, otherVariables) = visibleVariables - .partition { it.name() == THIS_NAME || it.name().startsWith("$THIS_NAME ") } + .partition { it.name() == AsmUtil.THIS || it is ThisLocalVariable } + + if (!removeThisObjectForContinuation(evaluationContext, children) && thisReferences.isNotEmpty()) { + val thisLabels = thisReferences.asSequence() + .filterIsInstance() + .mapNotNullTo(hashSetOf()) { it.label } + + remapThisObjectForOuterThis(evaluationContext, children, thisLabels) + } thisReferences.forEach(::addItem) otherVariables.forEach(::addItem) } - private fun removeThisObjectIfNeeded(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) { - val thisObject = evaluationContext.frameProxy?.thisObject() ?: return + private fun removeThisObjectForContinuation(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean { + val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false if (!thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) { + return false + } + + ExistingInstanceThis.find(children)?.remove() + return true + } + + private fun remapThisObjectForOuterThis( + evaluationContext: EvaluationContextImpl, + children: XValueChildrenList, + existingThisLabels: Set + ) { + val thisObject = evaluationContext.frameProxy?.thisObject() ?: return + val variable = ExistingInstanceThis.find(children) ?: return + + val thisLabel = thisObject.referenceType().name().substringAfterLast('.').substringAfterLast('$') + if (thisLabel.isEmpty() || thisLabel in existingThisLabels || thisLabel.all { it.isDigit() }) { + variable.remove() return } - ExistingVariable.find(children, "this")?.remove() + variable.remapName(getThisName(thisLabel)) } // Very Dirty Work-around. // Hopefully, there will be an API for that in 2019.1. - private class ExistingVariable( + private class ExistingInstanceThis( private val children: XValueChildrenList, private val index: Int, - private val name: String, private val value: XValue, private val size: Int ) { companion object { - fun find(children: XValueChildrenList, name: String): ExistingVariable? { + private const val THIS_NAME = "this" + + fun find(children: XValueChildrenList): ExistingInstanceThis? { val size = children.size() for (i in 0 until size) { - if (children.getName(i) == name) { + if (children.getName(i) == THIS_NAME) { val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor @Suppress("FoldInitializerAndIfToElvis") if (valueDescriptor !is ThisDescriptorImpl) { return null } - return ExistingInstanceThis(children, i, name, children.getValue(i), size) + return ExistingInstanceThis(children, i, children.getValue(i), size) } } @@ -98,6 +124,11 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe } } + fun remapName(newName: String) { + val (names, _) = getLists() ?: return + names[index] = newName + } + fun remove() { val (names, values) = getLists() ?: return names.removeAt(index) @@ -150,11 +181,18 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe } } - return allVisibleVariables.asSequence() + val (thisVariables, otherVariables) = allVisibleVariables.asSequence() .filter { !isHidden(it) } - .map { remapVariableName(it) } - .distinctBy { it.name() } - .toList() + .partition { it.name() == AsmUtil.THIS || it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER) } + + val (mainThis, otherThis) = thisVariables + .sortedByDescending { it.variable } + .let { it.firstOrNull() to it.drop(1) } + + val remappedMainThis = mainThis?.clone(AsmUtil.THIS, null) + val remappedOtherThis = otherThis.map { it.remapThisName() } + + return (listOfNotNull(remappedMainThis) + remappedOtherThis + otherVariables) .sortedBy { it.variable } } @@ -165,13 +203,25 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe || name == CONTINUATION_PARAMETER_NAME.asString() } - private fun remapVariableName(variable: LocalVariableProxyImpl) = with(variable) { - when { + private fun LocalVariableProxyImpl.remapThisName(): LocalVariableProxyImpl { + return when { isLabeledThisReference() -> { - val label = variable.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length) - clone(withName = "$THIS_NAME (@$label)") + val label = this@remapThisName.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length) + clone(getThisName(label), label) } - else -> variable + this@remapThisName.name() == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null) + else -> this@remapThisName + } + } + + private fun getThisName(label: String): String { + return "$THIS_NAME (@$label)" + } + + private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl { + return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable { + override fun name() = name + override val label = label } } @@ -181,10 +231,8 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe } } -private fun LocalVariableProxyImpl.clone(withName: String): LocalVariableProxyImpl { - return object : LocalVariableProxyImpl(frame, variable) { - override fun name() = withName - } +private interface ThisLocalVariable { + val label: String? } private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableProxyImpl { diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt new file mode 100644 index 00000000000..7b8645e9f96 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt @@ -0,0 +1,16 @@ +package hideSyntheticThis + +fun main() { + block("foo") { + //Breakpoint! + val b = 4 + } +} + +fun block(t: T, block: T.() -> Unit) { + t.block() +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.out new file mode 100644 index 00000000000..d6b078aaa58 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.out @@ -0,0 +1,30 @@ +LineBreakpoint created at hideSyntheticThis.kt:6 +Run Java +Connected to the target VM +hideSyntheticThis.kt:6 +package hideSyntheticThis + +fun main() { + block("foo") { + //Breakpoint! + val b = 4 + } +} + +fun block(t: T, block: T.() -> Unit) { + t.block() +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION + frame = invoke + local = this (expression = this) + field = value (expression = this.value) + element = 0 (expression = this.value[0]) + element = 1 (expression = this.value[1]) + element = 2 (expression = this.value[2]) + field = hash (expression = this.hash) +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.kt new file mode 100644 index 00000000000..5019e8f80f4 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.kt @@ -0,0 +1,20 @@ +package remapThis + +fun main() { + Bar().bar() +} + +class Bar { + fun bar() { + "a".foo() + } + + fun String.foo() { + //Breakpoint! + val a = this + } +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.out new file mode 100644 index 00000000000..15bf731bd0e --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.out @@ -0,0 +1,34 @@ +LineBreakpoint created at remapThis.kt:14 +Run Java +Connected to the target VM +remapThis.kt:14 +package remapThis + +fun main() { + Bar().bar() +} + +class Bar { + fun bar() { + "a".foo() + } + + fun String.foo() { + //Breakpoint! + val a = this + } +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES +// DESCRIPTOR_VIEW_OPTIONS: NAME_EXPRESSION + frame = foo + this = this (@Bar) (expression = this) + - + local = this (expression = this) + field = value (expression = this.value) + element = 0 (expression = this.value[0]) + field = hash (expression = this.hash) +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt index f78076a3d4a..2f5fec5c678 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt @@ -259,10 +259,10 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { } } - fun renderLabel(descriptor: NodeDescriptorImpl): String { + fun renderLabel(node: TreeNode, descriptor: NodeDescriptorImpl): String { return when { descriptor is WatchItemDescriptor -> descriptor.calcValueName() - viewOptions.toString().contains("NAME") -> descriptor.name ?: descriptor.label + viewOptions.toString().contains("NAME") -> (node as? XValueNodeImpl)?.name ?: descriptor.name ?: descriptor.label else -> descriptor.label } } @@ -299,7 +299,7 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() { if (descriptor is DefaultNodeDescriptor) return true if (config.variablesToSkipInPrintFrame.contains(descriptor.name)) return true - var label = config.renderLabel(descriptor) + var label = config.renderLabel(node, descriptor) // TODO: update presentation before calc label if (label == NodeDescriptorImpl.UNKNOWN_VALUE_MESSAGE && descriptor is StaticDescriptor) { 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 685a47ab798..f43fd2b541d 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -740,6 +740,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt"); } + @TestMetadata("hideSyntheticThis.kt") + public void testHideSyntheticThis() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt"); + } + + @TestMetadata("remapThis.kt") + public void testRemapThis() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/remapThis.kt"); + } + @TestMetadata("suspendContinuation.kt") public void testSuspendContinuation() throws Exception { runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");