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:
Nikolay Krasko
2016-11-28 20:39:45 +03:00
parent 21fba92c93
commit 730e561177
7 changed files with 90 additions and 17 deletions
@@ -22,6 +22,8 @@ import org.jetbrains.annotations.NotNull;
public class KotlinFileTypeFactory extends FileTypeFactory {
public final static String[] KOTLIN_EXTENSIONS = new String[] { "kt", "kts" };
@Override
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
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.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
@@ -39,9 +40,6 @@ import org.jetbrains.kotlin.serialization.deserialization.descriptors.Deserializ
import java.util.*
object DebuggerUtils {
private val KOTLIN_EXTENSIONS = Sets.newHashSet("kt", "kts")
fun findSourceFileForClassIncludeLibrarySources(
project: Project,
scope: GlobalSearchScope,
@@ -62,7 +60,7 @@ object DebuggerUtils {
className: JvmClassName,
fileName: String): KtFile? {
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
val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null
@@ -16,15 +16,17 @@
package org.jetbrains.kotlin.idea.filters
import com.intellij.execution.filters.ExceptionFilter
import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.HyperlinkInfo
import com.intellij.execution.filters.OpenFileHyperlinkInfo
import com.intellij.execution.filters.*
import com.intellij.execution.filters.impl.HyperlinkInfoFactoryImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.util.*
import java.util.regex.Pattern
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 {
val newHyperlinkInfo = createHyperlinkInfo(line) ?: return result
val newHyperlinkInfo = createHyperlinkInfo(line, result) ?: return result
return Filter.Result(result.resultItems.map {
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 element = parseStackTraceLine(line) ?: return null
val stackTraceElement = parseStackTraceLine(line) ?: return null
// All true classes should be handled correctly in the default ExceptionFilter. Special cases:
// - static facades;
// - package facades / package parts (generated by pre-M13 compiled);
// - 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"
val fullyQualifiedName = element.className
val lineNumber = element.lineNumber - 1
val fullyQualifiedName = stackTraceElement.className
val lineNumber = stackTraceElement.lineNumber - 1
val internalName = fullyQualifiedName.replace('.', '/')
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
@@ -0,0 +1,13 @@
package inlineFunSameFile
import inlineFunPackage.*
fun box() {
foo {
println()
}
}
// SMAP_APPLIED
// FILE: inlineFunctionFile.kt
// LINE: 4
@@ -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 val MOCK_LIBRARY_SOURCES = PluginTestCaseBase.getTestDataPathBase() + "/debugger/mockLibraryForExceptionFilter"
abstract class AbstractKotlinExceptionFilterTest: KotlinCodeInsightTestCase() {
abstract class AbstractKotlinExceptionFilterTest : KotlinCodeInsightTestCase() {
override fun getTestDataPath() = ""
protected fun doTest(path: String) {
@@ -101,7 +101,22 @@ abstract class AbstractKotlinExceptionFilterTest: KotlinCodeInsightTestCase() {
val filter = KotlinExceptionFilterFactory().create(GlobalSearchScope.allScope(project))
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 descriptor = info.descriptor!!
@@ -66,6 +66,12 @@ public class KotlinExceptionFilterTestGenerated extends AbstractKotlinExceptionF
doTest(fileName);
}
@TestMetadata("inlineFunctionAnotherFileWithSmapApplied")
public void testInlineFunctionAnotherFileWithSmapApplied() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionAnotherFileWithSmapApplied/");
doTest(fileName);
}
@TestMetadata("inlineFunctionInnerClass")
public void testInlineFunctionInnerClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/exceptionFilter/inlineFunctionInnerClass/");