Watches: Hide 'this' if it's a continuation

This commit is contained in:
Yan Zhulanow
2018-12-14 18:53:04 +09:00
parent 227a7e5609
commit f5a3a46e76
5 changed files with 124 additions and 1 deletions
@@ -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",
@@ -0,0 +1,13 @@
package hideContinuationThis
suspend fun main() {
foo()
}
var foo: suspend () -> Unit = {
//Breakpoint!
val a = 5
}
// PRINT_FRAME
// SHOW_KOTLIN_VARIABLES
@@ -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
@@ -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");