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.debugger.ui.impl.watch.ThisDescriptorImpl
import com.intellij.xdebugger.frame.XValue import com.intellij.xdebugger.frame.XValue
import com.intellij.xdebugger.frame.XValueChildrenList import com.intellij.xdebugger.frame.XValueChildrenList
import com.sun.jdi.ReferenceType
import com.sun.jdi.Type
import com.sun.jdi.Value import com.sun.jdi.Value
import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_PARAMETER_NAME 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 thisObject = evaluationContext.frameProxy?.thisObject() ?: return
val variable = ExistingInstanceThis.find(children) ?: return val variable = ExistingInstanceThis.find(children) ?: return
val thisLabel = thisObject.referenceType().name().substringAfterLast('.').substringAfterLast('$') val thisLabel = generateThisLabel(thisObject.referenceType())?.takeIf { it !in existingThisLabels }
if (thisLabel.isEmpty() || thisLabel in existingThisLabels || thisLabel.all { it.isDigit() }) { if (thisLabel == null) {
variable.remove() variable.remove()
return return
} }
// add additional checks?
variable.remapName(getThisName(thisLabel)) variable.remapName(getThisName(thisLabel))
} }
@@ -181,39 +184,78 @@ class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDe
} }
} }
val inlineDepth = VariableFinder.getInlineDepth(allVisibleVariables)
val (thisVariables, otherVariables) = allVisibleVariables.asSequence() val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
.filter { !isHidden(it) } .filter { !isHidden(it, inlineDepth) }
.partition { it.name() == AsmUtil.THIS || it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER) } .partition {
it.name() == AsmUtil.THIS
|| it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|| (VariableFinder.inlinedThisRegex.matches(it.name()))
}
val (mainThis, otherThis) = thisVariables val (mainThis, otherThis) = thisVariables
.sortedByDescending { it.variable } .sortedByDescending { it.variable }
.let { it.firstOrNull() to it.drop(1) } .let { it.firstOrNull() to it.drop(1) }
val remappedMainThis = mainThis?.clone(AsmUtil.THIS, null) val remappedMainThis = mainThis?.clone(AsmUtil.THIS, null)
val remappedOtherThis = otherThis.map { it.remapThisName() } val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
return (listOfNotNull(remappedMainThis) + remappedOtherThis + otherVariables)
.sortedBy { it.variable }
} }
private fun isHidden(variable: LocalVariableProxyImpl): Boolean { private fun isHidden(variable: LocalVariableProxyImpl, inlineDepth: Int): Boolean {
val name = variable.name() val name = variable.name()
return isFakeLocalVariableForInline(name) return isFakeLocalVariableForInline(name)
|| name.endsWith(INLINE_FUN_VAR_SUFFIX) || VariableFinder.getInlineDepth(variable.name()) != inlineDepth
|| name == CONTINUATION_PARAMETER_NAME.asString() || name == CONTINUATION_VARIABLE_NAME
} }
private fun LocalVariableProxyImpl.remapThisName(): LocalVariableProxyImpl { private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
val name = this.name().dropInlineSuffix()
return when { return when {
isLabeledThisReference() -> { 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) clone(getThisName(label), label)
} }
this@remapThisName.name() == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null) name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(AsmUtil.THIS + " (receiver)", null)
else -> this@remapThisName 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 { private fun getThisName(label: String): String {
return "$THIS_NAME (@$label)" 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.DO_RESUME_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes 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.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches 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 org.jetbrains.org.objectweb.asm.Type as AsmType
import java.util.* 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( fun isInsideInlineArgument(
inlineArgument: KtFunction, inlineArgument: KtFunction,
location: Location, location: Location,