Debugger: Fix AbstractPositionManagerTest
MockReferenceType can't be used anymore because the new DebuggerClassNameProvider requires much more from it. The new SmartMockReferenceType implements methods such as nestedTypes() or allLineLocations() properly. Also, as PositionManager can return more than one class, we should check if any of the classes it returned matches the pattern.
This commit is contained in:
@@ -56,11 +56,17 @@ class KotlinExtraSteppingFilter : ExtraSteppingFilter {
|
||||
|
||||
val settings = DebuggerSettings.getInstance()
|
||||
if (settings.TRACING_FILTERS_ENABLED) {
|
||||
val className = positionManager.originalClassNameForPosition(sourcePosition)?.replace('/', '.') ?: return false
|
||||
for (filter in settings.steppingFilters) {
|
||||
if (filter.isEnabled) {
|
||||
if (filter.matches(className)) {
|
||||
return true
|
||||
val classNames = positionManager.originalClassNameForPosition(sourcePosition).map { it.replace('/', '.') }
|
||||
if (classNames.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (className in classNames) {
|
||||
for (filter in settings.steppingFilters) {
|
||||
if (filter.isEnabled) {
|
||||
if (filter.matches(className)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
* Copyright 2010-2015 KtBrains 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.
|
||||
@@ -18,415 +18,272 @@ 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.debugger.engine.DebuggerUtils
|
||||
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.coroutines.CoroutineCodegen
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.containsNonTailSuspensionCalls
|
||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
|
||||
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.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.EMPTY
|
||||
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.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.Cached
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.NonCached
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import java.util.*
|
||||
|
||||
class DebuggerClassNameProvider(
|
||||
private val debugProcess: DebugProcess,
|
||||
scopes: List<GlobalSearchScope>,
|
||||
val findInlineUseSites: Boolean = true,
|
||||
val alwaysReturnLambdaParentClass: Boolean = true
|
||||
) {
|
||||
companion object {
|
||||
internal val CLASS_ELEMENT_TYPES = arrayOf<Class<out PsiElement>>(
|
||||
KtFile::class.java,
|
||||
KtClassOrObject::class.java,
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtAnonymousInitializer::class.java)
|
||||
|
||||
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 elementOfClassName = element.readAction { getElementToCalculateClassName(it.parent) }
|
||||
when (element) {
|
||||
is KtClassOrObject -> return CachedClassNames(getClassNameForClass(element, typeMapper))
|
||||
is KtSecondaryConstructor -> {
|
||||
val klass = element.readAction { it.getContainingClassOrObject() }
|
||||
return CachedClassNames(getClassNameForClass(klass, typeMapper))
|
||||
}
|
||||
is KtFunction -> {
|
||||
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, typeMapper.bindingContext) }
|
||||
if (descriptor != null) {
|
||||
val classNamesForParent = classNamesForPosition(elementOfClassName, 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)
|
||||
val className = asmType.internalName
|
||||
|
||||
val inlineCallClassPatterns = inlineCallClassPatterns(typeMapper, element)
|
||||
val names = listOf(className) + inlineCallClassPatterns
|
||||
|
||||
return CachedClassNames(names)
|
||||
}
|
||||
element is KtAnonymousInitializer -> {
|
||||
// Class-object initializer
|
||||
if (elementOfClassName is KtObjectDeclaration && runReadAction { elementOfClassName.isCompanion() }) {
|
||||
return CachedClassNames(classNamesForPosition(elementOfClassName.parent, withInlines))
|
||||
}
|
||||
return CachedClassNames(classNamesForPosition(elementOfClassName, 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) as? PropertyDescriptor ?:
|
||||
return CachedClassNames(classNamesForPosition(elementOfClassName, withInlines))
|
||||
|
||||
return CachedClassNames(getJvmInternalNameForPropertyOwner(typeMapper, descriptor))
|
||||
}
|
||||
element is KtNamedFunction -> {
|
||||
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||
|
||||
val parentInternalName = when {
|
||||
isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext) -> {
|
||||
CodegenBinding.asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName
|
||||
}
|
||||
elementOfClassName is KtClassOrObject -> getClassNameForClass(elementOfClassName, typeMapper)
|
||||
elementOfClassName != 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)
|
||||
|
||||
val inlineCallPatterns = inlineCallClassPatterns(typeMapper, element)
|
||||
|
||||
return CachedClassNames((listOf(parentInternalName) + inlinedCalls + inlineCallPatterns).filterNotNull())
|
||||
}
|
||||
}
|
||||
|
||||
return CachedClassNames(getClassNameForFile(file))
|
||||
}
|
||||
|
||||
private fun isFunctionWithSuspendStateMachine(descriptor: DeclarationDescriptor?, bindingContext: BindingContext): Boolean {
|
||||
return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext)
|
||||
}
|
||||
|
||||
private fun inlineCallClassPatterns(typeMapper: KotlinTypeMapper, element: KtElement): List<String> {
|
||||
val context = typeMapper.bindingContext
|
||||
|
||||
val inlineCall = runReadAction {
|
||||
val parentCallsWithLambdas = element.parents.map {
|
||||
val ktCallExpression: KtCallExpression =
|
||||
when (it) {
|
||||
is KtFunctionLiteral -> {
|
||||
val lambdaExpression = it.parent as? KtLambdaExpression
|
||||
// call(param, { <it> })
|
||||
lambdaExpression?.typedParent<KtValueArgument>()?.typedParent<KtValueArgumentList>()?.typedParent<KtCallExpression>() ?:
|
||||
|
||||
// call { <it> }
|
||||
lambdaExpression?.typedParent<KtLambdaArgument>()?.typedParent<KtCallExpression>()
|
||||
}
|
||||
|
||||
is KtNamedFunction -> {
|
||||
// call(fun () {})
|
||||
it.typedParent<KtValueArgument>()?.typedParent<KtValueArgumentList>()?.typedParent<KtCallExpression>()
|
||||
}
|
||||
|
||||
else -> null
|
||||
} ?: return@map null
|
||||
|
||||
ktCallExpression to (it as KtElement)
|
||||
}.toList()
|
||||
|
||||
parentCallsWithLambdas.lastOrNull {
|
||||
it != null && isInlineCall(context, it.component1())
|
||||
}?.first
|
||||
} ?: return emptyList()
|
||||
|
||||
val lexicalScope = runReadAction { inlineCall.getResolutionScope(context) } ?: return emptyList()
|
||||
val baseClassName = classNamesForPosition(inlineCall, false).firstOrNull() ?: return emptyList()
|
||||
|
||||
val resolvedCall = runReadAction { inlineCall.getResolvedCall(context) } ?: return emptyList()
|
||||
val inlineFunctionName = resolvedCall.resultingDescriptor.name
|
||||
|
||||
val ownerDescriptor = lexicalScope.ownerDescriptor
|
||||
val ownerDeclaration = if (ownerDescriptor is DeclarationDescriptor) DescriptorToSourceUtils.getSourceFromDescriptor(ownerDescriptor) else null
|
||||
|
||||
val ownerDescriptorName =
|
||||
if (isFunctionWithSuspendStateMachine(ownerDescriptor, typeMapper.bindingContext) ||
|
||||
(ownerDescriptor is CallableDescriptor && ownerDeclaration is KtElement && CoroutineCodegen.shouldCreateByLambda(ownerDescriptor, ownerDeclaration))) {
|
||||
Name.identifier(DO_RESUME_METHOD_NAME)
|
||||
}
|
||||
else if (ownerDescriptor is PropertyDescriptor && lexicalScope.kind == LexicalScopeKind.PROPERTY_INITIALIZER_OR_DELEGATE &&
|
||||
ownerDescriptor.containingDeclaration is ClassDescriptor) {
|
||||
(ownerDescriptor.containingDeclaration as ClassDescriptor).constructors.first().name
|
||||
}
|
||||
else {
|
||||
ownerDescriptor.name
|
||||
}
|
||||
|
||||
val ownerJvmName = if (ownerDescriptorName.isSpecial) InlineCodegenUtil.SPECIAL_TRANSFORMATION_NAME else ownerDescriptorName.asString()
|
||||
val mangledInternalClassName =
|
||||
baseClassName + "$" + ownerJvmName + InlineCodegenUtil.INLINE_CALL_TRANSFORMATION_SUFFIX + "$" + inlineFunctionName
|
||||
|
||||
return listOf("$mangledInternalClassName*")
|
||||
}
|
||||
|
||||
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,
|
||||
KtSecondaryConstructor::class.java)
|
||||
|
||||
private fun getElementToCalculateClassName(notPositionedElement: PsiElement?): KtElement? {
|
||||
if (notPositionedElement?.let { it::class.java } 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)
|
||||
}
|
||||
}
|
||||
internal fun getRelevantElement(element: PsiElement): PsiElement? {
|
||||
for (elementType in CLASS_ELEMENT_TYPES) {
|
||||
if (elementType.isInstance(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (!applicationEx.isUnitTestMode && (!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) }
|
||||
// Do not copy the array (*elementTypes) if the element is one we look for
|
||||
return runReadAction { PsiTreeUtil.getNonStrictParentOfType(element, *CLASS_ELEMENT_TYPES) }
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
val inlineUsagesSearcher = InlineCallableUsagesSearcher(debugProcess, scopes)
|
||||
|
||||
/**
|
||||
* Returns classes in which the given line number *is* present.
|
||||
*/
|
||||
fun getClassesForPosition(position: SourcePosition): List<ReferenceType> = with (debugProcess) {
|
||||
val lineNumber = position.line
|
||||
|
||||
return doGetClassesForPosition(position)
|
||||
.flatMap { className -> virtualMachineProxy.classesByName(className) }
|
||||
.flatMap { referenceType -> findTargetClasses(referenceType, lineNumber) }
|
||||
}
|
||||
|
||||
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$*"
|
||||
/**
|
||||
* Returns classes names in JDI format (my.app.App$Nested) in which the given line number *may be* present.
|
||||
*/
|
||||
fun getOuterClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return doGetClassesForPosition(position).toList()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
private fun doGetClassesForPosition(position: SourcePosition): Set<String> {
|
||||
val relevantElement = runReadAction { getRelevantElement(position.elementAt) }
|
||||
|
||||
val inlineFunction = this.parents.firstIsInstanceOrNull<KtNamedFunction>() ?: return emptySet()
|
||||
val result = getOrComputeClassNames(relevantElement) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}.toMutableSet()
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (lambda in position.readAction(::getLambdasAtLineIfAny)) {
|
||||
result += getOrComputeClassNames(lambda) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@PublishedApi
|
||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||
internal tailrec fun getOuterClassNamesForElement(element: PsiElement?): ComputedClassNames {
|
||||
if (element == null) return EMPTY
|
||||
|
||||
return when (element) {
|
||||
is KtFile -> {
|
||||
val fileClassName = runReadAction { NoResolveFileClassesProvider.getFileClassInternalName(element) }.toJdiName()
|
||||
ComputedClassNames.Cached(fileClassName)
|
||||
}
|
||||
is KtClassOrObject -> {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
if (enclosingElementForLocal != null) { // A local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
}
|
||||
else if (runReadAction { element.isObjectLiteral() }) {
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
else { // Guaranteed to be non-local class or object
|
||||
element.readAction {
|
||||
if (it is KtClass && runReadAction { it.isInterface() }) {
|
||||
val name = getNameForNonLocalClass(it)
|
||||
|
||||
if (name != null)
|
||||
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
||||
else
|
||||
ComputedClassNames.EMPTY
|
||||
} else {
|
||||
getNameForNonLocalClass(it)?.let { ComputedClassNames.Cached(it) } ?: ComputedClassNames.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is KtProperty -> {
|
||||
// inline top level property!
|
||||
if (runReadAction { element.isTopLevel }) {
|
||||
return getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
if (enclosingElementForLocal != null) {
|
||||
return getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
}
|
||||
|
||||
// Handle non-local property
|
||||
val containingClassOrFile = runReadAction {
|
||||
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
||||
}
|
||||
|
||||
if (containingClassOrFile is KtObjectDeclaration && runReadAction { containingClassOrFile.isCompanion() }) {
|
||||
// Properties from the companion object can be placed in the companion object's containing class
|
||||
return (getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
|
||||
getOuterClassNamesForElement(containingClassOrFile)).distinct()
|
||||
}
|
||||
|
||||
if (containingClassOrFile != null)
|
||||
getOuterClassNamesForElement(containingClassOrFile)
|
||||
else
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
is KtNamedFunction -> {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
val descriptor = typeMapper.bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, element)
|
||||
|
||||
val classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
|
||||
val nonInlineClasses: ComputedClassNames
|
||||
if (runReadAction { element.name == null || element.isLocal } || isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext)) {
|
||||
nonInlineClasses = classNamesOfContainingDeclaration + ComputedClassNames.Cached(
|
||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName())
|
||||
} else {
|
||||
nonInlineClasses = classNamesOfContainingDeclaration
|
||||
}
|
||||
|
||||
if (!findInlineUseSites) {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
|
||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(
|
||||
element,
|
||||
KotlinDebuggerCaches.getOrCreateTypeMapper(element).bindingContext
|
||||
) { this.getOuterClassNamesForElement(it) }
|
||||
|
||||
nonInlineClasses + inlineCallSiteClasses
|
||||
}
|
||||
is KtAnonymousInitializer -> {
|
||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||
|
||||
if (initializerOwner is KtObjectDeclaration && runReadAction { initializerOwner.isCompanion() }) {
|
||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
||||
}
|
||||
|
||||
getOuterClassNamesForElement(initializerOwner)
|
||||
}
|
||||
is KtFunctionLiteral -> {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
|
||||
val nonInlinedLambdaClassName = runReadAction {
|
||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName()
|
||||
}
|
||||
|
||||
if (!alwaysReturnLambdaParentClass && !InlineUtil.isInlinedArgument(element, typeMapper.bindingContext, true)) {
|
||||
return ComputedClassNames.Cached(nonInlinedLambdaClassName)
|
||||
}
|
||||
|
||||
ComputedClassNames.Cached(nonInlinedLambdaClassName) + getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
else -> getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
}
|
||||
private fun getNameForNonLocalClass(nonLocalClassOrObject: KtClassOrObject): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(nonLocalClassOrObject)
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.CLASS, nonLocalClassOrObject] ?: return null
|
||||
|
||||
private fun isInlineCall(context: BindingContext, expr: KtCallExpression): Boolean {
|
||||
val resolvedCall = expr.getResolvedCall(context) ?: return false
|
||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||
}
|
||||
val type = typeMapper.mapClass(descriptor)
|
||||
if (type.sort != Type.OBJECT) {
|
||||
return null
|
||||
}
|
||||
|
||||
private inline fun <reified T : PsiElement> PsiElement.typedParent(): T? = parent as? T
|
||||
|
||||
private fun String.substringIndex(): String {
|
||||
if (lastIndexOf("$") < 0) return this
|
||||
|
||||
val suffix = substringAfterLast("$")
|
||||
if (suffix.all(Char::isDigit)) {
|
||||
return substringBeforeLast("$") + "$"
|
||||
return type.className
|
||||
}
|
||||
return this
|
||||
|
||||
private fun isFunctionWithSuspendStateMachine(descriptor: DeclarationDescriptor?, bindingContext: BindingContext): Boolean {
|
||||
return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext)
|
||||
}
|
||||
|
||||
private val PsiElement.relevantParentInReadAction
|
||||
get() = runReadAction { getRelevantElement(this.parent) }
|
||||
}
|
||||
|
||||
private fun String.toJdiName() = replace('/', '.')
|
||||
|
||||
private fun DebugProcess.findTargetClasses(outerClass: ReferenceType, lineAt: Int): List<ReferenceType> {
|
||||
val vmProxy = virtualMachineProxy
|
||||
if (!outerClass.isPrepared) return emptyList()
|
||||
|
||||
val targetClasses = ArrayList<ReferenceType>(1)
|
||||
|
||||
try {
|
||||
for (location in outerClass.allLineLocations()) {
|
||||
val locationLine = location.lineNumber() - 1
|
||||
if (locationLine < 0) {
|
||||
// such locations are not correspond to real lines in code
|
||||
continue
|
||||
}
|
||||
|
||||
val method = location.method()
|
||||
if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge) {
|
||||
// skip synthetic methods
|
||||
continue
|
||||
}
|
||||
|
||||
if (lineAt == locationLine) {
|
||||
targetClasses += outerClass
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The same line number may appear in different classes so we have to scan nested classes as well.
|
||||
// For example, in the next example line 3 appears in both Foo and Foo$Companion.
|
||||
|
||||
/* class Foo {
|
||||
companion object {
|
||||
val a = Foo() /* line 3 */
|
||||
}
|
||||
} */
|
||||
|
||||
val nestedTypes = vmProxy.nestedTypes(outerClass)
|
||||
for (nested in nestedTypes) {
|
||||
targetClasses += findTargetClasses(nested, lineAt)
|
||||
}
|
||||
}
|
||||
catch (_: AbsentInformationException) {}
|
||||
|
||||
return targetClasses
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.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.ui.MessageType
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
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.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerClassNameProvider.Companion.getRelevantElement
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
|
||||
class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
|
||||
fun findInlinedCalls(function: KtNamedFunction, context: BindingContext, transformer: (PsiElement) -> ComputedClassNames): ComputedClassNames {
|
||||
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
|
||||
return ComputedClassNames.EMPTY
|
||||
}
|
||||
else {
|
||||
val searchResult = hashSetOf<PsiElement>()
|
||||
val functionName = runReadAction { function.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
||||
if (!runReadAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
||||
if (usage != null) {
|
||||
searchResult.add(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (!applicationEx.isUnitTestMode && (!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)
|
||||
}
|
||||
|
||||
val results = searchResult.map { transformer(it) }
|
||||
return ComputedClassNames(results.flatMap { it.classNames }, shouldBeCached = results.all { it.shouldBeCached })
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,6 @@ import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import java.util.*
|
||||
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
||||
|
||||
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
||||
@@ -140,7 +139,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
val methodName = location.method().name()
|
||||
return when {
|
||||
JvmAbi.isGetterName(methodName) -> {
|
||||
val parameterForGetter = contextElement.primaryConstructor?.valueParameters?.firstOrNull() {
|
||||
val parameterForGetter = contextElement.primaryConstructor?.valueParameters?.firstOrNull {
|
||||
it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
|
||||
} ?: return null
|
||||
parameterForGetter
|
||||
@@ -163,7 +162,9 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
val elementAt = file.findElementAt(start) ?: return null
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(elementAt)
|
||||
|
||||
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).internalName
|
||||
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName))
|
||||
.internalName.replace('/', '.')
|
||||
|
||||
for (literal in literalsOrFunctions) {
|
||||
if (InlineUtil.isInlinedArgument(literal, typeMapper.bindingContext, true)) {
|
||||
if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl)) {
|
||||
@@ -172,7 +173,10 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
continue
|
||||
}
|
||||
|
||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(literal.firstChild, true)
|
||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, scopes, alwaysReturnLambdaParentClass = false)
|
||||
.getOuterClassNamesForElement(literal.firstChild)
|
||||
.classNames
|
||||
|
||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||
return literal
|
||||
}
|
||||
@@ -223,32 +227,8 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
override fun getAllClasses(sourcePosition: SourcePosition): List<ReferenceType> {
|
||||
val psiFile = sourcePosition.file
|
||||
if (psiFile is KtFile) {
|
||||
val result = ArrayList<ReferenceType>()
|
||||
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return result
|
||||
|
||||
val names = DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(sourcePosition, true)
|
||||
for (name in names) {
|
||||
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
|
||||
}
|
||||
|
||||
val allClasses = runReadAction { myDebugProcess.getAllClassesAtLine(sourcePosition) }
|
||||
// TEMP START
|
||||
if (result.isNotEmpty()) {
|
||||
val r1 = result.toSet().toList().sorted()
|
||||
val r2 = allClasses.toSet().toList().sorted()
|
||||
for ((i, r11) in r1.withIndex()) {
|
||||
if (i >= r2.size) {
|
||||
println("MIS: $r11")
|
||||
}
|
||||
else if (r11 != r2[i]) {
|
||||
println("OLD: $r11")
|
||||
println("NEW: ${r2[i]}")
|
||||
}
|
||||
}
|
||||
}
|
||||
// TEMP END
|
||||
return allClasses
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return emptyList()
|
||||
return DebuggerClassNameProvider(myDebugProcess, scopes).getClassesForPosition(sourcePosition)
|
||||
}
|
||||
|
||||
if (psiFile is ClsFileImpl) {
|
||||
@@ -263,8 +243,8 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
fun originalClassNameForPosition(sourcePosition: SourcePosition): String? {
|
||||
return DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(sourcePosition, false).firstOrNull()
|
||||
fun originalClassNameForPosition(position: SourcePosition): List<String> {
|
||||
return DebuggerClassNameProvider(myDebugProcess, scopes).getOuterClassNamesForPosition(position)
|
||||
}
|
||||
|
||||
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
||||
@@ -307,9 +287,9 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return DebuggerClassNameProvider(myDebugProcess, scopes).classNamesForPosition(position, true).mapNotNull {
|
||||
className ->
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, className.replace('/', '.'))
|
||||
val classNames = DebuggerClassNameProvider(myDebugProcess, scopes).getOuterClassNamesForPosition(position)
|
||||
return classNames.mapNotNull { name ->
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, name + "*")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,9 @@ class KotlinDebuggerCaches(project: Project) {
|
||||
return newCompiledData
|
||||
}
|
||||
|
||||
fun <T : PsiElement> getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List<String> {
|
||||
fun <T : PsiElement> getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List<String> {
|
||||
if (psiElement == null) return Collections.emptyList()
|
||||
|
||||
val cache = getInstance(runReadAction { psiElement.project })
|
||||
|
||||
val classNamesCache = cache.cachedClassNames.value
|
||||
@@ -233,14 +235,20 @@ class KotlinDebuggerCaches(project: Project) {
|
||||
|
||||
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 ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
|
||||
companion object {
|
||||
val EMPTY = ComputedClassNames.Cached(emptyList())
|
||||
|
||||
fun Cached(classNames: List<String>) = ComputedClassNames(classNames, true)
|
||||
fun Cached(className: String) = ComputedClassNames(Collections.singletonList(className), true)
|
||||
|
||||
fun NonCached(classNames: List<String>) = ComputedClassNames(classNames, false)
|
||||
}
|
||||
|
||||
class NonCachedClassNames(classNames: List<String>) : ComputedClassNames(classNames, false) {
|
||||
constructor(className: String?) : this(className.toList())
|
||||
}
|
||||
fun distinct() = ComputedClassNames(classNames.distinct(), shouldBeCached)
|
||||
|
||||
operator fun plus(other: ComputedClassNames) = ComputedClassNames(
|
||||
classNames + other.classNames, shouldBeCached && other.shouldBeCached)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2015 KtBrains 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.debugger.engine.DebuggerUtils
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
|
||||
fun DebugProcess.getAllClassesAtLine(position: SourcePosition): List<ReferenceType> {
|
||||
val result = hashSetOf<ReferenceType>()
|
||||
|
||||
result.addAll(getAllClassesAtElement(position.elementAt, position.line))
|
||||
|
||||
getLambdasAtLineIfAny(position).forEach {
|
||||
result.addAll(getAllClassesAtElement(it, position.line))
|
||||
}
|
||||
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
private fun DebugProcess.getAllClassesAtElement(elementAt: PsiElement, lineAt: Int): List<ReferenceType> {
|
||||
|
||||
var depthToLocalOrAnonymousClass = 0
|
||||
|
||||
fun calc(element: KtElement?): String? {
|
||||
when (element) {
|
||||
null -> return null
|
||||
is KtClassOrObject -> {
|
||||
if (element.isLocal) {
|
||||
depthToLocalOrAnonymousClass++
|
||||
return calc(element.getElementToCalculateClassName())
|
||||
}
|
||||
|
||||
return element.getNameForNonAnonymousClass()
|
||||
}
|
||||
is KtFunctionLiteral -> {
|
||||
if (!isInlinedLambda(element)) {
|
||||
depthToLocalOrAnonymousClass++
|
||||
}
|
||||
return calc(element.getElementToCalculateClassName())
|
||||
}
|
||||
is KtClassInitializer -> {
|
||||
val parent = element.getElementToCalculateClassName()
|
||||
|
||||
if (parent is KtObjectDeclaration && parent.isCompanion()) {
|
||||
// Companion-object initializer
|
||||
return calc(parent.getElementToCalculateClassName())
|
||||
}
|
||||
|
||||
return calc(parent)
|
||||
}
|
||||
is KtPropertyAccessor -> {
|
||||
return calc(element.getClassOfFile())
|
||||
}
|
||||
is KtProperty -> {
|
||||
if (element.isTopLevel || element.isLocal) {
|
||||
return calc(element.getElementToCalculateClassName())
|
||||
}
|
||||
|
||||
val containingClass = element.getClassOfFile()
|
||||
if (containingClass is KtObjectDeclaration && containingClass.isCompanion()) {
|
||||
// Properties from companion object are moved into class
|
||||
val descriptor = element.resolveToDescriptor() as PropertyDescriptor
|
||||
if (AsmUtil.isPropertyWithBackingFieldCopyInOuterClass(descriptor)) {
|
||||
return calc(containingClass.getElementToCalculateClassName())
|
||||
}
|
||||
}
|
||||
|
||||
return calc(containingClass)
|
||||
}
|
||||
is KtSecondaryConstructor -> {
|
||||
return calc(element.getElementToCalculateClassName())
|
||||
}
|
||||
is KtNamedFunction -> {
|
||||
if (element.name == null) {
|
||||
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, element.analyze()) }
|
||||
if (descriptor == null || descriptor.isCrossinline) {
|
||||
depthToLocalOrAnonymousClass++
|
||||
}
|
||||
}
|
||||
else if (element.isLocal) {
|
||||
depthToLocalOrAnonymousClass++
|
||||
}
|
||||
val parent = element.getElementToCalculateClassName()
|
||||
if (parent is KtClassInitializer) {
|
||||
// TODO BUG? anonymous functions from companion object constructor should be inner class of companion object, not class
|
||||
return calc(parent.getElementToCalculateClassName())
|
||||
}
|
||||
|
||||
return calc(parent)
|
||||
}
|
||||
is KtFile -> {
|
||||
return NoResolveFileClassesProvider.getFileClassInternalName(element)
|
||||
}
|
||||
else -> throw IllegalStateException("Unsupported container ${element.javaClass}")
|
||||
}
|
||||
}
|
||||
|
||||
val elementToCalcClassName = elementAt.getElementToCalculateClassName(true)
|
||||
val className = calc(elementToCalcClassName) ?: return emptyList()
|
||||
|
||||
if (depthToLocalOrAnonymousClass == 0) {
|
||||
return virtualMachineProxy.classesByName(className)
|
||||
}
|
||||
|
||||
// the name is a parent class for a local or anonymous class
|
||||
val outers = virtualMachineProxy.classesByName(className)
|
||||
return outers.map { findNested(it, 0, depthToLocalOrAnonymousClass, elementAt, lineAt) }.filterNotNull()
|
||||
}
|
||||
|
||||
private fun KtClassOrObject.getNameForNonAnonymousClass(addTraitImplSuffix: Boolean = true): String? {
|
||||
if (isLocal) return null
|
||||
if (this.isObjectLiteral()) return null
|
||||
|
||||
val name = name ?: return null
|
||||
|
||||
val parentClass = PsiTreeUtil.getParentOfType(this, KtClassOrObject::class.java, true)
|
||||
if (parentClass != null) {
|
||||
val shouldAddTraitImplSuffix = !(parentClass is KtClass && this is KtObjectDeclaration && this.isCompanion())
|
||||
val parentName = parentClass.getNameForNonAnonymousClass(shouldAddTraitImplSuffix)
|
||||
if (parentName == null) {
|
||||
return null
|
||||
}
|
||||
return parentName + "$" + name
|
||||
}
|
||||
|
||||
val className = if (addTraitImplSuffix && this is KtClass && this.isInterface()) name + JvmAbi.DEFAULT_IMPLS_SUFFIX else name
|
||||
|
||||
val packageFqName = this.containingKtFile.packageFqName
|
||||
return if (packageFqName.isRoot) className else packageFqName.asString() + "." + className
|
||||
}
|
||||
|
||||
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
|
||||
arrayOf(KtFile::class.java,
|
||||
KtClass::class.java,
|
||||
KtObjectDeclaration::class.java,
|
||||
KtEnumEntry::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtPropertyAccessor::class.java,
|
||||
KtProperty::class.java,
|
||||
KtClassInitializer::class.java,
|
||||
KtSecondaryConstructor::class.java)
|
||||
|
||||
private fun PsiElement?.getElementToCalculateClassName(withItSelf: Boolean = false): KtElement? {
|
||||
if (withItSelf && this?.let { it::class.java } as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return this as KtElement
|
||||
|
||||
return readAction { PsiTreeUtil.getParentOfType(this, *TYPES_TO_CALCULATE_CLASSNAME) }
|
||||
}
|
||||
|
||||
private fun PsiElement.getClassOfFile(): KtElement? {
|
||||
return PsiTreeUtil.getParentOfType(this, KtFile::class.java, KtClassOrObject::class.java)
|
||||
}
|
||||
|
||||
private fun DebugProcess.findNested(
|
||||
fromClass: ReferenceType,
|
||||
currentDepth: Int,
|
||||
requiredDepth: Int,
|
||||
elementAt: PsiElement,
|
||||
lineAt: Int
|
||||
): ReferenceType? {
|
||||
val vmProxy = virtualMachineProxy
|
||||
if (fromClass.isPrepared) {
|
||||
try {
|
||||
if (currentDepth < requiredDepth) {
|
||||
val nestedTypes = vmProxy.nestedTypes(fromClass)
|
||||
for (nested in nestedTypes) {
|
||||
val found = findNested(nested, currentDepth + 1, requiredDepth, elementAt, lineAt)
|
||||
if (found != null) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
for (location in fromClass.allLineLocations()) {
|
||||
val locationLine = location.lineNumber() - 1
|
||||
if (locationLine <= 0) {
|
||||
// such locations are not correspond to real lines in code
|
||||
continue
|
||||
}
|
||||
val method = location.method()
|
||||
if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge) {
|
||||
// skip synthetic methods
|
||||
continue
|
||||
}
|
||||
|
||||
if (lineAt == locationLine) {
|
||||
// TODO: compare elementAt, can be significant for lambdas
|
||||
// val candidatePosition = KotlinPositionManager(this).getSourcePosition(location)
|
||||
// if (candidatePosition?.elementAt == elementAt) {
|
||||
return fromClass
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ignored: AbsentInformationException) {
|
||||
}
|
||||
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isInlinedLambda(functionLiteral: KtFunctionLiteral): Boolean {
|
||||
val functionLiteralExpression = functionLiteral.parent
|
||||
|
||||
var parent = functionLiteralExpression.parent
|
||||
|
||||
while (parent is KtParenthesizedExpression || parent is KtBinaryExpressionWithTypeRHS || parent is KtLabeledExpression) {
|
||||
parent = parent.parent
|
||||
}
|
||||
|
||||
while (parent is ValueArgument || parent is KtValueArgumentList) {
|
||||
parent = parent.parent
|
||||
}
|
||||
|
||||
if (parent !is KtElement) return false
|
||||
|
||||
return InlineUtil.isInlinedArgument(functionLiteral, functionLiteral.analyze(), true)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
LineBreakpoint created at anonymousFunAsParamDefaultValue.kt:6
|
||||
LineBreakpoint created at anonymousFunAsParamDefaultValue.kt:10
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
anonymousFunAsParamDefaultValue.kt:6
|
||||
anonymousFunAsParamDefaultValue.kt:10
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -3,8 +3,8 @@ Run Java
|
||||
Connected to the target VM
|
||||
functionReference.kt:11
|
||||
functionReference.kt:4
|
||||
functionReference.kt:7
|
||||
functionReference.kt:11
|
||||
functionReference.kt:7
|
||||
functionReference.kt:4
|
||||
functionReference.kt:5
|
||||
functionReference.kt:12
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
LineBreakpoint created at kt15823.kt:13
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
kt15823.kt:13
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,7 @@
|
||||
LineBreakpoint created at kt17295.kt:9
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
kt17295.kt:9
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -3,8 +3,8 @@ Run Java
|
||||
Connected to the target VM
|
||||
propertyReference.kt:11
|
||||
propertyReference.kt:4
|
||||
propertyReference.kt:7
|
||||
propertyReference.kt:11
|
||||
propertyReference.kt:7
|
||||
propertyReference.kt:4
|
||||
propertyReference.kt:5
|
||||
propertyReference.kt:12
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package anonymousFunAsParamDefaultValue
|
||||
|
||||
fun foo(a: (String) -> String = fun(b: String): String {
|
||||
listOf("").map {
|
||||
//Breakpoint!
|
||||
val a = it.length
|
||||
}
|
||||
|
||||
//Breakpoint!
|
||||
return b
|
||||
}) {
|
||||
a("")
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
foo()
|
||||
}
|
||||
|
||||
// RESUME: 2
|
||||
@@ -0,0 +1,28 @@
|
||||
package kt15823
|
||||
|
||||
object Some {
|
||||
val collection = mutableListOf<() -> Unit>()
|
||||
|
||||
inline fun inlineWithReified(crossinline lambda: () -> Unit) {
|
||||
collection.add({ lambda() })
|
||||
}
|
||||
|
||||
init {
|
||||
inlineWithReified {
|
||||
//Breakpoint!
|
||||
foo() // Will marked as (X), and never hit, but executes
|
||||
}
|
||||
}
|
||||
|
||||
fun foo() {}
|
||||
|
||||
fun magic() {
|
||||
collection.forEach { it.invoke() }
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
Some.magic()
|
||||
}
|
||||
|
||||
// RESUME: 1
|
||||
@@ -0,0 +1,18 @@
|
||||
package kt17295
|
||||
|
||||
import java.lang.Thread.sleep
|
||||
import kotlin.concurrent.timer
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
timer("Repeating println", period=100) {
|
||||
//Breakpoint!
|
||||
foo()
|
||||
System.exit(0)
|
||||
}
|
||||
|
||||
sleep(2000)
|
||||
}
|
||||
|
||||
fun foo() {}
|
||||
|
||||
// RESUME: 1
|
||||
@@ -1010,6 +1010,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/stepping/custom"), Pattern.compile("^([^.]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("anonymousFunAsParamDefaultValue.kt")
|
||||
public void testAnonymousFunAsParamDefaultValue() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/anonymousFunAsParamDefaultValue.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("coroutine.kt")
|
||||
public void testCoroutine() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/coroutine.kt");
|
||||
@@ -1064,6 +1070,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt15823.kt")
|
||||
public void testKt15823() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("kt17295.kt")
|
||||
public void testKt17295() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt17295.kt");
|
||||
doCustomTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("manyFilesWithInlineCalls1Dex.kt")
|
||||
public void testManyFilesWithInlineCalls1Dex() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/manyFilesWithInlineCalls1Dex.kt");
|
||||
|
||||
Reference in New Issue
Block a user