Do not compute classnames during stepping
#KT-10827 Fixed
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user