JVM: parse KotlinDebug when regenerating anonymous objects
This commit is contained in:
+2
-4
@@ -114,12 +114,10 @@ class AnonymousObjectTransformer(
|
|||||||
override fun visitEnd() {}
|
override fun visitEnd() {}
|
||||||
}, ClassReader.SKIP_FRAMES)
|
}, ClassReader.SKIP_FRAMES)
|
||||||
|
|
||||||
// When regenerating objects in lambdas, we have to pass the SMAP straight through and keep
|
// When regenerating objects in inline lambdas, keep the old SMAP and don't remap the line numbers to
|
||||||
// the original line numbers because SMAPParser does not parse call site markers.
|
// save time. The result is effectively the same anyway.
|
||||||
val debugInfoToParse = if (inliningContext.isInliningLambda) null else debugInfo
|
val debugInfoToParse = if (inliningContext.isInliningLambda) null else debugInfo
|
||||||
sourceMap = SMAPParser.parseOrCreateDefault(debugInfoToParse, sourceInfo, oldObjectType.internalName, 1, 65535)
|
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 })
|
sourceMapper = SourceMapper(sourceMap.sourceInfo.takeIf { debugInfoToParse?.isEmpty() == false })
|
||||||
|
|
||||||
val allCapturedParamBuilder = ParametersBuilder.newBuilder()
|
val allCapturedParamBuilder = ParametersBuilder.newBuilder()
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
|
|||||||
|
|
||||||
protected fun inlineCall(nodeAndSmap: SMAPAndMethodNode, inlineDefaultLambda: Boolean, isCallOfFunctionInCorrespondingDefaultDispatch: Boolean): InlineResult {
|
protected fun inlineCall(nodeAndSmap: SMAPAndMethodNode, inlineDefaultLambda: Boolean, isCallOfFunctionInCorrespondingDefaultDispatch: Boolean): InlineResult {
|
||||||
assert(delayedHiddenWriting == null) { "'putHiddenParamsIntoLocals' should be called after 'processAndPutHiddenParameters(true)'" }
|
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
|
val node = nodeAndSmap.node
|
||||||
if (inlineDefaultLambda) {
|
if (inlineDefaultLambda) {
|
||||||
for (lambda in extractDefaultLambdas(node)) {
|
for (lambda in extractDefaultLambdas(node)) {
|
||||||
|
|||||||
@@ -21,46 +21,41 @@ object SMAPBuilder {
|
|||||||
if (realMappings.isEmpty()) {
|
if (realMappings.isEmpty()) {
|
||||||
return null
|
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()) {
|
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${SMAP.END}\n$debugStrata${SMAP.END}\n"
|
||||||
}
|
}
|
||||||
return "SMAP\n${fileMappings[0].name}\n$KOTLIN_STRATA_NAME\n$defaultStrata$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 {
|
private fun Collection<FileMapping>.toSMAP(stratumName: String, mapToFirstLine: Boolean): String = if (isEmpty()) "" else
|
||||||
val fileData = realMappings.mapIndexed { id, file -> file.toSMAPFile(id + 1) }.joinToString("")
|
"${SMAP.STRATA_SECTION} $stratumName\n" +
|
||||||
val lineData = realMappings.mapIndexed { id, file -> file.toSMAPMapping(id + 1) }.joinToString("")
|
"${SMAP.FILE_SECTION}\n${mapIndexed { id, file -> file.toSMAPFile(id + 1) }.joinToString("")}" +
|
||||||
return "${SMAP.STRATA_SECTION} $KOTLIN_STRATA_NAME\n${SMAP.FILE_SECTION}\n$fileData${SMAP.LINE_SECTION}\n$lineData"
|
"${SMAP.LINE_SECTION}\n${mapIndexed { id, file -> file.toSMAPMapping(id + 1, mapToFirstLine) }.joinToString("")}"
|
||||||
}
|
|
||||||
|
|
||||||
private fun generateDebugStrata(realMappings: List<FileMapping>): String {
|
private fun RangeMapping.toSMAP(fileId: Int, oneLine: Boolean): String =
|
||||||
val combinedMapping = FileMapping(realMappings[0].name, realMappings[0].path)
|
if (range == 1) "$source#$fileId:$dest\n" else if (oneLine) "$source#$fileId:$dest,$range\n" else "$source#$fileId,$range:$dest\n"
|
||||||
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 FileMapping.toSMAPFile(id: Int): String =
|
private fun FileMapping.toSMAPFile(id: Int): String =
|
||||||
"+ $id $name\n$path\n"
|
"+ $id $name\n$path\n"
|
||||||
|
|
||||||
private fun FileMapping.toSMAPMapping(id: Int): String =
|
private fun FileMapping.toSMAPMapping(id: Int, mapToFirstLine: Boolean): String =
|
||||||
lineMappings.joinToString("") { it.toSMAP(id) }
|
lineMappings.joinToString("") { it.toSMAP(id, mapToFirstLine) }
|
||||||
}
|
}
|
||||||
|
|
||||||
class SourceMapCopier(val parent: SourceMapper, private val smap: SMAP, private val keepCallSites: Boolean = false) {
|
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 }
|
val range = lastVisitedRange?.takeIf { lineNumber in it }
|
||||||
?: smap.findRange(lineNumber)
|
?: smap.findRange(lineNumber)
|
||||||
?: error("Can't find range to map line $lineNumber in ${smap.sourceInfo.source}: ${smap.sourceInfo.pathOrCleanFQN}")
|
?: error("Can't find range to map line $lineNumber in ${smap.sourceInfo.source}: ${smap.sourceInfo.pathOrCleanFQN}")
|
||||||
val sourceLineNumber = range.mapDestToSource(lineNumber)
|
val inlineSource = range.mapDestToSource(lineNumber)
|
||||||
if (sourceLineNumber < 0) {
|
if (inlineSource.line < 0) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
val callSiteMarker = if (keepCallSites) range.callSiteMarker else parent.callSiteMarker
|
val inlineCallSite = if (keepCallSites) range.callSite else parent.callSiteMarker?.let {
|
||||||
val newLineNumber = parent.mapLineNumber(sourceLineNumber, range.parent.name, range.parent.path, callSiteMarker)
|
SourcePosition(it, parent.sourceInfo!!.source, parent.sourceInfo.pathOrCleanFQN)
|
||||||
|
}
|
||||||
|
val newLineNumber = parent.mapLineNumber(inlineSource, inlineCallSite)
|
||||||
visitedLines.put(lineNumber, newLineNumber)
|
visitedLines.put(lineNumber, newLineNumber)
|
||||||
lastVisitedRange = range
|
lastVisitedRange = range
|
||||||
return newLineNumber
|
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?) {
|
class SourceMapper(val sourceInfo: SourceInfo?) {
|
||||||
private var maxUsedValue: Int = sourceInfo?.linesInFile ?: 0
|
private var maxUsedValue: Int = sourceInfo?.linesInFile ?: 0
|
||||||
private var fileMappings: LinkedHashMap<Pair<String, String>, FileMapping> = linkedMapOf()
|
private var fileMappings: LinkedHashMap<Pair<String, String>, FileMapping> = linkedMapOf()
|
||||||
|
|
||||||
var callSiteMarker: CallSiteMarker? = null
|
var callSiteMarker: Int? = null
|
||||||
|
|
||||||
val resultMappings: List<FileMapping>
|
val resultMappings: List<FileMapping>
|
||||||
get() = fileMappings.values.toList()
|
get() = fileMappings.values.toList()
|
||||||
@@ -113,9 +110,9 @@ class SourceMapper(val sourceInfo: SourceInfo?) {
|
|||||||
private fun getOrRegisterNewSource(name: String, path: String): FileMapping =
|
private fun getOrRegisterNewSource(name: String, path: String): FileMapping =
|
||||||
fileMappings.getOrPut(name to path) { FileMapping(name, path) }
|
fileMappings.getOrPut(name to path) { FileMapping(name, path) }
|
||||||
|
|
||||||
fun mapLineNumber(source: Int, sourceName: String, sourcePath: String, callSiteMarker: CallSiteMarker?): Int {
|
fun mapLineNumber(inlineSource: SourcePosition, inlineCallSite: SourcePosition?): Int {
|
||||||
val fileMapping = getOrRegisterNewSource(sourceName, sourcePath)
|
val fileMapping = getOrRegisterNewSource(inlineSource.file, inlineSource.path)
|
||||||
val mappedLineIndex = fileMapping.mapNewLineNumber(source, maxUsedValue, callSiteMarker)
|
val mappedLineIndex = fileMapping.mapNewLineNumber(inlineSource.line, maxUsedValue, inlineCallSite)
|
||||||
maxUsedValue = max(maxUsedValue, mappedLineIndex)
|
maxUsedValue = max(maxUsedValue, mappedLineIndex)
|
||||||
return mappedLineIndex
|
return mappedLineIndex
|
||||||
}
|
}
|
||||||
@@ -149,22 +146,22 @@ data class SMAPAndMethodNode(val node: MethodNode, val classSMAP: SMAP)
|
|||||||
class FileMapping(val name: String, val path: String) {
|
class FileMapping(val name: String, val path: String) {
|
||||||
val lineMappings = arrayListOf<RangeMapping>()
|
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.
|
// 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.
|
// 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?
|
// 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) }
|
val mapping = lineMappings.lastOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSite) }
|
||||||
?: lineMappings.firstOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSiteMarker) }
|
?: lineMappings.firstOrNull()?.takeIf { it.canReuseFor(source, currentIndex, callSite) }
|
||||||
?: mapNewInterval(source, currentIndex + 1, 1, callSiteMarker)
|
?: mapNewInterval(source, currentIndex + 1, 1, callSite)
|
||||||
mapping.range = max(mapping.range, source - mapping.source + 1)
|
mapping.range = max(mapping.range, source - mapping.source + 1)
|
||||||
return mapping.mapSourceToDest(source)
|
return mapping.mapSourceToDest(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RangeMapping.canReuseFor(newSource: Int, globalMaxDest: Int, newCallSite: CallSiteMarker?): Boolean =
|
private fun RangeMapping.canReuseFor(newSource: Int, globalMaxDest: Int, newCallSite: SourcePosition?): Boolean =
|
||||||
callSiteMarker == newCallSite && (newSource - source) in 0 until range + (if (maxDest == globalMaxDest) 10 else 0)
|
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 =
|
fun mapNewInterval(source: Int, dest: Int, range: Int, callSite: SourcePosition? = null): RangeMapping =
|
||||||
RangeMapping(source, dest, range, callSiteMarker, parent = this).also { lineMappings.add(it) }
|
RangeMapping(source, dest, range, callSite, parent = this).also { lineMappings.add(it) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SKIP = FileMapping("no-source-info", "no-source-info").apply {
|
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(
|
data class RangeMapping(val source: Int, val dest: Int, var range: Int, val callSite: SourcePosition?, val parent: FileMapping) {
|
||||||
val source: Int, val dest: Int, var range: Int, val callSiteMarker: CallSiteMarker?,
|
|
||||||
val parent: FileMapping
|
|
||||||
) {
|
|
||||||
private val skip = source == -1 && dest == -1
|
private val skip = source == -1 && dest == -1
|
||||||
|
|
||||||
val maxDest: Int
|
operator fun contains(destLine: Int): Boolean =
|
||||||
get() = dest + range - 1
|
skip || (dest <= destLine && destLine < dest + range)
|
||||||
|
|
||||||
operator fun contains(destLine: Int): Boolean {
|
fun hasMappingForSource(sourceLine: Int): Boolean =
|
||||||
return skip || (dest <= destLine && destLine < dest + range)
|
skip || (source <= sourceLine && sourceLine < source + range)
|
||||||
}
|
|
||||||
|
|
||||||
fun hasMappingForSource(sourceLine: Int): Boolean {
|
fun mapDestToSource(destLine: Int): SourcePosition =
|
||||||
return skip || (source <= sourceLine && sourceLine < source + range)
|
SourcePosition(if (skip) -1 else source + (destLine - dest), parent.name, parent.path)
|
||||||
}
|
|
||||||
|
|
||||||
fun mapDestToSource(destLine: Int): Int {
|
fun mapSourceToDest(sourceLine: Int): Int =
|
||||||
return if (skip) -1 else source + (destLine - dest)
|
if (skip) -1 else dest + (sourceLine - source)
|
||||||
}
|
|
||||||
|
|
||||||
fun mapSourceToDest(sourceLine: Int): Int {
|
|
||||||
return if (skip) -1 else dest + (sourceLine - source)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val RangeMapping.toRange: IntRange
|
val RangeMapping.toRange: IntRange
|
||||||
get() = this.dest..this.maxDest
|
get() = dest until dest + range
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ object SMAPParser {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun parseOrCreateDefault(mappingInfo: String?, source: String?, path: String, methodStartLine: Int, methodEndLine: Int): SMAP {
|
fun parseOrCreateDefault(mappingInfo: String?, source: String?, path: String, methodStartLine: Int, methodEndLine: Int): SMAP {
|
||||||
if (mappingInfo != null && mappingInfo.isNotEmpty()) {
|
if (mappingInfo != null && mappingInfo.isNotEmpty()) {
|
||||||
return parse(mappingInfo)
|
parseOrNull(mappingInfo)?.let { return it }
|
||||||
}
|
}
|
||||||
if (source == null || source.isEmpty() || methodStartLine > methodEndLine) {
|
if (source == null || source.isEmpty() || methodStartLine > methodEndLine) {
|
||||||
return SMAP(listOf(FileMapping.SKIP))
|
return SMAP(listOf(FileMapping.SKIP))
|
||||||
@@ -32,43 +32,53 @@ object SMAPParser {
|
|||||||
return SMAP(listOf(mapping))
|
return SMAP(listOf(mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
fun parseOrNull(mappingInfo: String): SMAP? =
|
||||||
fun parse(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 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).
|
for (line in iterator) {
|
||||||
// Also, JSR-045 allows the line section to come before the file section, but we don't generate SMAPs like this.
|
when {
|
||||||
val iterator = mappingInfo.lineSequence().dropWhile { it.trim() != SMAP.FILE_SECTION }.drop(1).iterator()
|
line == SMAP.LINE_SECTION -> break
|
||||||
while (iterator.hasNext()) {
|
line == SMAP.FILE_SECTION || line == SMAP.END || line.startsWith(SMAP.STRATA_SECTION) -> return null
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val indexAndFileInternalName = fileDeclaration.substringAfter("+ ").trim()
|
val indexAndFileInternalName = if (line.startsWith("+ ")) line.substring(2) else line
|
||||||
val fileIndex = indexAndFileInternalName.substringBefore(' ').toInt()
|
val fileIndex = indexAndFileInternalName.substringBefore(' ').toInt()
|
||||||
val fileName = indexAndFileInternalName.substringAfter(' ')
|
val fileName = indexAndFileInternalName.substringAfter(' ')
|
||||||
val path = iterator.next().trim()
|
val path = if (line.startsWith("+ ")) iterator.next() else fileName
|
||||||
fileMappings[fileIndex] = FileMapping(fileName, path)
|
fileMappings[fileIndex] = FileMapping(fileName, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (lineMapping in iterator) {
|
for (line in iterator) {
|
||||||
// The stratum is terminated either by *E or another stratum.
|
when {
|
||||||
if (lineMapping.trim().startsWith("*")) break
|
line == SMAP.LINE_SECTION || line == SMAP.FILE_SECTION -> return null
|
||||||
/*only simple mapping now*/
|
line == SMAP.END || line.startsWith(SMAP.STRATA_SECTION) -> break
|
||||||
val targetSplit = lineMapping.indexOf(':')
|
}
|
||||||
val originalPart = lineMapping.substring(0, targetSplit)
|
|
||||||
val rangeSeparator = originalPart.indexOf(',').let { if (it < 0) targetSplit else it }
|
|
||||||
|
|
||||||
val fileSeparator = lineMapping.indexOf('#')
|
// <source>#<file>,<sourceRange>:<dest>,<destMultiplier>
|
||||||
val originalIndex = originalPart.substring(0, fileSeparator).toInt()
|
val fileSeparator = line.indexOf('#')
|
||||||
val range = if (rangeSeparator == targetSplit) 1 else originalPart.substring(rangeSeparator + 1, targetSplit).toInt()
|
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 file = fileMappings[line.substring(fileSeparator + 1, sourceRangeSeparator).toInt()] ?: return null
|
||||||
val targetIndex = lineMapping.substring(targetSplit + 1).toInt()
|
val source = line.substring(0, fileSeparator).toInt()
|
||||||
fileMappings[fileIndex]!!.mapNewInterval(originalIndex, targetIndex, range)
|
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())
|
return SMAP(fileMappings.values.toList())
|
||||||
|
|||||||
+2
-7
@@ -1,7 +1,7 @@
|
|||||||
//FILE: 1.kt
|
//FILE: 1.kt
|
||||||
|
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
|
|
||||||
inline fun annotatedWith2(crossinline predicate: () -> Boolean) =
|
inline fun annotatedWith2(crossinline predicate: () -> Boolean) =
|
||||||
{ any { predicate() } }()
|
{ any { predicate() } }()
|
||||||
|
|
||||||
@@ -33,11 +33,6 @@ inline fun test(z: () -> Unit) {
|
|||||||
|
|
||||||
|
|
||||||
// FILE: 2.smap
|
// FILE: 2.smap
|
||||||
//*L
|
|
||||||
//1#1,15:1
|
|
||||||
//17#1:19
|
|
||||||
|
|
||||||
|
|
||||||
SMAP
|
SMAP
|
||||||
2.kt
|
2.kt
|
||||||
Kotlin
|
Kotlin
|
||||||
|
|||||||
@@ -58,5 +58,5 @@ test/_1Kt
|
|||||||
_2Kt
|
_2Kt
|
||||||
*L
|
*L
|
||||||
5#1:9
|
5#1:9
|
||||||
5#1,6:10
|
5#1:10,6
|
||||||
*E
|
*E
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ object SMAPTestUtil {
|
|||||||
if (compiledSmap == null) return
|
if (compiledSmap == null) return
|
||||||
|
|
||||||
compiledSmap.mapNotNull(SMAPAndFile::smap).forEach { smapString ->
|
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 ->
|
val conflictingLines = smap.fileMappings.flatMap { fileMapping ->
|
||||||
fileMapping.lineMappings.flatMap { lineMapping: RangeMapping ->
|
fileMapping.lineMappings.flatMap { lineMapping: RangeMapping ->
|
||||||
lineMapping.toRange.keysToMap { lineMapping }.entries
|
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.Location
|
||||||
import com.sun.jdi.ReferenceType
|
import com.sun.jdi.ReferenceType
|
||||||
import com.sun.jdi.VirtualMachine
|
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.project.implementingModules
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
import org.jetbrains.kotlin.idea.core.util.getLineCount
|
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)
|
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 debugInfo = readBytecodeInfo(project, jvmClassName, virtualFile) ?: return listOf()
|
||||||
val smapData = debugInfo.smapData ?: return listOf()
|
val smapData = debugInfo.smapData ?: return listOf()
|
||||||
|
|
||||||
val smap = smapData.kotlinStrata ?: return listOf()
|
val mappingsToInlinedFile = smapData.fileMappings.filter { it.name == inlineFileName }
|
||||||
|
|
||||||
val mappingsToInlinedFile = smap.fileMappings.filter { it.name == inlineFileName }
|
|
||||||
val mappingIntervals = mappingsToInlinedFile.flatMap { it.lineMappings }
|
val mappingIntervals = mappingsToInlinedFile.flatMap { it.lineMappings }
|
||||||
|
|
||||||
return mappingIntervals.asSequence().filter { rangeMapping -> rangeMapping.hasMappingForSource(inlineLineNumber) }
|
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.openapi.project.Project
|
||||||
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.*
|
||||||
import org.jetbrains.kotlin.codegen.inline.SMAP
|
|
||||||
import org.jetbrains.kotlin.codegen.inline.SMAPParser
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||||
@@ -22,36 +20,27 @@ enum class SourceLineKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapStacktraceLineToSource(
|
fun mapStacktraceLineToSource(
|
||||||
smapData: SmapData,
|
smapData: SMAP,
|
||||||
line: Int,
|
line: Int,
|
||||||
project: Project,
|
project: Project,
|
||||||
lineKind: SourceLineKind,
|
lineKind: SourceLineKind,
|
||||||
searchScope: GlobalSearchScope
|
searchScope: GlobalSearchScope
|
||||||
): Pair<KtFile, Int>? {
|
): Pair<KtFile, Int>? {
|
||||||
val smap = when (lineKind) {
|
val interval = smapData.findRange(line) ?: return null
|
||||||
SourceLineKind.CALL_LINE -> smapData.kotlinDebugStrata
|
val location = when (lineKind) {
|
||||||
SourceLineKind.EXECUTED_LINE -> smapData.kotlinStrata
|
SourceLineKind.CALL_LINE -> interval.callSite
|
||||||
|
SourceLineKind.EXECUTED_LINE -> interval.mapDestToSource(line)
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
val mappingInfo = smap.fileMappings.firstOrNull {
|
val jvmName = JvmClassName.byInternalName(location.path)
|
||||||
it.getIntervalIfContains(line) != null
|
|
||||||
} ?: return null
|
|
||||||
|
|
||||||
val jvmName = JvmClassName.byInternalName(mappingInfo.path)
|
|
||||||
val sourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(
|
val sourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(
|
||||||
project, searchScope, jvmName, mappingInfo.name
|
project, searchScope, jvmName, location.file
|
||||||
) ?: return null
|
) ?: return null
|
||||||
|
|
||||||
val interval = mappingInfo.getIntervalIfContains(line)!!
|
return sourceFile to location.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): SMAP? {
|
||||||
}
|
|
||||||
|
|
||||||
fun readDebugInfo(bytes: ByteArray): SmapData? {
|
|
||||||
val cr = ClassReader(bytes)
|
val cr = ClassReader(bytes)
|
||||||
var debugInfo: String? = null
|
var debugInfo: String? = null
|
||||||
cr.accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
cr.accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||||
@@ -59,30 +48,5 @@ 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)
|
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/");
|
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")
|
@TestMetadata("inlineFunFromLibrary")
|
||||||
public void testInlineFunFromLibrary() throws Exception {
|
public void testInlineFunFromLibrary() throws Exception {
|
||||||
runTest("idea/testData/debugger/exceptionFilter/inlineFunFromLibrary/");
|
runTest("idea/testData/debugger/exceptionFilter/inlineFunFromLibrary/");
|
||||||
|
|||||||
Reference in New Issue
Block a user