Debugger: Support inline functions in Variables view

This commit is contained in:
Yan Zhulanow
2018-12-28 19:50:05 +03:00
parent 7fb8357b28
commit d2c2a6c99f
2 changed files with 57 additions and 27 deletions
@@ -26,6 +26,8 @@ import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl
import com.intellij.xdebugger.frame.XValue
import com.intellij.xdebugger.frame.XValueChildrenList
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.coroutines.CONTINUATION_PARAMETER_NAME
@@ -86,12 +88,13 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
val variable = ExistingInstanceThis.find(children) ?: return
val thisLabel = thisObject.referenceType().name().substringAfterLast('.').substringAfterLast('$')
if (thisLabel.isEmpty() || thisLabel in existingThisLabels || thisLabel.all { it.isDigit() }) {
val thisLabel = generateThisLabel(thisObject.referenceType())?.takeIf { it !in existingThisLabels }
if (thisLabel == null) {
variable.remove()
return
}
// add additional checks?
variable.remapName(getThisName(thisLabel))
}
@@ -181,39 +184,78 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
}
}
val inlineDepth = VariableFinder.getInlineDepth(allVisibleVariables)
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
.filter { !isHidden(it) }
.partition { it.name() == AsmUtil.THIS || it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER) }
.filter { !isHidden(it, inlineDepth) }
.partition {
it.name() == AsmUtil.THIS
|| it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|| (VariableFinder.inlinedThisRegex.matches(it.name()))
}
val (mainThis, otherThis) = thisVariables
.sortedByDescending { it.variable }
.let { it.firstOrNull() to it.drop(1) }
val remappedMainThis = mainThis?.clone(AsmUtil.THIS, null)
val remappedOtherThis = otherThis.map { it.remapThisName() }
return (listOfNotNull(remappedMainThis) + remappedOtherThis + otherVariables)
.sortedBy { it.variable }
val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
}
private fun isHidden(variable: LocalVariableProxyImpl): Boolean {
private fun isHidden(variable: LocalVariableProxyImpl, inlineDepth: Int): Boolean {
val name = variable.name()
return isFakeLocalVariableForInline(name)
|| name.endsWith(INLINE_FUN_VAR_SUFFIX)
|| name == CONTINUATION_PARAMETER_NAME.asString()
|| VariableFinder.getInlineDepth(variable.name()) != inlineDepth
|| name == CONTINUATION_VARIABLE_NAME
}
private fun LocalVariableProxyImpl.remapThisName(): LocalVariableProxyImpl {
private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
val name = this.name().dropInlineSuffix()
return when {
isLabeledThisReference() -> {
val label = this@remapThisName.name().drop(AsmUtil.LABELED_THIS_PARAMETER.length)
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
clone(getThisName(label), label)
}
this@remapThisName.name() == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null)
else -> this@remapThisName
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null)
VariableFinder.inlinedThisRegex.matches(name) -> {
val label = generateThisLabel(frame.getValue(this)?.type())
if (label != null) {
clone(getThisName(label), label)
} else {
this@remapVariableNameIfNeeded
}
}
name != this.name() -> {
object : LocalVariableProxyImpl(frame, variable) {
override fun name() = name
}
}
else -> this@remapVariableNameIfNeeded
}
}
private fun generateThisLabel(type: Type?): String? {
val referenceType = type as? ReferenceType ?: return null
val label = referenceType.name().substringAfterLast('.').substringAfterLast('$')
if (label.isEmpty() || label.any { it.isDigit() }) {
return null
}
return label
}
private fun String.dropInlineSuffix(): String {
val depth = VariableFinder.getInlineDepth(this)
if (depth == 0) {
return this
}
return dropLast(depth * INLINE_FUN_VAR_SUFFIX.length)
}
private fun getThisName(label: String): String {
return "$THIS_NAME (@$label)"
}
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousCl
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
@@ -33,17 +32,6 @@ import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.org.objectweb.asm.Type as AsmType
import java.util.*
fun calculateInlineDepth(variableName: String): Int {
var lastIndex = variableName.lastIndex
var depth = 0
while (variableName.lastIndexOf(INLINE_FUN_VAR_SUFFIX, startIndex = lastIndex) >= 0) {
lastIndex -= INLINE_FUN_VAR_SUFFIX.length
depth++
}
return depth
}
fun isInsideInlineArgument(
inlineArgument: KtFunction,
location: Location,