diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt index f84cef8ae96..d0dc21fc893 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinExtraSteppingFilter.kt @@ -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 + } } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt index 9ab7258861d..2c98eea00a5 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt @@ -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, + val findInlineUseSites: Boolean = true, + val alwaysReturnLambdaParentClass: Boolean = true +) { + companion object { + internal val CLASS_ELEMENT_TYPES = arrayOf>( + 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) { - fun classNamesForPosition(sourcePosition: SourcePosition, withInlines: Boolean): List { - val element = sourcePosition.readAction { it.elementAt } ?: return emptyList() - val names = classNamesForPosition(element, withInlines) - - val lambdas = findLambdas(sourcePosition) - if (lambdas.isEmpty()) { - return names - } - - return names + lambdas - } - - fun classNamesForPosition(element: PsiElement?, withInlines: Boolean): List { - if (DumbService.getInstance(myDebugProcess.project).isDumb) { - return emptyList() - } - - val baseElement = getElementToCalculateClassName(element) ?: return emptyList() - return getOrComputeClassNames(baseElement) { element -> - val file = element.readAction { it.containingFile as KtFile } - val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null - val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element) - - getInternalClassNameForElement(element, typeMapper, file, isInLibrary, withInlines) - } - } - - private fun findLambdas(sourcePosition: SourcePosition): Collection { - val lambdas = sourcePosition.readAction(::getLambdasAtLineIfAny) - return lambdas.flatMap { classNamesForPosition(it, true) } - } - - - private fun getInternalClassNameForElement( - element: KtElement, - typeMapper: KotlinTypeMapper, - file: KtFile, - isInLibrary: Boolean, - withInlines: Boolean - ): KotlinDebuggerCaches.ComputedClassNames { - val 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 { - 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, { }) - lambdaExpression?.typedParent()?.typedParent()?.typedParent() ?: - - // call { } - lambdaExpression?.typedParent()?.typedParent() - } - - is KtNamedFunction -> { - // call(fun () {}) - it.typedParent()?.typedParent()?.typedParent() - } - - 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> = - 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(BindingContext.CLASS, ktClass) ?: return null - - if (ktClass is KtClass && ktClass.isInterface()) { - return typeMapper.mapDefaultImpls(classDescriptor).internalName - } - - return typeMapper.mapClass(classDescriptor).internalName - } - - private fun findInlinedCalls(function: KtNamedFunction, context: BindingContext): List { - if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) { - return emptyList() - } - else { - val searchResult = hashSetOf() - val functionName = function.readAction { it.name } - - val task = Runnable { - ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach { - if (!it.readAction { it.isImportUsage() }) { - val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) } - if (usage != null) { - searchResult.add(usage) - } - } + 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 { - 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 = 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 { + return doGetClassesForPosition(position).toList() } - private fun KtElement.containsCrossInlineParameterUsages(context: BindingContext): Collection { - fun KtElement.hasParameterCall(parameter: KtParameter): Boolean { - return ReferencesSearch.search(parameter).any { - this.textRange.contains(it.element.textRange) - } - } + private fun doGetClassesForPosition(position: SourcePosition): Set { + val relevantElement = runReadAction { getRelevantElement(position.elementAt) } - val inlineFunction = this.parents.firstIsInstanceOrNull() ?: 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, context: BindingContext): Set { - // We could calculate className only for one of parameters, because we add '*' to match all crossInlined parameter calls - val parameter = usedParameters.first() - val result = hashSetOf() - val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet() - - ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach { - runReadAction { - if (!it.isImportUsage()) { - val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) } - if (call != null) { - val resolvedCall = call.getResolvedCall(context) - val argument = resolvedCall?.valueArguments?.entries?.firstOrNull { it.key.original == parameter }?.value - if (argument != null) { - val argumentExpression = getArgumentExpression(argument.arguments.first()) - if (argumentExpression is KtFunction) { - result.add(getCrossInlineArgumentClassName(argumentExpression, inlineFunction.name!!, context)) - } - } - } - } + 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 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 { + val vmProxy = virtualMachineProxy + if (!outerClass.isPrepared) return emptyList() + + val targetClasses = ArrayList(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 } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt new file mode 100644 index 00000000000..3a6f7db5963 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt @@ -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) { + 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() + 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 + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index aa554f79e33..d7bddb2f229 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -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 { val psiFile = sourcePosition.file if (psiFile is KtFile) { - val result = ArrayList() - - 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 { + return DebuggerClassNameProvider(myDebugProcess, scopes).getOuterClassNamesForPosition(position) } override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List { @@ -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 + "*") } } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt index bb2271d89d4..d01efd09c47 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt @@ -120,7 +120,9 @@ class KotlinDebuggerCaches(project: Project) { return newCompiledData } - fun getOrComputeClassNames(psiElement: T, create: (T) -> ComputedClassNames): List { + fun getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List { + 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, val shouldBeCached: Boolean) { - class CachedClassNames(classNames: List) : ComputedClassNames(classNames, true) { - constructor(className: String?) : this(className.toList()) + class ComputedClassNames(val classNames: List, val shouldBeCached: Boolean) { + companion object { + val EMPTY = ComputedClassNames.Cached(emptyList()) + + fun Cached(classNames: List) = ComputedClassNames(classNames, true) + fun Cached(className: String) = ComputedClassNames(Collections.singletonList(className), true) + + fun NonCached(classNames: List) = ComputedClassNames(classNames, false) } - class NonCachedClassNames(classNames: List) : 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) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/positionManagerUtils.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/positionManagerUtils.kt deleted file mode 100644 index 47d0ee4f193..00000000000 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/positionManagerUtils.kt +++ /dev/null @@ -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 { - val result = hashSetOf() - - 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 { - - 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> = - 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) -} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/outs/anonymousFunAsParamDefaultValue.out b/idea/testData/debugger/tinyApp/outs/anonymousFunAsParamDefaultValue.out new file mode 100644 index 00000000000..537dd7d8822 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/anonymousFunAsParamDefaultValue.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/functionReference.out b/idea/testData/debugger/tinyApp/outs/functionReference.out index 89339a3520a..1e833606988 100644 --- a/idea/testData/debugger/tinyApp/outs/functionReference.out +++ b/idea/testData/debugger/tinyApp/outs/functionReference.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/kt15823.out b/idea/testData/debugger/tinyApp/outs/kt15823.out new file mode 100644 index 00000000000..c64e51e440a --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/kt15823.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/kt17295.out b/idea/testData/debugger/tinyApp/outs/kt17295.out new file mode 100644 index 00000000000..7632aee70ab --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/kt17295.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/outs/propertyReference.out b/idea/testData/debugger/tinyApp/outs/propertyReference.out index 3e0573cd027..17923b9b69c 100644 --- a/idea/testData/debugger/tinyApp/outs/propertyReference.out +++ b/idea/testData/debugger/tinyApp/outs/propertyReference.out @@ -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 diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/anonymousFunAsParamDefaultValue.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/anonymousFunAsParamDefaultValue.kt new file mode 100644 index 00000000000..835ba8eb9d1 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/anonymousFunAsParamDefaultValue.kt @@ -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) { + foo() +} + +// RESUME: 2 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt new file mode 100644 index 00000000000..87850bb3e85 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt @@ -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) { + Some.magic() +} + +// RESUME: 1 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/kt17295.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/kt17295.kt new file mode 100644 index 00000000000..20f79cd5212 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/kt17295.kt @@ -0,0 +1,18 @@ +package kt17295 + +import java.lang.Thread.sleep +import kotlin.concurrent.timer + +fun main(args: Array) { + timer("Repeating println", period=100) { + //Breakpoint! + foo() + System.exit(0) + } + + sleep(2000) +} + +fun foo() {} + +// RESUME: 1 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index 8b8fe9bc9df..57e30657578 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -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");