diff --git a/.idea/dictionaries/Nikolay_Krasko.xml b/.idea/dictionaries/Nikolay_Krasko.xml index 2995eb1ba67..abace9e7952 100644 --- a/.idea/dictionaries/Nikolay_Krasko.xml +++ b/.idea/dictionaries/Nikolay_Krasko.xml @@ -16,6 +16,7 @@ preloading preprocess redeclarations + smap subclassed subgraph substep diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt index d9f8cd23ac8..172c7832867 100644 --- a/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt @@ -20,34 +20,29 @@ 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.openapi.compiler.CompilerPaths import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ProjectFileIndex import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.search.GlobalSearchScope -import org.jetbrains.kotlin.codegen.inline.FileMapping -import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil -import org.jetbrains.kotlin.codegen.inline.SMAP -import org.jetbrains.kotlin.codegen.inline.SMAPParser -import org.jetbrains.kotlin.idea.refactoring.getLineCount -import org.jetbrains.kotlin.idea.refactoring.toPsiFile import org.jetbrains.kotlin.idea.util.DebuggerUtils -import org.jetbrains.kotlin.idea.util.ProjectRootsUtil -import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder -import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.name.tail -import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.jvm.JvmClassName -import org.jetbrains.kotlin.utils.addToStdlib.check -import org.jetbrains.org.objectweb.asm.ClassReader -import org.jetbrains.org.objectweb.asm.ClassVisitor -import java.io.File import java.util.regex.Pattern class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter { private val exceptionFilter = ExceptionFilter(searchScope) + override fun applyFilter(line: String, entireLength: Int): Filter.Result? { + val result = exceptionFilter.applyFilter(line, entireLength) + return if (result == null) null else patchResult(result, line) + } + + private fun patchResult(result: Filter.Result, line: String): Filter.Result { + val newHyperlinkInfo = createHyperlinkInfo(line) ?: return result + + return Filter.Result(result.resultItems.map { + Filter.ResultItem(it.getHighlightStartOffset(), it.getHighlightEndOffset(), newHyperlinkInfo, it.getHighlightAttributes()) + }) + } + private fun createHyperlinkInfo(line: String): HyperlinkInfo? { val project = searchScope.project ?: return null @@ -69,7 +64,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter val virtualFile = file.virtualFile ?: return null - val hyperlinkInfoForInline = getHyperlinkInfoIfInline(jvmClassName, virtualFile, lineNumber + 1, project) + val hyperlinkInfoForInline = createHyperlinks(jvmClassName, virtualFile, lineNumber + 1, project) if (hyperlinkInfoForInline != null) { return hyperlinkInfoForInline } @@ -77,47 +72,30 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter return OpenFileHyperlinkInfo(project, virtualFile, lineNumber) } - private fun getHyperlinkInfoIfInline(jvmName: JvmClassName, file: VirtualFile, lineNumber: Int, project: Project): InlineFunctionHyperLinkInfo? { - val bytes = readClassFile(jvmName, file, project, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null - return readDebugInfoForInlineFun(bytes, lineNumber, project) - } - - private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): InlineFunctionHyperLinkInfo? { + private fun createHyperlinks(jvmName: JvmClassName, file: VirtualFile, line: Int, project: Project): InlineFunctionHyperLinkInfo? { + val bytes = readClassFile(project, jvmName, file, + sourceFileFilter = { sourceFile -> isInlineFunctionLineNumber(sourceFile, line, project) }) ?: return null val smapData = readDebugInfo(bytes) ?: return null - val inlineInfo = arrayListOf() + val inlineInfos = arrayListOf() - val (inlineFunctionBodyFile, inlineFunctionBodyLine) = parseStrata(smapData.kotlin1, line, project, false, searchScope) ?: return null - inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo( + val (inlineFunctionBodyFile, inlineFunctionBodyLine) = + mapStacktraceLineToSource(smapData, line, project, SourceLineKind.EXECUTED_LINE, searchScope) ?: return null + + inlineInfos.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo( inlineFunctionBodyFile.virtualFile, inlineFunctionBodyLine)) - val kotlin2 = parseStrata(smapData.kotlin2, line, project, true, searchScope) - if (kotlin2 != null) { - inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo( - kotlin2.first.virtualFile, - kotlin2.second)) + val inlineFunCallInfo = mapStacktraceLineToSource(smapData, line, project, SourceLineKind.CALL_LINE, searchScope) + if (inlineFunCallInfo != null) { + val (callSiteFile, callSiteLine) = inlineFunCallInfo + inlineInfos.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo(callSiteFile.virtualFile, callSiteLine)) } - return InlineFunctionHyperLinkInfo(project, inlineInfo) - } - - private fun patchResult(result: Filter.Result, line: String): Filter.Result { - val newHyperlinkInfo = createHyperlinkInfo(line) ?: return result - - return Filter.Result(result.resultItems.map { - Filter.ResultItem(it.getHighlightStartOffset(), it.getHighlightEndOffset(), newHyperlinkInfo, it.getHighlightAttributes()) - }) - - } - - override fun applyFilter(line: String, entireLength: Int): Filter.Result? { - val result = exceptionFilter.applyFilter(line, entireLength) - return if (result == null) null else patchResult(result, line) + return InlineFunctionHyperLinkInfo(project, inlineInfos) } companion object { - // Matches strings like "\tat test.TestPackage$foo$f$1.invoke(a.kt:3)\n" // or "\tBreakpoint reached at test.TestPackage$foo$f$1.invoke(a.kt:3)\n" private val STACK_TRACE_ELEMENT_PATTERN = Pattern.compile("^[\\w|\\s]*at\\s+(.+)\\.(.+)\\((.+):(\\d+)\\)\\s*$") diff --git a/idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt b/idea/src/org/jetbrains/kotlin/idea/filters/smapUtil.kt similarity index 68% rename from idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt rename to idea/src/org/jetbrains/kotlin/idea/filters/smapUtil.kt index 0a94b8446f7..34fa2cb7a9c 100644 --- a/idea/src/org/jetbrains/kotlin/idea/filters/InlineSmapPositionUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/filters/smapUtil.kt @@ -45,11 +45,15 @@ fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Proj return lineNumber > linesInFile } -fun readClassFile(jvmName: JvmClassName, file: VirtualFile, project: Project, sourceCondition: (VirtualFile) -> Boolean = { true }): ByteArray? { +fun readClassFile(project: Project, + jvmName: JvmClassName, + file: VirtualFile, + sourceFileFilter: (VirtualFile) -> Boolean = { true }, + libFileFilter: (VirtualFile) -> Boolean = { true }): ByteArray? { val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName) when { - ProjectRootsUtil.isLibrarySourceFile(project, file) -> { + ProjectRootsUtil.isLibrarySourceFile(project, file) && libFileFilter(file) -> { val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString())) val fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project) @@ -57,7 +61,7 @@ fun readClassFile(jvmName: JvmClassName, file: VirtualFile, project: Project, so return classFile.contentsToByteArray() } - ProjectRootsUtil.isProjectSourceFile(project, file) && sourceCondition(file) -> { + ProjectRootsUtil.isProjectSourceFile(project, file) && sourceFileFilter(file) -> { val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file) val outputDir = CompilerPaths.getModuleOutputDirectory(module, /*forTests = */ false) ?: return null @@ -72,7 +76,7 @@ fun readClassFile(jvmName: JvmClassName, file: VirtualFile, project: Project, so } private fun findClassFileByPath(packageName: String, className: String, outputDir: VirtualFile): File? { - val outDirFile = File(outputDir.path).check { it.exists() } ?: return null + val outDirFile = File(outputDir.path).check(File::exists) ?: return null val parentDirectory = File(outDirFile, packageName.replace(".", File.separator)) if (!parentDirectory.exists()) return null @@ -85,20 +89,36 @@ private fun findClassFileByPath(packageName: String, className: String, outputDi return null } -fun parseStrata(strata: String?, line: Int, project: Project, isKotlin2: Boolean, searchScope: GlobalSearchScope): Pair? { - if (strata == null) return null +enum class SourceLineKind { + CALL_LINE, + EXECUTED_LINE +} - val smap = SMAPParser.parse(strata) +fun mapStacktraceLineToSource(smapData: SmapData, + line: Int, + project: Project, + lineKind: SourceLineKind, + searchScope: GlobalSearchScope): Pair? { + val smap = when (lineKind) { + SourceLineKind.CALL_LINE -> smapData.kotlinDebugStrata + SourceLineKind.EXECUTED_LINE -> smapData.kotlinStrata + } ?: return null val mappingInfo = smap.fileMappings.firstOrNull { it.getIntervalIfContains(line) != null } ?: return null - val newJvmName = JvmClassName.byInternalName(mappingInfo.path) - val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null + val jvmName = JvmClassName.byInternalName(mappingInfo.path) + val sourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources( + project, searchScope, jvmName, mappingInfo.name) ?: return null val interval = mappingInfo.getIntervalIfContains(line)!! - return newSourceFile to (if (isKotlin2) interval.source else interval.mapDestToSource(line)) - 1 + val sourceLine = when (lineKind) { + SourceLineKind.CALL_LINE -> interval.source - 1 + SourceLineKind.EXECUTED_LINE -> interval.mapDestToSource(line) - 1 + } + + return sourceFile to sourceLine } fun readDebugInfo(bytes: ByteArray): SmapData? { @@ -109,24 +129,27 @@ fun readDebugInfo(bytes: ByteArray): SmapData? { debugInfo = debug } }, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE) - return debugInfo?.let { SmapData(it) } + return debugInfo?.let(::SmapData) } class SmapData(debugInfo: String) { - var kotlin1: String? = null - private set - var kotlin2: String? = null - private set + var kotlinStrata: SMAP? + var kotlinDebugStrata: SMAP? init { - val intervals = debugInfo.split(SMAP.END).filter { it.isNotBlank() } + val intervals = debugInfo.split(SMAP.END).filter(String::isNotBlank) when(intervals.count()) { 1 -> { - kotlin1 = intervals[0] + SMAP.END + kotlinStrata = SMAPParser.parse(intervals[0] + SMAP.END) + kotlinDebugStrata = null + } + 2 -> { + kotlinStrata = SMAPParser.parse(intervals[0] + SMAP.END) + kotlinDebugStrata = SMAPParser.parse(intervals[1] + SMAP.END) } else -> { - kotlin1 = intervals[0] + SMAP.END - kotlin2 = intervals[1] + SMAP.END + kotlinStrata = null + kotlinDebugStrata = null } } }