Navigation to inline functions doesn't work for thread dump captured using "Get thread dump" (KT-14916)
#KT-14916 Fixed
This commit is contained in:
@@ -22,6 +22,8 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
|
|
||||||
public class KotlinFileTypeFactory extends FileTypeFactory {
|
public class KotlinFileTypeFactory extends FileTypeFactory {
|
||||||
|
public final static String[] KOTLIN_EXTENSIONS = new String[] { "kt", "kts" };
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
|
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
|
||||||
consumer.consume(KotlinFileType.INSTANCE, "kt;kts");
|
consumer.consume(KotlinFileType.INSTANCE, "kt;kts");
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.openapi.util.io.FileUtilRt
|
import com.intellij.openapi.util.io.FileUtilRt
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
@@ -39,9 +40,6 @@ import org.jetbrains.kotlin.serialization.deserialization.descriptors.Deserializ
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object DebuggerUtils {
|
object DebuggerUtils {
|
||||||
|
|
||||||
private val KOTLIN_EXTENSIONS = Sets.newHashSet("kt", "kts")
|
|
||||||
|
|
||||||
fun findSourceFileForClassIncludeLibrarySources(
|
fun findSourceFileForClassIncludeLibrarySources(
|
||||||
project: Project,
|
project: Project,
|
||||||
scope: GlobalSearchScope,
|
scope: GlobalSearchScope,
|
||||||
@@ -62,7 +60,7 @@ object DebuggerUtils {
|
|||||||
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 (extension !in KotlinFileTypeFactory.KOTLIN_EXTENSIONS) return null
|
||||||
if (DumbService.getInstance(project).isDumb) return null
|
if (DumbService.getInstance(project).isDumb) return null
|
||||||
|
|
||||||
val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null
|
val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null
|
||||||
|
|||||||
@@ -16,15 +16,17 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.filters
|
package org.jetbrains.kotlin.idea.filters
|
||||||
|
|
||||||
import com.intellij.execution.filters.ExceptionFilter
|
import com.intellij.execution.filters.*
|
||||||
import com.intellij.execution.filters.Filter
|
import com.intellij.execution.filters.impl.HyperlinkInfoFactoryImpl
|
||||||
import com.intellij.execution.filters.HyperlinkInfo
|
|
||||||
import com.intellij.execution.filters.OpenFileHyperlinkInfo
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.io.FileUtilRt
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.psi.search.FilenameIndex
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
|
||||||
import org.jetbrains.kotlin.idea.debugger.*
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
|
import java.util.*
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter {
|
class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter {
|
||||||
@@ -36,31 +38,62 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun patchResult(result: Filter.Result, line: String): Filter.Result {
|
private fun patchResult(result: Filter.Result, line: String): Filter.Result {
|
||||||
val newHyperlinkInfo = createHyperlinkInfo(line) ?: return result
|
val newHyperlinkInfo = createHyperlinkInfo(line, result) ?: return result
|
||||||
|
|
||||||
return Filter.Result(result.resultItems.map {
|
return Filter.Result(result.resultItems.map {
|
||||||
Filter.ResultItem(it.getHighlightStartOffset(), it.getHighlightEndOffset(), newHyperlinkInfo, it.getHighlightAttributes())
|
Filter.ResultItem(it.getHighlightStartOffset(), it.getHighlightEndOffset(), newHyperlinkInfo, it.getHighlightAttributes())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createHyperlinkInfo(line: String): HyperlinkInfo? {
|
private fun createHyperlinkInfo(line: String, defaultResult: Filter.Result): HyperlinkInfo? {
|
||||||
val project = searchScope.project ?: return null
|
val project = searchScope.project ?: return null
|
||||||
|
|
||||||
val element = parseStackTraceLine(line) ?: return null
|
val stackTraceElement = parseStackTraceLine(line) ?: return null
|
||||||
|
|
||||||
// All true classes should be handled correctly in the default ExceptionFilter. Special cases:
|
// All true classes should be handled correctly in the default ExceptionFilter. Special cases:
|
||||||
// - static facades;
|
// - static facades;
|
||||||
// - package facades / package parts (generated by pre-M13 compiled);
|
// - package facades / package parts (generated by pre-M13 compiled);
|
||||||
// - local classes (and closures) in top-level function and property declarations.
|
// - local classes (and closures) in top-level function and property declarations.
|
||||||
val fileName = element.fileName
|
// - bad line numbers for inline functions
|
||||||
|
// - already applied smap for inline functions
|
||||||
|
|
||||||
|
val fileName = stackTraceElement.fileName
|
||||||
|
|
||||||
|
val extension = FileUtilRt.getExtension(fileName).toLowerCase()
|
||||||
|
if (extension !in KotlinFileTypeFactory.KOTLIN_EXTENSIONS) return null
|
||||||
|
|
||||||
// fullyQualifiedName is of format "package.Class$Inner"
|
// fullyQualifiedName is of format "package.Class$Inner"
|
||||||
val fullyQualifiedName = element.className
|
val fullyQualifiedName = stackTraceElement.className
|
||||||
val lineNumber = element.lineNumber - 1
|
val lineNumber = stackTraceElement.lineNumber - 1
|
||||||
|
|
||||||
val internalName = fullyQualifiedName.replace('.', '/')
|
val internalName = fullyQualifiedName.replace('.', '/')
|
||||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
val jvmClassName = JvmClassName.byInternalName(internalName)
|
||||||
|
|
||||||
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null
|
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName)
|
||||||
|
|
||||||
|
if (file == null) {
|
||||||
|
// File can't be found by class name and file name this can happen when smap info is already applied.
|
||||||
|
// Default filter favours looking for file from class name and that can lead to wrong navigation to inline fun call file and
|
||||||
|
// line from inline function definition.
|
||||||
|
val defaultLinkFileNames = defaultResult.resultItems.mapNotNullTo(HashSet()) { (it as? FileHyperlinkInfo)?.descriptor?.file?.name }
|
||||||
|
if (!defaultLinkFileNames.contains(fileName)) {
|
||||||
|
val filesByName = FilenameIndex.getFilesByName(project, fileName, searchScope).mapNotNullTo(HashSet()) {
|
||||||
|
if (!it.isValid) return@mapNotNullTo null
|
||||||
|
it.virtualFile
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filesByName.isNotEmpty()) {
|
||||||
|
if (filesByName.size > 1) {
|
||||||
|
return HyperlinkInfoFactoryImpl.getInstance().createMultipleFilesHyperlinkInfo(filesByName.toList(), lineNumber, project)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return OpenFileHyperlinkInfo(project, filesByName.first(), lineNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
val virtualFile = file.virtualFile ?: return null
|
val virtualFile = file.virtualFile ?: return null
|
||||||
|
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package inlineFunSameFile
|
||||||
|
|
||||||
|
import inlineFunPackage.*
|
||||||
|
|
||||||
|
fun box() {
|
||||||
|
foo {
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMAP_APPLIED
|
||||||
|
// FILE: inlineFunctionFile.kt
|
||||||
|
// LINE: 4
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package inlineFunPackage
|
||||||
|
|
||||||
|
inline fun foo(f: () -> Unit) {
|
||||||
|
null!!
|
||||||
|
f()
|
||||||
|
}
|
||||||
@@ -42,7 +42,7 @@ import java.net.URLClassLoader
|
|||||||
private var MOCK_LIBRARY_JAR: File? = null
|
private var MOCK_LIBRARY_JAR: File? = null
|
||||||
private val MOCK_LIBRARY_SOURCES = PluginTestCaseBase.getTestDataPathBase() + "/debugger/mockLibraryForExceptionFilter"
|
private val MOCK_LIBRARY_SOURCES = PluginTestCaseBase.getTestDataPathBase() + "/debugger/mockLibraryForExceptionFilter"
|
||||||
|
|
||||||
abstract class AbstractKotlinExceptionFilterTest: KotlinCodeInsightTestCase() {
|
abstract class AbstractKotlinExceptionFilterTest : KotlinCodeInsightTestCase() {
|
||||||
override fun getTestDataPath() = ""
|
override fun getTestDataPath() = ""
|
||||||
|
|
||||||
protected fun doTest(path: String) {
|
protected fun doTest(path: String) {
|
||||||
@@ -101,7 +101,22 @@ abstract class AbstractKotlinExceptionFilterTest: KotlinCodeInsightTestCase() {
|
|||||||
|
|
||||||
val filter = KotlinExceptionFilterFactory().create(GlobalSearchScope.allScope(project))
|
val filter = KotlinExceptionFilterFactory().create(GlobalSearchScope.allScope(project))
|
||||||
val prefix = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// PREFIX: ") ?: "at"
|
val prefix = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// PREFIX: ") ?: "at"
|
||||||
val result = filter.applyFilter("$prefix $stackTraceElement", 0) ?: throw AssertionError("Couldn't apply filter to $stackTraceElement")
|
val stackTraceString = stackTraceElement.toString()
|
||||||
|
var result = filter.applyFilter("$prefix $stackTraceString", 0) ?: throw AssertionError("Couldn't apply filter to $stackTraceElement")
|
||||||
|
|
||||||
|
if (InTextDirectivesUtils.isDirectiveDefined(fileText, "SMAP_APPLIED")) {
|
||||||
|
val fileHyperlinkInfo = result.firstHyperlinkInfo as FileHyperlinkInfo
|
||||||
|
val descriptor = fileHyperlinkInfo.descriptor!!
|
||||||
|
|
||||||
|
val file = descriptor.file
|
||||||
|
val line = descriptor.line + 1
|
||||||
|
|
||||||
|
val newStackString = stackTraceString
|
||||||
|
.replace(mainFile.name, file.name)
|
||||||
|
.replace(Regex("\\:\\d+\\)"), ":$line)")
|
||||||
|
|
||||||
|
result = filter.applyFilter("$prefix $newStackString", 0) ?: throw AssertionError("Couldn't apply filter to $stackTraceElement")
|
||||||
|
}
|
||||||
|
|
||||||
val info = result.firstHyperlinkInfo as FileHyperlinkInfo
|
val info = result.firstHyperlinkInfo as FileHyperlinkInfo
|
||||||
val descriptor = info.descriptor!!
|
val descriptor = info.descriptor!!
|
||||||
|
|||||||
@@ -66,6 +66,12 @@ public class KotlinExceptionFilterTestGenerated extends AbstractKotlinExceptionF
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("inlineFunctionAnotherFileWithSmapApplied")
|
||||||
|
public void testInlineFunctionAnotherFileWithSmapApplied() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("inlineFunctionInnerClass")
|
@TestMetadata("inlineFunctionInnerClass")
|
||||||
public void testInlineFunctionInnerClass() throws Exception {
|
public void testInlineFunctionInnerClass() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionInnerClass/");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionInnerClass/");
|
||||||
|
|||||||
Reference in New Issue
Block a user