Further refinements in variable displaying (Kotlin mode)

- Remove synthetic outer this for lambdas
- Recognize captured this for interface default methods
This commit is contained in:
Yan Zhulanow
2019-01-30 18:26:12 +03:00
parent 300c102c3f
commit 65b6098242
6 changed files with 103 additions and 14 deletions
@@ -30,12 +30,12 @@ import com.sun.jdi.ReferenceType
import com.sun.jdi.Type
import com.sun.jdi.Value
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.AsmUtil.THIS
import org.jetbrains.kotlin.codegen.DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_PARAMETER_NAME
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_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.idea.debugger.evaluate.variables.VariableFinder
import org.jetbrains.kotlin.utils.getSafe
import java.lang.reflect.Modifier
import java.util.*
@@ -57,9 +57,9 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
}
val (thisReferences, otherVariables) = visibleVariables
.partition { it.name() == AsmUtil.THIS || it is ThisLocalVariable }
.partition { it.name() == THIS || it is ThisLocalVariable }
if (!removeThisObjectForContinuation(evaluationContext, children) && thisReferences.isNotEmpty()) {
if (!removeSyntheticThisObject(evaluationContext, children) && thisReferences.isNotEmpty()) {
val thisLabels = thisReferences.asSequence()
.filterIsInstance<ThisLocalVariable>()
.mapNotNullTo(hashSetOf()) { it.label }
@@ -71,14 +71,22 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
otherVariables.forEach(::addItem)
}
private fun removeThisObjectForContinuation(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
private fun removeSyntheticThisObject(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
if (!thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) {
return false
if (thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) {
ExistingInstanceThis.find(children)?.remove()
return true
}
ExistingInstanceThis.find(children)?.remove()
return true
val thisObjectType = thisObject.type()
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
ExistingInstanceThis.find(children)?.remove()
return true
}
return false
}
private fun remapThisObjectForOuterThis(
@@ -190,7 +198,8 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
.filter { !isHidden(it, inlineDepth) }
.partition {
it.name() == AsmUtil.THIS
it.name() == THIS
|| it.name() == AsmUtil.getCapturedFieldName(AsmUtil.THIS)
|| it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|| (VariableFinder.inlinedThisRegex.matches(it.name()))
}
@@ -199,7 +208,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
.sortedByDescending { it.variable }
.let { it.firstOrNull() to it.drop(1) }
val remappedMainThis = mainThis?.clone(AsmUtil.THIS, null)
val remappedMainThis = mainThis?.clone(THIS, null)
val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
}
@@ -216,12 +225,14 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
val name = this.name().dropInlineSuffix()
@Suppress("ConvertToStringTemplate")
return when {
isLabeledThisReference() -> {
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
clone(getThisName(label), label)
}
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null)
name == AsmUtil.getCapturedFieldName(AsmUtil.THIS) -> clone(THIS + " (outer)", null)
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(THIS + " (receiver)", null)
VariableFinder.inlinedThisRegex.matches(name) -> {
val label = generateThisLabel(frame.getValue(this)?.type())
if (label != null) {
@@ -260,7 +271,7 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
}
private fun getThisName(label: String): String {
return "$THIS_NAME (@$label)"
return "$THIS (@$label)"
}
private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl {
@@ -0,0 +1,24 @@
package defaultImplsMangling
interface IFoo {
val x: Int
fun foo() {
//Breakpoint!
val a = 5
}
}
class Foo : IFoo {
override val x: Int = 1
}
fun main() {
Foo().foo()
}
// SHOW_KOTLIN_VARIABLES
// PRINT_FRAME
// EXPRESSION: x
// RESULT: 1: I
@@ -0,0 +1,11 @@
LineBreakpoint created at defaultImplsMangling.kt:8
Run Java
Connected to the target VM
defaultImplsMangling.kt:8
Compile bytecode for x
frame = foo:8, IFoo$DefaultImpls {defaultImplsMangling}
local = this: defaultImplsMangling.IFoo = {defaultImplsMangling.Foo@uniqueID} (sp = null)
field = x: int = 1 (sp = defaultImplsMangling.kt, 13)
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,18 @@
package lambdaThisMangling
private fun block(block: (String) -> Unit) {
block("foo")
}
fun main() {
block { foo ->
//Breakpoint!
val a = 5
}
}
// SHOW_KOTLIN_VARIABLES
// PRINT_FRAME
// EXPRESSION: foo
// RESULT: "foo": Ljava/lang/String;
@@ -0,0 +1,15 @@
LineBreakpoint created at lambdaThisMangling.kt:10
Run Java
Connected to the target VM
lambdaThisMangling.kt:10
Compile bytecode for foo
frame = invoke:10, LambdaThisManglingKt$main$1 {lambdaThisMangling}
local = foo: java.lang.String = foo (sp = lambdaThisMangling.kt, 8)
field = value: char[] = {char[3]@uniqueID} (sp = String.!EXT!)
element = 0 = 'f' 102
element = 1 = 'o' 111
element = 2 = 'o' 111
field = hash: int = 0 (sp = String.!EXT!)
Disconnected from the target VM
Process finished with exit code 0
@@ -620,6 +620,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/coroutineContextLambda.kt");
}
@TestMetadata("defaultImplsMangling.kt")
public void testDefaultImplsMangling() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/defaultImplsMangling.kt");
}
@TestMetadata("delegatedPropertyInClass.kt")
public void testDelegatedPropertyInClass() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/delegatedPropertyInClass.kt");
@@ -760,6 +765,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaParameterMangling.kt");
}
@TestMetadata("lambdaThisMangling.kt")
public void testLambdaThisMangling() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/lambdaThisMangling.kt");
}
@TestMetadata("localFunctionMangling.kt")
public void testLocalFunctionMangling() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/frame/localFunctionMangling.kt");