Refactoring: additional changes in smap functions and KotlinExceptionFilter
This commit is contained in:
Generated
+1
@@ -16,6 +16,7 @@
|
||||
<w>preloading</w>
|
||||
<w>preprocess</w>
|
||||
<w>redeclarations</w>
|
||||
<w>smap</w>
|
||||
<w>subclassed</w>
|
||||
<w>subgraph</w>
|
||||
<w>substep</w>
|
||||
|
||||
@@ -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<InlineFunctionHyperLinkInfo.InlineInfo>()
|
||||
val inlineInfos = arrayListOf<InlineFunctionHyperLinkInfo.InlineInfo>()
|
||||
|
||||
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*$")
|
||||
|
||||
+42
-19
@@ -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<KtFile, Int>? {
|
||||
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<KtFile, Int>? {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user