diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt index 2c98eea00a5..6e66d345b1f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/DebuggerClassNameProvider.kt @@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companio 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.lexer.KtTokens import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject @@ -137,38 +138,52 @@ class DebuggerClassNameProvider( Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX)) else ComputedClassNames.EMPTY - } else { + } + 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 + val nonInlineClasses = if (runReadAction { element.isTopLevel }) { + // Top level property getOuterClassNamesForElement(element.relevantParentInReadAction) + } + else { + val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) } + if (enclosingElementForLocal != null) { + // Local class + getOuterClassNamesForElement(enclosingElementForLocal) + } + else { + val containingClassOrFile = runReadAction { + PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java) + } + + if (containingClassOrFile is KtObjectDeclaration && containingClassOrFile.isCompanionInReadAction) { + // Properties from the companion object can be placed in the companion object's containing class + (getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) + + getOuterClassNamesForElement(containingClassOrFile)).distinct() + } + else if (containingClassOrFile != null) { + getOuterClassNamesForElement(containingClassOrFile) + } + else { + getOuterClassNamesForElement(element.relevantParentInReadAction) + } + } + } + + if (findInlineUseSites && ( + element.isInlineInReadAction || + runReadAction { element.accessors.any { it.hasModifier(KtTokens.INLINE_KEYWORD) } }) + ) { + nonInlineClasses + inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) } + } + else { + return NonCached(nonInlineClasses.classNames) + } } is KtNamedFunction -> { val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element) @@ -180,25 +195,23 @@ class DebuggerClassNameProvider( if (runReadAction { element.name == null || element.isLocal } || isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext)) { nonInlineClasses = classNamesOfContainingDeclaration + ComputedClassNames.Cached( asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName()) - } else { + } + else { nonInlineClasses = classNamesOfContainingDeclaration } - if (!findInlineUseSites) { + if (!findInlineUseSites || !element.isInlineInReadAction) { return NonCached(nonInlineClasses.classNames) } - val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls( - element, - KotlinDebuggerCaches.getOrCreateTypeMapper(element).bindingContext - ) { this.getOuterClassNamesForElement(it) } + val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) } nonInlineClasses + inlineCallSiteClasses } is KtAnonymousInitializer -> { val initializerOwner = runReadAction { element.containingDeclaration } - if (initializerOwner is KtObjectDeclaration && runReadAction { initializerOwner.isCompanion() }) { + if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) { return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject }) } @@ -237,6 +250,12 @@ class DebuggerClassNameProvider( return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext) } + private val KtDeclaration.isInlineInReadAction: Boolean + get() = runReadAction { hasModifier(KtTokens.INLINE_KEYWORD) } + + private val KtObjectDeclaration.isCompanionInReadAction: Boolean + get() = runReadAction { isCompanion() } + private val PsiElement.relevantParentInReadAction get() = runReadAction { getRelevantElement(this.parent) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt index 3a6f7db5963..f23915f5319 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/InlineCallableUsagesSearcher.kt @@ -25,6 +25,8 @@ 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.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.application.runReadAction @@ -32,19 +34,24 @@ 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 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))) { + fun findInlinedCalls( + declaration: KtDeclaration, + bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext, + transformer: (PsiElement) -> ComputedClassNames + ): ComputedClassNames { + if (!checkIfInline(declaration, bindingContext)) { return ComputedClassNames.EMPTY } else { val searchResult = hashSetOf() - val functionName = runReadAction { function.name } + val declarationName = runReadAction { declaration.name } val task = Runnable { - ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach { + ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach { if (!runReadAction { it.isImportUsage() }) { val usage = (it.element as? KtElement)?.let(::getRelevantElement) if (usage != null) { @@ -61,7 +68,7 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: { isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously( task, - "Compute class names for function $functionName", + "Compute class names for declaration $declarationName", true, myDebugProcess.project) }, ModalityState.NON_MODAL) @@ -73,7 +80,8 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: if (!isSuccess) { XDebugSessionImpl.NOTIFICATION_GROUP.createNotification( - "Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING + "Debugger can skip some executions of $declarationName because the computation of class names was interrupted", + MessageType.WARNING ).notify(myDebugProcess.project) } @@ -82,8 +90,17 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: } } - private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope { - val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile } + private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean { + val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) ?: return false + return when (descriptor) { + is FunctionDescriptor -> InlineUtil.isInline(descriptor) + is PropertyDescriptor -> InlineUtil.hasInlineAccessors(descriptor) + else -> false + } + } + + private fun getScopeForInlineDeclarationUsages(inlineDeclaration: KtDeclaration): GlobalSearchScope { + val virtualFile = runReadAction { inlineDeclaration.containingFile.virtualFile } if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) { return GlobalSearchScope.union(scopes.toTypedArray()) } diff --git a/idea/testData/debugger/tinyApp/outs/inlineProperties.out b/idea/testData/debugger/tinyApp/outs/inlineProperties.out new file mode 100644 index 00000000000..3bbc9d51e21 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/inlineProperties.out @@ -0,0 +1,9 @@ +LineBreakpoint created at inlineProperties.kt:8 +LineBreakpoint created at inlineProperties.kt:20 +Run Java +Connected to the target VM +inlineProperties.kt:8 +inlineProperties.kt:20 +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/inlinePropertyAccessors.out b/idea/testData/debugger/tinyApp/outs/inlinePropertyAccessors.out new file mode 100644 index 00000000000..c52d20fb339 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/inlinePropertyAccessors.out @@ -0,0 +1,9 @@ +LineBreakpoint created at inlinePropertyAccessors.kt:8 +LineBreakpoint created at inlinePropertyAccessors.kt:20 +Run Java +Connected to the target VM +inlinePropertyAccessors.kt:8 +inlinePropertyAccessors.kt:20 +Disconnected from the target VM + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/inlineProperties.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/inlineProperties.kt new file mode 100644 index 00000000000..08602cc129c --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/inlineProperties.kt @@ -0,0 +1,24 @@ +package inlineProperties + +fun main(args: Array) { + class A { + inline val a: String + get() { + //Breakpoint! + return System.nanoTime().toString() + } + } + + A().apply { a } + B().apply { b } +} + +class B { + inline val b: String + get() { + //Breakpoint! + return System.nanoTime().toString() + } +} + +// RESUME: 2 \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/inlinePropertyAccessors.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/inlinePropertyAccessors.kt new file mode 100644 index 00000000000..312ad194931 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/inlinePropertyAccessors.kt @@ -0,0 +1,24 @@ +package inlinePropertyAccessors + +fun main(args: Array) { + class A { + val a: String + inline get() { + //Breakpoint! + return System.nanoTime().toString() + } + } + + A().apply { a } + B().apply { b } +} + +class B { + val b: String + inline get() { + //Breakpoint! + return System.nanoTime().toString() + } +} + +// RESUME: 2 \ 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 57e30657578..5df51f26893 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -1070,6 +1070,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { doCustomTest(fileName); } + @TestMetadata("inlineProperties.kt") + public void testInlineProperties() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/inlineProperties.kt"); + doCustomTest(fileName); + } + + @TestMetadata("inlinePropertyAccessors.kt") + public void testInlinePropertyAccessors() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/inlinePropertyAccessors.kt"); + doCustomTest(fileName); + } + @TestMetadata("kt15823.kt") public void testKt15823() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt");