From 622430a2964fada0a02da69c5a18be01838463c7 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Mon, 10 Apr 2017 22:36:23 +0300 Subject: [PATCH] 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. --- .../kotlin/idea/KotlinExtraSteppingFilter.kt | 16 +- .../debugger/DebuggerClassNameProvider.kt | 601 +++++++----------- .../debugger/InlineCallableUsagesSearcher.kt | 94 +++ .../idea/debugger/KotlinPositionManager.kt | 50 +- .../debugger/evaluate/KotlinDebuggerCaches.kt | 22 +- .../idea/debugger/positionManagerUtils.kt | 249 -------- .../outs/anonymousFunAsParamDefaultValue.out | 9 + .../tinyApp/outs/functionReference.out | 2 +- .../debugger/tinyApp/outs/kt15823.out | 7 + .../debugger/tinyApp/outs/kt17295.out | 7 + .../tinyApp/outs/propertyReference.out | 2 +- .../custom/anonymousFunAsParamDefaultValue.kt | 19 + .../tinyApp/src/stepping/custom/kt15823.kt | 28 + .../tinyApp/src/stepping/custom/kt17295.kt | 18 + .../debugger/KotlinSteppingTestGenerated.java | 18 + 15 files changed, 472 insertions(+), 670 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt delete mode 100644 idea/src/org/jetbrains/kotlin/idea/debugger/positionManagerUtils.kt create mode 100644 idea/testData/debugger/tinyApp/outs/anonymousFunAsParamDefaultValue.out create mode 100644 idea/testData/debugger/tinyApp/outs/kt15823.out create mode 100644 idea/testData/debugger/tinyApp/outs/kt17295.out create mode 100644 idea/testData/debugger/tinyApp/src/stepping/custom/anonymousFunAsParamDefaultValue.kt create mode 100644 idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt create mode 100644 idea/testData/debugger/tinyApp/src/stepping/custom/kt17295.kt 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");