Use fake local variable for inline argument in debugger
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user