Debugger, minor: Move FrameInfo out of KotlinCodeFragmentFactory
This commit is contained in:
+80
@@ -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<LocalVariable, Value?>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
-74
@@ -30,31 +30,25 @@ import com.intellij.openapi.progress.ProgressManager
|
|||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.psi.util.PsiTypesUtil
|
import com.intellij.psi.util.PsiTypesUtil
|
||||||
import com.intellij.util.IncorrectOperationException
|
import com.intellij.util.IncorrectOperationException
|
||||||
import com.intellij.util.concurrency.Semaphore
|
import com.intellij.util.concurrency.Semaphore
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.eval4j.jdi.asValue
|
|
||||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
import org.jetbrains.kotlin.idea.core.util.getKotlinJvmRuntimeMarkerClass
|
import org.jetbrains.kotlin.idea.core.util.getKotlinJvmRuntimeMarkerClass
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
|
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.debugger.getContextElement
|
||||||
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
|
||||||
import org.jetbrains.kotlin.idea.j2k.convertToKotlin
|
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.j2k.j2kText
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||||
@@ -110,25 +104,32 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
return@putCopyableUserData emptyFile
|
return@putCopyableUserData emptyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
val frameDescriptor = getFrameInfo(contextElement, debuggerContext)
|
val frameInfo = getFrameInfo(contextElement, debuggerContext) ?: run {
|
||||||
if (frameDescriptor == null) {
|
val position = "${debuggerContext.sourcePosition?.file?.name}:${debuggerContext.sourcePosition?.line}"
|
||||||
LOG.warn(
|
LOG.warn("Couldn't get info about 'this' and local variables for $position")
|
||||||
"Couldn't get info about 'this' and local variables for " +
|
|
||||||
"${debuggerContext.sourcePosition?.file?.name}:${debuggerContext.sourcePosition?.line}"
|
|
||||||
)
|
|
||||||
return@putCopyableUserData emptyFile
|
return@putCopyableUserData emptyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
val receiverTypeReference =
|
val fakeFunctionText = buildString {
|
||||||
frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) }
|
append("fun ")
|
||||||
?.typeReference
|
|
||||||
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
|
||||||
|
|
||||||
val kotlinVariablesText =
|
val thisType = frameInfo.thisObject?.asProperty()?.typeReference?.typeElement?.unwrapNullableType()
|
||||||
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
if (thisType != null) {
|
||||||
|
append(thisType.text).append('.')
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("RemoveCurlyBracesFromTemplate")
|
append(FAKE_JAVA_CONTEXT_FUNCTION_NAME).append("() {\n")
|
||||||
val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\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 fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||||
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||||
@@ -141,6 +142,10 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
return codeFragment
|
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?) {
|
private fun supplyDebugInformation(item: TextWithImports, codeFragment: KtCodeFragment, context: PsiElement?) {
|
||||||
val project = codeFragment.project
|
val project = codeFragment.project
|
||||||
val debugProcess = getDebugProcess(project, context) ?: return
|
val debugProcess = getDebugProcess(project, context) ?: return
|
||||||
@@ -170,19 +175,13 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
val worker = object : DebuggerCommandImpl() {
|
val worker = object : DebuggerCommandImpl() {
|
||||||
override fun action() {
|
override fun action() {
|
||||||
try {
|
try {
|
||||||
val frame = if (ApplicationManager.getApplication().isUnitTestMode)
|
val frame = if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame
|
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 {
|
} else {
|
||||||
emptyMap()
|
debuggerContext.frameProxy?.stackFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
|
frameInfo = FrameInfo.from(debuggerContext.project, frame)
|
||||||
} catch (ignored: AbsentInformationException) {
|
} catch (ignored: AbsentInformationException) {
|
||||||
// Debug info unavailable
|
// Debug info unavailable
|
||||||
} catch (ignored: InvalidStackFrameException) {
|
} catch (ignored: InvalidStackFrameException) {
|
||||||
@@ -202,8 +201,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
return frameInfo
|
return frameInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
|
|
||||||
|
|
||||||
private fun initImports(imports: String?): String? {
|
private fun initImports(imports: String?): String? {
|
||||||
if (imports != null && imports.isNotEmpty()) {
|
if (imports != null && imports.isNotEmpty()) {
|
||||||
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
||||||
@@ -306,49 +303,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
val EVALUATION_TYPE: Key<KotlinDebuggerEvaluator.EvaluationType> = Key.create("DEBUG_EVALUATION_TYPE")
|
val EVALUATION_TYPE: Key<KotlinDebuggerEvaluator.EvaluationType> = Key.create("DEBUG_EVALUATION_TYPE")
|
||||||
|
|
||||||
const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_"
|
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<String, Value>.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 {
|
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
|
||||||
|
|||||||
Reference in New Issue
Block a user