From f5a3a46e76faa6d8792985f47e1e039e6deb05e3 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Fri, 14 Dec 2018 18:53:04 +0900 Subject: [PATCH] Watches: Hide 'this' if it's a continuation --- .../kotlin/idea/debugger/KotlinStackFrame.kt | 82 +++++++++++++++++++ .../idea/debugger/evaluate/VariableFinder.kt | 2 +- .../frame/hideContinuationThis.kt | 13 +++ .../frame/hideContinuationThis.out | 23 ++++++ ...KotlinEvaluateExpressionTestGenerated.java | 5 ++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.out 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 b4002f41750..577ce274e7b 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.xdebugger.frame.XValue import com.intellij.xdebugger.frame.XValueChildrenList import com.sun.jdi.Value import org.jetbrains.kotlin.codegen.AsmUtil @@ -30,6 +31,11 @@ import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_PARAMETER_NAME import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline import org.jetbrains.kotlin.idea.debugger.evaluate.THIS_NAME +import org.jetbrains.kotlin.idea.debugger.evaluate.VariableFinder +import org.jetbrains.kotlin.utils.getSafe +import java.lang.reflect.Modifier +import java.util.* +import org.jetbrains.kotlin.idea.debugger.evaluate.LOG as DebuggerLog class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) { private val kotlinVariableViewService = ToggleKotlinVariablesState.getService() @@ -41,6 +47,8 @@ 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)) @@ -53,6 +61,80 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe otherVariables.forEach(::addItem) } + private fun removeThisObjectIfNeeded(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) { + val thisObject = evaluationContext.frameProxy?.thisObject() ?: return + if (!thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) { + return + } + + ExistingVariable.find(children, "this")?.remove() + } + + // Very Dirty Work-around. + // Hopefully, there will be an API for that in 2019.1. + private class ExistingVariable( + 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? { + val size = children.size() + for (i in 0 until size) { + if (children.getName(i) == 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 null + } + } + + fun remove() { + if (children.size() != size) { + throw IllegalStateException("Children list was modified") + } + + var namesList: MutableList<*>? = null + var valuesList: MutableList<*>? = null + + for (field in XValueChildrenList::class.java.declaredFields) { + val mods = field.modifiers + if (Modifier.isPrivate(mods) && Modifier.isFinal(mods) && !Modifier.isStatic(mods) && field.type == List::class.java) { + val list = (field.getSafe(children) as? MutableList<*>)?.takeIf { it.size == size } ?: continue + if (list[index] == name) { + namesList = list + } else if (list[index] === value) { + valuesList = list + } + } + + if (namesList != null && valuesList != null) { + break + } + } + + if (namesList == null || valuesList == null) { + org.jetbrains.kotlin.idea.debugger.evaluate.LOG.error( + "Can't find name/value lists, existing fields: " + + Arrays.toString(XValueChildrenList::class.java.declaredFields) + ) + return + } + + namesList.removeAt(index) + valuesList.removeAt(index) + } + } + override fun getVisibleVariables(): List { val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables() diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt index de0015533fe..17ff4c2e455 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/VariableFinder.kt @@ -34,7 +34,7 @@ import com.sun.jdi.Type as JdiType class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) { companion object { private val COROUTINE_CONTEXT_SIMPLE_NAME = COROUTINE_CONTEXT_1_3_FQ_NAME.shortName().asString() - private val CONTINUATION_TYPE = AsmType.getType(Continuation::class.java) + val CONTINUATION_TYPE: AsmType = AsmType.getType(Continuation::class.java) val SUSPEND_LAMBDA_CLASSES = listOf( "kotlin.coroutines.jvm.internal.SuspendLambda", diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt new file mode 100644 index 00000000000..931e5e61306 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt @@ -0,0 +1,13 @@ +package hideContinuationThis + +suspend fun main() { + foo() +} + +var foo: suspend () -> Unit = { + //Breakpoint! + val a = 5 +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.out new file mode 100644 index 00000000000..1478bc3c25a --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.out @@ -0,0 +1,23 @@ +LineBreakpoint created at hideContinuationThis.kt:9 +Run Java +Connected to the target VM +hideContinuationThis.kt:9 +package hideContinuationThis + +suspend fun main() { + foo() +} + +var foo: suspend () -> Unit = { + //Breakpoint! + val a = 5 +} + +// PRINT_FRAME +// SHOW_KOTLIN_VARIABLES + frame = invokeSuspend:9, HideContinuationThisKt$foo$1 {hideContinuationThis} + local = result: java.lang.Object = {kotlin.Unit@uniqueID}kotlin.Unit (sp = null) + - No fields to display +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 e01cdc57601..685a47ab798 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -735,6 +735,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameThis0This0.kt"); } + @TestMetadata("hideContinuationThis.kt") + public void testHideContinuationThis() throws Exception { + runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideContinuationThis.kt"); + } + @TestMetadata("suspendContinuation.kt") public void testSuspendContinuation() throws Exception { runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");