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>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*$")
@@ -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
}
}
}