Refactoring: extract DebuggerClassNameProvider
This commit is contained in:
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.roots.libraries.LibraryUtil
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.CachedClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.NonCachedClassNames
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
|
||||
|
||||
class DebuggerClassNameProvider(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
|
||||
fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> {
|
||||
val element = sourcePosition.readAction { it.elementAt } ?: return emptyList()
|
||||
val names = classNamesForPosition(element, withInlines)
|
||||
|
||||
val lambdas = findLambdas(sourcePosition)
|
||||
if (lambdas.isEmpty()) {
|
||||
return names
|
||||
}
|
||||
|
||||
return names + lambdas
|
||||
}
|
||||
|
||||
fun classNamesForPosition(element: PsiElement?, withInlines: Boolean): List<String> {
|
||||
if (DumbService.getInstance(myDebugProcess.project).isDumb) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val baseElement = getElementToCalculateClassName(element) ?: return emptyList()
|
||||
return getOrComputeClassNames(baseElement) { element ->
|
||||
val file = element.readAction { it.containingFile as KtFile }
|
||||
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
|
||||
getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findLambdas(sourcePosition: SourcePosition): Collection<String> {
|
||||
val lambdas = sourcePosition.readAction(::getLambdasAtLineIfAny)
|
||||
return lambdas.flatMap { classNamesForPosition(it, true) }
|
||||
}
|
||||
|
||||
|
||||
private fun getInternalClassNameForElement(
|
||||
element: KtElement,
|
||||
typeMapper: KotlinTypeMapper,
|
||||
file: KtFile,
|
||||
isInLibrary: Boolean,
|
||||
withInlines: Boolean
|
||||
): KotlinDebuggerCaches.ComputedClassNames {
|
||||
val parent = element.readAction { getElementToCalculateClassName(it.parent) }
|
||||
when (element) {
|
||||
is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
|
||||
is KtFunction -> {
|
||||
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, typeMapper.bindingContext) }
|
||||
if (descriptor != null) {
|
||||
val classNamesForParent = classNamesForPosition(parent, withInlines)
|
||||
if (descriptor.isCrossinline) {
|
||||
return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext))
|
||||
}
|
||||
return CachedClassNames(classNamesForParent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val crossInlineParameterUsages = element.readAction { it.containsCrossInlineParameterUsages(typeMapper.bindingContext) }
|
||||
if (crossInlineParameterUsages.isNotEmpty()) {
|
||||
return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList())
|
||||
}
|
||||
|
||||
when {
|
||||
element is KtFunctionLiteral -> {
|
||||
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
||||
return CachedClassNames(asmType.internalName)
|
||||
}
|
||||
element is KtAnonymousInitializer -> {
|
||||
// Class-object initializer
|
||||
if (parent is KtObjectDeclaration && parent.isCompanion()) {
|
||||
return CachedClassNames(classNamesForPosition(parent.parent, withInlines))
|
||||
}
|
||||
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
||||
}
|
||||
element is KtPropertyAccessor && (!element.readAction { it.property.isTopLevel } || !isInLibrary) -> {
|
||||
val classOrObject = element.readAction { PsiTreeUtil.getParentOfType(it, KtClassOrObject::class.java) }
|
||||
if (classOrObject != null) {
|
||||
return CachedClassNames(getClassNameForClass(classOrObject, typeMapper))
|
||||
}
|
||||
}
|
||||
element is KtProperty && (!element.readAction { it.isTopLevel } || !isInLibrary) -> {
|
||||
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||
if (descriptor !is PropertyDescriptor) {
|
||||
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
||||
}
|
||||
|
||||
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
|
||||
}
|
||||
element is KtNamedFunction -> {
|
||||
val parentInternalName = if (parent is KtClassOrObject) {
|
||||
getClassNameForClass(parent, typeMapper)
|
||||
}
|
||||
else if (parent != null) {
|
||||
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
||||
asmType.internalName
|
||||
}
|
||||
else {
|
||||
getClassNameForFile(file)
|
||||
}
|
||||
|
||||
if (!withInlines) return NonCachedClassNames(parentInternalName)
|
||||
|
||||
val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext)
|
||||
if (parentInternalName == null) return CachedClassNames(inlinedCalls)
|
||||
|
||||
return CachedClassNames(listOf(parentInternalName) + inlinedCalls)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return CachedClassNames(getClassNameForFile(file))
|
||||
}
|
||||
|
||||
private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: KotlinTypeMapper) = klass.readAction { getJvmInternalNameForImpl(typeMapper, it) }
|
||||
private fun getClassNameForFile(file: KtFile) = file.readAction { NoResolveFileClassesProvider.getFileClassInternalName(it) }
|
||||
|
||||
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
|
||||
arrayOf(KtClass::class.java,
|
||||
KtObjectDeclaration::class.java,
|
||||
KtEnumEntry::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtPropertyAccessor::class.java,
|
||||
KtProperty::class.java,
|
||||
KtClassInitializer::class.java)
|
||||
|
||||
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
|
||||
if (notPositionedElement?.javaClass as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return notPositionedElement as KtElement
|
||||
|
||||
return readAction { PsiTreeUtil.getParentOfType(notPositionedElement, *TYPES_TO_CALCULATE_CLASSNAME) }
|
||||
}
|
||||
|
||||
fun getJvmInternalNameForPropertyOwner(typeMapper: KotlinTypeMapper, descriptor: PropertyDescriptor): String {
|
||||
return descriptor.readAction {
|
||||
typeMapper.mapOwner(
|
||||
if (JvmAbi.isPropertyWithBackingFieldInOuterClass(it)) it.containingDeclaration else it
|
||||
).internalName
|
||||
}
|
||||
}
|
||||
|
||||
private fun getJvmInternalNameForImpl(typeMapper: KotlinTypeMapper, ktClass: KtClassOrObject): String? {
|
||||
val classDescriptor = typeMapper.bindingContext.get<PsiElement, ClassDescriptor>(BindingContext.CLASS, ktClass) ?: return null
|
||||
|
||||
if (ktClass is KtClass && ktClass.isInterface()) {
|
||||
return typeMapper.mapDefaultImpls(classDescriptor).internalName
|
||||
}
|
||||
|
||||
return typeMapper.mapClass(classDescriptor).internalName
|
||||
}
|
||||
|
||||
private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): List<String> {
|
||||
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
||||
return emptyList()
|
||||
}
|
||||
else {
|
||||
val searchResult = hashSetOf<KtElement>()
|
||||
val functionName = function.readAction { it.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
||||
if (!it.readAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
|
||||
if (usage != null) {
|
||||
searchResult.add(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (!applicationEx.holdsReadLock() || applicationEx.isDispatchThread) {
|
||||
applicationEx.invokeAndWait(
|
||||
{
|
||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
task,
|
||||
"Compute class names for function $functionName",
|
||||
true,
|
||||
myDebugProcess.project)
|
||||
}, ModalityState.NON_MODAL)
|
||||
}
|
||||
else {
|
||||
// Pooled thread with read lock. Can't invoke task under UI progress, so call it directly.
|
||||
task.run()
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||
"Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING
|
||||
).notify(myDebugProcess.project)
|
||||
}
|
||||
|
||||
// TODO recursive search
|
||||
return searchResult.flatMap { classNamesForPosition(it, true) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun findCrossInlineArguments(argument: KtFunction, parameterDescriptor: ValueParameterDescriptor, context: BindingContext): Set<String> {
|
||||
return runReadAction {
|
||||
val source = parameterDescriptor.original.source.getPsi() as? KtParameter
|
||||
val functionName = source?.ownerFunction?.name
|
||||
if (functionName != null) {
|
||||
return@runReadAction setOf(getCrossInlineArgumentClassName(argument, functionName, context))
|
||||
}
|
||||
return@runReadAction emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCrossInlineArgumentClassName(argument: KtFunction, inlineFunctionName: String, context: BindingContext): String {
|
||||
val anonymousClassNameForArgument = CodegenBinding.asmTypeForAnonymousClass(context, argument).internalName
|
||||
val newName = anonymousClassNameForArgument.substringIndex() + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName
|
||||
return "$newName$*"
|
||||
}
|
||||
|
||||
private fun KtElement.containsCrossInlineParameterUsages(context: BindingContext): Collection<ValueParameterDescriptor> {
|
||||
fun KtElement.hasParameterCall(parameter: KtParameter): Boolean {
|
||||
return ReferencesSearch.search(parameter).any {
|
||||
this.textRange.contains(it.element.textRange)
|
||||
}
|
||||
}
|
||||
|
||||
val inlineFunction = this.parents.firstIsInstanceOrNull<KtNamedFunction>() ?: return emptySet()
|
||||
|
||||
val inlineFunctionDescriptor = context[BindingContext.FUNCTION, inlineFunction]
|
||||
if (inlineFunctionDescriptor == null || !InlineUtil.isInline(inlineFunctionDescriptor)) return emptySet()
|
||||
|
||||
return inlineFunctionDescriptor.valueParameters
|
||||
.filter { it.isCrossinline }
|
||||
.mapNotNull {
|
||||
val psiParameter = it.source.getPsi() as? KtParameter
|
||||
if (psiParameter != null && this@containsCrossInlineParameterUsages.hasParameterCall(psiParameter))
|
||||
it
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun classNamesForCrossInlineParameters(usedParameters: Collection<ValueParameterDescriptor>, context: BindingContext): Set<String> {
|
||||
// We could calculate className only for one of parameters, because we add '*' to match all crossInlined parameter calls
|
||||
val parameter = usedParameters.first()
|
||||
val result = hashSetOf<String>()
|
||||
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
|
||||
|
||||
ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach {
|
||||
runReadAction {
|
||||
if (!it.isImportUsage()) {
|
||||
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
|
||||
if (call != null) {
|
||||
val resolvedCall = call.getResolvedCall(context)
|
||||
val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value
|
||||
if (argument != null) {
|
||||
val argumentExpression = getArgumentExpression(argument.arguments.first())
|
||||
if (argumentExpression is KtFunction) {
|
||||
result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
|
||||
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
|
||||
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||
return GlobalSearchScope.union(scopes.toTypedArray())
|
||||
}
|
||||
else {
|
||||
return myDebugProcess.searchScope
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun String.substringIndex(): String {
|
||||
if (lastIndexOf("$") < 0) return this
|
||||
|
||||
val suffix = substringAfterLast("$")
|
||||
if (suffix.all(Char::isDigit)) {
|
||||
return substringBeforeLast("$") + "$"
|
||||
}
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -25,65 +25,41 @@ import com.intellij.debugger.engine.PositionManagerEx
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.roots.libraries.LibraryUtil
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.ThreeState
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.request.ClassPrepareRequest
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
|
||||
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.CachedClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.NonCachedClassNames
|
||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.DebuggerUtils
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.util.*
|
||||
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
||||
|
||||
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
||||
|
||||
private val scopes = listOf(
|
||||
private val scopes: List<GlobalSearchScope> = listOf(
|
||||
myDebugProcess.searchScope,
|
||||
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
|
||||
)
|
||||
@@ -184,7 +160,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
continue
|
||||
}
|
||||
|
||||
val internalClassNames = classNamesForPosition(literal.firstChild, true)
|
||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(literal.firstChild, true)
|
||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||
return literal
|
||||
}
|
||||
@@ -241,7 +217,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return result
|
||||
|
||||
val names = classNamesForPosition(sourcePosition, true)
|
||||
val names = DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(sourcePosition, true)
|
||||
for (name in names) {
|
||||
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
|
||||
}
|
||||
@@ -261,39 +237,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
}
|
||||
|
||||
fun originalClassNameForPosition(sourcePosition: SourcePosition): String? {
|
||||
return classNamesForPosition(sourcePosition, false).firstOrNull()
|
||||
}
|
||||
|
||||
private fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> {
|
||||
val element = sourcePosition.readAction { it.elementAt } ?: return emptyList()
|
||||
val names = classNamesForPosition(element, withInlines)
|
||||
|
||||
val lambdas = findLambdas(sourcePosition)
|
||||
if (lambdas.isEmpty()) {
|
||||
return names
|
||||
}
|
||||
|
||||
return names + lambdas
|
||||
}
|
||||
|
||||
private fun classNamesForPosition(element: PsiElement?, withInlines: Boolean): List<String> {
|
||||
if (DumbService.getInstance(myDebugProcess.project).isDumb) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val baseElement = getElementToCalculateClassName(element) ?: return emptyList()
|
||||
return getOrComputeClassNames(baseElement) { element ->
|
||||
val file = element.readAction { it.containingFile as KtFile }
|
||||
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
|
||||
getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findLambdas(sourcePosition: SourcePosition): Collection<String> {
|
||||
val lambdas = sourcePosition.readAction { getLambdasAtLineIfAny(it) }
|
||||
return lambdas.flatMap { classNamesForPosition(it, true) }
|
||||
return DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(sourcePosition, false).firstOrNull()
|
||||
}
|
||||
|
||||
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
||||
@@ -324,266 +268,15 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return classNamesForPosition(position, true).mapNotNull {
|
||||
return DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(position, true).mapNotNull {
|
||||
className ->
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, className.replace('/', '.'))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInternalClassNameForElement(
|
||||
element: KtElement,
|
||||
typeMapper: KotlinTypeMapper,
|
||||
file: KtFile,
|
||||
isInLibrary: Boolean,
|
||||
withInlines: Boolean
|
||||
): KotlinDebuggerCaches.ComputedClassNames {
|
||||
val parent = element.readAction { getElementToCalculateClassName(it.parent) }
|
||||
when (element) {
|
||||
is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
|
||||
is KtFunction -> {
|
||||
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, typeMapper.bindingContext) }
|
||||
if (descriptor != null) {
|
||||
val classNamesForParent = classNamesForPosition(parent, withInlines)
|
||||
if (descriptor.isCrossinline) {
|
||||
return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext))
|
||||
}
|
||||
return CachedClassNames(classNamesForParent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val crossInlineParameterUsages = element.readAction { it.containsCrossInlineParameterUsages(typeMapper.bindingContext) }
|
||||
if (crossInlineParameterUsages.isNotEmpty()) {
|
||||
return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList())
|
||||
}
|
||||
|
||||
when {
|
||||
element is KtFunctionLiteral -> {
|
||||
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
||||
return CachedClassNames(asmType.internalName)
|
||||
}
|
||||
element is KtAnonymousInitializer -> {
|
||||
// Class-object initializer
|
||||
if (parent is KtObjectDeclaration && parent.isCompanion()) {
|
||||
return CachedClassNames(classNamesForPosition(parent.parent, withInlines))
|
||||
}
|
||||
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
||||
}
|
||||
element is KtPropertyAccessor && (!element.readAction { it.property.isTopLevel } || !isInLibrary) -> {
|
||||
val classOrObject = element.readAction { PsiTreeUtil.getParentOfType(it, KtClassOrObject::class.java) }
|
||||
if (classOrObject != null) {
|
||||
return CachedClassNames(getClassNameForClass(classOrObject, typeMapper))
|
||||
}
|
||||
}
|
||||
element is KtProperty && (!element.readAction { it.isTopLevel } || !isInLibrary) -> {
|
||||
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||
if (descriptor !is PropertyDescriptor) {
|
||||
return CachedClassNames(classNamesForPosition(parent, withInlines))
|
||||
}
|
||||
|
||||
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
|
||||
}
|
||||
element is KtNamedFunction -> {
|
||||
val parentInternalName = if (parent is KtClassOrObject) {
|
||||
getClassNameForClass(parent, typeMapper)
|
||||
}
|
||||
else if (parent != null) {
|
||||
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
|
||||
asmType.internalName
|
||||
}
|
||||
else {
|
||||
getClassNameForFile(file)
|
||||
}
|
||||
|
||||
if (!withInlines) return NonCachedClassNames(parentInternalName)
|
||||
|
||||
val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext)
|
||||
if (parentInternalName == null) return CachedClassNames(inlinedCalls)
|
||||
|
||||
return CachedClassNames(listOf(parentInternalName) + inlinedCalls)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return CachedClassNames(getClassNameForFile(file))
|
||||
}
|
||||
|
||||
private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: KotlinTypeMapper) = klass.readAction { getJvmInternalNameForImpl(typeMapper, it) }
|
||||
private fun getClassNameForFile(file: KtFile) = file.readAction { NoResolveFileClassesProvider.getFileClassInternalName(it) }
|
||||
|
||||
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
|
||||
arrayOf(KtClass::class.java,
|
||||
KtObjectDeclaration::class.java,
|
||||
KtEnumEntry::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtPropertyAccessor::class.java,
|
||||
KtProperty::class.java,
|
||||
KtClassInitializer::class.java)
|
||||
|
||||
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
|
||||
if (notPositionedElement?.javaClass as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return notPositionedElement as KtElement
|
||||
|
||||
return readAction { PsiTreeUtil.getParentOfType(notPositionedElement, *TYPES_TO_CALCULATE_CLASSNAME) }
|
||||
}
|
||||
|
||||
fun getJvmInternalNameForPropertyOwner(typeMapper: KotlinTypeMapper, descriptor: PropertyDescriptor): String {
|
||||
return descriptor.readAction {
|
||||
typeMapper.mapOwner(
|
||||
if (JvmAbi.isPropertyWithBackingFieldInOuterClass(it)) it.containingDeclaration else it
|
||||
).internalName
|
||||
}
|
||||
}
|
||||
|
||||
private fun getJvmInternalNameForImpl(typeMapper: KotlinTypeMapper, ktClass: KtClassOrObject): String? {
|
||||
val classDescriptor = typeMapper.bindingContext.get<PsiElement, ClassDescriptor>(BindingContext.CLASS, ktClass) ?: return null
|
||||
|
||||
if (ktClass is KtClass && ktClass.isInterface()) {
|
||||
return typeMapper.mapDefaultImpls(classDescriptor).internalName
|
||||
}
|
||||
|
||||
return typeMapper.mapClass(classDescriptor).internalName
|
||||
}
|
||||
|
||||
private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): List<String> {
|
||||
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
||||
return emptyList()
|
||||
}
|
||||
else {
|
||||
val searchResult = hashSetOf<KtElement>()
|
||||
val functionName = function.readAction { it.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
||||
if (!it.readAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
|
||||
if (usage != null) {
|
||||
searchResult.add(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (!applicationEx.holdsReadLock() || applicationEx.isDispatchThread) {
|
||||
applicationEx.invokeAndWait(
|
||||
{
|
||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
task,
|
||||
"Compute class names for function $functionName",
|
||||
true,
|
||||
myDebugProcess.project)
|
||||
}, ModalityState.NON_MODAL)
|
||||
}
|
||||
else {
|
||||
// Pooled thread with read lock. Can't invoke task under UI progress, so call it directly.
|
||||
task.run()
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||
"Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING
|
||||
).notify(myDebugProcess.project)
|
||||
}
|
||||
|
||||
// TODO recursive search
|
||||
return searchResult.flatMap { classNamesForPosition(it, true) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun findCrossInlineArguments(argument: KtFunction, parameterDescriptor: ValueParameterDescriptor, context: BindingContext): Set<String> {
|
||||
return runReadAction {
|
||||
val source = parameterDescriptor.original.source.getPsi() as? KtParameter
|
||||
val functionName = source?.ownerFunction?.name
|
||||
if (functionName != null) {
|
||||
return@runReadAction setOf(getCrossInlineArgumentClassName(argument, functionName, context))
|
||||
}
|
||||
return@runReadAction emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCrossInlineArgumentClassName(argument: KtFunction, inlineFunctionName: String, context: BindingContext): String {
|
||||
val anonymousClassNameForArgument = CodegenBinding.asmTypeForAnonymousClass(context, argument).internalName
|
||||
val newName = anonymousClassNameForArgument.substringIndex() + InlineCodegenUtil.INLINE_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName
|
||||
return "$newName$*"
|
||||
}
|
||||
|
||||
private fun KtElement.containsCrossInlineParameterUsages(context: BindingContext): Collection<ValueParameterDescriptor> {
|
||||
fun KtElement.hasParameterCall(parameter: KtParameter): Boolean {
|
||||
return ReferencesSearch.search(parameter).any {
|
||||
this.textRange.contains(it.element.textRange)
|
||||
}
|
||||
}
|
||||
|
||||
val inlineFunction = this.parents.firstIsInstanceOrNull<KtNamedFunction>() ?: return emptySet()
|
||||
|
||||
val inlineFunctionDescriptor = context[BindingContext.FUNCTION, inlineFunction]
|
||||
if (inlineFunctionDescriptor == null || !InlineUtil.isInline(inlineFunctionDescriptor)) return emptySet()
|
||||
|
||||
return inlineFunctionDescriptor.valueParameters
|
||||
.filter { it.isCrossinline }
|
||||
.mapNotNull {
|
||||
val psiParameter = it.source.getPsi() as? KtParameter
|
||||
if (psiParameter != null && this@containsCrossInlineParameterUsages.hasParameterCall(psiParameter))
|
||||
it
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun classNamesForCrossInlineParameters(usedParameters: Collection<ValueParameterDescriptor>, context: BindingContext): Set<String> {
|
||||
// We could calculate className only for one of parameters, because we add '*' to match all crossInlined parameter calls
|
||||
val parameter = usedParameters.first()
|
||||
val result = hashSetOf<String>()
|
||||
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
|
||||
|
||||
ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach {
|
||||
runReadAction {
|
||||
if (!it.isImportUsage()) {
|
||||
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
|
||||
if (call != null) {
|
||||
val resolvedCall = call.getResolvedCall(context)
|
||||
val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value
|
||||
if (argument != null) {
|
||||
val argumentExpression = getArgumentExpression(argument.arguments.first())
|
||||
if (argumentExpression is KtFunction) {
|
||||
result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
|
||||
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
|
||||
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||
return GlobalSearchScope.union(scopes.toTypedArray())
|
||||
}
|
||||
else {
|
||||
return myDebugProcess.searchScope
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun String.substringIndex(): String {
|
||||
if (lastIndexOf("$") < 0) return this
|
||||
|
||||
val suffix = substringAfterLast("$")
|
||||
if (suffix.all { it.isDigit() }) {
|
||||
return substringBeforeLast("$") + "$"
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin")
|
||||
}
|
||||
|
||||
private inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||
return runReadAction { f(this) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user