diff --git a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameInfo.kt b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameInfo.kt new file mode 100644 index 00000000000..c3ab84a2ea4 --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/FrameInfo.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.evaluate + +import com.intellij.openapi.project.Project +import com.intellij.psi.CommonClassNames +import com.intellij.psi.PsiElementFactory +import com.intellij.psi.PsiNameHelper +import com.intellij.psi.PsiType +import com.intellij.psi.search.GlobalSearchScope +import com.sun.jdi.* +import org.jetbrains.eval4j.jdi.asValue +import org.jetbrains.kotlin.idea.debugger.getClassDescriptor +import org.jetbrains.kotlin.idea.j2k.j2k +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded +import org.jetbrains.kotlin.types.typeUtil.makeNullable + +class FrameInfo private constructor(val project: Project, thisObject: Value?, variables: Map) { + val thisObject = run { + if (thisObject == null) { + return@run null + } + + Variable(FAKE_JAVA_THIS_NAME, thisObject.type().name(), thisObject) + } + + val variables = variables.map { (v, value) -> Variable(v.name(), v.typeName(), value) } + + companion object { + private const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_" + + fun from(project: Project, frame: StackFrame?): FrameInfo { + if (frame == null) { + return FrameInfo(project, null, emptyMap()) + } + + return FrameInfo(project, frame.thisObject(), frame.getValues(frame.visibleVariables())) + } + + private fun createKotlinProperty(project: Project, name: String, typeName: String, value: Value?): KtProperty? { + val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project)) + if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) { + val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable()) + return KtPsiFactory(project).createProperty(name.quoteIfNeeded(), renderedType, false) + } + + val className = typeName.replace("$", ".").substringBefore("[]") + val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project)) + + val elementType = when { + value !is PrimitiveValue && classType.resolve() == null -> CommonClassNames.JAVA_LANG_OBJECT + else -> className + } + + val propertyType = if (value is ArrayReference) "$elementType[]" else elementType + val psiType = PsiType.getTypeByName(propertyType, project, GlobalSearchScope.allScope(project)) + + val field = PsiElementFactory.SERVICE.getInstance(project).createField(name, psiType) + val ktProperty = field.j2k() as? KtProperty + ktProperty?.modifierList?.delete() + return ktProperty + } + } + + inner class Variable(val name: String, val typeName: String, val value: Value?) { + fun asProperty(): KtProperty? { + if (!PsiNameHelper.getInstance(project).isIdentifier(name)) { + return null + } + + return createKotlinProperty(project, name, typeName, value) + } + } +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index c272c23240e..1756008584b 100644 --- a/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/jvm-debugger/jvm-debugger-evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -30,31 +30,25 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.psi.* -import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTypesUtil import com.intellij.util.IncorrectOperationException import com.intellij.util.concurrency.Semaphore import com.sun.jdi.* import org.jetbrains.annotations.TestOnly -import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.core.util.getKotlinJvmRuntimeMarkerClass import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider -import org.jetbrains.kotlin.idea.debugger.getClassDescriptor import org.jetbrains.kotlin.idea.debugger.getContextElement import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.idea.j2k.convertToKotlin -import org.jetbrains.kotlin.idea.j2k.j2k import org.jetbrains.kotlin.idea.j2k.j2kText -import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.j2k.AfterConversionPass import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.typeUtil.makeNullable import java.util.concurrent.atomic.AtomicReference class KotlinCodeFragmentFactory : CodeFragmentFactory() { @@ -110,25 +104,32 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return@putCopyableUserData emptyFile } - val frameDescriptor = getFrameInfo(contextElement, debuggerContext) - if (frameDescriptor == null) { - LOG.warn( - "Couldn't get info about 'this' and local variables for " + - "${debuggerContext.sourcePosition?.file?.name}:${debuggerContext.sourcePosition?.line}" - ) + val frameInfo = getFrameInfo(contextElement, debuggerContext) ?: run { + val position = "${debuggerContext.sourcePosition?.file?.name}:${debuggerContext.sourcePosition?.line}" + LOG.warn("Couldn't get info about 'this' and local variables for $position") return@putCopyableUserData emptyFile } - val receiverTypeReference = - frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) } - ?.typeReference - val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: "" + val fakeFunctionText = buildString { + append("fun ") - val kotlinVariablesText = - frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) + val thisType = frameInfo.thisObject?.asProperty()?.typeReference?.typeElement?.unwrapNullableType() + if (thisType != null) { + append(thisType.text).append('.') + } - @Suppress("RemoveCurlyBracesFromTemplate") - val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}" + append(FAKE_JAVA_CONTEXT_FUNCTION_NAME).append("() {\n") + + for (variable in frameInfo.variables) { + val text = variable.asProperty()?.text ?: continue + append(" ").append(text).append("\n") + } + + // There should be at least one declaration inside the function (or 'fakeContext' below won't work). + append(" val _debug_context_val = 1\n") + + append("}") + } val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement) val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction @@ -141,6 +142,10 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return codeFragment } + private fun KtTypeElement.unwrapNullableType(): KtTypeElement { + return if (this is KtNullableType) innerType ?: this else this + } + private fun supplyDebugInformation(item: TextWithImports, codeFragment: KtCodeFragment, context: PsiElement?) { val project = codeFragment.project val debugProcess = getDebugProcess(project, context) ?: return @@ -170,19 +175,13 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { val worker = object : DebuggerCommandImpl() { override fun action() { try { - val frame = if (ApplicationManager.getApplication().isUnitTestMode) + val frame = if (ApplicationManager.getApplication().isUnitTestMode) { contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame - else - debuggerContext.frameProxy?.stackFrame - - val visibleVariables = if (frame != null) { - val values = frame.getValues(frame.visibleVariables()) - values.filterValues { it != null } } else { - emptyMap() + debuggerContext.frameProxy?.stackFrame } - frameInfo = FrameInfo(frame?.thisObject(), visibleVariables) + frameInfo = FrameInfo.from(debuggerContext.project, frame) } catch (ignored: AbsentInformationException) { // Debug info unavailable } catch (ignored: InvalidStackFrameException) { @@ -202,8 +201,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return frameInfo } - private class FrameInfo(val thisObject: Value?, val visibleVariables: Map) - private fun initImports(imports: String?): String? { if (imports != null && imports.isNotEmpty()) { return imports.split(KtCodeFragment.IMPORT_SEPARATOR) @@ -306,49 +303,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { val EVALUATION_TYPE: Key = Key.create("DEBUG_EVALUATION_TYPE") const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_" - const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_" - - private fun Map.kotlinVariablesAsText(project: Project): String { - val sb = StringBuilder() - - val psiNameHelper = PsiNameHelper.getInstance(project) - for ((variableName, variableValue) in entries) { - if (!psiNameHelper.isIdentifier(variableName)) continue - - val variableTypeName = variableValue.type()?.name() ?: continue - - val kotlinProperty = createKotlinProperty(project, variableName, variableTypeName, variableValue) ?: continue - - sb.append("${kotlinProperty.text}\n") - } - - sb.append("val _debug_context_val = 1\n") - - return sb.toString() - } - - private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? { - val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project)) - if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) { - val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable()) - return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false) - } - - fun String.addArraySuffix() = if (value is ArrayReference) "$this[]" else this - - val className = variableTypeName.replace("$", ".").substringBefore("[]") - val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project)) - val type = (if (value !is PrimitiveValue && classType.resolve() == null) - CommonClassNames.JAVA_LANG_OBJECT - else - className).addArraySuffix() - - val field = PsiElementFactory.SERVICE.getInstance(project) - .createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project))) - val ktField = field.j2k() as? KtProperty - ktField?.modifierList?.delete() - return ktField - } } private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {