KotlinPositionManager: add progress when computing classnames for inline function

This commit is contained in:
Natalia Ukhorskaya
2016-02-10 12:08:57 +03:00
parent 9ac2da8c96
commit 9070eff6ad
2 changed files with 112 additions and 78 deletions
@@ -25,8 +25,12 @@ import com.intellij.debugger.engine.PositionManagerEx
import com.intellij.debugger.engine.evaluation.EvaluationContext import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.requests.ClassPrepareRequestor import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.DumbService
import com.intellij.openapi.roots.libraries.LibraryUtil import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.openapi.ui.MessageType
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.impl.compiled.ClsFileImpl import com.intellij.psi.impl.compiled.ClsFileImpl
@@ -34,6 +38,7 @@ import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.ThreeState import com.intellij.util.ThreeState
import com.intellij.xdebugger.frame.XStackFrame import com.intellij.xdebugger.frame.XStackFrame
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.sun.jdi.AbsentInformationException import com.sun.jdi.AbsentInformationException
import com.sun.jdi.Location import com.sun.jdi.Location
import com.sun.jdi.ReferenceType import com.sun.jdi.ReferenceType
@@ -238,7 +243,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
if (psiFile is ClsFileImpl) { if (psiFile is ClsFileImpl) {
val decompiledPsiFile = runReadAction { psiFile.decompiledPsiFile } val decompiledPsiFile = psiFile.readAction { it.decompiledPsiFile }
if (decompiledPsiFile is KtClsFile && sourcePosition.line == -1) { if (decompiledPsiFile is KtClsFile && sourcePosition.line == -1) {
val className = val className =
JvmFileClassUtil.getFileClassInfoNoResolve(decompiledPsiFile).fileClassFqName.internalNameWithoutInnerClasses JvmFileClassUtil.getFileClassInfoNoResolve(decompiledPsiFile).fileClassFqName.internalNameWithoutInnerClasses
@@ -254,7 +259,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
private fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> { private fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> {
val element = runReadAction { sourcePosition.elementAt } ?: return emptyList() val element = sourcePosition.readAction { it.elementAt } ?: return emptyList()
val names = classNamesForPosition(element, withInlines) val names = classNamesForPosition(element, withInlines)
val lambdas = findLambdas(sourcePosition) val lambdas = findLambdas(sourcePosition)
@@ -265,27 +270,25 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
return names + lambdas return names + lambdas
} }
private fun classNamesForPosition(element: PsiElement, withInlines: Boolean): List<String> { private fun classNamesForPosition(element: PsiElement?, withInlines: Boolean): List<String> {
return runReadAction { if (DumbService.getInstance(myDebugProcess.project).isDumb) {
if (DumbService.getInstance(element.project).isDumb) { return emptyList()
emptyList() }
} else {
else { val baseElement = getElementToCalculateClassName(element) ?: return emptyList()
val baseElement = getElementToCalculateClassName(element) ?: return@runReadAction emptyList() return getOrComputeClassNames(baseElement) {
getOrComputeClassNames(baseElement) { element ->
element -> val file = element.readAction { it.containingFile as KtFile }
val file = element.containingFile as KtFile val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines) getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines)
}
} }
} }
} }
private fun findLambdas(sourcePosition: SourcePosition): Collection<String> { private fun findLambdas(sourcePosition: SourcePosition): Collection<String> {
val lambdas = runReadAction { getLambdasAtLineIfAny(sourcePosition) } val lambdas = sourcePosition.readAction { getLambdasAtLineIfAny(it) }
return lambdas.flatMap { classNamesForPosition(it, true) } return lambdas.flatMap { classNamesForPosition(it, true) }
} }
@@ -330,12 +333,13 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
isInLibrary: Boolean, isInLibrary: Boolean,
withInlines: Boolean withInlines: Boolean
): KotlinDebuggerCaches.ComputedClassNames { ): KotlinDebuggerCaches.ComputedClassNames {
val parent = element.readAction { getElementToCalculateClassName(it.parent) }
when (element) { when (element) {
is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper)) is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
is KtFunction -> { is KtFunction -> {
val descriptor = InlineUtil.getInlineArgumentDescriptor(element, typeMapper.bindingContext) val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, typeMapper.bindingContext) }
if (descriptor != null) { if (descriptor != null) {
val classNamesForParent = classNamesForPosition(element.parent, withInlines) val classNamesForParent = classNamesForPosition(parent, withInlines)
if (descriptor.isCrossinline) { if (descriptor.isCrossinline) {
return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext)) return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext))
} }
@@ -344,7 +348,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
} }
val crossInlineParameterUsages = element.containsCrossInlineParameterUsages(typeMapper.bindingContext) val crossInlineParameterUsages = element.readAction { it.containsCrossInlineParameterUsages(typeMapper.bindingContext) }
if (crossInlineParameterUsages.isNotEmpty()) { if (crossInlineParameterUsages.isNotEmpty()) {
return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList()) return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList())
} }
@@ -355,38 +359,36 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
return CachedClassNames(asmType.internalName) return CachedClassNames(asmType.internalName)
} }
element is KtAnonymousInitializer -> { element is KtAnonymousInitializer -> {
val parent = getElementToCalculateClassName(element.parent)
// Class-object initializer // Class-object initializer
if (parent is KtObjectDeclaration && parent.isCompanion()) { if (parent is KtObjectDeclaration && parent.isCompanion()) {
return CachedClassNames(classNamesForPosition(parent.parent, withInlines)) return CachedClassNames(classNamesForPosition(parent.parent, withInlines))
} }
return CachedClassNames(classNamesForPosition(element.parent, withInlines)) return CachedClassNames(classNamesForPosition(parent, withInlines))
} }
element is KtPropertyAccessor && (!element.property.isTopLevel || !isInLibrary)-> { element is KtPropertyAccessor && (!element.readAction { it.property.isTopLevel } || !isInLibrary) -> {
val classOrObject = PsiTreeUtil.getParentOfType(element, KtClassOrObject::class.java) val classOrObject = element.readAction { PsiTreeUtil.getParentOfType(it, KtClassOrObject::class.java) }
if (classOrObject != null) { if (classOrObject != null) {
return CachedClassNames(getClassNameForClass(classOrObject, typeMapper)) return CachedClassNames(getClassNameForClass(classOrObject, typeMapper))
} }
} }
element is KtProperty && (!element.isTopLevel || !isInLibrary) -> { element is KtProperty && (!element.readAction { it.isTopLevel } || !isInLibrary) -> {
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element) val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
if (descriptor !is PropertyDescriptor) { if (descriptor !is PropertyDescriptor) {
return CachedClassNames(classNamesForPosition(element.parent, withInlines)) return CachedClassNames(classNamesForPosition(parent, withInlines))
} }
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor)) return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
} }
element is KtNamedFunction -> { element is KtNamedFunction -> {
val parent = getElementToCalculateClassName(element.parent)
val parentInternalName = if (parent is KtClassOrObject) { val parentInternalName = if (parent is KtClassOrObject) {
getJvmInternalNameForImpl(typeMapper, parent) getClassNameForClass(parent, typeMapper)
} }
else if (parent != null) { else if (parent != null) {
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element) val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
asmType.internalName asmType.internalName
} }
else { else {
NoResolveFileClassesProvider.getFileClassInternalName(file) getClassNameForFile(file)
} }
if (!withInlines) return NonCachedClassNames(parentInternalName) if (!withInlines) return NonCachedClassNames(parentInternalName)
@@ -399,29 +401,34 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
} }
return CachedClassNames(NoResolveFileClassesProvider.getFileClassInternalName(file)) return CachedClassNames(getClassNameForFile(file))
} }
private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: JetTypeMapper) = getJvmInternalNameForImpl(typeMapper, klass) private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: JetTypeMapper) = 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>> = private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
arrayOf(KtClassOrObject::class.java, arrayOf(KtClass::class.java,
KtObjectDeclaration::class.java,
KtEnumEntry::class.java,
KtFunctionLiteral::class.java, KtFunctionLiteral::class.java,
KtNamedFunction::class.java, KtNamedFunction::class.java,
KtPropertyAccessor::class.java, KtPropertyAccessor::class.java,
KtProperty::class.java, KtProperty::class.java,
KtAnonymousInitializer::class.java) KtClassInitializer::class.java)
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? { private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
if (notPositionedElement?.javaClass as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return notPositionedElement as KtElement if (notPositionedElement?.javaClass as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return notPositionedElement as KtElement
return PsiTreeUtil.getParentOfType(notPositionedElement, *TYPES_TO_CALCULATE_CLASSNAME) return readAction { PsiTreeUtil.getParentOfType(notPositionedElement, *TYPES_TO_CALCULATE_CLASSNAME) }
} }
fun getJvmInternalNameForPropertyOwner(typeMapper: JetTypeMapper, descriptor: PropertyDescriptor): String { fun getJvmInternalNameForPropertyOwner(typeMapper: JetTypeMapper, descriptor: PropertyDescriptor): String {
return typeMapper.mapOwner( return descriptor.readAction {
if (JvmAbi.isPropertyWithBackingFieldInOuterClass(descriptor)) descriptor.containingDeclaration else descriptor typeMapper.mapOwner(
).internalName if (JvmAbi.isPropertyWithBackingFieldInOuterClass(it)) it.containingDeclaration else it
).internalName
}
} }
private fun getJvmInternalNameForImpl(typeMapper: JetTypeMapper, ktClass: KtClassOrObject): String? { private fun getJvmInternalNameForImpl(typeMapper: JetTypeMapper, ktClass: KtClassOrObject): String? {
@@ -439,21 +446,38 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
return emptyList() return emptyList()
} }
else { else {
return runReadAction { val searchResult = hashSetOf<KtElement>()
val result = hashSetOf<String>() val functionName = function.readAction { it.name }
val task = Runnable {
ReferencesSearch.search(function, myDebugProcess.searchScope).forEach { ReferencesSearch.search(function, myDebugProcess.searchScope).forEach {
if (!it.isImportUsage()) { if (!it.readAction { it.isImportUsage() }) {
val usage = it.element val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
if (usage is KtElement) { if (usage != null) {
//TODO recursive search searchResult.add(usage)
val names = classNamesForPosition(usage, true)
result.addAll(names)
} }
} }
} }
result.toList()
} }
var isSuccess = true
ApplicationManager.getApplication().invokeAndWait(
{
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
task,
"Compute class names for function $functionName",
true,
myDebugProcess.project)
}, ModalityState.NON_MODAL)
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) }
} }
} }
@@ -504,15 +528,17 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet() val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
ReferencesSearch.search(inlineFunction, myDebugProcess.searchScope).forEach { ReferencesSearch.search(inlineFunction, myDebugProcess.searchScope).forEach {
if (!it.isImportUsage()) { runReadAction {
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) } if (!it.isImportUsage()) {
if (call != null) { val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
val resolvedCall = call.getResolvedCall(context) if (call != null) {
val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value val resolvedCall = call.getResolvedCall(context)
if (argument != null) { val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value
val argumentExpression = getArgumentExpression(argument.arguments.first()) if (argument != null) {
if (argumentExpression is KtFunction) { val argumentExpression = getArgumentExpression(argument.arguments.first())
result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context)) if (argumentExpression is KtFunction) {
result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context))
}
} }
} }
} }
@@ -536,3 +562,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin") private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin")
} }
private inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
return runReadAction { f(this) }
}
@@ -95,7 +95,7 @@ class KotlinDebuggerCaches(private val project: Project) {
} }
fun <T: PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List<String> { fun <T: PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List<String> {
val cache = getInstance(psiElement.project) val cache = getInstance(runReadAction { psiElement.project })
synchronized(cache.cachedClassNames) { synchronized(cache.cachedClassNames) {
val classNamesCache = cache.cachedClassNames.value val classNamesCache = cache.cachedClassNames.value
@@ -112,11 +112,11 @@ class KotlinDebuggerCaches(private val project: Project) {
} }
fun getOrCreateTypeMapper(psiElement: PsiElement): JetTypeMapper { fun getOrCreateTypeMapper(psiElement: PsiElement): JetTypeMapper {
val cache = getInstance(psiElement.project) val cache = getInstance(runReadAction { psiElement.project })
synchronized(cache.cachedTypeMappers) { synchronized(cache.cachedTypeMappers) {
val typeMappersCache = cache.cachedTypeMappers.value val typeMappersCache = cache.cachedTypeMappers.value
val file = psiElement.containingFile as KtFile val file = runReadAction { psiElement.containingFile as KtFile }
val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null
if (!isInLibrary) { if (!isInLibrary) {
@@ -142,33 +142,37 @@ class KotlinDebuggerCaches(private val project: Project) {
} }
private fun createTypeMapperForLibraryFile(element: KtElement, file: KtFile): JetTypeMapper { private fun createTypeMapperForLibraryFile(element: KtElement, file: KtFile): JetTypeMapper {
val analysisResult = element.analyzeAndGetResult() return runReadAction {
val analysisResult = element.analyzeAndGetResult()
val state = GenerationState( val state = GenerationState(
file.project, file.project,
ClassBuilderFactories.THROW_EXCEPTION, ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.moduleDescriptor, analysisResult.moduleDescriptor,
analysisResult.bindingContext, analysisResult.bindingContext,
listOf(file)) listOf(file))
state.beforeCompile() state.beforeCompile()
return state.typeMapper state.typeMapper
}
} }
private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) = private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) =
if (element is KtElement) element else PsiTreeUtil.getParentOfType(element, KtElement::class.java)!! runReadAction { if (element is KtElement) element else PsiTreeUtil.getParentOfType(element, KtElement::class.java)!! }
private fun createTypeMapperForSourceFile(file: KtFile): JetTypeMapper { private fun createTypeMapperForSourceFile(file: KtFile): JetTypeMapper {
val analysisResult = file.analyzeFullyAndGetResult() return runReadAction {
analysisResult.throwIfError() val analysisResult = file.analyzeFullyAndGetResult()
analysisResult.throwIfError()
val state = GenerationState( val state = GenerationState(
file.project, file.project,
ClassBuilderFactories.THROW_EXCEPTION, ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.moduleDescriptor, analysisResult.moduleDescriptor,
analysisResult.bindingContext, analysisResult.bindingContext,
listOf(file)) listOf(file))
state.beforeCompile() state.beforeCompile()
return state.typeMapper state.typeMapper
}
} }
@TestOnly fun addTypeMapper(file: KtFile, typeMapper: JetTypeMapper) { @TestOnly fun addTypeMapper(file: KtFile, typeMapper: JetTypeMapper) {