Debugger: Show captured outer this in lambdas (Variables view) (KT-30220)

This commit is contained in:
Yan Zhulanow
2019-03-05 01:06:59 +03:00
parent 723f8df23e
commit ba0989801a
10 changed files with 270 additions and 13 deletions
@@ -16,16 +16,27 @@
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.DebuggerContext
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.descriptors.data.DescriptorData
import com.intellij.debugger.impl.descriptors.data.DisplayKey
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
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.debugger.ui.impl.watch.ValueDescriptorImpl
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiExpression
import com.intellij.util.IncorrectOperationException
import com.intellij.xdebugger.frame.XValue
import com.intellij.xdebugger.frame.XValueChildrenList
import com.sun.jdi.ObjectReference
import com.sun.jdi.ReferenceType
import com.sun.jdi.Type
import com.sun.jdi.Value
@@ -49,9 +60,13 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
return super.superBuildVariables(evaluationContext, children)
}
val nodeManager = evaluationContext.debugProcess.xdebugProcess!!.nodeManager
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager
fun addItem(variable: LocalVariableProxyImpl) {
if (nodeManager == null) {
return
}
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
}
@@ -59,7 +74,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
val (thisReferences, otherVariables) = visibleVariables
.partition { it.name() == THIS || it is ThisLocalVariable }
if (!removeSyntheticThisObject(evaluationContext, children) && thisReferences.isNotEmpty()) {
if (!removeSyntheticThisObject(evaluationContext, children, thisReferences) && thisReferences.isNotEmpty()) {
val thisLabels = thisReferences.asSequence()
.filterIsInstance<ThisLocalVariable>()
.mapNotNullTo(hashSetOf()) { it.label }
@@ -71,10 +86,13 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
otherVariables.forEach(::addItem)
}
private fun removeSyntheticThisObject(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
private fun removeSyntheticThisObject(
evaluationContext: EvaluationContextImpl,
children: XValueChildrenList,
thisReferences: List<LocalVariableProxyImpl>
): Boolean {
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
if (thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) {
ExistingInstanceThis.find(children)?.remove()
return true
@@ -82,13 +100,57 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
val thisObjectType = thisObject.type()
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
ExistingInstanceThis.find(children)?.remove()
val existingThis = ExistingInstanceThis.find(children)
if (existingThis != null) {
existingThis.remove()
val javaValue = existingThis.value as? JavaValue
if (javaValue != null) {
attachCapturedThisFromLambda(evaluationContext, children, javaValue, thisReferences)
}
}
return true
}
return false
}
private fun attachCapturedThisFromLambda(
evaluationContext: EvaluationContextImpl,
children: XValueChildrenList,
javaValue: JavaValue,
thisReferences: List<LocalVariableProxyImpl>
) {
try {
val value = javaValue.descriptor.calcValue(evaluationContext) as? ObjectReference ?: return
val thisField = value.referenceType().fieldByName(AsmUtil.CAPTURED_THIS_FIELD) ?: return
val thisValue = value.getValue(thisField) as? ObjectReference ?: return
val thisType = thisValue.referenceType()
val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return
val label = checkLabel(unsafeLabel)
if (label != null) {
val thisName = getThisName(label)
if (thisReferences.any { it.name() == thisName }) {
// Avoid label duplication
return
}
}
val thisName = when {
thisReferences.isEmpty() -> THIS
label != null -> getThisName(label)
else -> "$THIS (anonymous fun)"
}
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return
val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(thisName, thisValue))
children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false))
} catch (e: EvaluateException) {
// do nothing
}
}
private fun remapThisObjectForOuterThis(
evaluationContext: EvaluationContextImpl,
children: XValueChildrenList,
@@ -112,7 +174,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
private class ExistingInstanceThis(
private val children: XValueChildrenList,
private val index: Int,
private val value: XValue,
val value: XValue,
private val size: Int
) {
companion object {
@@ -251,10 +313,16 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
}
private fun generateThisLabel(type: Type?): String? {
val referenceType = type as? ReferenceType ?: return null
val label = referenceType.name().substringAfterLast('.').substringAfterLast('$')
return checkLabel(generateThisLabelUnsafe(type) ?: return null)
}
if (label.isEmpty() || label.any { it.isDigit() }) {
private fun generateThisLabelUnsafe(type: Type?): String? {
val referenceType = type as? ReferenceType ?: return null
return referenceType.name().substringAfterLast('.').substringAfterLast('$')
}
private fun checkLabel(label: String): String? {
if (label.isEmpty() || label.all { it.isDigit() }) {
return null
}
@@ -270,10 +338,6 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
return dropLast(depth * INLINE_FUN_VAR_SUFFIX.length)
}
private fun getThisName(label: String): String {
return "$THIS (@$label)"
}
private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl {
return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable {
override fun name() = name
@@ -298,4 +362,32 @@ private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableP
}
}
return LocalVariableProxyImpl(proxyWrapper, variable)
}
private fun getThisName(label: String): String {
return "$THIS (@$label)"
}
private class LabeledThisData(val name: String, val value: ObjectReference) : DescriptorData<ValueDescriptorImpl>() {
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
return object : ValueDescriptorImpl(project, value) {
override fun getName() = this@LabeledThisData.name
override fun calcValue(evaluationContext: EvaluationContextImpl?) = value
override fun canSetValue() = false
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression {
// TODO change to labeled this
val elementFactory = JavaPsiFacade.getElementFactory(myProject)
try {
return elementFactory.createExpressionFromText("this", null)
} catch (e: IncorrectOperationException) {
throw EvaluateException(e.message, e)
}
}
}
}
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(this)
override fun equals(other: Any?) = other is LabeledThisData && other.name == name
override fun hashCode() = name.hashCode()
}
@@ -0,0 +1,22 @@
package lambdaFun1
fun foo() {
block {
//Breakpoint!
val a = 5
}
}
fun <T> block(block: () -> T): T {
return block()
}
fun main() {
foo()
}
// PRINT_FRAME
// SHOW_KOTLIN_VARIABLES
// EXPRESSION: this
// RESULT: 'this' is not defined in this context
@@ -0,0 +1,8 @@
LineBreakpoint created at lambdaFun1.kt:6
Run Java
Connected to the target VM
lambdaFun1.kt:6
frame = invoke:6, LambdaFun1Kt$foo$1 {lambdaFun1}
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,24 @@
package lambdaFun2
class Foo {
fun foo() {
block {
//Breakpoint!
val a = this@Foo
}
}
}
fun <T> block(block: () -> T): T {
return block()
}
fun main() {
Foo().foo()
}
// PRINT_FRAME
// SHOW_KOTLIN_VARIABLES
// EXPRESSION: this
// RESULT: instance of lambdaFun2.Foo(id=ID): LlambdaFun2/Foo;
@@ -0,0 +1,11 @@
LineBreakpoint created at lambdaFun2.kt:7
Run Java
Connected to the target VM
lambdaFun2.kt:7
Compile bytecode for this
frame = invoke:7, Foo$foo$1 {lambdaFun2}
unknown = this = {lambdaFun2.Foo@uniqueID}
- Class has no fields
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,24 @@
package lambdaFun3
object Foo {
fun foo() {
block {
//Breakpoint!
val a = this@Foo
}
}
}
fun <T> block(block: () -> T): T {
return block()
}
fun main() {
Foo.foo()
}
// PRINT_FRAME
// SHOW_KOTLIN_VARIABLES
// EXPRESSION: this
// RESULT: instance of lambdaFun3.Foo(id=ID): LlambdaFun3/Foo;
@@ -0,0 +1,9 @@
LineBreakpoint created at lambdaFun3.kt:7
Run Java
Connected to the target VM
lambdaFun3.kt:7
Compile bytecode for this
frame = invoke:7, Foo$foo$1 {lambdaFun3}
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,29 @@
package lambdaFun4
interface Foo {
fun foo() {
block {
with ("abc") {
//Breakpoint!
val a = this@Foo
}
}
}
}
fun <T> block(block: () -> T): T {
return block()
}
fun main() {
(object : Foo {}).foo()
}
// PRINT_FRAME
// SHOW_KOTLIN_VARIABLES
// EXPRESSION: this
// RESULT: "abc": Ljava/lang/String;
// EXPRESSION: this@Foo
// RESULT: instance of lambdaFun4.LambdaFun4Kt$main$1(id=ID): LlambdaFun4/LambdaFun4Kt$main$1;
@@ -0,0 +1,18 @@
LineBreakpoint created at lambdaFun4.kt:8
Run Java
Connected to the target VM
lambdaFun4.kt:8
Compile bytecode for this
Compile bytecode for this@Foo
frame = invoke:8, Foo$foo$1 {lambdaFun4}
unknown = this (anonymous fun) = {lambdaFun4.LambdaFun4Kt$main$1@uniqueID}
- Class has no fields
local = this: java.lang.String = abc (sp = null)
field = value: char[] = {char[3]@uniqueID} (sp = String.!EXT!)
element = 0 = 'a' 97
element = 1 = 'b' 98
element = 2 = 'c' 99
field = hash: int = 0 (sp = String.!EXT!)
Disconnected from the target VM
Process finished with exit code 0
@@ -795,6 +795,26 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/hideSyntheticThis.kt");
}
@TestMetadata("lambdaFun1.kt")
public void testLambdaFun1() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun1.kt");
}
@TestMetadata("lambdaFun2.kt")
public void testLambdaFun2() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun2.kt");
}
@TestMetadata("lambdaFun3.kt")
public void testLambdaFun3() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun3.kt");
}
@TestMetadata("lambdaFun4.kt")
public void testLambdaFun4() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaFun4.kt");
}
@TestMetadata("lambdaParameterMangling.kt")
public void testLambdaParameterMangling() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaParameterMangling.kt");