Debugger: fix breakpoints inside inline functions in libraries sources
This commit is contained in:
@@ -191,6 +191,7 @@ Issues fixed:
|
|||||||
- Avoid 1s delay in completion in debugger fields if session is not stopped on a breakpoint
|
- 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
|
- Avoid cast to runtime type unavailable in current scope
|
||||||
- Fix text with line breaks in popup with line breakpoint variants
|
- Fix text with line breaks in popup with line breakpoint variants
|
||||||
|
- Fix breakpoints inside inline functions in libraries sources
|
||||||
|
|
||||||
### Java to Kotlin converter
|
### Java to Kotlin converter
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.intellij.openapi.ui.MessageType
|
|||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.util.ThreeState
|
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.decompiler.classFile.KtClsFile
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
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.DebuggerUtils
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
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() {
|
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? {
|
override fun evaluateCondition(context: EvaluationContext, frame: StackFrameProxyImpl, location: Location, expression: String): ThreeState? {
|
||||||
return ThreeState.UNSURE
|
return ThreeState.UNSURE
|
||||||
}
|
}
|
||||||
@@ -106,7 +113,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
val javaClassName = JvmClassName.byInternalName(defaultInternalName(location))
|
val javaClassName = JvmClassName.byInternalName(defaultInternalName(location))
|
||||||
val project = myDebugProcess.project
|
val project = myDebugProcess.project
|
||||||
|
|
||||||
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(project, myDebugProcess.searchScope, javaClassName, javaSourceFileName)
|
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(project, scopes, javaClassName, javaSourceFileName)
|
||||||
if (defaultPsiFile != null) {
|
if (defaultPsiFile != null) {
|
||||||
return SourcePosition.createFromLine(defaultPsiFile, 0)
|
return SourcePosition.createFromLine(defaultPsiFile, 0)
|
||||||
}
|
}
|
||||||
@@ -218,7 +225,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
|
|
||||||
val project = myDebugProcess.project
|
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 {
|
private fun defaultInternalName(location: Location): String {
|
||||||
@@ -450,7 +457,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
val functionName = function.readAction { it.name }
|
val functionName = function.readAction { it.name }
|
||||||
|
|
||||||
val task = Runnable {
|
val task = Runnable {
|
||||||
ReferencesSearch.search(function, myDebugProcess.searchScope).forEach {
|
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
|
||||||
if (!it.readAction { it.isImportUsage() }) {
|
if (!it.readAction { it.isImportUsage() }) {
|
||||||
val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
|
val usage = (it.element as? KtElement)?.let { getElementToCalculateClassName(it) }
|
||||||
if (usage != null) {
|
if (usage != null) {
|
||||||
@@ -527,7 +534,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
val result = hashSetOf<String>()
|
val result = hashSetOf<String>()
|
||||||
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
|
val inlineFunction = parameter.containingDeclaration.source.getPsi() as? KtNamedFunction ?: return emptySet()
|
||||||
|
|
||||||
ReferencesSearch.search(inlineFunction, myDebugProcess.searchScope).forEach {
|
ReferencesSearch.search(inlineFunction, getScopeForInlineFunctionUsages(inlineFunction)).forEach {
|
||||||
runReadAction {
|
runReadAction {
|
||||||
if (!it.isImportUsage()) {
|
if (!it.isImportUsage()) {
|
||||||
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
|
val call = (it.element as? KtExpression)?.let { KtPsiUtil.getParentCallIfPresent(it) }
|
||||||
@@ -548,6 +555,16 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
return result
|
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 getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||||
|
|
||||||
private fun String.substringIndex(): String {
|
private fun String.substringIndex(): String {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
val internalName = fullyQualifiedName.replace('.', '/')
|
val internalName = fullyQualifiedName.replace('.', '/')
|
||||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
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
|
val virtualFile = file.virtualFile ?: return null
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
val newJvmName = JvmClassName.byInternalName(mappingInfo.path)
|
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)
|
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")
|
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(
|
fun findSourceFileForClass(
|
||||||
project: Project,
|
project: Project,
|
||||||
searchScope: GlobalSearchScope,
|
scopes: List<GlobalSearchScope>,
|
||||||
className: JvmClassName,
|
className: JvmClassName,
|
||||||
fileName: String): KtFile? {
|
fileName: String): KtFile? {
|
||||||
val extension = FileUtilRt.getExtension(fileName)
|
val extension = FileUtilRt.getExtension(fileName)
|
||||||
if (!KOTLIN_EXTENSIONS.contains(extension)) return null
|
if (!KOTLIN_EXTENSIONS.contains(extension)) return null
|
||||||
if (DumbService.getInstance(project).isDumb) return null
|
if (DumbService.getInstance(project).isDumb) return null
|
||||||
|
|
||||||
val filesWithExactName = findFilesByNameInPackage(className, fileName, project, searchScope).check { it.isNotEmpty() }
|
val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null
|
||||||
// Source files for libraries aren't included into ModuleWithDependencies scope
|
|
||||||
?: findFilesByNameInPackage(
|
|
||||||
className, fileName, project,
|
|
||||||
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(project), project))
|
|
||||||
|
|
||||||
if (filesWithExactName.isEmpty()) return null
|
if (filesWithExactName.isEmpty()) return null
|
||||||
|
|
||||||
@@ -65,7 +73,7 @@ object DebuggerUtils {
|
|||||||
|
|
||||||
// Static facade or inner class of such facade?
|
// Static facade or inner class of such facade?
|
||||||
val partFqName = className.fqNameForClassNameWithoutDollars
|
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()) {
|
if (!filesForPart.isEmpty()) {
|
||||||
for (file in filesForPart) {
|
for (file in filesForPart) {
|
||||||
if (file.name == fileName) {
|
if (file.name == fileName) {
|
||||||
@@ -79,6 +87,15 @@ object DebuggerUtils {
|
|||||||
return filesWithExactName.first()
|
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)
|
private fun findFilesByNameInPackage(className: JvmClassName, fileName: String, project: Project, searchScope: GlobalSearchScope)
|
||||||
= findFilesWithExactPackage(className.packageFqName, searchScope, project).filter { it.name == fileName }
|
= findFilesWithExactPackage(className.packageFqName, searchScope, project).filter { it.name == fileName }
|
||||||
|
|
||||||
|
|||||||
+8
@@ -2,4 +2,12 @@ package customLib.inlineFunInLibrary
|
|||||||
|
|
||||||
public inline fun inlineFun(f: () -> Unit) {
|
public inline fun inlineFun(f: () -> Unit) {
|
||||||
1 + 1
|
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: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
|
!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'
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
inlineFunInLibrary.kt:4
|
inlineFunInLibrary.kt:4
|
||||||
|
inlineFunInLibrary.kt:12
|
||||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
Process finished with exit code 0
|
Process finished with exit code 0
|
||||||
|
|||||||
+4
-1
@@ -6,4 +6,7 @@ fun main(args: Array<String>) {
|
|||||||
inlineFun { }
|
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
|
||||||
Reference in New Issue
Block a user