Debugger: fix breakpoints inside inline functions in libraries sources

This commit is contained in:
Natalia Ukhorskaya
2016-04-06 15:25:59 +03:00
parent 3ef51159b8
commit 8e3ec5b7f0
7 changed files with 62 additions and 14 deletions
+1
View File
@@ -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
@@ -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<String>()
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 {
@@ -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)
}
@@ -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<GlobalSearchScope>,
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 <T, R> Collection<T>.findFirstNotEmpty(predicate: (T) -> Collection<R>): Collection<R>? {
var result: Collection<R> = 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 }
@@ -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
}
@@ -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
@@ -6,4 +6,7 @@ fun main(args: Array<String>) {
inlineFun { }
}
// ADDITIONAL_BREAKPOINT: inlineFunInLibrary.kt:public inline fun inlineFun
// RESUME: 2
// ADDITIONAL_BREAKPOINT: inlineFunInLibrary.kt:public inline fun inlineFun
// ADDITIONAL_BREAKPOINT: inlineFunInLibrary.kt: Breakpoint 2