JVM: parse KotlinDebug when regenerating anonymous objects
This commit is contained in:
+2
-4
@@ -114,12 +114,10 @@ class AnonymousObjectTransformer(
|
||||
override fun visitEnd() {}
|
||||
}, ClassReader.SKIP_FRAMES)
|
||||
|
||||
// When regenerating objects in lambdas, we have to pass the SMAP straight through and keep
|
||||
// the original line numbers because SMAPParser does not parse call site markers.
|
||||
// When regenerating objects in inline lambdas, keep the old SMAP and don't remap the line numbers to
|
||||
// save time. The result is effectively the same anyway.
|
||||
val debugInfoToParse = if (inliningContext.isInliningLambda) null else debugInfo
|
||||
sourceMap = SMAPParser.parseOrCreateDefault(debugInfoToParse, sourceInfo, oldObjectType.internalName, 1, 65535)
|
||||
// TODO source info should be for the file into which the function is being inlined, else we cannot
|
||||
// generate correct call site markers for lambdas inlined into the object
|
||||
sourceMapper = SourceMapper(sourceMap.sourceInfo.takeIf { debugInfoToParse?.isEmpty() == false })
|
||||
|
||||
val allCapturedParamBuilder = ParametersBuilder.newBuilder()
|
||||
|
||||
@@ -208,7 +208,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
||||
|
||||
protected fun inlineCall(nodeAndSmap: SMAPAndMethodNode, inlineDefaultLambda: Boolean, isCallOfFunctionInCorrespondingDefaultDispatch: Boolean): InlineResult {
|
||||
assert(delayedHiddenWriting == null) { "'putHiddenParamsIntoLocals' should be called after 'processAndPutHiddenParameters(true)'" }
|
||||
if (!isCallOfFunctionInCorrespondingDefaultDispatch) defaultSourceMapper.callSiteMarker = CallSiteMarker(codegen.lastLineNumber)
|
||||
if (!isCallOfFunctionInCorrespondingDefaultDispatch) defaultSourceMapper.callSiteMarker = codegen.lastLineNumber
|
||||
val node = nodeAndSmap.node
|
||||
if (inlineDefaultLambda) {
|
||||
for (lambda in extractDefaultLambdas(node)) {
|
||||
|
||||
@@ -21,46 +21,41 @@ object SMAPBuilder {
|
||||
if (realMappings.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
val defaultStrata = generateDefaultStrata(realMappings)
|
||||
val debugStrata = generateDebugStrata(realMappings)
|
||||
|
||||
val debugMappings = linkedMapOf<Pair<String, String>, FileMapping>()
|
||||
for (fileMapping in realMappings) {
|
||||
for ((_, dest, range, callSite) in fileMapping.lineMappings) {
|
||||
callSite?.let { (line, file, path) ->
|
||||
debugMappings.getOrPut(file to path) { FileMapping(file, path) }.mapNewInterval(line, dest, range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Old versions of kotlinc and the IDEA plugin have incorrect implementations of SMAPParser:
|
||||
// 1. they require *E between strata, which is not correct syntax according to JSR-045;
|
||||
// 2. in KotlinDebug, they use `1#2,3:4` to mean "map lines 4..6 to line 1 of #2", when in reality (and in
|
||||
// the non-debug stratum) this maps lines 4..6 to lines 1..3. The correct syntax is `1#2:4,3`.
|
||||
val defaultStrata = realMappings.toSMAP(KOTLIN_STRATA_NAME, mapToFirstLine = false)
|
||||
val debugStrata = debugMappings.values.toSMAP(KOTLIN_DEBUG_STRATA_NAME, mapToFirstLine = !backwardsCompatibleSyntax)
|
||||
if (backwardsCompatibleSyntax && defaultStrata.isNotEmpty() && debugStrata.isNotEmpty()) {
|
||||
// Old versions of kotlinc might fail if there is no END between defaultStrata and debugStrata.
|
||||
// This is not actually correct syntax according to JSR-045.
|
||||
return "SMAP\n${fileMappings[0].name}\n$KOTLIN_STRATA_NAME\n$defaultStrata${SMAP.END}\n$debugStrata${SMAP.END}\n"
|
||||
}
|
||||
return "SMAP\n${fileMappings[0].name}\n$KOTLIN_STRATA_NAME\n$defaultStrata$debugStrata${SMAP.END}\n"
|
||||
}
|
||||
|
||||
private fun generateDefaultStrata(realMappings: List<FileMapping>): String {
|
||||
val fileData = realMappings.mapIndexed { id, file -> file.toSMAPFile(id + 1) }.joinToString("")
|
||||
val lineData = realMappings.mapIndexed { id, file -> file.toSMAPMapping(id + 1) }.joinToString("")
|
||||
return "${SMAP.STRATA_SECTION} $KOTLIN_STRATA_NAME\n${SMAP.FILE_SECTION}\n$fileData${SMAP.LINE_SECTION}\n$lineData"
|
||||
}
|
||||
private fun Collection<FileMapping>.toSMAP(stratumName: String, mapToFirstLine: Boolean): String = if (isEmpty()) "" else
|
||||
"${SMAP.STRATA_SECTION} $stratumName\n" +
|
||||
"${SMAP.FILE_SECTION}\n${mapIndexed { id, file -> file.toSMAPFile(id + 1) }.joinToString("")}" +
|
||||
"${SMAP.LINE_SECTION}\n${mapIndexed { id, file -> file.toSMAPMapping(id + 1, mapToFirstLine) }.joinToString("")}"
|
||||
|
||||
private fun generateDebugStrata(realMappings: List<FileMapping>): String {
|
||||
val combinedMapping = FileMapping(realMappings[0].name, realMappings[0].path)
|
||||
for (fileMapping in realMappings) {
|
||||
for ((_, dest, range, callSiteMarker) in fileMapping.lineMappings) {
|
||||
callSiteMarker?.let { combinedMapping.mapNewInterval(it.lineNumber, dest, range) }
|
||||
}
|
||||
}
|
||||
|
||||
if (combinedMapping.lineMappings.isEmpty()) return ""
|
||||
val fileData = combinedMapping.toSMAPFile(1)
|
||||
// TODO: this generates entries like `1#2,3:4` which means "map lines 4..6 to lines 1..3 of file #2".
|
||||
// What we want is `1#2:4,3`, i.e. "map lines 4..6 to line 1 of #2", but currently IDEA cannot handle that.
|
||||
val lineData = combinedMapping.toSMAPMapping(1)
|
||||
return "${SMAP.STRATA_SECTION} $KOTLIN_DEBUG_STRATA_NAME\n${SMAP.FILE_SECTION}\n$fileData${SMAP.LINE_SECTION}\n$lineData"
|
||||
}
|
||||
|
||||
private fun RangeMapping.toSMAP(fileId: Int): String =
|
||||
if (range == 1) "$source#$fileId:$dest\n" else "$source#$fileId,$range:$dest\n"
|
||||
private fun RangeMapping.toSMAP(fileId: Int, oneLine: Boolean): String =
|
||||
if (range == 1) "$source#$fileId:$dest\n" else if (oneLine) "$source#$fileId:$dest,$range\n" else "$source#$fileId,$range:$dest\n"
|
||||
|
||||
private fun FileMapping.toSMAPFile(id: Int): String =
|
||||
"+ $id $name\n$path\n"
|
||||
|
||||
private fun FileMapping.toSMAPMapping(id: Int): String =
|
||||
lineMappings.joinToString("") { it.toSMAP(id) }
|
||||
private fun FileMapping.toSMAPMapping(id: Int, mapToFirstLine: Boolean): String =
|
||||
lineMappings.joinToString("") { it.toSMAP(id, mapToFirstLine) }
|
||||
}
|
||||
|
||||
class SourceMapCopier(val parent: SourceMapper, private val smap: SMAP, private val keepCallSites: Boolean = false) {
|
||||
@@ -80,25 +75,27 @@ class SourceMapCopier(val parent: SourceMapper, private val smap: SMAP, private
|
||||
val range = lastVisitedRange?.takeIf { lineNumber in it }
|
||||
?: smap.findRange(lineNumber)
|
||||
?: error("Can't find range to map line $lineNumber in ${smap.sourceInfo.source}: ${smap.sourceInfo.pathOrCleanFQN}")
|
||||
val sourceLineNumber = range.mapDestToSource(lineNumber)
|
||||
if (sourceLineNumber < 0) {
|
||||
val inlineSource = range.mapDestToSource(lineNumber)
|
||||
if (inlineSource.line < 0) {
|
||||
return -1
|
||||
}
|
||||
val callSiteMarker = if (keepCallSites) range.callSiteMarker else parent.callSiteMarker
|
||||
val newLineNumber = parent.mapLineNumber(sourceLineNumber, range.parent.name, range.parent.path, callSiteMarker)
|
||||
val inlineCallSite = if (keepCallSites) range.callSite else parent.callSiteMarker?.let {
|
||||
SourcePosition(it, parent.sourceInfo!!.source, parent.sourceInfo.pathOrCleanFQN)
|
||||
}
|
||||
val newLineNumber = parent.mapLineNumber(inlineSource, inlineCallSite)
|
||||
visitedLines.put(lineNumber, newLineNumber)
|
||||
lastVisitedRange = range
|
||||
return newLineNumber
|
||||
}
|
||||
}
|
||||
|
||||
data class CallSiteMarker(val lineNumber: Int)
|
||||
data class SourcePosition(val line: Int, val file: String, val path: String)
|
||||
|
||||
class SourceMapper(val sourceInfo: SourceInfo?) {
|
||||
private var maxUsedValue: Int = sourceInfo?.linesInFile ?: 0
|
||||
private var fileMappings: LinkedHashMap<Pair<String, String>, FileMapping> = linkedMapOf()
|
||||
|
||||
var callSiteMarker: CallSiteMarker? = null
|
||||
var callSiteMarker: Int? = null
|
||||
|
||||
val resultMappings: List<FileMapping>
|
||||
get() = fileMappings.values.toList()
|
||||
@@ -113,9 +110,9 @@ class SourceMapper(val sourceInfo: SourceInfo?) {
|
||||
private fun getOrRegisterNewSource(name: String, path: String): FileMapping =
|
||||
fileMappings.getOrPut(name to path) { FileMapping(name, path) }
|
||||
|
||||
fun mapLineNumber(source: Int, sourceName: String, sourcePath: String, callSiteMarker: CallSiteMarker?): Int {
|
||||
val fileMapping = getOrRegisterNewSource(sourceName, sourcePath)
|
||||
val mappedLineIndex = fileMapping.mapNewLineNumber(source, maxUsedValue, callSiteMarker)
|
||||
fun mapLineNumber(inlineSource: SourcePosition, inlineCallSite: SourcePosition?): Int {
|
||||
val fileMapping = getOrRegisterNewSource(inlineSource.file, inlineSource.path)
|
||||
val mappedLineIndex = fileMapping.mapNewLineNumber(inlineSource.line, maxUsedValue, inlineCallSite)
|
||||
maxUsedValue = max(maxUsedValue, mappedLineIndex)
|
||||
return mappedLineIndex
|
||||
}
|
||||
@@ -149,22 +146,22 @@ data class SMAPAndMethodNode(val node: MethodNode, val classSMAP: SMAP)
|
||||
class FileMapping(val name: String, val path: String) {
|
||||
val lineMappings = arrayListOf<RangeMapping>()
|
||||
|
||||
fun mapNewLineNumber(source: Int, currentIndex: Int, callSiteMarker: CallSiteMarker?): Int {
|
||||
fun mapNewLineNumber(source: Int, currentIndex: Int, callSite: SourcePosition?): Int {
|
||||
// Save some space in the SMAP by reusing (or extending if it's the last one) the existing range.
|
||||
// TODO some *other* range may already cover `source`; probably too slow to check them all though.
|
||||
// Maybe keep the list ordered by `source` and use binary search to locate the closest range on the left?
|
||||
val mapping = lineMappings.lastOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSiteMarker) }
|
||||
?: lineMappings.firstOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSiteMarker) }
|
||||
?: mapNewInterval(source, currentIndex + 1, 1, callSiteMarker)
|
||||
val mapping = lineMappings.lastOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSite) }
|
||||
?: lineMappings.firstOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSite) }
|
||||
?: mapNewInterval(source, currentIndex + 1, 1, callSite)
|
||||
mapping.range = max(mapping.range, source - mapping.source + 1)
|
||||
return mapping.mapSourceToDest(source)
|
||||
}
|
||||
|
||||
private fun RangeMapping.canReuseFor(newSource: Int, globalMaxDest: Int, newCallSite: CallSiteMarker?): Boolean =
|
||||
callSiteMarker == newCallSite && (newSource - source) in 0 until range + (if (maxDest == globalMaxDest) 10 else 0)
|
||||
private fun RangeMapping.canReuseFor(newSource: Int, globalMaxDest: Int, newCallSite: SourcePosition?): Boolean =
|
||||
callSite == newCallSite && (newSource - source) in 0 until range + (if (globalMaxDest in this) 10 else 0)
|
||||
|
||||
fun mapNewInterval(source: Int, dest: Int, range: Int, callSiteMarker: CallSiteMarker? = null): RangeMapping =
|
||||
RangeMapping(source, dest, range, callSiteMarker, parent = this).also { lineMappings.add(it) }
|
||||
fun mapNewInterval(source: Int, dest: Int, range: Int, callSite: SourcePosition? = null): RangeMapping =
|
||||
RangeMapping(source, dest, range, callSite, parent = this).also { lineMappings.add(it) }
|
||||
|
||||
companion object {
|
||||
val SKIP = FileMapping("no-source-info", "no-source-info").apply {
|
||||
@@ -173,31 +170,21 @@ class FileMapping(val name: String, val path: String) {
|
||||
}
|
||||
}
|
||||
|
||||
data class RangeMapping(
|
||||
val source: Int, val dest: Int, var range: Int, val callSiteMarker: CallSiteMarker?,
|
||||
val parent: FileMapping
|
||||
) {
|
||||
data class RangeMapping(val source: Int, val dest: Int, var range: Int, val callSite: SourcePosition?, val parent: FileMapping) {
|
||||
private val skip = source == -1 && dest == -1
|
||||
|
||||
val maxDest: Int
|
||||
get() = dest + range - 1
|
||||
operator fun contains(destLine: Int): Boolean =
|
||||
skip || (dest <= destLine && destLine < dest + range)
|
||||
|
||||
operator fun contains(destLine: Int): Boolean {
|
||||
return skip || (dest <= destLine && destLine < dest + range)
|
||||
}
|
||||
fun hasMappingForSource(sourceLine: Int): Boolean =
|
||||
skip || (source <= sourceLine && sourceLine < source + range)
|
||||
|
||||
fun hasMappingForSource(sourceLine: Int): Boolean {
|
||||
return skip || (source <= sourceLine && sourceLine < source + range)
|
||||
}
|
||||
fun mapDestToSource(destLine: Int): SourcePosition =
|
||||
SourcePosition(if (skip) -1 else source + (destLine - dest), parent.name, parent.path)
|
||||
|
||||
fun mapDestToSource(destLine: Int): Int {
|
||||
return if (skip) -1 else source + (destLine - dest)
|
||||
}
|
||||
|
||||
fun mapSourceToDest(sourceLine: Int): Int {
|
||||
return if (skip) -1 else dest + (sourceLine - source)
|
||||
}
|
||||
fun mapSourceToDest(sourceLine: Int): Int =
|
||||
if (skip) -1 else dest + (sourceLine - source)
|
||||
}
|
||||
|
||||
val RangeMapping.toRange: IntRange
|
||||
get() = this.dest..this.maxDest
|
||||
get() = dest until dest + range
|
||||
|
||||
@@ -21,7 +21,7 @@ object SMAPParser {
|
||||
@JvmStatic
|
||||
fun parseOrCreateDefault(mappingInfo: String?, source: String?, path: String, methodStartLine: Int, methodEndLine: Int): SMAP {
|
||||
if (mappingInfo != null && mappingInfo.isNotEmpty()) {
|
||||
return parse(mappingInfo)
|
||||
parseOrNull(mappingInfo)?.let { return it }
|
||||
}
|
||||
if (source == null || source.isEmpty() || methodStartLine > methodEndLine) {
|
||||
return SMAP(listOf(FileMapping.SKIP))
|
||||
@@ -32,43 +32,53 @@ object SMAPParser {
|
||||
return SMAP(listOf(mapping))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parse(mappingInfo: String): SMAP {
|
||||
fun parseOrNull(mappingInfo: String): SMAP? =
|
||||
parseStratum(mappingInfo, KOTLIN_STRATA_NAME, parseStratum(mappingInfo, KOTLIN_DEBUG_STRATA_NAME, null))
|
||||
|
||||
private fun parseStratum(mappingInfo: String, stratum: String, callSites: SMAP?): SMAP? {
|
||||
val fileMappings = linkedMapOf<Int, FileMapping>()
|
||||
val iterator = mappingInfo.lineSequence().dropWhile { it != "${SMAP.STRATA_SECTION} $stratum" }.drop(1).iterator()
|
||||
// JSR-045 allows the line section to come before the file section, but we don't generate SMAPs like this.
|
||||
if (!iterator.hasNext() || iterator.next() != SMAP.FILE_SECTION) return null
|
||||
|
||||
// Assuming we want the first stratum (which should be "Kotlin" for Kotlin classes, though you never know).
|
||||
// Also, JSR-045 allows the line section to come before the file section, but we don't generate SMAPs like this.
|
||||
val iterator = mappingInfo.lineSequence().dropWhile { it.trim() != SMAP.FILE_SECTION }.drop(1).iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val fileDeclaration = iterator.next().trim()
|
||||
if (fileDeclaration == SMAP.LINE_SECTION) break
|
||||
|
||||
if (!fileDeclaration.startsWith('+')) {
|
||||
throw AssertionError("File declaration should be in extended form, but: $fileDeclaration in $mappingInfo")
|
||||
for (line in iterator) {
|
||||
when {
|
||||
line == SMAP.LINE_SECTION -> break
|
||||
line == SMAP.FILE_SECTION || line == SMAP.END || line.startsWith(SMAP.STRATA_SECTION) -> return null
|
||||
}
|
||||
|
||||
val indexAndFileInternalName = fileDeclaration.substringAfter("+ ").trim()
|
||||
val indexAndFileInternalName = if (line.startsWith("+ ")) line.substring(2) else line
|
||||
val fileIndex = indexAndFileInternalName.substringBefore(' ').toInt()
|
||||
val fileName = indexAndFileInternalName.substringAfter(' ')
|
||||
val path = iterator.next().trim()
|
||||
val path = if (line.startsWith("+ ")) iterator.next() else fileName
|
||||
fileMappings[fileIndex] = FileMapping(fileName, path)
|
||||
}
|
||||
|
||||
for (lineMapping in iterator) {
|
||||
// The stratum is terminated either by *E or another stratum.
|
||||
if (lineMapping.trim().startsWith("*")) break
|
||||
/*only simple mapping now*/
|
||||
val targetSplit = lineMapping.indexOf(':')
|
||||
val originalPart = lineMapping.substring(0, targetSplit)
|
||||
val rangeSeparator = originalPart.indexOf(',').let { if (it < 0) targetSplit else it }
|
||||
for (line in iterator) {
|
||||
when {
|
||||
line == SMAP.LINE_SECTION || line == SMAP.FILE_SECTION -> return null
|
||||
line == SMAP.END || line.startsWith(SMAP.STRATA_SECTION) -> break
|
||||
}
|
||||
|
||||
val fileSeparator = lineMapping.indexOf('#')
|
||||
val originalIndex = originalPart.substring(0, fileSeparator).toInt()
|
||||
val range = if (rangeSeparator == targetSplit) 1 else originalPart.substring(rangeSeparator + 1, targetSplit).toInt()
|
||||
// <source>#<file>,<sourceRange>:<dest>,<destMultiplier>
|
||||
val fileSeparator = line.indexOf('#')
|
||||
if (fileSeparator < 0) return null
|
||||
val destSeparator = line.indexOf(':', fileSeparator)
|
||||
if (destSeparator < 0) return null
|
||||
val sourceRangeSeparator = line.indexOf(',').let { if (it !in fileSeparator..destSeparator) destSeparator else it }
|
||||
val destMultiplierSeparator = line.indexOf(',', destSeparator).let { if (it < 0) line.length else it }
|
||||
|
||||
val fileIndex = lineMapping.substring(fileSeparator + 1, rangeSeparator).toInt()
|
||||
val targetIndex = lineMapping.substring(targetSplit + 1).toInt()
|
||||
fileMappings[fileIndex]!!.mapNewInterval(originalIndex, targetIndex, range)
|
||||
val file = fileMappings[line.substring(fileSeparator + 1, sourceRangeSeparator).toInt()] ?: return null
|
||||
val source = line.substring(0, fileSeparator).toInt()
|
||||
val dest = line.substring(destSeparator + 1, destMultiplierSeparator).toInt()
|
||||
val range = when {
|
||||
// These two fields have a different meaning, but for compatibility we treat them the same. See `SMAPBuilder`.
|
||||
destMultiplierSeparator != line.length -> line.substring(destMultiplierSeparator + 1).toInt()
|
||||
sourceRangeSeparator != destSeparator -> line.substring(sourceRangeSeparator + 1, destSeparator).toInt()
|
||||
else -> 1
|
||||
}
|
||||
// Here we assume that each range in `Kotlin` is entirely within at most one range in `KotlinDebug`.
|
||||
file.mapNewInterval(source, dest, range, callSites?.findRange(dest)?.let { it.mapDestToSource(it.dest) })
|
||||
}
|
||||
|
||||
return SMAP(fileMappings.values.toList())
|
||||
|
||||
+2
-7
@@ -1,7 +1,7 @@
|
||||
//FILE: 1.kt
|
||||
|
||||
|
||||
package test
|
||||
|
||||
|
||||
inline fun annotatedWith2(crossinline predicate: () -> Boolean) =
|
||||
{ any { predicate() } }()
|
||||
|
||||
@@ -33,11 +33,6 @@ inline fun test(z: () -> Unit) {
|
||||
|
||||
|
||||
// FILE: 2.smap
|
||||
//*L
|
||||
//1#1,15:1
|
||||
//17#1:19
|
||||
|
||||
|
||||
SMAP
|
||||
2.kt
|
||||
Kotlin
|
||||
|
||||
@@ -58,5 +58,5 @@ test/_1Kt
|
||||
_2Kt
|
||||
*L
|
||||
5#1:9
|
||||
5#1,6:10
|
||||
5#1:10,6
|
||||
*E
|
||||
|
||||
@@ -95,7 +95,7 @@ object SMAPTestUtil {
|
||||
if (compiledSmap == null) return
|
||||
|
||||
compiledSmap.mapNotNull(SMAPAndFile::smap).forEach { smapString ->
|
||||
val smap = SMAPParser.parse(smapString)
|
||||
val smap = SMAPParser.parseOrNull(smapString) ?: throw AssertionError("bad SMAP: $smapString")
|
||||
val conflictingLines = smap.fileMappings.flatMap { fileMapping ->
|
||||
fileMapping.lineMappings.flatMap { lineMapping: RangeMapping ->
|
||||
lineMapping.toRange.keysToMap { lineMapping }.entries
|
||||
|
||||
+3
-4
@@ -20,6 +20,7 @@ import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.VirtualMachine
|
||||
import org.jetbrains.kotlin.codegen.inline.SMAP
|
||||
import org.jetbrains.kotlin.idea.caches.project.implementingModules
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineCount
|
||||
@@ -74,7 +75,7 @@ fun createWeakBytecodeDebugInfoStorage(): ConcurrentMap<BinaryCacheKey, Bytecode
|
||||
}
|
||||
}
|
||||
|
||||
class BytecodeDebugInfo(val smapData: SmapData?, val lineTableMapping: Map<BytecodeMethodKey, Map<String, Set<Int>>>)
|
||||
class BytecodeDebugInfo(val smapData: SMAP?, val lineTableMapping: Map<BytecodeMethodKey, Map<String, Set<Int>>>)
|
||||
|
||||
data class BytecodeMethodKey(val methodName: String, val signature: String)
|
||||
|
||||
@@ -259,9 +260,7 @@ private fun inlinedLinesNumbers(
|
||||
val debugInfo = readBytecodeInfo(project, jvmClassName, virtualFile) ?: return listOf()
|
||||
val smapData = debugInfo.smapData ?: return listOf()
|
||||
|
||||
val smap = smapData.kotlinStrata ?: return listOf()
|
||||
|
||||
val mappingsToInlinedFile = smap.fileMappings.filter { it.name == inlineFileName }
|
||||
val mappingsToInlinedFile = smapData.fileMappings.filter { it.name == inlineFileName }
|
||||
val mappingIntervals = mappingsToInlinedFile.flatMap { it.lineMappings }
|
||||
|
||||
return mappingIntervals.asSequence().filter { rangeMapping -> rangeMapping.hasMappingForSource(inlineLineNumber) }
|
||||
|
||||
+11
-47
@@ -7,9 +7,7 @@ package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.codegen.inline.FileMapping
|
||||
import org.jetbrains.kotlin.codegen.inline.SMAP
|
||||
import org.jetbrains.kotlin.codegen.inline.SMAPParser
|
||||
import org.jetbrains.kotlin.codegen.inline.*
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
@@ -22,36 +20,27 @@ enum class SourceLineKind {
|
||||
}
|
||||
|
||||
fun mapStacktraceLineToSource(
|
||||
smapData: SmapData,
|
||||
smapData: SMAP,
|
||||
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
|
||||
val interval = smapData.findRange(line) ?: return null
|
||||
val location = when (lineKind) {
|
||||
SourceLineKind.CALL_LINE -> interval.callSite
|
||||
SourceLineKind.EXECUTED_LINE -> interval.mapDestToSource(line)
|
||||
} ?: return null
|
||||
|
||||
val mappingInfo = smap.fileMappings.firstOrNull {
|
||||
it.getIntervalIfContains(line) != null
|
||||
} ?: return null
|
||||
|
||||
val jvmName = JvmClassName.byInternalName(mappingInfo.path)
|
||||
val jvmName = JvmClassName.byInternalName(location.path)
|
||||
val sourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(
|
||||
project, searchScope, jvmName, mappingInfo.name
|
||||
project, searchScope, jvmName, location.file
|
||||
) ?: return null
|
||||
|
||||
val interval = mappingInfo.getIntervalIfContains(line)!!
|
||||
val sourceLine = when (lineKind) {
|
||||
SourceLineKind.CALL_LINE -> interval.source - 1
|
||||
SourceLineKind.EXECUTED_LINE -> interval.mapDestToSource(line) - 1
|
||||
}
|
||||
|
||||
return sourceFile to sourceLine
|
||||
return sourceFile to location.line - 1
|
||||
}
|
||||
|
||||
fun readDebugInfo(bytes: ByteArray): SmapData? {
|
||||
fun readDebugInfo(bytes: ByteArray): SMAP? {
|
||||
val cr = ClassReader(bytes)
|
||||
var debugInfo: String? = null
|
||||
cr.accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
@@ -59,30 +48,5 @@ fun readDebugInfo(bytes: ByteArray): SmapData? {
|
||||
debugInfo = debug
|
||||
}
|
||||
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
|
||||
return debugInfo?.let(::SmapData)
|
||||
return debugInfo?.let(SMAPParser::parseOrNull)
|
||||
}
|
||||
|
||||
class SmapData(debugInfo: String) {
|
||||
var kotlinStrata: SMAP?
|
||||
var kotlinDebugStrata: SMAP?
|
||||
|
||||
init {
|
||||
val intervals = debugInfo.split(SMAP.END).filter(String::isNotBlank)
|
||||
when (intervals.count()) {
|
||||
1 -> {
|
||||
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 -> {
|
||||
kotlinStrata = null
|
||||
kotlinDebugStrata = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FileMapping.getIntervalIfContains(destLine: Int) = lineMappings.firstOrNull { it.contains(destLine) }
|
||||
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
inline fun foo(crossinline x: () -> Unit) = object {
|
||||
fun run() {
|
||||
barNonThrowing()
|
||||
x()
|
||||
}
|
||||
}.run()
|
||||
|
||||
inline fun barNonThrowing() {
|
||||
null
|
||||
}
|
||||
|
||||
inline fun bar() {
|
||||
null!!
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fun box() {
|
||||
foo {
|
||||
bar()
|
||||
}
|
||||
}
|
||||
|
||||
// NAVIGATE_TO_CALL_SITE
|
||||
// FILE: inlineFunCallSiteInLambdaInlinedIntoObject.kt
|
||||
// LINE: 3
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
inline fun foo(crossinline x: () -> Unit) = object {
|
||||
fun run() {
|
||||
bar()
|
||||
x()
|
||||
}
|
||||
}.run()
|
||||
|
||||
inline fun barNonThrowing() {
|
||||
null
|
||||
}
|
||||
|
||||
inline fun bar() {
|
||||
null!!
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
fun box() {
|
||||
foo {
|
||||
barNonThrowing()
|
||||
}
|
||||
}
|
||||
|
||||
// NAVIGATE_TO_CALL_SITE
|
||||
// FILE: inlineFun.kt
|
||||
// LINE: 3
|
||||
+10
@@ -48,6 +48,16 @@ public class KotlinExceptionFilterTestGenerated extends AbstractKotlinExceptionF
|
||||
runTest("idea/testData/debugger/exceptionFilter/inlineFunCallSiteInInlineLambda/");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineFunCallSiteInLambdaInlinedIntoObject")
|
||||
public void testInlineFunCallSiteInLambdaInlinedIntoObject() throws Exception {
|
||||
runTest("idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineFunCallSiteInRegeneratedObject")
|
||||
public void testInlineFunCallSiteInRegeneratedObject() throws Exception {
|
||||
runTest("idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/");
|
||||
}
|
||||
|
||||
@TestMetadata("inlineFunFromLibrary")
|
||||
public void testInlineFunFromLibrary() throws Exception {
|
||||
runTest("idea/testData/debugger/exceptionFilter/inlineFunFromLibrary/");
|
||||
|
||||
Reference in New Issue
Block a user