From e68700190d8588adbf13eebb3dcb8bf219b57fb3 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Tue, 13 Oct 2015 11:24:25 +0300 Subject: [PATCH] Use fake local variable for inline argument in debugger --- .../idea/debugger/JetPositionManager.kt | 4 + .../kotlin/idea/debugger/debuggerUtil.kt | 119 ++++++++++++++---- .../evaluate/KotlinCodeFragmentFactory.kt | 72 +++++------ 3 files changed, 134 insertions(+), 61 deletions(-) diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt index 7172d1ccbc3..55791d0ba74 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt @@ -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 } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index 1ae85c784cc..665ff03eab9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -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>): 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>): Boolean { - return visibleVariables.any { - if (it.first.name() != parameter.name.asString()) return false +fun isInsideInlineArgument(inlineArgument: KtFunction, visibleVariables: List): 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 { + val parent = elementAt.parents.firstIsInstanceOrNull() - return runReadAction { DescriptorUtils.isSubclass(actualClassDescriptor, parameterClassDescriptor) } + val actualLambdaOrdinals = hashMapOf() + 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 { + 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? = 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(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 { + 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): Map = emptyMap() + override fun setValue(variable: LocalVariable, value: Value) { + } + + override fun getArgumentValues(): List = emptyList() + override fun virtualMachine() = vm +} diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index ff4feb75a45..5a212d1c585 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -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>? = null + var visibleVariables: Map? = 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() } 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 { - val start = contextElement.startOffset - val end = contextElement.endOffset + private fun getInlineLambdaIfInside(contextElement: KtElement): KtFunction? { + val parentLambda = contextElement.getParentOfType(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(), javaClass()) - .filterIsInstance() - .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> { + private fun createCodeFragmentForVisibleVariables(project: Project, visibleVariables: Map): Pair> { val sb = StringBuilder() val labeledObjects = HashMap() for ((variable, value) in visibleVariables) {