From 8e3ec5b7f0df55335aeb9347be217397aa3c3a8d Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Wed, 6 Apr 2016 15:25:59 +0300 Subject: [PATCH] Debugger: fix breakpoints inside inline functions in libraries sources --- ChangeLog.md | 1 + .../idea/debugger/KotlinPositionManager.kt | 25 ++++++++++++--- .../idea/filters/KotlinExceptionFilter.kt | 4 +-- .../kotlin/idea/util/DebuggerUtils.kt | 31 ++++++++++++++----- .../inlineFunInLibrary/inlineFunInLibrary.kt | 8 +++++ .../tinyApp/outs/breakpointInInlineFun.out | 2 ++ .../singleBreakpoint/breakpointInInlineFun.kt | 5 ++- 7 files changed, 62 insertions(+), 14 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 4c5440aea0a..5b79ed2cb2b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -191,6 +191,7 @@ Issues fixed: - Avoid 1s delay in completion in debugger fields if session is not stopped on a breakpoint - Avoid cast to runtime type unavailable in current scope - Fix text with line breaks in popup with line breakpoint variants +- Fix breakpoints inside inline functions in libraries sources ### Java to Kotlin converter diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 962aefdabee..0564d96ff86 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -34,6 +34,7 @@ import com.intellij.openapi.ui.MessageType import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.impl.compiled.ClsFileImpl +import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.searches.ReferencesSearch import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.ThreeState @@ -63,6 +64,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Computed import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage +import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.util.DebuggerUtils import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.application.runReadAction @@ -81,6 +83,11 @@ import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() { + private val scopes = listOf( + myDebugProcess.searchScope, + KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project) + ) + override fun evaluateCondition(context: EvaluationContext, frame: StackFrameProxyImpl, location: Location, expression: String): ThreeState? { return ThreeState.UNSURE } @@ -106,7 +113,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq val javaClassName = JvmClassName.byInternalName(defaultInternalName(location)) val project = myDebugProcess.project - val defaultPsiFile = DebuggerUtils.findSourceFileForClass(project, myDebugProcess.searchScope, javaClassName, javaSourceFileName) + val defaultPsiFile = DebuggerUtils.findSourceFileForClass(project, scopes, javaClassName, javaSourceFileName) if (defaultPsiFile != null) { return SourcePosition.createFromLine(defaultPsiFile, 0) } @@ -218,7 +225,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq val project = myDebugProcess.project - return DebuggerUtils.findSourceFileForClass(project, myDebugProcess.searchScope, className, sourceName) + return DebuggerUtils.findSourceFileForClass(project, scopes, className, sourceName) } private fun defaultInternalName(location: Location): String { @@ -450,7 +457,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq val functionName = function.readAction { it.name } val task = Runnable { - ReferencesSearch.search(function, myDebugProcess.searchScope).forEach { + ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach { if (!it.readAction { it.isImportUsage() }) { val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) } if (usage != null) { @@ -527,7 +534,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq val result = hashSetOf() val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet() - ReferencesSearch.search(inlineFunction, myDebugProcess.searchScope).forEach { + ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach { runReadAction { if (!it.isImportUsage()) { val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) } @@ -548,6 +555,16 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq 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 + } + } + private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression() private fun String.substringIndex(): String { diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt index a08b8ee3ec7..2c26660fc81 100644 --- a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt @@ -63,7 +63,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter val internalName = fullyQualifiedName.replace('.', '/') val jvmClassName = JvmClassName.byInternalName(internalName) - val file = DebuggerUtils.findSourceFileForClass(project, searchScope, jvmClassName, fileName) ?: return null + val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null val virtualFile = file.virtualFile ?: return null @@ -121,7 +121,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter } ?: return null val newJvmName = JvmClassName.byInternalName(mappingInfo.path) - val newSourceFile = DebuggerUtils.findSourceFileForClass(project, searchScope, newJvmName, mappingInfo.name) ?: return null + val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null return OpenFileHyperlinkInfo(project, newSourceFile.virtualFile, mappingInfo.getIntervalIfContains(line)!!.map(line) - 1) } diff --git a/idea/src/org/jetbrains/kotlin/idea/util/DebuggerUtils.kt b/idea/src/org/jetbrains/kotlin/idea/util/DebuggerUtils.kt index b3fd96aa6b9..c13a4127b41 100644 --- a/idea/src/org/jetbrains/kotlin/idea/util/DebuggerUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/util/DebuggerUtils.kt @@ -42,20 +42,28 @@ object DebuggerUtils { private val KOTLIN_EXTENSIONS = Sets.newHashSet("kt", "kts") + fun findSourceFileForClassIncludeLibrarySources( + project: Project, + scope: GlobalSearchScope, + className: JvmClassName, + fileName: String): KtFile? { + return findSourceFileForClass( + project, + listOf(scope, KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(project), project)), + className, + fileName) + } + fun findSourceFileForClass( project: Project, - searchScope: GlobalSearchScope, + scopes: List, className: JvmClassName, fileName: String): KtFile? { val extension = FileUtilRt.getExtension(fileName) if (!KOTLIN_EXTENSIONS.contains(extension)) return null if (DumbService.getInstance(project).isDumb) return null - val filesWithExactName = findFilesByNameInPackage(className, fileName, project, searchScope).check { it.isNotEmpty() } - // Source files for libraries aren't included into ModuleWithDependencies scope - ?: findFilesByNameInPackage( - className, fileName, project, - KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(project), project)) + val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null if (filesWithExactName.isEmpty()) return null @@ -65,7 +73,7 @@ object DebuggerUtils { // Static facade or inner class of such facade? val partFqName = className.fqNameForClassNameWithoutDollars - val filesForPart = StaticFacadeIndexUtil.findFilesForFilePart(partFqName, searchScope, project) + val filesForPart = scopes.findFirstNotEmpty { StaticFacadeIndexUtil.findFilesForFilePart(partFqName, it, project) } ?: return null if (!filesForPart.isEmpty()) { for (file in filesForPart) { if (file.name == fileName) { @@ -79,6 +87,15 @@ object DebuggerUtils { return filesWithExactName.first() } + private fun Collection.findFirstNotEmpty(predicate: (T) -> Collection): Collection? { + var result: Collection = emptyList() + for (e in this) { + result = predicate(e) + if (result.isNotEmpty()) break + } + return result + } + private fun findFilesByNameInPackage(className: JvmClassName, fileName: String, project: Project, searchScope: GlobalSearchScope) = findFilesWithExactPackage(className.packageFqName, searchScope, project).filter { it.name == fileName } diff --git a/idea/testData/debugger/customLibraryForTinyApp/inlineFunInLibrary/inlineFunInLibrary.kt b/idea/testData/debugger/customLibraryForTinyApp/inlineFunInLibrary/inlineFunInLibrary.kt index f5ef1ab10e7..cc756012e1b 100644 --- a/idea/testData/debugger/customLibraryForTinyApp/inlineFunInLibrary/inlineFunInLibrary.kt +++ b/idea/testData/debugger/customLibraryForTinyApp/inlineFunInLibrary/inlineFunInLibrary.kt @@ -2,4 +2,12 @@ package customLib.inlineFunInLibrary public inline fun inlineFun(f: () -> Unit) { 1 + 1 + inlineFunInner { + 1 + 1 + } +} + +public inline fun inlineFunInner(f: () -> Unit) { + // Breakpoint 2 + 1 + 1 } diff --git a/idea/testData/debugger/tinyApp/outs/breakpointInInlineFun.out b/idea/testData/debugger/tinyApp/outs/breakpointInInlineFun.out index a0d3bb4c16b..5134c79cc1f 100644 --- a/idea/testData/debugger/tinyApp/outs/breakpointInInlineFun.out +++ b/idea/testData/debugger/tinyApp/outs/breakpointInInlineFun.out @@ -1,7 +1,9 @@ LineBreakpoint created at inlineFunInLibrary.kt:4 +LineBreakpoint created at inlineFunInLibrary.kt:12 !JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! breakpointInInlineFun.BreakpointInInlineFunKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' inlineFunInLibrary.kt:4 +inlineFunInLibrary.kt:12 Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/breakpointInInlineFun.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/breakpointInInlineFun.kt index 027b287037e..348c5891042 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/breakpointInInlineFun.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/breakpointInInlineFun.kt @@ -6,4 +6,7 @@ fun main(args: Array) { inlineFun { } } -// ADDITIONAL_BREAKPOINT: inlineFunInLibrary.kt:public inline fun inlineFun \ No newline at end of file +// RESUME: 2 + +// ADDITIONAL_BREAKPOINT: inlineFunInLibrary.kt:public inline fun inlineFun +// ADDITIONAL_BREAKPOINT: inlineFunInLibrary.kt: Breakpoint 2 \ No newline at end of file