Watches: Hide 'this' if it's a continuation
This commit is contained in:
@@ -23,6 +23,7 @@ import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
|||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||||
|
import com.intellij.xdebugger.frame.XValue
|
||||||
import com.intellij.xdebugger.frame.XValueChildrenList
|
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||||
import com.sun.jdi.Value
|
import com.sun.jdi.Value
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
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.INLINE_FUN_VAR_SUFFIX
|
||||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.THIS_NAME
|
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) {
|
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
|
||||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||||
@@ -41,6 +47,8 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
|
|
||||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager
|
val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager
|
||||||
|
|
||||||
|
removeThisObjectIfNeeded(evaluationContext, children)
|
||||||
|
|
||||||
fun addItem(variable: LocalVariableProxyImpl) {
|
fun addItem(variable: LocalVariableProxyImpl) {
|
||||||
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
||||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||||
@@ -53,6 +61,80 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
|
|||||||
otherVariables.forEach(::addItem)
|
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<LocalVariableProxyImpl> {
|
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||||
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import com.sun.jdi.Type as JdiType
|
|||||||
class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) {
|
class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) {
|
||||||
companion object {
|
companion object {
|
||||||
private val COROUTINE_CONTEXT_SIMPLE_NAME = COROUTINE_CONTEXT_1_3_FQ_NAME.shortName().asString()
|
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(
|
val SUSPEND_LAMBDA_CLASSES = listOf(
|
||||||
"kotlin.coroutines.jvm.internal.SuspendLambda",
|
"kotlin.coroutines.jvm.internal.SuspendLambda",
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package hideContinuationThis
|
||||||
|
|
||||||
|
suspend fun main() {
|
||||||
|
foo()
|
||||||
|
}
|
||||||
|
|
||||||
|
var foo: suspend () -> Unit = {
|
||||||
|
//Breakpoint!
|
||||||
|
val a = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRINT_FRAME
|
||||||
|
// SHOW_KOTLIN_VARIABLES
|
||||||
Vendored
+23
@@ -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
|
||||||
Generated
+5
@@ -735,6 +735,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/frameThis0This0.kt");
|
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")
|
@TestMetadata("suspendContinuation.kt")
|
||||||
public void testSuspendContinuation() throws Exception {
|
public void testSuspendContinuation() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/suspendContinuation.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user