Refactoring: extract DebuggerClassNameProvider

This commit is contained in:
Nikolay Krasko
2016-07-27 20:23:19 +03:00
parent 50e196dfb9
commit 5a5cd0e760
2 changed files with 352 additions and 317 deletions
@@ -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) }
}