Debugger, minor: Move FrameInfo out of KotlinCodeFragmentFactory

This commit is contained in:
Yan Zhulanow
2019-07-24 17:04:27 +09:00
parent 98b4844f51
commit ed03d6e48e
2 changed files with 108 additions and 74 deletions
@@ -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)
}
}
}
@@ -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<LocalVariable, Value>)
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<KotlinDebuggerEvaluator.EvaluationType> = 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<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 {