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.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<LocalVariableProxyImpl> {
|
||||
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) {
|
||||
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",
|
||||
|
||||
+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");
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
Reference in New Issue
Block a user