org.jetbrains.kotlin.codegen.inline.SMAPBuilder.kt Maven / Gradle / Ivy
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.inline
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap
import org.jetbrains.kotlin.codegen.SourceInfo
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
import kotlin.math.max
object SMAPBuilder {
fun build(fileMappings: List, backwardsCompatibleSyntax: Boolean): String? {
if (fileMappings.isEmpty()) {
return null
}
val debugMappings = linkedMapOf, FileMapping>()
for (fileMapping in fileMappings) {
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 = fileMappings.toSMAP(KOTLIN_STRATA_NAME, mapToFirstLine = false)
val debugStrata = debugMappings.values.toSMAP(KOTLIN_DEBUG_STRATA_NAME, mapToFirstLine = !backwardsCompatibleSyntax)
if (backwardsCompatibleSyntax && defaultStrata.isNotEmpty() && debugStrata.isNotEmpty()) {
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 Collection.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 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, mapToFirstLine: Boolean): String =
lineMappings.joinToString("") { it.toSMAP(id, mapToFirstLine) }
}
/**
* This class is an "SMAP delegate". It uses the info from [smap] and fills [parent].
* Used mostly to fill line numbers of an inlined function.
*/
class SourceMapCopier(val parent: SourceMapper, private val smap: SMAP, val callSite: SourcePosition? = null) {
private val visitedLines = Int2IntOpenHashMap()
private var lastVisitedRange: RangeMapping? = null
fun mapLineNumber(lineNumber: Int): Int {
val mappedLineNumber = visitedLines.get(lineNumber)
if (mappedLineNumber > 0) {
return mappedLineNumber
}
val range = lastVisitedRange?.takeIf { lineNumber in it } ?: smap.findRange(lineNumber) ?: return -1
lastVisitedRange = range
val newLineNumber = parent.mapLineNumber(range.mapDestToSource(lineNumber), callSite ?: range.callSite)
visitedLines.put(lineNumber, newLineNumber)
return newLineNumber
}
}
class SourceMapCopyingMethodVisitor(private val smapCopier: SourceMapCopier, mv: MethodVisitor) : MethodVisitor(Opcodes.API_VERSION, mv) {
constructor(target: SourceMapper, source: SMAP, mv: MethodVisitor) : this(SourceMapCopier(target, source), mv)
override fun visitLineNumber(line: Int, start: Label) =
super.visitLineNumber(smapCopier.mapLineNumber(line), start)
override fun visitLocalVariable(name: String, descriptor: String, signature: String?, start: Label, end: Label, index: Int) =
if (JvmAbi.isFakeLocalVariableForInline(name))
super.visitLocalVariable(updateCallSiteLineNumber(name, smapCopier::mapLineNumber), descriptor, signature, start, end, index)
else
super.visitLocalVariable(name, descriptor, signature, start, end, index)
}
/**
* This class allows us to build new SMAP from scratch by mapping lines one by one.
*/
class SourceMapper(val sourceInfo: SourceInfo?) {
constructor(name: String?, original: SMAP) : this(original.fileMappings.firstOrNull { it.name == name }?.toSourceInfo())
private var maxUsedValue: Int = sourceInfo?.linesInFile ?: 0
private var fileMappings: LinkedHashMap, FileMapping> = linkedMapOf()
val resultMappings: List
get() = fileMappings.values.toList()
companion object {
const val FAKE_FILE_NAME = "fake.kt"
const val FAKE_PATH = "kotlin/jvm/internal/FakeKt"
const val LOCAL_VARIABLE_INLINE_ARGUMENT_SYNTHETIC_LINE_NUMBER = 1
}
init {
sourceInfo?.let { sourceInfo ->
// If 'sourceFileName' is null we are dealing with a synthesized class
// (e.g., multi-file class facade with multiple parts). Such classes
// only have synthetic debug information and we use a fake file name.
val sourceFileName = sourceInfo.sourceFileName ?: FAKE_FILE_NAME
// Explicitly map the file to itself -- we'll probably need a lot of lines from it, so this will produce fewer ranges.
getOrRegisterNewSource(sourceFileName, sourceInfo.pathOrCleanFQN)
.mapNewInterval(1, 1, sourceInfo.linesInFile)
}
}
val isTrivial: Boolean
get() = maxUsedValue == 0 || maxUsedValue == sourceInfo?.linesInFile
private fun getOrRegisterNewSource(name: String, path: String): FileMapping =
fileMappings.getOrPut(name to path) { FileMapping(name, path) }
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
}
fun mapSyntheticLineNumber(id: Int): Int {
return mapLineNumber(SourcePosition(id, FAKE_FILE_NAME, FAKE_PATH), null)
}
}
data class SMAPAndMethodNode(val node: MethodNode, val classSMAP: SMAP)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy