Refactoring: additional changes in smap functions and KotlinExceptionFilter

This commit is contained in:
Nikolay Krasko
2016-07-26 15:22:53 +03:00
parent f71e24403f
commit 071285fad7
3 changed files with 70 additions and 68 deletions
+1
View File
@@ -16,6 +16,7 @@
<w>preloading</w> <w>preloading</w>
<w>preprocess</w> <w>preprocess</w>
<w>redeclarations</w> <w>redeclarations</w>
<w>smap</w>
<w>subclassed</w> <w>subclassed</w>
<w>subgraph</w> <w>subgraph</w>
<w>substep</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.Filter
import com.intellij.execution.filters.HyperlinkInfo import com.intellij.execution.filters.HyperlinkInfo
import com.intellij.execution.filters.OpenFileHyperlinkInfo import com.intellij.execution.filters.OpenFileHyperlinkInfo
import com.intellij.openapi.compiler.CompilerPaths
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope 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.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.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 import java.util.regex.Pattern
class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter { class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter {
private val exceptionFilter = ExceptionFilter(searchScope) 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? { private fun createHyperlinkInfo(line: String): HyperlinkInfo? {
val project = searchScope.project ?: return null val project = searchScope.project ?: return null
@@ -69,7 +64,7 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
val virtualFile = file.virtualFile ?: return null 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) { if (hyperlinkInfoForInline != null) {
return hyperlinkInfoForInline return hyperlinkInfoForInline
} }
@@ -77,47 +72,30 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
return OpenFileHyperlinkInfo(project, virtualFile, lineNumber) return OpenFileHyperlinkInfo(project, virtualFile, lineNumber)
} }
private fun getHyperlinkInfoIfInline(jvmName: JvmClassName, file: VirtualFile, lineNumber: Int, project: Project): InlineFunctionHyperLinkInfo? { private fun createHyperlinks(jvmName: JvmClassName, file: VirtualFile, line: Int, project: Project): InlineFunctionHyperLinkInfo? {
val bytes = readClassFile(jvmName, file, project, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null val bytes = readClassFile(project, jvmName, file,
return readDebugInfoForInlineFun(bytes, lineNumber, project) sourceFileFilter = { sourceFile -> isInlineFunctionLineNumber(sourceFile, line, project) }) ?: return null
}
private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): InlineFunctionHyperLinkInfo? {
val smapData = readDebugInfo(bytes) ?: 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 val (inlineFunctionBodyFile, inlineFunctionBodyLine) =
inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo( mapStacktraceLineToSource(smapData, line, project, SourceLineKind.EXECUTED_LINE, searchScope) ?: return null
inlineInfos.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo(
inlineFunctionBodyFile.virtualFile, inlineFunctionBodyFile.virtualFile,
inlineFunctionBodyLine)) inlineFunctionBodyLine))
val kotlin2 = parseStrata(smapData.kotlin2, line, project, true, searchScope) val inlineFunCallInfo = mapStacktraceLineToSource(smapData, line, project, SourceLineKind.CALL_LINE, searchScope)
if (kotlin2 != null) { if (inlineFunCallInfo != null) {
inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo( val (callSiteFile, callSiteLine) = inlineFunCallInfo
kotlin2.first.virtualFile, inlineInfos.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo(callSiteFile.virtualFile, callSiteLine))
kotlin2.second))
} }
return InlineFunctionHyperLinkInfo(project, inlineInfo) return InlineFunctionHyperLinkInfo(project, inlineInfos)
}
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)
} }
companion object { companion object {
// Matches strings like "\tat test.TestPackage$foo$f$1.invoke(a.kt:3)\n" // 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" // 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*$") private val STACK_TRACE_ELEMENT_PATTERN = Pattern.compile("^[\\w|\\s]*at\\s+(.+)\\.(.+)\\((.+):(\\d+)\\)\\s*$")
@@ -45,11 +45,15 @@ fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Proj
return lineNumber > linesInFile 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) val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
when { when {
ProjectRootsUtil.isLibrarySourceFile(project, file) -> { ProjectRootsUtil.isLibrarySourceFile(project, file) && libFileFilter(file) -> {
val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString())) val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString()))
val fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project) val fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project)
@@ -57,7 +61,7 @@ fun readClassFile(jvmName: JvmClassName, file: VirtualFile, project: Project, so
return classFile.contentsToByteArray() return classFile.contentsToByteArray()
} }
ProjectRootsUtil.isProjectSourceFile(project, file) && sourceCondition(file) -> { ProjectRootsUtil.isProjectSourceFile(project, file) && sourceFileFilter(file) -> {
val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file) val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file)
val outputDir = CompilerPaths.getModuleOutputDirectory(module, /*forTests = */ false) ?: return null 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? { 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)) val parentDirectory = File(outDirFile, packageName.replace(".", File.separator))
if (!parentDirectory.exists()) return null if (!parentDirectory.exists()) return null
@@ -85,20 +89,36 @@ private fun findClassFileByPath(packageName: String, className: String, outputDi
return null return null
} }
fun parseStrata(strata: String?, line: Int, project: Project, isKotlin2: Boolean, searchScope: GlobalSearchScope): Pair<KtFile, Int>? { enum class SourceLineKind {
if (strata == null) return null 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 { val mappingInfo = smap.fileMappings.firstOrNull {
it.getIntervalIfContains(line) != null it.getIntervalIfContains(line) != null
} ?: return null } ?: return null
val newJvmName = JvmClassName.byInternalName(mappingInfo.path) val jvmName = JvmClassName.byInternalName(mappingInfo.path)
val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null val sourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(
project, searchScope, jvmName, mappingInfo.name) ?: return null
val interval = mappingInfo.getIntervalIfContains(line)!! 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? { fun readDebugInfo(bytes: ByteArray): SmapData? {
@@ -109,24 +129,27 @@ fun readDebugInfo(bytes: ByteArray): SmapData? {
debugInfo = debug debugInfo = debug
} }
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE) }, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
return debugInfo?.let { SmapData(it) } return debugInfo?.let(::SmapData)
} }
class SmapData(debugInfo: String) { class SmapData(debugInfo: String) {
var kotlin1: String? = null var kotlinStrata: SMAP?
private set var kotlinDebugStrata: SMAP?
var kotlin2: String? = null
private set
init { init {
val intervals = debugInfo.split(SMAP.END).filter { it.isNotBlank() } val intervals = debugInfo.split(SMAP.END).filter(String::isNotBlank)
when(intervals.count()) { when(intervals.count()) {
1 -> { 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 -> { else -> {
kotlin1 = intervals[0] + SMAP.END kotlinStrata = null
kotlin2 = intervals[1] + SMAP.END kotlinDebugStrata = null
} }
} }
} }