Move out JVM debugger functionality
This commit is contained in:
@@ -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() }
|
||||
}
|
||||
+14
@@ -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" }
|
||||
}
|
||||
+52
@@ -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
|
||||
}
|
||||
}
|
||||
+44
@@ -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")
|
||||
}
|
||||
}
|
||||
+206
@@ -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
|
||||
)
|
||||
+358
@@ -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)
|
||||
}
|
||||
}
|
||||
+439
@@ -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)
|
||||
+110
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+43
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -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?
|
||||
}
|
||||
+56
@@ -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
|
||||
}
|
||||
}
|
||||
+116
@@ -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
|
||||
}
|
||||
}
|
||||
+143
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+287
@@ -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('/', '.')
|
||||
+380
@@ -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
|
||||
}
|
||||
}
|
||||
+221
@@ -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
|
||||
)
|
||||
+43
@@ -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)
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
+104
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+264
@@ -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)
|
||||
+466
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user