Move out JVM debugger functionality

This commit is contained in:
Yan Zhulanow
2019-04-05 15:10:14 +03:00
parent 5843336d42
commit ae7550c5af
318 changed files with 1068 additions and 723 deletions
@@ -0,0 +1,21 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compile(project(":compiler:backend"))
compile(project(":idea:jvm-debugger:eval4j"))
compile(project(":idea:idea-core"))
compile(project(":idea:idea-j2k"))
compile(project(":idea:jvm-debugger:jvm-debugger-util"))
compile(files("${System.getProperty("java.home")}/../lib/tools.jar"))
testCompile(project(":kotlin-test:kotlin-test-junit"))
testCompile(commonDep("junit:junit"))
}
sourceSets {
"main" { projectDefault() }
"test" { none() }
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2018 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 org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.completion.CompletionInformationProvider
class DebuggerFieldCompletionInformationProvider : CompletionInformationProvider {
override fun getContainerAndReceiverInformation(descriptor: DeclarationDescriptor) =
(descriptor as? DebuggerFieldPropertyDescriptor)?.description?.let { " $it" }
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2018 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 org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
class DebuggerFieldExpressionCodegenExtension : ExpressionCodegenExtension {
override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return null
if (propertyDescriptor is DebuggerFieldPropertyDescriptor) {
return StackValue.StackValueWithSimpleReceiver.field(
c.typeMapper.mapType(propertyDescriptor.type),
propertyDescriptor.ownerType(c.codegen.state),
propertyDescriptor.fieldName,
false,
receiver
)
}
if (propertyDescriptor is JavaPropertyDescriptor) {
val containingClass = propertyDescriptor.containingDeclaration as? JavaClassDescriptor
if (containingClass != null) {
val correspondingGetter = JavaSyntheticPropertiesScope(LockBasedStorageManager.NO_LOCKS, LookupTracker.DO_NOTHING)
.getSyntheticExtensionProperties(listOf(containingClass.defaultType), NoLookupLocation.FROM_BACKEND)
.firstOrNull { it.name == propertyDescriptor.name }
if (correspondingGetter != null) {
return c.codegen.intermediateValueForProperty(
correspondingGetter, false, false,
c.codegen.getSuperCallTarget(resolvedCall.call),
false, receiver, resolvedCall, false
)
}
}
}
return null
}
}
@@ -0,0 +1,44 @@
/*
* 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 org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.idea.core.extension.KotlinIndicesHelperExtension
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
import org.jetbrains.kotlin.types.KotlinType
import java.lang.IllegalStateException
class DebuggerFieldKotlinIndicesHelperExtension : KotlinIndicesHelperExtension {
override fun appendExtensionCallables(
consumer: MutableList<in CallableDescriptor>,
moduleDescriptor: ModuleDescriptor,
receiverTypes: Collection<KotlinType>,
nameFilter: (String) -> Boolean,
lookupLocation: LookupLocation
) {
val javaPropertiesScope = JavaSyntheticPropertiesScope(LockBasedStorageManager.NO_LOCKS, LookupTracker.DO_NOTHING)
val fieldScope = DebuggerFieldSyntheticScope(javaPropertiesScope)
for (property in fieldScope.getSyntheticExtensionProperties(receiverTypes, lookupLocation)) {
if (nameFilter(property.name.asString())) {
consumer += property
}
}
}
override fun appendExtensionCallables(
consumer: MutableList<in CallableDescriptor>,
moduleDescriptor: ModuleDescriptor,
receiverTypes: Collection<KotlinType>,
nameFilter: (String) -> Boolean
) {
throw IllegalStateException("Should not be called")
}
}
@@ -0,0 +1,206 @@
/*
* Copyright 2010-2018 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 org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.incremental.KotlinLookupLocation
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.components.JavaSourceElementFactoryImpl
import org.jetbrains.kotlin.load.java.components.TypeUsage
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.load.java.lazy.types.toAttributes
import org.jetbrains.kotlin.load.java.structure.classId
import org.jetbrains.kotlin.load.kotlin.internalName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorFactory
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.platform.jvm.isJvm
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.SyntheticScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
import org.jetbrains.kotlin.synthetic.SyntheticScopeProviderExtension
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
import org.jetbrains.org.objectweb.asm.Type
class DebuggerFieldSyntheticScopeProvider : SyntheticScopeProviderExtension {
override fun getScopes(
moduleDescriptor: ModuleDescriptor,
javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope
): List<SyntheticScope> {
return listOf<SyntheticScope>(DebuggerFieldSyntheticScope(javaSyntheticPropertiesScope))
}
}
class DebuggerFieldSyntheticScope(val javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope) : SyntheticScope.Default() {
private val javaSourceElementFactory = JavaSourceElementFactoryImpl()
override fun getSyntheticExtensionProperties(
receiverTypes: Collection<KotlinType>,
name: Name,
location: LookupLocation
): Collection<PropertyDescriptor> {
return getSyntheticExtensionProperties(receiverTypes, location).filter { it.name == name }
}
override fun getSyntheticExtensionProperties(
receiverTypes: Collection<KotlinType>,
location: LookupLocation
): Collection<PropertyDescriptor> {
if (!isInEvaluator(location)) {
return emptyList()
}
val result = mutableListOf<PropertyDescriptor>()
for (type in receiverTypes) {
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor ?: continue
result += getSyntheticPropertiesForClass(clazz)
}
return result
}
private fun isInEvaluator(location: LookupLocation): Boolean {
val element = (location as? KotlinLookupLocation)?.element ?: return false
val containingFile = element.containingFile?.takeIf { it.isValid } as? KtFile ?: return false
val platform = containingFile.platform
if (!platform.isJvm() && !platform.isCommon()) {
return false
}
return containingFile is KtCodeFragment
}
private fun getSyntheticPropertiesForClass(clazz: ClassDescriptor): Collection<PropertyDescriptor> {
val collected = mutableMapOf<Name, PropertyDescriptor>()
val syntheticPropertyNames = javaSyntheticPropertiesScope
.getSyntheticExtensionProperties(listOf(clazz.defaultType), NoLookupLocation.FROM_SYNTHETIC_SCOPE)
.mapTo(mutableSetOf()) { it.name }
collectPropertiesWithParent(clazz, syntheticPropertyNames, collected)
return collected.values
}
private tailrec fun collectPropertiesWithParent(
clazz: ClassDescriptor,
syntheticNames: Set<Name>,
consumer: MutableMap<Name, PropertyDescriptor>
) {
when (clazz) {
is LazyJavaClassDescriptor -> collectJavaProperties(clazz, syntheticNames, consumer)
is JavaClassDescriptor -> error("Unsupported Java class type")
else -> collectKotlinProperties(clazz, consumer)
}
val superClass = clazz.getSuperClassNotAny()
if (superClass != null) {
collectPropertiesWithParent(superClass, syntheticNames, consumer)
}
}
private fun collectKotlinProperties(clazz: ClassDescriptor, consumer: MutableMap<Name, PropertyDescriptor>) {
for (descriptor in clazz.unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES)) {
val propertyDescriptor = descriptor as? PropertyDescriptor ?: continue
val name = propertyDescriptor.name
if (propertyDescriptor.backingField == null || name in consumer) continue
val type = propertyDescriptor.type
val sourceElement = propertyDescriptor.source
consumer[name] = createSyntheticPropertyDescriptor(clazz, type, name.asString(), "Backing field", sourceElement) { state ->
state.typeMapper.mapType(clazz.defaultType)
}
}
}
private fun collectJavaProperties(
clazz: LazyJavaClassDescriptor,
syntheticNames: Set<Name>,
consumer: MutableMap<Name, PropertyDescriptor>
) {
val javaClass = clazz.jClass
for (field in javaClass.fields) {
val fieldName = field.name
if (field.isEnumEntry || field.isStatic || fieldName in consumer || fieldName !in syntheticNames) continue
val ownerClassName = javaClass.classId?.internalName ?: continue
val typeResolver = clazz.outerContext.typeResolver
val type = typeResolver.transformJavaType(field.type, TypeUsage.COMMON.toAttributes()).replaceArgumentsWithStarProjections()
val sourceElement = javaSourceElementFactory.source(field)
consumer[fieldName] = createSyntheticPropertyDescriptor(clazz, type, fieldName.asString(), "Java field", sourceElement) {
Type.getObjectType(ownerClassName)
}
}
}
private fun createSyntheticPropertyDescriptor(
clazz: ClassDescriptor,
type: KotlinType,
fieldName: String,
description: String,
getterSource: SourceElement,
ownerType: (GenerationState) -> Type
): PropertyDescriptor {
val propertyDescriptor = DebuggerFieldPropertyDescriptor(clazz, fieldName, description, ownerType)
val extensionReceiverParameter = DescriptorFactory.createExtensionReceiverParameterForCallable(
propertyDescriptor,
clazz.defaultType.replaceArgumentsWithStarProjections(),
Annotations.EMPTY
)
propertyDescriptor.setType(type, emptyList(), null, extensionReceiverParameter)
val getter = PropertyGetterDescriptorImpl(
propertyDescriptor, Annotations.EMPTY, Modality.FINAL,
Visibilities.PUBLIC, false, false, false,
CallableMemberDescriptor.Kind.SYNTHESIZED,
null, getterSource
)
propertyDescriptor.initialize(getter, null)
return propertyDescriptor
}
}
internal class DebuggerFieldPropertyDescriptor(
containingDeclaration: DeclarationDescriptor,
val fieldName: String,
val description: String,
val ownerType: (GenerationState) -> Type
) : PropertyDescriptorImpl(
containingDeclaration,
null,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
/*isVar = */true,
Name.identifier(fieldName + "_field"),
CallableMemberDescriptor.Kind.SYNTHESIZED,
SourceElement.NO_SOURCE,
/*lateInit = */false,
/*isConst = */false,
/*isExpect = */false,
/*isActual = */false,
/*isExternal = */false,
/*isDelegated = */false
)
@@ -0,0 +1,358 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl
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.impl.DebuggerContextImpl
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
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.JavaToKotlinConverterFactory
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() {
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val contextElement = getContextElement(context)
val constructor = when (item.kind) {
null -> error("Code fragment kind should be set")
CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
}
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
supplyDebugLabels(codeFragment, context)
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession
if (debuggerSession == null || debuggerContext.suspendContext == null) {
null
} else {
val semaphore = Semaphore()
semaphore.down()
val nameRef = AtomicReference<KotlinType>()
val worker = object : KotlinRuntimeTypeEvaluator(
null, expression, debuggerContext, ProgressManager.getInstance().progressIndicator!!
) {
override fun typeCalculationFinished(type: KotlinType?) {
nameRef.set(type)
semaphore.up()
}
}
debuggerContext.debugProcess?.managerThread?.invoke(worker)
for (i in 0..50) {
ProgressManager.checkCanceled()
if (semaphore.waitFor(20)) break
}
nameRef.get()
}
})
if (contextElement != null && contextElement !is KtElement) {
codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, {
val emptyFile = createFakeFileWithJavaContextElement("", contextElement)
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession
if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) {
LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint")
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}"
)
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 kotlinVariablesText =
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}"
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull()
return@putCopyableUserData fakeContext ?: emptyFile
})
}
return codeFragment
}
private fun supplyDebugLabels(codeFragment: KtCodeFragment, context: PsiElement?) {
val project = codeFragment.project
val debugProcess = getDebugProcess(project, context) ?: return
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
}
private fun getDebugProcess(project: Project, context: PsiElement?): DebugProcessImpl? {
return if (ApplicationManager.getApplication().isUnitTestMode) {
context?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess
} else {
DebuggerManagerEx.getInstanceEx(project).context.debugProcess
}
}
private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? {
val semaphore = Semaphore()
semaphore.down()
var frameInfo: FrameInfo? = null
val worker = object : DebuggerCommandImpl() {
override fun action() {
try {
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()
}
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
} catch (ignored: AbsentInformationException) {
// Debug info unavailable
} catch (ignored: InvalidStackFrameException) {
// Thread is resumed, the frame we have is not valid anymore
} finally {
semaphore.up()
}
}
}
debuggerContext.debugProcess?.managerThread?.invoke(worker)
for (i in 0..50) {
if (semaphore.waitFor(20)) break
}
return frameInfo
}
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
private fun initImports(imports: String?): String? {
if (imports != null && !imports.isEmpty()) {
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
.mapNotNull { fixImportIfNeeded(it) }
.joinToString(KtCodeFragment.IMPORT_SEPARATOR)
}
return null
}
private fun fixImportIfNeeded(import: String): String? {
// skip arrays
if (import.endsWith("[]")) {
return fixImportIfNeeded(import.removeSuffix("[]").trim())
}
// skip primitive types
if (PsiTypesUtil.boxIfPossible(import) != import) {
return null
}
return import
}
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val kotlinCodeFragment = createCodeFragment(item, context, project)
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
val javaExpression = try {
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
} catch (e: IncorrectOperationException) {
null
}
val importList = try {
kotlinCodeFragment.importsAsImportList()?.let {
(PsiFileFactory.getInstance(project).createFileFromText(
"dummy.java", JavaFileType.INSTANCE, it.text
) as? PsiJavaFile)?.importList
}
} catch (e: IncorrectOperationException) {
null
}
if (javaExpression != null && !PsiTreeUtil.hasErrorElements(javaExpression)) {
var convertedFragment: KtExpressionCodeFragment? = null
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
try {
val (elementResults, _, conversionContext) = javaExpression.convertToKotlin() ?: return@executeWriteCommand
val newText = elementResults.singleOrNull()?.text
val newImports = importList?.j2kText()
if (newText != null) {
convertedFragment = KtExpressionCodeFragment(
project,
kotlinCodeFragment.name,
newText,
newImports,
kotlinCodeFragment.context
)
AfterConversionPass(project, JavaToKotlinConverterFactory.createPostProcessor(formatCode = false))
.run(
convertedFragment!!,
conversionContext,
range = null
)
}
} catch (e: Throwable) {
// ignored because text can be invalid
LOG.error("Couldn't convert expression:\n`${javaExpression.text}`", e)
}
}
return convertedFragment ?: kotlinCodeFragment
}
}
return kotlinCodeFragment
}
override fun isContextAccepted(contextElement: PsiElement?): Boolean = runReadAction {
when {
// PsiCodeBlock -> DummyHolder -> originalElement
contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
contextElement == null -> false
contextElement.language == KotlinFileType.INSTANCE.language -> true
contextElement.language == JavaFileType.INSTANCE.language -> {
getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null
}
else -> false
}
}
override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE
override fun getEvaluatorBuilder() = KotlinEvaluatorBuilder
companion object {
private val LOG = Logger.getInstance(this::class.java)
@get:TestOnly
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
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 {
val javaFile = javaContext.containingFile as? PsiJavaFile
val sb = StringBuilder()
javaFile?.packageName?.takeUnless { it.isBlank() }?.let {
sb.append("package ").append(it.quoteIfNeeded()).append("\n")
}
javaFile?.importList?.let { sb.append(it.text).append("\n") }
sb.append(funWithLocalVariables)
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
}
}
@@ -0,0 +1,439 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.evaluation.expression.*
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiElement
import com.intellij.testFramework.runInEdtAndWait
import com.sun.jdi.*
import com.sun.jdi.Value
import org.jetbrains.eval4j.*
import org.jetbrains.eval4j.Value as Eval4JValue
import org.jetbrains.eval4j.jdi.JDIEval
import org.jetbrains.eval4j.jdi.asJdiValue
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.eval4j.jdi.makeInitialFrame
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.idea.core.util.attachmentByPsiFile
import org.jetbrains.kotlin.idea.core.util.mergeAttachments
import org.jetbrains.kotlin.idea.core.util.runInReadActionWithWriteActionPriorityWithPCE
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_CLASS_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_FUNCTION_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import java.util.*
internal val LOG = Logger.getInstance(KotlinEvaluator::class.java)
object KotlinEvaluatorBuilder : EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
if (codeFragment !is KtCodeFragment) {
return EvaluatorBuilderImpl.getInstance().build(codeFragment, position)
}
val context = codeFragment.context ?: evaluationException("Cannot evaluate an expression without a context")
val file = context.containingFile
if (file !is KtFile) {
reportError(codeFragment, position, "Unknown context${codeFragment.context?.javaClass}")
evaluationException("Couldn't evaluate Kotlin expression in this context")
}
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position))
}
}
class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: SourcePosition?) : Evaluator {
override fun evaluate(context: EvaluationContextImpl): Any? {
if (codeFragment.text.isEmpty()) {
return context.debugProcess.virtualMachineProxy.mirrorOfVoid()
}
if (DumbService.getInstance(codeFragment.project).isDumb) {
evaluationException("Code fragment evaluation is not available in the dumb mode")
}
val frameProxy = context.frameProxy
?: evaluationException("Cannot evaluate a code fragment: frame proxy is not available")
val operatingThread = context.suspendContext.thread
?: evaluationException("Cannot evaluate a code fragment: thread is not available")
if (!operatingThread.isSuspended) {
evaluationException("Evaluation is available only for the suspended threads")
}
try {
val executionContext = ExecutionContext(context, frameProxy)
return evaluateSafe(executionContext)
} catch (e: EvaluateException) {
throw e
} catch (e: ProcessCanceledException) {
evaluationException(e)
} catch (e: Eval4JInterpretingException) {
evaluationException(e.cause)
} catch (e: Exception) {
val isSpecialException = isSpecialException(e)
if (isSpecialException) {
evaluationException(e)
}
reportError(codeFragment, sourcePosition, e.message ?: "An exception occurred", e)
val cause = if (e.message != null) ": ${e.message}" else ""
evaluationException("Cannot evaluate the expression: $cause")
}
}
private fun evaluateSafe(context: ExecutionContext): Any? {
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
val result = if (classLoaderRef != null) {
evaluateWithCompilation(context, compiledData, classLoaderRef)
?: evaluateWithEval4J(context, compiledData, classLoaderRef)
} else {
evaluateWithEval4J(context, compiledData, classLoaderRef)
}
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
return evaluateWithEval4J(context, recompiledData, classLoaderRef).toJdiValue(context)
}
return when (result) {
is InterpreterResult -> result.toJdiValue(context)
else -> result
}
}
private fun compileCodeFragment(context: ExecutionContext): CompiledDataDescriptor {
val debugProcess = context.debugProcess
var analysisResult = checkForErrors(codeFragment, debugProcess)
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
// Repeat analysis with toString() added
analysisResult = checkForErrors(codeFragment, debugProcess)
}
val (bindingContext) = runReadAction {
DebuggerUtils.analyzeInlinedFunctions(
KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)),
codeFragment, false, analysisResult.bindingContext
)
}
val moduleDescriptor = analysisResult.moduleDescriptor
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor)
return createCompiledDataDescriptor(result, sourcePosition)
}
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
if (this !is KtExpressionCodeFragment) {
return false
}
val contentElement = runReadAction { getContentElement() }
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
if (contentElement != null && expressionType?.isInlineClassType() == true) {
val newExpression = runReadAction {
val expressionText = contentElement.text
KtPsiFactory(project).createExpression("($expressionText).toString()")
}
runInEdtAndWait {
project.executeWriteCommand("Wrap with 'toString()'") {
contentElement.replace(newExpression)
}
}
return true
}
return false
}
private data class ErrorCheckingResult(
val bindingContext: BindingContext,
val moduleDescriptor: ModuleDescriptor,
val files: List<KtFile>
)
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
return runInReadActionWithWriteActionPriorityWithPCE {
try {
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
} catch (e: IllegalArgumentException) {
evaluationException(e.message ?: e.toString())
}
val filesToAnalyze = listOf(codeFragment)
val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze)
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
if (analysisResult.isError()) {
evaluationException(analysisResult.error)
}
val bindingContext = analysisResult.bindingContext
bindingContext.diagnostics
.filter { it.factory !in IGNORED_DIAGNOSTICS }
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
?.let { evaluationException(DefaultErrorMessages.render(it)) }
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
}
}
private fun evaluateWithCompilation(
context: ExecutionContext,
compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference
): Value? {
return try {
runEvaluation(context, compiledData, classLoader) { args ->
val mainClassType = context.findClass(GENERATED_CLASS_NAME, classLoader) as? ClassType
?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME }
val returnValue = context.invokeMethod(mainClassType, mainMethod, args)
EvaluatorValueConverter(context).unref(returnValue)
}
} catch (e: Throwable) {
LOG.error("Unable to evaluate the expression with compilation", e)
return null
}
}
private fun evaluateWithEval4J(
context: ExecutionContext,
compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference?
): InterpreterResult {
val mainClassBytecode = compiledData.mainClass.bytes
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) }
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME }
return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args ->
val vm = context.vm.virtualMachine
val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isSuspended }
?: error("Can not find a thread to run evaluation on")
val eval = JDIEval(vm, classLoader, thread, context.invokePolicy)
interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval)
}
}
private fun <T> runEvaluation(
context: ExecutionContext,
compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference?,
block: (List<Value?>) -> T
): T {
// Preload additional classes
compiledData.classes
.filter { !it.isMainClass }
.forEach { context.findClass(it.className, classLoader) }
return context.vm.virtualMachine.executeWithBreakpointsDisabled {
for (parameterType in compiledData.mainMethodSignature.parameterTypes) {
context.findClass(parameterType, classLoader)
}
val args = calculateMainMethodCallArguments(context, compiledData)
block(args)
}
}
private fun calculateMainMethodCallArguments(context: ExecutionContext, compiledData: CompiledDataDescriptor): List<Value?> {
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
val valueParameters = compiledData.parameters
require(asmValueParameters.size == valueParameters.size)
val args = valueParameters.zip(asmValueParameters)
val variableFinder = VariableFinder(context)
return args.map { (parameter, asmType) ->
val result = variableFinder.find(parameter, asmType)
if (result == null) {
val name = parameter.debugString
fun isInsideDefaultInterfaceMethod(): Boolean {
val method = context.frameProxy.safeLocation()?.safeMethod() ?: return false
val desc = method.signature()
return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") }
}
if (parameter in compiledData.crossingBounds) {
evaluationException("'$name' is not captured")
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
evaluationException("Cannot find the backing field '${parameter.name}'")
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
evaluationException("Parameter evaluation is not supported for '\$default' methods")
} else {
throw VariableFinder.variableNotFound(context, buildString {
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
})
}
}
result.value
}
}
override fun getModifier() = null
companion object {
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> =
Errors.INVISIBLE_REFERENCE_DIAGNOSTICS + setOf(Errors.EXPERIMENTAL_API_USAGE_ERROR)
private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER)
private fun InterpreterResult.toJdiValue(context: ExecutionContext): Value? {
val jdiValue = when (this) {
is ValueReturned -> result
is ExceptionThrown -> {
when {
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
evaluationException(InvocationException(this.exception.value as ObjectReference))
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
throw exception.value as Throwable
else ->
evaluationException(exception.toString())
}
}
is AbnormalTermination -> evaluationException(message)
else -> throw IllegalStateException("Unknown result value produced by eval4j")
}
val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm.virtualMachine, jdiValue.asmType)
}
private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? {
val obj = value.obj(value.asmType) as? ObjectReference ?: return null
return VariableFinder.Result(EvaluatorValueConverter(context).unref(obj))
}
}
}
private fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
try {
allRequests.forEach { it.disable() }
return block()
} finally {
allRequests.forEach { it.enable() }
}
}
private fun isSpecialException(th: Throwable): Boolean {
return when (th) {
is ClassNotPreparedException,
is InternalException,
is AbsentInformationException,
is ClassNotLoadedException,
is IncompatibleThreadStateException,
is InconsistentDebugInfoException,
is ObjectCollectedException,
is VMDisconnectedException -> true
else -> false
}
}
private fun reportError(codeFragment: KtCodeFragment, position: SourcePosition?, message: String, throwable: Throwable? = null) {
runReadAction {
val contextFile = codeFragment.context?.containingFile
val attachments = arrayOf(
attachmentByPsiFile(contextFile),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "Position: " + position?.run { "${file.name}:$line" }),
Attachment("context.info", runReadAction { codeFragment.context?.text ?: "null" })
)
LOG.error(
"Cannot evaluate a code fragment of type " + codeFragment::class.java + ": " + message.decapitalize(),
throwable,
mergeAttachments(*attachments)
)
}
}
fun createCompiledDataDescriptor(result: CodeFragmentCompiler.CompilationResult, sourcePosition: SourcePosition?): CompiledDataDescriptor {
val localFunctionSuffixes = result.localFunctionSuffixes
val dumbParameters = ArrayList<CodeFragmentParameter.Dumb>(result.parameterInfo.parameters.size)
for (parameter in result.parameterInfo.parameters) {
val dumb = parameter.dumb
if (dumb.kind == CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
val suffix = localFunctionSuffixes[dumb]
if (suffix != null) {
dumbParameters += dumb.copy(name = dumb.name + suffix)
continue
}
}
dumbParameters += dumb
}
return CompiledDataDescriptor(
result.classes,
dumbParameters,
result.parameterInfo.crossingBounds,
result.mainMethodSignature,
sourcePosition
)
}
private fun evaluationException(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
private fun evaluationException(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.DebuggerBundle
import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.engine.ContextUtil
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.ui.EditorEvaluationCommand
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.psi.CommonClassNames
import com.intellij.psi.search.GlobalSearchScope
import com.sun.jdi.ClassType
import com.sun.jdi.Value
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type as AsmType
abstract class KotlinRuntimeTypeEvaluator(
editor: Editor?,
expression: KtExpression,
context: DebuggerContextImpl,
indicator: ProgressIndicator
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
override fun threadAction() {
var type: KotlinType? = null
try {
type = evaluate()
} catch (ignored: ProcessCanceledException) {
} catch (ignored: EvaluateException) {
} finally {
typeCalculationFinished(type)
}
}
protected abstract fun typeCalculationFinished(type: KotlinType?)
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
val project = evaluationContext.project
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
myElement.text, myElement.containingFile.context
)
KotlinEvaluatorBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
}
val value = evaluator.evaluate(evaluationContext)
if (value != null) {
return runReadAction { getCastableRuntimeType(evaluationContext.debugProcess.searchScope, value) }
}
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.surrounded.expression.null"))
}
companion object {
private fun getCastableRuntimeType(scope: GlobalSearchScope, value: Value): KotlinType? {
val myValue = value.asValue()
var psiClass = myValue.asmType.getClassDescriptor(scope)
if (psiClass != null) {
return psiClass.defaultType
}
val type = value.type()
if (type is ClassType) {
val superclass = type.superclass()
if (superclass != null && CommonClassNames.JAVA_LANG_OBJECT != superclass.name()) {
psiClass = AsmType.getType(superclass.signature()).getClassDescriptor(scope)
if (psiClass != null) {
return psiClass.defaultType
}
}
for (interfaceType in type.interfaces()) {
psiClass = AsmType.getType(interfaceType.signature()).getClassDescriptor(scope)
if (psiClass != null) {
return psiClass.defaultType
}
}
}
return null
}
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
protected fun dex(context: ExecutionContext, classes: Collection<ClassToLoad>): ByteArray? {
return AndroidDexer.getInstances(context.project).single().dex(classes)
}
protected fun wrapToByteBuffer(bytes: ArrayReference, context: ExecutionContext): ObjectReference {
val classLoader = context.classLoader
val byteBufferClass = context.findClass("java.nio.ByteBuffer", classLoader) as ClassType
val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
?: error("'wrap' method not found")
return context.invokeMethod(byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
}
protected fun tryLoadClass(context: ExecutionContext, fqName: String, classLoader: ClassLoaderReference?): ReferenceType? {
return try {
context.debugProcess.loadClass(context.evaluationContext, fqName, classLoader)
} catch (e: Throwable) {
null
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
interface AndroidDexer {
companion object : ProjectExtensionDescriptor<AndroidDexer>(
"org.jetbrains.kotlin.androidDexer", AndroidDexer::class.java
)
fun dex(classes: Collection<ClassToLoad>): ByteArray?
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
import com.intellij.debugger.engine.JVMNameUtil
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.isDexDebug
class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
isCompilingEvaluatorPreferred && context.debugProcess.isDexDebug()
}
private fun resolveClassLoaderClass(context: ExecutionContext): ClassType? {
return try {
val classLoader = context.classLoader
tryLoadClass(context, "dalvik.system.InMemoryDexClassLoader", classLoader) as? ClassType
} catch (e: EvaluateException) {
null
}
}
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"
) ?: error("Constructor method not found")
val dexBytes = dex(context, classes) ?: error("Can't dex classes")
val dexBytesMirror = mirrorOfByteArray(dexBytes, context)
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context)
val classLoader = context.classLoader
val args = listOf(dexByteBuffer, classLoader)
val newClassLoader = context.newInstance(inMemoryClassLoaderClass, constructorMethod, args) as ClassLoaderReference
context.keepReference(newClassLoader)
return newClassLoader
}
}
@@ -0,0 +1,116 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
import com.sun.jdi.ArrayReference
import com.sun.jdi.ArrayType
import com.sun.jdi.ClassLoaderReference
import com.sun.jdi.Value
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.tree.*
import kotlin.math.min
interface ClassLoadingAdapter {
companion object {
private const val CHUNK_SIZE = 4096
private val ADAPTERS = listOf(
AndroidOClassLoadingAdapter(),
OrdinaryClassLoadingAdapter()
)
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
val mainClass = classes.firstOrNull { it.isMainClass } ?: return null
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
if (!info.containsAdditionalClasses) {
info = analyzeClass(mainClass, info)
}
for (adapter in ADAPTERS) {
if (adapter.isApplicable(context, info)) {
return adapter.loadClasses(context, classes)
}
}
return null
}
data class ClassInfoForEvaluator(
val containsLoops: Boolean = false,
val containsCodeUnsupportedInEval4J: Boolean = false,
val containsAdditionalClasses: Boolean = false
) {
val isCompilingEvaluatorPreferred: Boolean
get() = containsLoops || containsCodeUnsupportedInEval4J || containsAdditionalClasses
}
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) }
val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME }
val visitedLabels = hashSetOf<Label>()
tailrec fun analyzeInsn(insn: AbstractInsnNode, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
when (insn) {
is LabelNode -> visitedLabels += insn.label
is JumpInsnNode -> {
if (insn.label.label in visitedLabels) {
return info.copy(containsLoops = true)
}
}
is TableSwitchInsnNode, is LookupSwitchInsnNode -> {
return info.copy(containsCodeUnsupportedInEval4J = true)
}
}
val nextInsn = insn.next ?: return info
return analyzeInsn(nextInsn, info)
}
val firstInsn = methodToRun.instructions?.first ?: return info
return analyzeInsn(firstInsn, info)
}
}
fun isApplicable(context: ExecutionContext, info: ClassInfoForEvaluator): Boolean
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference
fun mirrorOfByteArray(bytes: ByteArray, context: ExecutionContext): ArrayReference {
val classLoader = context.classLoader
val arrayClass = context.findClass("byte[]", classLoader) as ArrayType
val reference = context.newInstance(arrayClass, bytes.size)
context.keepReference(reference)
val mirrors = ArrayList<Value>(bytes.size)
for (byte in bytes) {
mirrors += context.vm.mirrorOf(byte)
}
var loaded = 0
while (loaded < mirrors.size) {
val chunkSize = min(CHUNK_SIZE, mirrors.size - loaded)
reference.setValues(loaded, mirrors, loaded, chunkSize)
loaded += chunkSize
}
return reference
}
}
@@ -0,0 +1,143 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.impl.ClassLoadingUtils
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.sun.jdi.ClassLoaderReference
import com.sun.jdi.ClassType
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.isDexDebug
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.ClassWriter
import org.jetbrains.org.objectweb.asm.Opcodes
class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
private companion object {
// This list should contain all superclasses of lambda classes.
// The order is relevant here: if we load Lambda first instead, during the definition of Lambda the class loader will try
// to load its superclass. It will succeed, probably with the help of some parent class loader, and the subsequent attempt to define
// the patched version of that superclass will fail with LinkageError (cannot redefine class)
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
// Copied from com.intellij.debugger.ui.impl.watch.CompilingEvaluator.changeSuperToMagicAccessor
fun changeSuperToMagicAccessor(bytes: ByteArray): ByteArray {
val classWriter = ClassWriter(0)
val classVisitor = object : ClassVisitor(Opcodes.API_VERSION, classWriter) {
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<String>?
) {
var newSuperName = superName
if ("java/lang/Object" == newSuperName) {
newSuperName = "sun/reflect/MagicAccessorImpl"
}
super.visit(version, access, name, signature, newSuperName, interfaces)
}
}
ClassReader(bytes).accept(classVisitor, 0)
return classWriter.toByteArray()
}
fun useMagicAccessor(context: ExecutionContext): Boolean {
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
return !javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9)
}
}
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator): Boolean {
return info.isCompilingEvaluatorPreferred && context.classLoader != null && !context.debugProcess.isDexDebug()
}
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
val process = context.debugProcess
val classLoader = try {
ClassLoadingUtils.getClassLoader(context.evaluationContext, process)
} catch (e: Exception) {
throw EvaluateException("Error creating evaluation class loader: $e", e)
}
try {
defineClasses(classes, context, classLoader)
} catch (e: Exception) {
throw EvaluateException("Error during classes definition $e", e)
}
return classLoader
}
private fun defineClasses(
classes: Collection<ClassToLoad>,
context: ExecutionContext,
classLoader: ClassLoaderReference
) {
val classesToLoad = if (classes.size == 1) {
// No need in loading lambda superclass if there're no lambdas
classes
} else {
val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map {
ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes)
}
lambdaSuperclasses + classes
}
for ((className, _, bytes) in classesToLoad) {
val patchedBytes = if (useMagicAccessor(context)) changeSuperToMagicAccessor(bytes) else bytes
defineClass(className, patchedBytes, context, classLoader)
}
}
private fun defineClass(
name: String,
bytes: ByteArray,
context: ExecutionContext,
classLoader: ClassLoaderReference
) {
try {
val vm = context.vm
val classLoaderType = classLoader.referenceType() as ClassType
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
val nameObj = vm.mirrorOf(name)
val args = listOf(nameObj, mirrorOfByteArray(bytes, context), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
context.invokeMethod(classLoader, defineMethod, args)
} catch (e: Exception) {
throw EvaluateException("Error during class $name definition: $e", e)
}
}
private class ClassBytes(val name: String) {
val bytes: ByteArray by lazy {
val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class")
?: throw EvaluateException("Couldn't find $name class in current class loader")
inputStream.use {
it.readBytes()
}
}
}
}
@@ -0,0 +1,287 @@
/*
* 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.compilation
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.CodeFragmentCodegen.Companion.getSharedTypeIfApplicable
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.*
import org.jetbrains.kotlin.idea.debugger.evaluate.*
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_CLASS_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.GENERATED_FUNCTION_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CompiledDataDescriptor.MethodSignature
import org.jetbrains.kotlin.idea.project.languageVersionSettings
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.data.KtClassOrObjectInfo
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.SimpleType
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.org.objectweb.asm.Type
class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
data class CompilationResult(
val classes: List<ClassToLoad>,
val parameterInfo: CodeFragmentParameterInfo,
val localFunctionSuffixes: Map<CodeFragmentParameter.Dumb, String>,
val mainMethodSignature: MethodSignature
)
fun compile(codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): CompilationResult {
return runReadAction { doCompile(codeFragment, bindingContext, moduleDescriptor) }
}
private fun doCompile(
codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor
): CompilationResult {
require(codeFragment is KtBlockCodeFragment || codeFragment is KtExpressionCodeFragment) {
"Unsupported code fragment type: $codeFragment"
}
val project = codeFragment.project
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(listOf(codeFragment))
val resolveSession = resolutionFacade.getFrontendService(ResolveSession::class.java)
val moduleDescriptorWrapper = EvaluatorModuleDescriptor(codeFragment, moduleDescriptor, resolveSession)
val defaultReturnType = moduleDescriptor.builtIns.unitType
val returnType = getReturnType(codeFragment, bindingContext, defaultReturnType)
val compilerConfiguration = CompilerConfiguration()
compilerConfiguration.languageVersionSettings = codeFragment.languageVersionSettings
val generationState = GenerationState.Builder(
project, ClassBuilderFactories.BINARIES, moduleDescriptorWrapper,
bindingContext, listOf(codeFragment), compilerConfiguration
).build()
val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext).analyze()
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
)
val codegenInfo = CodeFragmentCodegenInfo(classDescriptor, methodDescriptor, parameterInfo.parameters)
CodeFragmentCodegen.setCodeFragmentInfo(codeFragment, codegenInfo)
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
val classes = generationState.factory.asList().filterClassFiles()
.map { ClassToLoad(it.internalClassName, it.relativePath, it.asByteArray()) }
val methodSignature = getMethodSignature(methodDescriptor, parameterInfo.parameters, generationState)
val functionSuffixes = getLocalFunctionSuffixes(parameterInfo.parameters, generationState.typeMapper)
generationState.destroy()
return CompilationResult(classes, parameterInfo, functionSuffixes, methodSignature)
}
private fun getLocalFunctionSuffixes(
parameters: List<CodeFragmentParameter.Smart>,
typeMapper: KotlinTypeMapper
): Map<CodeFragmentParameter.Dumb, String> {
val result = mutableMapOf<CodeFragmentParameter.Dumb, String>()
for (parameter in parameters) {
if (parameter.kind != CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
continue
}
val ownerClassName = typeMapper.mapOwner(parameter.targetDescriptor).internalName
val lastDollarIndex = ownerClassName.lastIndexOf('$').takeIf { it >= 0 } ?: continue
result[parameter.dumb] = ownerClassName.drop(lastDollarIndex)
}
return result
}
private fun getMethodSignature(
methodDescriptor: FunctionDescriptor,
parameters: List<CodeFragmentParameter.Smart>,
state: GenerationState
): MethodSignature {
val typeMapper = state.typeMapper
val asmSignature = typeMapper.mapSignatureSkipGeneric(methodDescriptor)
val asmParameters = parameters.zip(asmSignature.valueParameters).map { (param, sigParam) ->
getSharedTypeIfApplicable(param.targetDescriptor, typeMapper) ?: sigParam.asmType
}
return MethodSignature(asmParameters, asmSignature.returnType)
}
private fun getReturnType(
codeFragment: KtCodeFragment,
bindingContext: BindingContext,
defaultReturnType: SimpleType
): KotlinType {
return when (codeFragment) {
is KtExpressionCodeFragment -> {
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, codeFragment.getContentElement()]
typeInfo?.type ?: defaultReturnType
}
is KtBlockCodeFragment -> {
val blockExpression = codeFragment.getContentElement()
val lastStatement = blockExpression.statements.lastOrNull() ?: return defaultReturnType
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, lastStatement]
typeInfo?.type ?: defaultReturnType
}
else -> defaultReturnType
}
}
private fun createDescriptorsForCodeFragment(
declaration: KtCodeFragment,
className: Name,
methodName: Name,
parameterInfo: CodeFragmentParameterInfo,
returnType: KotlinType,
packageFragmentDescriptor: PackageFragmentDescriptor
): Pair<ClassDescriptor, FunctionDescriptor> {
val classDescriptor = ClassDescriptorImpl(
packageFragmentDescriptor, className, Modality.FINAL, ClassKind.OBJECT,
emptyList(),
KotlinSourceElement(declaration),
false,
LockBasedStorageManager.NO_LOCKS
)
val methodDescriptor = SimpleFunctionDescriptorImpl.create(
classDescriptor, Annotations.EMPTY, methodName,
CallableMemberDescriptor.Kind.SYNTHESIZED, classDescriptor.source
)
val parameters = parameterInfo.parameters.mapIndexed { index, parameter ->
ValueParameterDescriptorImpl(
methodDescriptor, null, index, Annotations.EMPTY, Name.identifier("p$index"),
parameter.targetType,
declaresDefaultValue = false,
isCrossinline = false,
isNoinline = false,
varargElementType = null,
source = SourceElement.NO_SOURCE
)
}
methodDescriptor.initialize(
null, classDescriptor.thisAsReceiverParameter, emptyList(),
parameters, returnType, Modality.FINAL, Visibilities.PUBLIC
)
val memberScope = EvaluatorMemberScopeForMethod(methodDescriptor)
val constructor = ClassConstructorDescriptorImpl.create(classDescriptor, Annotations.EMPTY, true, classDescriptor.source)
classDescriptor.initialize(memberScope, setOf(constructor), constructor)
return Pair(classDescriptor, methodDescriptor)
}
}
private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() {
override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
return if (name == methodDescriptor.name) {
listOf(methodDescriptor)
} else {
emptyList()
}
}
override fun getContributedDescriptors(
kindFilter: DescriptorKindFilter,
nameFilter: (Name) -> Boolean
): Collection<DeclarationDescriptor> {
return if (kindFilter.accepts(methodDescriptor) && nameFilter(methodDescriptor.name)) {
listOf(methodDescriptor)
} else {
emptyList()
}
}
override fun getFunctionNames() = setOf(methodDescriptor.name)
override fun printScopeStructure(p: Printer) {
p.println(this::class.java.simpleName)
}
}
private class EvaluatorModuleDescriptor(
val codeFragment: KtCodeFragment,
val moduleDescriptor: ModuleDescriptor,
resolveSession: ResolveSession
) : ModuleDescriptor by moduleDescriptor {
private val declarationProvider = object : PackageMemberDeclarationProvider {
override fun getPackageFiles() = listOf(codeFragment)
override fun containsFile(file: KtFile) = file == codeFragment
override fun getDeclarationNames() = emptySet<Name>()
override fun getDeclarations(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) = emptyList<KtDeclaration>()
override fun getClassOrObjectDeclarations(name: Name) = emptyList<KtClassOrObjectInfo<*>>()
override fun getAllDeclaredSubPackages(nameFilter: (Name) -> Boolean) = emptyList<FqName>()
override fun getFunctionDeclarations(name: Name) = emptyList<KtNamedFunction>()
override fun getPropertyDeclarations(name: Name) = emptyList<KtProperty>()
override fun getTypeAliasDeclarations(name: Name) = emptyList<KtTypeAlias>()
override fun getDestructuringDeclarationsEntries(name: Name) = emptyList<KtDestructuringDeclarationEntry>()
override fun getScriptDeclarations(name: Name) = emptyList<KtScriptInfo>()
}
val packageFragmentForEvaluator = LazyPackageDescriptor(this, FqName.ROOT, resolveSession, declarationProvider)
override fun getPackage(fqName: FqName): PackageViewDescriptor {
val originalPackageDescriptor = moduleDescriptor.getPackage(fqName)
if (fqName != FqName.ROOT) {
return originalPackageDescriptor
}
return object : DeclarationDescriptorImpl(Annotations.EMPTY, fqName.shortNameOrSpecial()), PackageViewDescriptor {
override fun getContainingDeclaration() = originalPackageDescriptor.containingDeclaration
override val fqName get() = originalPackageDescriptor.fqName
override val module get() = this@EvaluatorModuleDescriptor
override val memberScope by lazy {
if (fragments.isEmpty()) {
MemberScope.Empty
} else {
val scopes = fragments.map { it.getMemberScope() } + SubpackagesScope(module, fqName)
ChainedMemberScope("package view scope for $fqName in ${module.name}", scopes)
}
}
override val fragments by lazy { originalPackageDescriptor.fragments + packageFragmentForEvaluator }
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
return visitor.visitPackageViewDescriptor(this, data)
}
}
}
}
private val OutputFile.internalClassName: String
get() = relativePath.removeSuffix(".class").replace('/', '.')
@@ -0,0 +1,380 @@
/*
* 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.compilation
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
import org.jetbrains.kotlin.idea.debugger.safeLocation
import org.jetbrains.kotlin.idea.debugger.safeMethod
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.KotlinType
class CodeFragmentParameterInfo(
val parameters: List<Smart>,
val crossingBounds: Set<Dumb>
)
/*
The purpose of this class is to figure out what parameters the received code fragment captures.
It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers).
*/
class CodeFragmentParameterAnalyzer(
private val context: ExecutionContext,
private val codeFragment: KtCodeFragment,
private val bindingContext: BindingContext
) {
private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>()
private val crossingBounds = mutableSetOf<Dumb>()
private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java)
private val containingPrimaryConstructor: ConstructorDescriptor? by lazy {
context.frameProxy.safeLocation()?.safeMethod()?.takeIf { it.isConstructor } ?: return@lazy null
val constructor = codeFragment.context?.getParentOfType<KtPrimaryConstructor>(false) ?: return@lazy null
bindingContext[BindingContext.CONSTRUCTOR, constructor]
}
fun analyze(): CodeFragmentParameterInfo {
onceUsedChecker.trigger()
codeFragment.accept(object : KtTreeVisitor<Unit>() {
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression, data: Unit?): Void? {
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return null
processResolvedCall(resolvedCall, expression)
return null
}
private fun processResolvedCall(resolvedCall: ResolvedCall<*>, expression: KtSimpleNameExpression) {
// Capture dispatch receiver for the extension callable
run {
val descriptor = resolvedCall.resultingDescriptor
val containingClass = descriptor?.containingDeclaration as? ClassDescriptor
val extensionParameter = descriptor?.extensionReceiverParameter
if (descriptor != null && descriptor !is DebuggerFieldPropertyDescriptor
&& extensionParameter != null && containingClass != null
) {
if (containingClass.kind != ClassKind.OBJECT) {
processDispatchReceiver(containingClass)
}
}
}
if (runReadAction { expression.isDotSelector() }) {
// The receiver expression is already captured for this reference
return
}
if (isCodeFragmentDeclaration(resolvedCall.resultingDescriptor)) {
// The reference is from the code fragment we analyze, no need to capture
return
}
var processed = false
val extensionReceiver = resolvedCall.extensionReceiver
if (extensionReceiver is ImplicitReceiver) {
val descriptor = extensionReceiver.declarationDescriptor
val parameter = processReceiver(extensionReceiver)
checkBounds(descriptor, expression, parameter)
processed = true
}
val dispatchReceiver = resolvedCall.dispatchReceiver
if (dispatchReceiver is ImplicitReceiver) {
val descriptor = dispatchReceiver.declarationDescriptor
val parameter = processReceiver(dispatchReceiver)
if (parameter != null) {
checkBounds(descriptor, expression, parameter)
processed = true
}
}
if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) {
val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor
val parameter = processSyntheticFieldVariable(descriptor)
if (parameter != null) {
checkBounds(descriptor, expression, parameter)
processed = true
}
}
// If a reference has receivers, we can calculate its value using them, no need to capture
if (!processed) {
if (resolvedCall is VariableAsFunctionResolvedCall) {
processResolvedCall(resolvedCall.functionCall, expression)
processResolvedCall(resolvedCall.variableCall, expression)
} else {
processDescriptor(resolvedCall.resultingDescriptor, expression)
}
}
}
private fun processDescriptor(descriptor: DeclarationDescriptor, expression: KtSimpleNameExpression) {
val parameter = processDebugLabel(descriptor)
?: processCoroutineContextCall(descriptor)
?: processSimpleNameExpression(descriptor)
checkBounds(descriptor, expression, parameter)
}
override fun visitThisExpression(expression: KtThisExpression, data: Unit?): Void? {
val instanceReference = runReadAction { expression.instanceReference }
val target = bindingContext[BindingContext.REFERENCE_TARGET, instanceReference]
if (isCodeFragmentDeclaration(target)) {
// The reference is from the code fragment we analyze, no need to capture
return null
}
val parameter = when (target) {
is ClassDescriptor -> processDispatchReceiver(target)
is CallableDescriptor -> {
val type = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type
type?.let { processExtensionReceiver(target, type, expression.getLabelName()) }
}
else -> null
}
if (parameter != null) {
checkBounds(target, expression, parameter)
}
return null
}
override fun visitSuperExpression(expression: KtSuperExpression, data: Unit?): Void {
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'super' call expression is not supported")
}
}, Unit)
return CodeFragmentParameterInfo(parameters.values.toList(), crossingBounds)
}
private fun processReceiver(receiver: ImplicitReceiver): Smart? {
return when (receiver) {
is ImplicitClassReceiver -> processDispatchReceiver(receiver.classDescriptor)
is ExtensionReceiver -> processExtensionReceiver(receiver.declarationDescriptor, receiver.type, null)
else -> null
}
}
private fun processDispatchReceiver(descriptor: ClassDescriptor): Smart? {
if (descriptor.kind == ClassKind.OBJECT || containingPrimaryConstructor != null) {
return null
}
val type = descriptor.defaultType
return parameters.getOrPut(descriptor) {
Smart(Dumb(Kind.DISPATCH_RECEIVER, "", AsmUtil.THIS + "@" + descriptor.name.asString()), type, descriptor)
}
}
private fun processExtensionReceiver(descriptor: CallableDescriptor, receiverType: KotlinType, label: String?): Smart? {
if (isFakeFunctionForJavaContext(descriptor)) {
return processFakeJavaCodeReceiver(descriptor)
}
val actualLabel = label ?: getLabel(descriptor) ?: return null
val receiverParameter = descriptor.extensionReceiverParameter ?: return null
return parameters.getOrPut(descriptor) {
Smart(Dumb(Kind.EXTENSION_RECEIVER, actualLabel, AsmUtil.THIS + "@" + actualLabel), receiverType, receiverParameter)
}
}
private fun getLabel(callableDescriptor: CallableDescriptor): String? {
val source = callableDescriptor.source.getPsi()
if (source is KtFunctionLiteral) {
getCallLabelForLambdaArgument(source, bindingContext)?.let { return it }
}
return callableDescriptor.name.takeIf { !it.isSpecial }?.asString()
}
private fun isFakeFunctionForJavaContext(descriptor: CallableDescriptor): Boolean {
return descriptor is FunctionDescriptor
&& descriptor.name.asString() == FAKE_JAVA_CONTEXT_FUNCTION_NAME
&& codeFragment.getCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE) != null
}
private fun processFakeJavaCodeReceiver(descriptor: CallableDescriptor): Smart? {
val receiverParameter = descriptor
.takeIf { descriptor is FunctionDescriptor }
?.extensionReceiverParameter
?: return null
val label = FAKE_JAVA_CONTEXT_FUNCTION_NAME
val type = receiverParameter.type
return parameters.getOrPut(descriptor) {
Smart(Dumb(Kind.FAKE_JAVA_OUTER_CLASS, label, AsmUtil.THIS), type, receiverParameter)
}
}
private fun processSyntheticFieldVariable(descriptor: SyntheticFieldDescriptor): Smart? {
val propertyDescriptor = descriptor.propertyDescriptor
val fieldName = propertyDescriptor.name.asString()
val type = propertyDescriptor.type
return parameters.getOrPut(descriptor) {
Smart(Dumb(Kind.FIELD_VAR, fieldName, "field"), type, descriptor)
}
}
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? {
if (target is ValueParameterDescriptor && target.isCrossinline) {
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported")
}
val isLocalTarget = (target as? DeclarationDescriptorWithVisibility)?.visibility == Visibilities.LOCAL
val isPrimaryConstructorParameter = !isLocalTarget
&& target is PropertyDescriptor
&& isContainingPrimaryConstructorParameter(target)
if (!isLocalTarget && !isPrimaryConstructorParameter) {
return null
}
return when (target) {
is FunctionDescriptor -> {
val type = SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(target, false)
parameters.getOrPut(target) {
Smart(Dumb(Kind.LOCAL_FUNCTION, target.name.asString()), type, target)
}
}
is ValueDescriptor -> {
parameters.getOrPut(target) {
val type = target.type
@Suppress("DEPRECATION")
val kind = if (target is LocalVariableDescriptor && target.isDelegated) Kind.DELEGATED else Kind.ORDINARY
Smart(Dumb(kind, target.name.asString()), type, target)
}
}
else -> null
}
}
private fun isContainingPrimaryConstructorParameter(target: PropertyDescriptor): Boolean {
val primaryConstructor = containingPrimaryConstructor ?: return false
for (parameter in primaryConstructor.valueParameters) {
val property = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameter]
if (target == property) {
return true
}
}
return false
}
private fun processCoroutineContextCall(target: DeclarationDescriptor): Smart? {
if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) {
return parameters.getOrPut(target) {
Smart(Dumb(Kind.COROUTINE_CONTEXT, ""), target.type, target)
}
}
return null
}
private fun processDebugLabel(target: DeclarationDescriptor): Smart? {
val debugLabelPropertyDescriptor = target as? DebugLabelPropertyDescriptor ?: return null
val labelName = debugLabelPropertyDescriptor.labelName
val debugString = debugLabelPropertyDescriptor.name.asString()
return parameters.getOrPut(target) {
val type = debugLabelPropertyDescriptor.type
Smart(Dumb(Kind.DEBUG_LABEL, labelName, debugString), type, debugLabelPropertyDescriptor)
}
}
fun checkBounds(descriptor: DeclarationDescriptor?, expression: KtExpression, parameter: Smart?) {
if (parameter == null || descriptor !is DeclarationDescriptorWithSource) {
return
}
val targetPsi = descriptor.source.getPsi()
if (targetPsi != null && doesCrossInlineBounds(expression, targetPsi)) {
crossingBounds += parameter.dumb
}
}
private fun doesCrossInlineBounds(expression: PsiElement, declaration: PsiElement): Boolean {
val declarationParent = declaration.parent ?: return false
var currentParent: PsiElement? = expression.parent?.takeIf { it.isInside(declarationParent) } ?: return false
while (currentParent != null && currentParent != declarationParent) {
if (currentParent is KtFunction) {
val functionDescriptor = bindingContext[BindingContext.FUNCTION, currentParent]
if (functionDescriptor != null && !functionDescriptor.isInline) {
return true
}
}
currentParent = when (currentParent) {
is KtCodeFragment -> currentParent.context
else -> currentParent.parent
}
}
return false
}
private fun isCodeFragmentDeclaration(descriptor: DeclarationDescriptor?): Boolean {
if (descriptor is ValueParameterDescriptor && isCodeFragmentDeclaration(descriptor.containingDeclaration)) {
return true
}
if (descriptor !is DeclarationDescriptorWithSource) {
return false
}
return descriptor.source.getPsi()?.containingFile is KtCodeFragment
}
private tailrec fun PsiElement.isInside(parent: PsiElement): Boolean {
if (parent.isAncestor(this)) {
return true
}
val context = (this.containingFile as? KtCodeFragment)?.context ?: return false
return context.isInside(parent)
}
}
private class OnceUsedChecker(private val clazz: Class<*>) {
private var used = false
fun trigger() {
if (used) {
error(clazz.name + " may be only used once")
}
used = true
}
}
@@ -0,0 +1,221 @@
/*
* 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.compilation
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
import org.jetbrains.kotlin.builtins.PrimitiveType
import com.sun.jdi.*
import org.jetbrains.kotlin.backend.common.SimpleMemberScope
import com.sun.jdi.Type as JdiType
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.externalDescriptors
import org.jetbrains.kotlin.platform.TargetPlatform
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.org.objectweb.asm.Type as AsmType
class DebugLabelPropertyDescriptorProvider(val codeFragment: KtCodeFragment, val debugProcess: DebugProcessImpl) {
companion object {
fun getMarkupMap(debugProcess: DebugProcessImpl) = doGetMarkupMap(debugProcess) ?: emptyMap()
private fun doGetMarkupMap(debugProcess: DebugProcessImpl): Map<out Value?, ValueMarkup>? {
if (ApplicationManager.getApplication().isUnitTestMode) {
return NodeDescriptorImpl.getMarkupMap(debugProcess)
}
val debugSession = debugProcess.session.xDebugSession as? XDebugSessionImpl
@Suppress("UNCHECKED_CAST")
return debugSession?.valueMarkers?.allMarkers?.filterKeys { it is Value? } as Map<out Value?, ValueMarkup>?
}
}
private val moduleDescriptor = DebugLabelModuleDescriptor
fun supplyDebugLabels() {
val packageFragment = object : PackageFragmentDescriptorImpl(moduleDescriptor, FqName.ROOT) {
val properties = createDebugLabelDescriptors(this)
override fun getMemberScope() = SimpleMemberScope(properties)
}
codeFragment.externalDescriptors = packageFragment.properties
}
private fun createDebugLabelDescriptors(containingDeclaration: PackageFragmentDescriptor): List<PropertyDescriptor> {
val markupMap = getMarkupMap(debugProcess)
val result = ArrayList<PropertyDescriptor>(markupMap.size)
nextValue@ for ((value, markup) in markupMap) {
val labelName = markup.text
val kotlinType = value?.type()?.let { convertType(it) } ?: moduleDescriptor.builtIns.nullableAnyType
result += createDebugLabelDescriptor(labelName, kotlinType, containingDeclaration)
}
return result
}
private fun createDebugLabelDescriptor(
labelName: String,
type: KotlinType,
containingDeclaration: PackageFragmentDescriptor
): PropertyDescriptor {
val propertyDescriptor = DebugLabelPropertyDescriptor(containingDeclaration, labelName)
propertyDescriptor.setType(type, emptyList(), null, null)
val getterDescriptor = PropertyGetterDescriptorImpl(
propertyDescriptor,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
/* isDefault = */ false,
/* isExternal = */ false,
/* isInline = */ false,
CallableMemberDescriptor.Kind.SYNTHESIZED,
/* original = */ null,
SourceElement.NO_SOURCE
).apply { initialize(type) }
propertyDescriptor.initialize(getterDescriptor, null)
return propertyDescriptor
}
private fun convertType(type: JdiType): KotlinType {
val builtIns = moduleDescriptor.builtIns
return when (type) {
is VoidType -> builtIns.unitType
is LongType -> builtIns.longType
is DoubleType -> builtIns.doubleType
is CharType -> builtIns.charType
is FloatType -> builtIns.floatType
is ByteType -> builtIns.byteType
is IntegerType -> builtIns.intType
is BooleanType -> builtIns.booleanType
is ShortType -> builtIns.shortType
is ArrayType -> {
when (val componentType = type.componentType()) {
is VoidType -> builtIns.getArrayType(Variance.INVARIANT, builtIns.unitType)
is LongType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.LONG)
is DoubleType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.DOUBLE)
is CharType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.CHAR)
is FloatType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.FLOAT)
is ByteType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BYTE)
is IntegerType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.INT)
is BooleanType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BOOLEAN)
is ShortType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.SHORT)
else -> builtIns.getArrayType(Variance.INVARIANT, convertReferenceType(componentType))
}
}
is ReferenceType -> convertReferenceType(type)
else -> builtIns.anyType
}
}
private fun convertReferenceType(type: JdiType): KotlinType {
require(type is ClassType || type is InterfaceType)
val asmType = AsmType.getType(type.signature())
val project = codeFragment.project
val classDescriptor = asmType.getClassDescriptor(GlobalSearchScope.allScope(project), mapBuiltIns = false)
?: return moduleDescriptor.builtIns.nullableAnyType
return classDescriptor.defaultType
}
}
private object DebugLabelModuleDescriptor
: DeclarationDescriptorImpl(Annotations.EMPTY, Name.identifier("DebugLabelExtensions")),
ModuleDescriptor
{
override val builtIns: KotlinBuiltIns
get() = DefaultBuiltIns.Instance
override val stableName: Name?
get() = name
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor) = false
override fun getPackage(fqName: FqName): PackageViewDescriptor {
return object : PackageViewDescriptor, DeclarationDescriptorImpl(Annotations.EMPTY, FqName.ROOT.shortNameOrSpecial()) {
override fun getContainingDeclaration(): PackageViewDescriptor? = null
override val fqName: FqName
get() = FqName.ROOT
override val memberScope: MemberScope
get() = MemberScope.Empty
override val module: ModuleDescriptor
get() = this@DebugLabelModuleDescriptor
override val fragments: List<PackageFragmentDescriptor>
get() = emptyList()
override fun <R : Any?, D : Any?> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
return visitor.visitPackageViewDescriptor(this, data)
}
}
}
override val platform: TargetPlatform?
get() = JvmPlatforms.unspecifiedJvmPlatform
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
return emptyList()
}
override val allDependencyModules: List<ModuleDescriptor>
get() = emptyList()
override val expectedByModules: List<ModuleDescriptor>
get() = emptyList()
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>): T? = null
override val isValid: Boolean
get() = true
override fun assertValid() {}
}
internal class DebugLabelPropertyDescriptor(
containingDeclaration: DeclarationDescriptor,
val labelName: String
) : PropertyDescriptorImpl(
containingDeclaration,
null,
Annotations.EMPTY,
Modality.FINAL,
Visibilities.PUBLIC,
/*isVar = */false,
Name.identifier(labelName + "_DebugLabel"),
CallableMemberDescriptor.Kind.SYNTHESIZED,
SourceElement.NO_SOURCE,
/*lateInit = */false,
/*isConst = */false,
/*isExpect = */false,
/*isActual = */false,
/*isExternal = */false,
/*isDelegated = */false
)
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.sun.jdi.ClassLoaderReference
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
fun loadClassesSafely(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
return try {
loadClasses(context, classes)
} catch (e: EvaluateException) {
throw e
} catch (e: Throwable) {
LOG.debug("Failed to evaluate expression", e)
null
}
}
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
if (classes.isEmpty()) {
return null
}
return ClassLoadingAdapter.loadClasses(context, classes)
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate.surroundWith;
import com.intellij.lang.surroundWith.Surrounder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.core.surroundWith.KotlinExpressionSurroundDescriptorBase;
import org.jetbrains.kotlin.idea.debugger.surroundWith.KotlinRuntimeTypeCastSurrounder;
public class KotlinDebuggerExpressionSurroundDescriptor extends KotlinExpressionSurroundDescriptorBase {
private static final Surrounder[] SURROUNDERS = {
new KotlinRuntimeTypeCastSurrounder()
};
@Override
@NotNull
public Surrounder[] getSurrounders() {
return SURROUNDERS;
}
}
@@ -0,0 +1,104 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.surroundWith
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.debugger.DebuggerBundle
import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.openapi.application.Result
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.util.ProgressWindow
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinRuntimeTypeEvaluator
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.surroundWith.KotlinExpressionSurrounder
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
class KotlinRuntimeTypeCastSurrounder: KotlinExpressionSurrounder() {
override fun isApplicable(expression: KtExpression): Boolean {
if (!super.isApplicable(expression)) return false
if (!expression.isPhysical) return false
val file = expression.containingFile
if (file !is KtCodeFragment) return false
val type = expression.analyze(BodyResolveMode.PARTIAL).getType(expression) ?: return false
return TypeUtils.canHaveSubtypes(KotlinTypeChecker.DEFAULT, type)
}
override fun surroundExpression(project: Project, editor: Editor, expression: KtExpression): TextRange? {
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession
if (debuggerSession != null) {
val progressWindow = ProgressWindow(true, expression.project)
val worker = SurroundWithCastWorker(editor, expression, debuggerContext, progressWindow)
progressWindow.title = DebuggerBundle.message("title.evaluating")
debuggerContext.debugProcess?.managerThread?.startProgress(worker, progressWindow)
}
return null
}
override fun getTemplateDescription(): String {
return KotlinBundle.message("surround.with.runtime.type.cast.template")
}
private inner class SurroundWithCastWorker(
private val myEditor: Editor,
expression: KtExpression,
context: DebuggerContextImpl,
indicator: ProgressIndicator
): KotlinRuntimeTypeEvaluator(myEditor, expression, context, indicator) {
override fun typeCalculationFinished(type: KotlinType?) {
if (type == null) return
hold()
val project = myEditor.project
DebuggerInvocationUtil.invokeLater(project, Runnable {
object : WriteCommandAction<Any>(project, CodeInsightBundle.message("command.name.surround.with.runtime.cast")) {
override fun run(result: Result<Any>) {
try {
val factory = KtPsiFactory(myElement.project)
val fqName = DescriptorUtils.getFqName(type.constructor.declarationDescriptor!!)
val parentCast = factory.createExpression("(expr as " + fqName.asString() + ")") as KtParenthesizedExpression
val cast = parentCast.expression as KtBinaryExpressionWithTypeRHS
cast.left.replace(myElement)
val expr = myElement.replace(parentCast) as KtExpression
ShortenReferences.DEFAULT.process(expr)
val range = expr.textRange
myEditor.selectionModel.setSelection(range.startOffset, range.endOffset)
myEditor.caretModel.moveToOffset(range.endOffset)
myEditor.scrollingModel.scrollToCaret(ScrollType.RELATIVE)
}
finally {
release()
}
}
}.execute()
}, myProgressIndicator.modalityState)
}
}
}
@@ -0,0 +1,264 @@
/*
* 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.variables
import com.sun.jdi.*
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Result
import org.jetbrains.kotlin.idea.debugger.isSubtype
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
import org.jetbrains.org.objectweb.asm.Type as AsmType
import com.sun.jdi.Type as JdiType
import kotlin.jvm.internal.Ref
@Suppress("SpellCheckingInspection")
class EvaluatorValueConverter(private val context: ExecutionContext) {
private companion object {
private val UNBOXING_METHOD_NAMES = mapOf(
"java/lang/Boolean" to "booleanValue",
"java/lang/Character" to "charValue",
"java/lang/Byte" to "byteValue",
"java/lang/Short" to "shortValue",
"java/lang/Integer" to "intValue",
"java/lang/Float" to "floatValue",
"java/lang/Long" to "longValue",
"java/lang/Double" to "doubleValue"
)
}
// Nearly accurate: doesn't do deep checks for Ref wrappers. Use `coerce()` for more precise check.
fun typeMatches(requestedType: AsmType, actualTypeObj: JdiType?): Boolean {
if (actualTypeObj == null) return true
// Main path
if (requestedType.descriptor == "Ljava/lang/Object;" || actualTypeObj.isSubtype(requestedType)) {
return true
}
val actualType = actualTypeObj.asmType()
fun isRefWrapper(wrapperType: AsmType, objType: AsmType): Boolean {
return !objType.isPrimitiveType && wrapperType.className == Ref.ObjectRef::class.java.name
}
if (isRefWrapper(actualType, requestedType) || isRefWrapper(requestedType, actualType)) {
return true
}
val unwrappedActualType = unwrap(actualType)
val unwrappedRequestedType = unwrap(requestedType)
return unwrappedActualType == unwrappedRequestedType
}
fun coerce(value: Value?, type: AsmType): Result? {
val unrefResult = coerceRef(value, type) ?: return null
return coerceBoxing(unrefResult.value, type)
}
private fun coerceRef(value: Value?, type: AsmType): Result? {
when {
type.isRefType -> {
if (value != null && value.asmType().isRefType) {
return Result(value)
}
return Result(ref(value))
}
value != null && value.asmType().isRefType -> {
if (type.isRefType) {
return Result(value)
}
return Result(unref(value))
}
else -> return Result(value)
}
}
private fun coerceBoxing(value: Value?, type: AsmType): Result? {
when {
value == null -> return Result(value)
type == AsmType.VOID_TYPE -> return Result(context.vm.mirrorOfVoid())
type.isBoxedType -> {
if (value.asmType().isBoxedType) {
return Result(value)
}
if (value !is PrimitiveValue) {
return null
}
return Result(box(value))
}
type.isPrimitiveType -> {
if (value is PrimitiveValue) {
return Result(value)
}
if (value !is ObjectReference || !value.asmType().isBoxedType) {
return null
}
return Result(unbox(value))
}
value is PrimitiveValue -> {
if (type.sort != AsmType.OBJECT) {
return null
}
val boxedValue = box(value)
if (!typeMatches(type, boxedValue?.type())) {
return null
}
return Result(boxedValue)
}
else -> return Result(value)
}
}
private fun box(value: Value?): Value? {
if (value !is PrimitiveValue) {
return value
}
val unboxedType = value.asmType()
val boxedType = box(unboxedType)
val boxedTypeClass = (context.findClass(boxedType) as ClassType?)
?: error("Class $boxedType is not loaded")
val methodDesc = AsmType.getMethodDescriptor(boxedType, unboxedType)
val valueOfMethod = boxedTypeClass.methodsByName("valueOf", methodDesc).first()
return context.invokeMethod(boxedTypeClass, valueOfMethod, listOf(value))
}
private fun unbox(value: Value?): Value? {
if (value !is ObjectReference) {
return value
}
val boxedTypeClass = value.referenceType() as? ClassType ?: return value
val boxedType = boxedTypeClass.asmType().takeIf { it.isBoxedType } ?: return value
val unboxedType = unbox(boxedType)
val unboxingMethodName = UNBOXING_METHOD_NAMES.getValue(boxedType.internalName)
val methodDesc = AsmType.getMethodDescriptor(unboxedType)
val valueMethod = boxedTypeClass.methodsByName(unboxingMethodName, methodDesc).first()
return context.invokeMethod(value, valueMethod, emptyList())
}
private fun ref(value: Value?): Value? {
if (value is VoidValue) {
return value
}
fun wrapRef(value: Value?, refTypeClass: ClassType): Value? {
val constructor = refTypeClass.methods().single { it.isConstructor }
val ref = context.newInstance(refTypeClass, constructor, emptyList())
context.keepReference(ref)
val elementField = refTypeClass.fieldByName("element") ?: error("'element' field not found")
ref.setValue(elementField, value)
return ref
}
if (value is PrimitiveValue) {
val primitiveType = value.asmType()
val refType = PRIMITIVE_TO_REF.getValue(primitiveType)
val refTypeClass = (context.findClass(refType) as ClassType?)
?: error("Class $refType is not loaded")
return wrapRef(value, refTypeClass)
} else {
val refType = AsmType.getType(Ref.ObjectRef::class.java)
val refTypeClass = (context.findClass(refType) as ClassType?)
?: error("Class $refType is not loaded")
return wrapRef(value, refTypeClass)
}
}
fun unref(value: Value?): Value? {
if (value !is ObjectReference) {
return value
}
val type = value.type()
if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) {
return value
}
val field = type.fieldByName("element") ?: return value
return value.getValue(field)
}
}
private fun unbox(type: AsmType): AsmType {
if (type.sort == AsmType.OBJECT) {
return BOXED_TO_PRIMITIVE[type] ?: type
}
return type
}
private fun box(type: AsmType): AsmType {
if (type.isPrimitiveType) {
return PRIMITIVE_TO_BOXED[type] ?: type
}
return type
}
private fun unwrap(type: AsmType): AsmType {
if (type.sort != AsmType.OBJECT) {
return type
}
return REF_TO_PRIMITIVE[type] ?: BOXED_TO_PRIMITIVE[type] ?: type
}
private val AsmType.isPrimitiveType: Boolean
get() = sort != AsmType.OBJECT && sort != AsmType.ARRAY
private val AsmType.isRefType: Boolean
get() = sort == AsmType.OBJECT && this in REF_TYPES
private val AsmType.isBoxedType: Boolean
get() = this in BOXED_TO_PRIMITIVE
private fun Value.asmType(): AsmType {
return type().asmType()
}
private fun JdiType.asmType(): AsmType {
return AsmType.getType(signature())
}
private val BOXED_TO_PRIMITIVE: Map<AsmType, AsmType> = JvmPrimitiveType.values()
.map { Pair(AsmType.getObjectType(it.wrapperFqName.internalNameWithoutInnerClasses), AsmType.getType(it.desc)) }
.toMap()
private val PRIMITIVE_TO_BOXED: Map<AsmType, AsmType> = BOXED_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
private val REF_TO_PRIMITIVE = mapOf(
Ref.ByteRef::class.java.name to AsmType.BYTE_TYPE,
Ref.ShortRef::class.java.name to AsmType.SHORT_TYPE,
Ref.IntRef::class.java.name to AsmType.INT_TYPE,
Ref.LongRef::class.java.name to AsmType.LONG_TYPE,
Ref.FloatRef::class.java.name to AsmType.FLOAT_TYPE,
Ref.DoubleRef::class.java.name to AsmType.DOUBLE_TYPE,
Ref.CharRef::class.java.name to AsmType.CHAR_TYPE,
Ref.BooleanRef::class.java.name to AsmType.BOOLEAN_TYPE
).mapKeys { (k, _) -> AsmType.getObjectType(k.replace('.', '/')) }
private val PRIMITIVE_TO_REF: Map<AsmType, AsmType> = REF_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
private val REF_TYPES: Set<AsmType> = REF_TO_PRIMITIVE.keys + AsmType.getType(Ref.ObjectRef::class.java)
@@ -0,0 +1,466 @@
/*
* 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.variables
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.openapi.diagnostic.Attachment
import com.sun.jdi.*
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.AsmUtil.getCapturedFieldName
import org.jetbrains.kotlin.codegen.AsmUtil.getLabeledThisName
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
import org.jetbrains.kotlin.idea.core.util.mergeAttachments
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import kotlin.coroutines.Continuation
import org.jetbrains.org.objectweb.asm.Type as AsmType
import com.sun.jdi.Type as JdiType
class VariableFinder(private val context: ExecutionContext) {
private val frameProxy = context.frameProxy
companion object {
private const val USE_UNSAFE_FALLBACK = true
fun variableNotFound(context: ExecutionContext, message: String): Exception {
val frameProxy = context.frameProxy
val location = frameProxy.safeLocation()
val scope = context.debugProcess.searchScope
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
val sourceName = location?.sourceName()
val declaringTypeName = location?.declaringType()?.name()?.replace('.', '/')?.let { JvmClassName.byInternalName(it) }
val sourceFile = if (sourceName != null && declaringTypeName != null) {
DebuggerUtils.findSourceFileForClassIncludeLibrarySources(context.project, scope, declaringTypeName, sourceName, location)
} else {
null
}
val sourceFileText = runReadAction { sourceFile?.text }
if (sourceName != null && sourceFileText != null) {
val attachments = mergeAttachments(
Attachment(sourceName, sourceFileText),
Attachment("location.txt", locationText)
)
LOG.error(message, attachments)
}
return EvaluateExceptionUtil.createEvaluateException(message)
}
private fun getCapturedVariableNameRegex(capturedName: String): Regex {
val escapedName = Regex.escape(capturedName)
val escapedSuffix = Regex.escape(INLINE_TRANSFORMATION_SUFFIX)
return Regex("^$escapedName(?:$escapedSuffix)?$")
}
}
private val evaluatorValueConverter = EvaluatorValueConverter(context)
sealed class VariableKind(val asmType: AsmType) {
abstract fun capturedNameMatches(name: String): Boolean
class Ordinary(val name: String, asmType: AsmType, val isDelegated: Boolean) : VariableKind(asmType) {
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
}
// TODO Support overloaded local functions
class LocalFunction(val name: String, asmType: AsmType) : VariableKind(asmType) {
@Suppress("ConvertToStringTemplate")
override fun capturedNameMatches(name: String) = name == "$" + name
}
class UnlabeledThis(asmType: AsmType) : VariableKind(asmType) {
override fun capturedNameMatches(name: String) =
(name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)))
}
class OuterClassThis(asmType: AsmType) : VariableKind(asmType) {
override fun capturedNameMatches(name: String) = false
}
class FieldVar(val fieldName: String, asmType: AsmType) : VariableKind(asmType) {
// Captured 'field' are not supported yet
override fun capturedNameMatches(name: String) = false
}
class ExtensionThis(val label: String, asmType: AsmType) : VariableKind(asmType) {
val parameterName = getLabeledThisName(label, AsmUtil.LABELED_THIS_PARAMETER, AsmUtil.RECEIVER_PARAMETER_NAME)
val fieldName = getLabeledThisName(label, getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD), AsmUtil.CAPTURED_RECEIVER_FIELD)
private val capturedNameRegex = getCapturedVariableNameRegex(fieldName)
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
}
}
class Result(val value: Value?)
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
companion object {
fun of(field: Field, owner: ObjectReference): NamedEntity {
return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }
}
fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity {
return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }
}
}
}
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
return when (parameter.kind) {
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = false))
Kind.DELEGATED -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = true))
Kind.FAKE_JAVA_OUTER_CLASS -> thisObject()?.let { Result(it) }
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
Kind.DISPATCH_RECEIVER -> findDispatchThis(VariableKind.OuterClassThis(asmType))
Kind.COROUTINE_CONTEXT -> findCoroutineContext()
Kind.FIELD_VAR -> findFieldVariable(VariableKind.FieldVar(parameter.name, asmType))
Kind.DEBUG_LABEL -> findDebugLabel(parameter.name)
}
}
private fun findOrdinary(kind: VariableKind.Ordinary): Result? {
val variables = frameProxy.safeVisibleVariables()
// Local variables direct search
findLocalVariable(variables, kind, kind.name)?.let { return it }
// Recursive search in local receiver variables
findCapturedVariableInReceiver(variables, kind)?.let { return it }
// Recursive search in captured this
val containingThis = thisObject() ?: return null
return findCapturedVariable(kind, containingThis)
}
private fun findFieldVariable(kind: VariableKind.FieldVar): Result? {
val thisObject = thisObject()
if (thisObject != null) {
val field = thisObject.referenceType().fieldByName(kind.fieldName) ?: return null
return Result(thisObject.getValue(field))
} else {
val containingType = frameProxy.safeLocation()?.declaringType() ?: return null
val field = containingType.fieldByName(kind.fieldName) ?: return null
return Result(containingType.getValue(field))
}
}
private fun findLocalFunction(kind: VariableKind.LocalFunction): Result? {
val variables = frameProxy.safeVisibleVariables()
// Local variables direct search, new convention
val newConventionName = AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + kind.name
findLocalVariable(variables, kind, newConventionName)?.let { return it }
// Local variables direct search, old convention (before 1.3.30)
findLocalVariable(variables, kind, kind.name + "$")?.let { return it }
// Recursive search in local receiver variables
findCapturedVariableInReceiver(variables, kind)?.let { return it }
// Recursive search in captured this
val containingThis = thisObject() ?: return null
return findCapturedVariable(kind, containingThis)
}
private fun findExtensionThis(kind: VariableKind.ExtensionThis): Result? {
val variables = frameProxy.safeVisibleVariables()
// Local variables direct search
val namePredicate = fun(name: String) = name == kind.parameterName || name.startsWith(kind.parameterName + '$')
findLocalVariable(variables, kind, namePredicate)?.let { return it }
// Recursive search in local receiver variables
findCapturedVariableInReceiver(variables, kind)?.let { return it }
// Recursive search in captured this
val containingThis = thisObject()
if (containingThis != null) {
findCapturedVariable(kind, containingThis)?.let { return it }
}
@Suppress("ConstantConditionIf")
if (USE_UNSAFE_FALLBACK) {
// Find an unlabeled this with the compatible type
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
}
return null
}
private fun findDispatchThis(kind: VariableKind.OuterClassThis): Result? {
val containingThis = thisObject()
if (containingThis != null) {
findCapturedVariable(kind, containingThis)?.let { return it }
}
if (isInsideDefaultImpls()) {
val variables = frameProxy.safeVisibleVariables()
findLocalVariable(variables, kind, AsmUtil.THIS_IN_DEFAULT_IMPLS)?.let { return it }
}
val variables = frameProxy.safeVisibleVariables()
val inlineDepth = getInlineDepth(variables)
if (inlineDepth > 0) {
variables.namedEntitySequence()
.filter { it.name.matches(INLINED_THIS_REGEX) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
}
@Suppress("ConstantConditionIf")
if (USE_UNSAFE_FALLBACK) {
// Find an unlabeled this with the compatible type
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
}
return null
}
private fun findDebugLabel(name: String): Result? {
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.debugProcess)
for ((value, markup) in markupMap) {
if (markup.text == name) {
return Result(value)
}
}
return null
}
private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? {
val variables = frameProxy.safeVisibleVariables()
// Recursive search in local receiver variables
findCapturedVariableInReceiver(variables, kind)?.let { return it }
val containingThis = thisObject() ?: return null
return findCapturedVariable(kind, containingThis)
}
private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? {
return findLocalVariable(variables, kind) { it == name }
}
private fun findLocalVariable(
variables: List<LocalVariableProxyImpl>,
kind: VariableKind,
namePredicate: (String) -> Boolean
): Result? {
val inlineDepth = getInlineDepth(variables)
if (inlineDepth > 0) {
val inlineAwareNamePredicate = fun(name: String): Boolean {
var endIndex = name.length
var depth = 0
val suffixLen = INLINE_FUN_VAR_SUFFIX.length
while (endIndex >= suffixLen) {
if (name.substring(endIndex - suffixLen, endIndex) != INLINE_FUN_VAR_SUFFIX) {
break
}
depth++
endIndex -= suffixLen
}
return namePredicate(name.take(endIndex))
}
variables.namedEntitySequence()
.filter { inlineAwareNamePredicate(it.name) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
}
variables.namedEntitySequence()
.filter { namePredicate(it.name) && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
return null
}
private fun isInsideDefaultImpls(): Boolean {
val declaringType = frameProxy.safeLocation()?.declaringType() ?: return false
return declaringType.name().endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX)
}
private fun findCoroutineContext(): Result? {
val method = frameProxy.safeLocation()?.safeMethod() ?: return null
val result = findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) ?: return null
return Result(result)
}
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
return null
}
val thisObject = thisObject() ?: return null
val thisType = thisObject.referenceType()
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
return null
}
return findCoroutineContextForContinuation(thisObject)
}
private fun findCoroutineContextForMethod(method: Method): ObjectReference? {
if (CONTINUATION_TYPE.descriptor + ")" !in method.signature()) {
return null
}
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
return findCoroutineContextForContinuation(continuation)
}
private fun findCoroutineContextForContinuation(continuation: ObjectReference): ObjectReference? {
val continuationType = (continuation.referenceType() as? ClassType)
?.allInterfaces()?.firstOrNull { it.name() == Continuation::class.java.name }
?: return null
val getContextMethod = continuationType
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
?: return null
return context.invokeMethod(continuation, getContextMethod, emptyList()) as? ObjectReference
}
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
fun isReceiverOrPassedThis(name: String) =
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|| name == AsmUtil.RECEIVER_PARAMETER_NAME
|| name == AsmUtil.THIS_IN_DEFAULT_IMPLS
|| INLINED_THIS_REGEX.matches(name)
if (kind is VariableKind.ExtensionThis) {
variables.namedEntitySequence()
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
}
return variables.namedEntitySequence()
.filter { isReceiverOrPassedThis(it.name) }
.mapNotNull { findCapturedVariable(kind, it.value) }
.firstOrNull()
}
private fun findCapturedVariable(kind: VariableKind, parentFactory: () -> Value?): Result? {
val parent = getUnwrapDelegate(kind, parentFactory)
return findCapturedVariable(kind, parent)
}
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
return Result(parent)
}
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
if (kind !is VariableKind.OuterClassThis) {
// Captured variables - direct search
fields.namedEntitySequence(parent)
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
// Recursive search in captured receivers
fields.namedEntitySequence(parent)
.filter { isCapturedReceiverFieldName(it.name) }
.mapNotNull { findCapturedVariable(kind, it.value) }
.firstOrNull()
?.let { return it }
}
// Recursive search in outer and captured this
fields.namedEntitySequence(parent)
.filter { it.name == AsmUtil.THIS_IN_DEFAULT_IMPLS || it.name == AsmUtil.CAPTURED_THIS_FIELD }
.mapNotNull { findCapturedVariable(kind, it.value) }
.firstOrNull()
?.let { return it }
return null
}
private fun getUnwrapDelegate(kind: VariableKind, valueFactory: () -> Value?): Value? {
val rawValue = valueFactory()
if (kind !is VariableKind.Ordinary || !kind.isDelegated) {
return rawValue
}
val delegateValue = rawValue as? ObjectReference ?: return rawValue
val getValueMethod = delegateValue.referenceType()
.methodsByName("getValue", "()Ljava/lang/Object;").firstOrNull()
?: return rawValue
return context.invokeMethod(delegateValue, getValueMethod, emptyList())
}
private fun isCapturedReceiverFieldName(name: String): Boolean {
return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
}
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
if (this is VariableKind.Ordinary && isDelegated) {
// We can't figure out the actual type of the value yet.
// No worries: it will be checked again (and more carefully) in `unwrapAndCheck()`.
return true
}
return evaluatorValueConverter.typeMatches(asmType, actualType)
}
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
return evaluatorValueConverter.coerce(getUnwrapDelegate(kind, value), kind.asmType)
}
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
return asSequence().map { NamedEntity.of(it, owner) }
}
private fun List<LocalVariableProxyImpl>.namedEntitySequence(): Sequence<NamedEntity> {
return asSequence().map { NamedEntity.of(it, frameProxy) }
}
private fun thisObject(): ObjectReference? {
val thisObjectFromEvaluation = context.evaluationContext.computeThisObject() as? ObjectReference
if (thisObjectFromEvaluation != null) {
return thisObjectFromEvaluation
}
return frameProxy.thisObject()
}
}