Use fake local variable for inline argument in debugger

This commit is contained in:
Natalia Ukhorskaya
2015-10-13 11:24:25 +03:00
parent dfbafc7173
commit e68700190d
3 changed files with 134 additions and 61 deletions
@@ -20,6 +20,7 @@ import com.intellij.debugger.MultiRequestPositionManager
import com.intellij.debugger.NoDataException
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcess
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.roots.libraries.LibraryUtil
@@ -139,6 +140,9 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
for (literal in literalsOrFunctions) {
val functionLiteral = literal as KtFunction
if (isInlinedLambda(functionLiteral, typeMapper.bindingContext)) {
if (isInsideInlineArgument(functionLiteral, location, myDebugProcess as DebugProcessImpl)) {
return functionLiteral
}
continue
}
@@ -16,35 +16,110 @@
package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.openapi.project.Project
import com.sun.jdi.Value
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.debugger.evaluate.getClassDescriptor
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.psi.PsiElement
import com.sun.jdi.*
import com.sun.tools.jdi.LocalVariableImpl
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.psi.KtFunctionLiteralExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.util.*
// This method check that all parameter of functional argument are present in current frame
fun isInsideInlinedArgument(lambda: KtFunction, visibleVariables: List<Pair<LocalVariableProxyImpl, Value>>): Boolean {
val function = lambda.analyze(BodyResolveMode.PARTIAL).get(BindingContext.FUNCTION, lambda) ?: return false
return function.valueParameters.all { isLocalVariableForParameterPresent(lambda.project, it, visibleVariables) }
fun isInsideInlineArgument(inlineArgument: KtFunction, location: Location, debugProcess: DebugProcessImpl): Boolean {
return isInsideInlineArgument(inlineArgument, location.visibleVariables(debugProcess))
}
private fun isLocalVariableForParameterPresent(project: Project, parameter: ValueParameterDescriptor, visibleVariables: List<Pair<LocalVariableProxyImpl, Value>>): Boolean {
return visibleVariables.any {
if (it.first.name() != parameter.name.asString()) return false
fun isInsideInlineArgument(inlineArgument: KtFunction, visibleVariables: List<LocalVariable>): Boolean {
val lambdaOrdinalIndex = runReadAction { lambdaOrdinalIndex(inlineArgument) }
val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) }
for (variable in markerLocalVariables) {
val lambdaOrdinal = lambdaOrdinal(variable.name())
if (lambdaOrdinalIndex[inlineArgument] == lambdaOrdinal) {
return true
}
}
return false
}
val parameterClassDescriptor = parameter.type.constructor.declarationDescriptor as? ClassDescriptor ?: return true
val actualClassDescriptor = it.second.asValue().asmType.getClassDescriptor(project) ?: return true
private fun lambdaOrdinalIndex(elementAt: PsiElement): HashMap<KtFunction, Int> {
val parent = elementAt.parents.firstIsInstanceOrNull<KtNamedFunction>()
return runReadAction { DescriptorUtils.isSubclass(actualClassDescriptor, parameterClassDescriptor) }
val actualLambdaOrdinals = hashMapOf<KtFunction, Int>()
parent?.accept(object : KtTreeVisitorVoid() {
override fun visitNamedFunction(function: KtNamedFunction) {
if (function != parent) {
actualLambdaOrdinals[function] = actualLambdaOrdinals.size + 1
}
super.visitNamedFunction(function)
}
override fun visitFunctionLiteralExpression(expression: KtFunctionLiteralExpression) {
actualLambdaOrdinals[expression.functionLiteral] = actualLambdaOrdinals.size + 1
super.visitFunctionLiteralExpression(expression)
}
})
return actualLambdaOrdinals
}
private fun Location.visibleVariables(debugProcess: DebugProcessImpl): List<LocalVariable> {
val stackFrame = MockStackFrame(this, debugProcess.virtualMachineProxy.virtualMachine)
return stackFrame.visibleVariables()
}
private fun lambdaOrdinal(name: String): Int {
return try {
return name.substringAfter(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT).toInt()
}
catch(e: NumberFormatException) {
0
}
}
private class MockStackFrame(private val location: Location, private val vm: VirtualMachine) : StackFrame {
private var visibleVariables: Map<String, LocalVariable>? = null
override fun location() = location
override fun thread() = null
override fun thisObject() = null
private fun createVisibleVariables() {
if (visibleVariables == null) {
val allVariables = location.method().variables()
val map = HashMap<String, LocalVariable>(allVariables.size)
for (allVariable in allVariables) {
val variable = allVariable as LocalVariableImpl
val name = variable.name()
if (variable.isVisible(this)) {
map.put(name, variable)
}
}
visibleVariables = map
}
}
override fun visibleVariables(): List<LocalVariable> {
createVisibleVariables()
val mapAsList = ArrayList(visibleVariables!!.values)
Collections.sort(mapAsList)
return mapAsList
}
override fun visibleVariableByName(name: String): LocalVariable? {
createVisibleVariables()
return visibleVariables!![name]
}
override fun getValue(variable: LocalVariable) = null
override fun getValues(variables: List<LocalVariable>): Map<LocalVariable, Value> = emptyMap()
override fun setValue(variable: LocalVariable, value: Value) {
}
override fun getArgumentValues(): List<Value> = emptyList()
override fun virtualMachine() = vm
}
@@ -21,7 +21,6 @@ import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
@@ -35,31 +34,22 @@ import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.ArrayReference
import com.sun.jdi.PrimitiveValue
import com.sun.jdi.Value
import com.sun.jdi.*
import org.jetbrains.annotations.TestOnly
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.asJava.KotlinLightClass
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.refactoring.j2kText
import org.jetbrains.kotlin.idea.core.refactoring.quoteIfNeeded
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
import org.jetbrains.kotlin.idea.debugger.isInsideInlinedArgument
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
@@ -67,7 +57,6 @@ import org.jetbrains.kotlin.types.typeUtil.makeNullable
import org.jetbrains.kotlin.utils.addToStdlib.check
import java.util.*
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.jvm.*
class KotlinCodeFragmentFactory: CodeFragmentFactory() {
private val LOG = Logger.getInstance(this.javaClass)
@@ -127,25 +116,32 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
})
if (contextElement != null) {
val lambdas = getInlinedLambdasInside(contextElement)
if (lambdas.isNotEmpty()) {
val lambda = getInlineLambdaIfInside(contextElement)
if (lambda != null) {
codeFragment.putCopyableUserData(KtCodeFragment.ADDITIONAL_CONTEXT_FOR_LAMBDA, lamdba@ {
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val semaphore = Semaphore()
semaphore.down()
var visibleVariables: List<Pair<LocalVariableProxyImpl, Value>>? = null
var visibleVariables: Map<LocalVariable, Value>? = null
val worker = object : DebuggerCommandImpl() {
override fun action() {
try {
val frameProxy = if (ApplicationManager.getApplication().isUnitTestMode)
context?.getCopyableUserData(DEBUG_FRAME_FOR_TESTS)
val frame = if (ApplicationManager.getApplication().isUnitTestMode)
context?.getCopyableUserData(DEBUG_FRAME_FOR_TESTS)?.stackFrame
else
debuggerContext.frameProxy
debuggerContext.frameProxy?.stackFrame
visibleVariables = frameProxy?.let { f -> f.visibleVariables().map { it to f.getValue(it) } } ?: emptyList()
visibleVariables = frame?.let {
f ->
val variables = f.visibleVariables().filter {
!it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) &&
!it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
}
f.getValues(variables)
} ?: emptyMap<LocalVariable, Value>()
}
catch(ignored: AbsentInformationException) {
// Debug info unavailable
@@ -164,17 +160,12 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
if (visibleVariables == null) return@lamdba null
for (lambda in lambdas) {
if (isInsideInlinedArgument(lambda, visibleVariables!!)) {
val fragmentForVisibleVariables = createCodeFragmentForVisibleVariables(lambda.project, visibleVariables!!)
return@lamdba createWrappingContext(
fragmentForVisibleVariables.first,
fragmentForVisibleVariables.second,
lambda.bodyExpression,
lambda.project)
}
}
return@lamdba null
val fragmentForVisibleVariables = createCodeFragmentForVisibleVariables(lambda.project, visibleVariables!!)
return@lamdba createWrappingContext(
fragmentForVisibleVariables.first,
fragmentForVisibleVariables.second,
lambda.bodyExpression,
lambda.project)
})
}
}
@@ -235,14 +226,17 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
private fun KtElement?.check(): Boolean = this != null && this.check { KotlinEditorTextProvider.isAcceptedAsCodeFragmentContext(it) } != null
private fun getInlinedLambdasInside(contextElement: KtElement): List<KtFunction> {
val start = contextElement.startOffset
val end = contextElement.endOffset
private fun getInlineLambdaIfInside(contextElement: KtElement): KtFunction? {
val parentLambda = contextElement.getParentOfType<KtFunction>(false) ?: return null
if (!parentLambda.isLocal) return null
if (KtPsiUtil.getParentCallIfPresent(parentLambda) == null) return null
val bindingContext = contextElement.analyze(BodyResolveMode.PARTIAL)
return CodeInsightUtils.findElementsOfClassInRange(contextElement.getContainingJetFile(), start, end, javaClass<KtFunctionLiteral>(), javaClass<KtNamedFunction>())
.filterIsInstance<KtFunction>()
.filter { KtPsiUtil.getParentCallIfPresent(it as KtExpression) != null && InlineUtil.isInlinedArgument(it, bindingContext, false) }
if (InlineUtil.isInlinedArgument(parentLambda, bindingContext, false)) {
return parentLambda
}
return null
}
//internal for tests
@@ -265,7 +259,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() {
return sb.toString() to labeledObjects
}
private fun createCodeFragmentForVisibleVariables(project: Project, visibleVariables: List<Pair<LocalVariableProxyImpl, Value>>): Pair<String, Map<String, Value>> {
private fun createCodeFragmentForVisibleVariables(project: Project, visibleVariables: Map<LocalVariable, Value>): Pair<String, Map<String, Value>> {
val sb = StringBuilder()
val labeledObjects = HashMap<String, Value>()
for ((variable, value) in visibleVariables) {