From 5a5cd0e760946ea877c9286f7c456c4ba0f522b3 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Wed, 27 Jul 2016 20:23:19 +0300 Subject: [PATCH] Refactoring: extract DebuggerClassNameProvider --- .../debugger/DebuggerClassNameProvider.kt | 342 ++++++++++++++++++ .../idea/debugger/KotlinPositionManager.kt | 327 +---------------- 2 files changed, 352 insertions(+), 317 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt new file mode 100644 index 00000000000..a02477f58e7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt @@ -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) { + fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List { + 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 { + 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 { + 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> = + 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(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 { + if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) { + return emptyList() + } + else { + val searchResult = hashSetOf() + 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 { + 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 { + fun KtElement.hasParameterCall(parameter: KtParameter): Boolean { + return ReferencesSearch.search(parameter).any { + this.textRange.contains(it.element.textRange) + } + } + + val inlineFunction = this.parents.firstIsInstanceOrNull() ?: 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, context: BindingContext): Set { + // 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() + 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 + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 3d7724fe499..9ea1aa34dd4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -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 = 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 { - 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 { - 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 { - 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 { @@ -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> = - 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(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 { - if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) { - return emptyList() - } - else { - val searchResult = hashSetOf() - 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 { - 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 { - fun KtElement.hasParameterCall(parameter: KtParameter): Boolean { - return ReferencesSearch.search(parameter).any { - this.textRange.contains(it.element.textRange) - } - } - - val inlineFunction = this.parents.firstIsInstanceOrNull() ?: 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, context: BindingContext): Set { - // 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() - 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.readAction(crossinline f: (U) -> V): V { +inline fun U.readAction(crossinline f: (U) -> V): V { return runReadAction { f(this) } }