Do not compute classnames during stepping

#KT-10827 Fixed
This commit is contained in:
Natalia Ukhorskaya
2016-02-05 17:06:47 +03:00
parent d238df8525
commit 25b8e4f1b2
2 changed files with 67 additions and 45 deletions
@@ -53,6 +53,8 @@ import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches 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.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.decompiler.classFile.KtClsFile
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
@@ -150,8 +152,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? { private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
val currentLocationFqName = location.declaringType().name() val currentLocationFqName = location.declaringType().name() ?: return null
if (currentLocationFqName == null) return null
val start = CodeInsightUtils.getStartLineOffset(file, lineNumber) val start = CodeInsightUtils.getStartLineOffset(file, lineNumber)
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber) val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
@@ -172,7 +173,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
continue continue
} }
val internalClassNames = classNamesForPosition(literal.firstChild) val internalClassNames = classNamesForPosition(literal.firstChild, true)
if (internalClassNames.any { it == currentLocationClassName }) { if (internalClassNames.any { it == currentLocationClassName }) {
return literal return literal
} }
@@ -229,7 +230,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return result if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return result
val names = classNamesForPosition(sourcePosition) val names = classNamesForPosition(sourcePosition, true)
for (name in names) { for (name in names) {
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name)) result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
} }
@@ -249,12 +250,12 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
fun originalClassNameForPosition(sourcePosition: SourcePosition): String? { fun originalClassNameForPosition(sourcePosition: SourcePosition): String? {
return classNamesForPosition(sourcePosition).firstOrNull() return classNamesForPosition(sourcePosition, false).firstOrNull()
} }
private fun classNamesForPosition(sourcePosition: SourcePosition): List<String> { private fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List<String> {
val element = runReadAction { sourcePosition.elementAt } ?: return emptyList() val element = runReadAction { sourcePosition.elementAt } ?: return emptyList()
val names = classNamesForPosition(element) val names = classNamesForPosition(element, withInlines)
val lambdas = findLambdas(sourcePosition) val lambdas = findLambdas(sourcePosition)
if (lambdas.isEmpty()) { if (lambdas.isEmpty()) {
@@ -264,7 +265,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
return names + lambdas return names + lambdas
} }
private fun classNamesForPosition(element: PsiElement): List<String> { private fun classNamesForPosition(element: PsiElement, withInlines: Boolean): List<String> {
return runReadAction { return runReadAction {
if (DumbService.getInstance(element.project).isDumb) { if (DumbService.getInstance(element.project).isDumb) {
emptyList() emptyList()
@@ -277,7 +278,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
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) getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines)
} }
} }
} }
@@ -285,7 +286,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
private fun findLambdas(sourcePosition: SourcePosition): Collection<String> { private fun findLambdas(sourcePosition: SourcePosition): Collection<String> {
val lambdas = runReadAction { getLambdasAtLineIfAny(sourcePosition) } val lambdas = runReadAction { getLambdasAtLineIfAny(sourcePosition) }
return lambdas.flatMap { classNamesForPosition(it) } return lambdas.flatMap { classNamesForPosition(it, true) }
} }
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> { override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
@@ -316,63 +317,64 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
throw NoDataException.INSTANCE throw NoDataException.INSTANCE
} }
return classNamesForPosition(position).mapNotNull { return classNamesForPosition(position, true).mapNotNull {
className -> myDebugProcess.requestsManager.createClassPrepareRequest(requestor, className.replace('/', '.')) className ->
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, className.replace('/', '.'))
} }
} }
// The order is used in ExtraSteppingFilter: original className should be the first
private fun getInternalClassNameForElement( private fun getInternalClassNameForElement(
element: KtElement, element: KtElement,
typeMapper: JetTypeMapper, typeMapper: JetTypeMapper,
file: KtFile, file: KtFile,
isInLibrary: Boolean isInLibrary: Boolean,
): List<String> { withInlines: Boolean
): KotlinDebuggerCaches.ComputedClassNames {
when (element) { when (element) {
is KtClassOrObject -> return getJvmInternalNameForImpl(typeMapper, element).toList() is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
is KtFunction -> { is KtFunction -> {
val descriptor = InlineUtil.getInlineArgumentDescriptor(element, typeMapper.bindingContext) val descriptor = InlineUtil.getInlineArgumentDescriptor(element, typeMapper.bindingContext)
if (descriptor != null) { if (descriptor != null) {
val classNamesForParent = classNamesForPosition(element.parent) val classNamesForParent = classNamesForPosition(element.parent, withInlines)
if (descriptor.isCrossinline) { if (descriptor.isCrossinline) {
return classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext) return CachedClassNames(classNamesForParent + findCrossInlineArguments(element, descriptor, typeMapper.bindingContext))
} }
return classNamesForParent return CachedClassNames(classNamesForParent)
} }
} }
} }
val crossInlineParameterUsages = element.containsCrossInlineParameterUsages(typeMapper.bindingContext) val crossInlineParameterUsages = element.containsCrossInlineParameterUsages(typeMapper.bindingContext)
if (crossInlineParameterUsages.isNotEmpty()) { if (crossInlineParameterUsages.isNotEmpty()) {
return classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList() return CachedClassNames(classNamesForCrossInlineParameters(crossInlineParameterUsages, typeMapper.bindingContext).toList())
} }
when { when {
element is KtFunctionLiteral -> { element is KtFunctionLiteral -> {
val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element) val asmType = CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element)
return asmType.internalName.toList() return CachedClassNames(asmType.internalName)
} }
element is KtAnonymousInitializer -> { element is KtAnonymousInitializer -> {
val parent = getElementToCalculateClassName(element.parent) val parent = getElementToCalculateClassName(element.parent)
// Class-object initializer // Class-object initializer
if (parent is KtObjectDeclaration && parent.isCompanion()) { if (parent is KtObjectDeclaration && parent.isCompanion()) {
return classNamesForPosition(parent.parent) return CachedClassNames(classNamesForPosition(parent.parent, withInlines))
} }
return classNamesForPosition(element.parent) return CachedClassNames(classNamesForPosition(element.parent, withInlines))
} }
element is KtPropertyAccessor && (!element.property.isTopLevel || !isInLibrary)-> { element is KtPropertyAccessor && (!element.property.isTopLevel || !isInLibrary)-> {
val classOrObject = PsiTreeUtil.getParentOfType(element, KtClassOrObject::class.java) val classOrObject = PsiTreeUtil.getParentOfType(element, KtClassOrObject::class.java)
if (classOrObject != null) { if (classOrObject != null) {
return getJvmInternalNameForImpl(typeMapper, classOrObject).toList() return CachedClassNames(getClassNameForClass(classOrObject, typeMapper))
} }
} }
element is KtProperty && (!element.isTopLevel || !isInLibrary) -> { element is KtProperty && (!element.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 classNamesForPosition(element.parent) return CachedClassNames(classNamesForPosition(element.parent, withInlines))
} }
return getJvmInternalNameForPropertyOwner(typeMapper, descriptor).toList() return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
} }
element is KtNamedFunction -> { element is KtNamedFunction -> {
val parent = getElementToCalculateClassName(element.parent) val parent = getElementToCalculateClassName(element.parent)
@@ -387,20 +389,27 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
NoResolveFileClassesProvider.getFileClassInternalName(file) NoResolveFileClassesProvider.getFileClassInternalName(file)
} }
if (!withInlines) return NonCachedClassNames(parentInternalName)
val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext) val inlinedCalls = findInlinedCalls(element, typeMapper.bindingContext)
return parentInternalName.toList() + inlinedCalls if (parentInternalName == null) return CachedClassNames(inlinedCalls)
return CachedClassNames(listOf(parentInternalName) + inlinedCalls)
} }
} }
return NoResolveFileClassesProvider.getFileClassInternalName(file).toList() return CachedClassNames(NoResolveFileClassesProvider.getFileClassInternalName(file))
} }
private fun getClassNameForClass(klass: KtClassOrObject, typeMapper: JetTypeMapper) = getJvmInternalNameForImpl(typeMapper, klass)
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(KtClassOrObject::class.java,
KtFunctionLiteral::class.java, KtFunctionLiteral::class.java,
KtNamedFunction::class.java, KtNamedFunction::class.java,
KtProperty::class.java,
KtPropertyAccessor::class.java, KtPropertyAccessor::class.java,
KtProperty::class.java,
KtAnonymousInitializer::class.java) KtAnonymousInitializer::class.java)
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? { private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
@@ -425,24 +434,26 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
return typeMapper.mapClass(classDescriptor).internalName return typeMapper.mapClass(classDescriptor).internalName
} }
private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): Set<String> { private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): List<String> {
return runReadAction { if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
val result = hashSetOf<String>() return emptyList()
}
else {
return runReadAction {
val result = hashSetOf<String>()
if (InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) { ReferencesSearch.search(function, myDebugProcess.searchScope).forEach {
ReferencesSearch.search(function).forEach {
if (!it.isImportUsage()) { if (!it.isImportUsage()) {
val usage = it.element val usage = it.element
if (usage is KtElement) { if (usage is KtElement) {
//TODO recursive search //TODO recursive search
val names = classNamesForPosition(usage) val names = classNamesForPosition(usage, true)
result.addAll(names) result.addAll(names)
} }
} }
true
} }
result.toList()
} }
result
} }
} }
@@ -492,7 +503,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
val result = hashSetOf<String>() val result = hashSetOf<String>()
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet() val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
ReferencesSearch.search(inlineFunction).forEach { ReferencesSearch.search(inlineFunction, myDebugProcess.searchScope).forEach {
if (!it.isImportUsage()) { if (!it.isImportUsage()) {
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) } val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
if (call != null) { if (call != null) {
@@ -506,7 +517,6 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
} }
} }
true
} }
return result return result
@@ -525,6 +535,4 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
} }
private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin") private fun ReferenceType.containsKotlinStrata() = availableStrata().contains("Kotlin")
private fun String?.toList() = if (this == null) emptyList() else listOf(this)
} }
@@ -94,7 +94,7 @@ class KotlinDebuggerCaches(private val project: Project) {
} }
} }
fun <T: PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> List<String>): List<String> { fun <T: PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List<String> {
val cache = getInstance(psiElement.project) val cache = getInstance(psiElement.project)
synchronized(cache.cachedClassNames) { synchronized(cache.cachedClassNames) {
val classNamesCache = cache.cachedClassNames.value val classNamesCache = cache.cachedClassNames.value
@@ -102,10 +102,12 @@ class KotlinDebuggerCaches(private val project: Project) {
val cachedValue = classNamesCache[psiElement] val cachedValue = classNamesCache[psiElement]
if (cachedValue != null) return cachedValue if (cachedValue != null) return cachedValue
val newValue = create(psiElement) val computedClassNames = create(psiElement)
classNamesCache[psiElement] = newValue if (computedClassNames.shouldBeCached) {
return newValue classNamesCache[psiElement] = computedClassNames.classNames
}
return computedClassNames.classNames
} }
} }
@@ -205,4 +207,16 @@ class KotlinDebuggerCaches(private val project: Project) {
} }
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null) data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null)
sealed class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
class CachedClassNames(classNames: List<String>): ComputedClassNames(classNames, true) {
constructor(className: String?): this(className.toList())
}
class NonCachedClassNames(classNames: List<String>): ComputedClassNames(classNames, false) {
constructor(className: String?): this(className.toList())
}
}
} }
private fun String?.toList() = if (this == null) emptyList() else listOf(this)