diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt index b75ce8b66d6..f51c528c009 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt @@ -54,7 +54,7 @@ class AnonymousObjectTransformer( val methodsToTransform = ArrayList() val metadataReader = ReadKotlinClassHeaderAnnotationVisitor() lateinit var superClassName: String - var sourceInfo: String? = null + var debugFileName: String? = null var debugInfo: String? = null var debugMetadataAnnotation: AnnotationNode? = null @@ -120,7 +120,7 @@ class AnonymousObjectTransformer( } override fun visitSource(source: String, debug: String?) { - sourceInfo = source + debugFileName = source debugInfo = debug } @@ -133,10 +133,9 @@ class AnonymousObjectTransformer( // 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 - val (firstLine, lastLine) = (methodsToTransform + listOfNotNull(constructor)).lineNumberRange() - sourceMap = SMAPParser.parseOrCreateDefault(debugInfoToParse, sourceInfo, oldObjectType.internalName, firstLine, lastLine) - sourceMapper = SourceMapper(sourceMap.fileMappings.firstOrNull { it.name == sourceInfo }?.toSourceInfo()) + sourceMap = debugInfo.takeIf { !inliningContext.isInliningLambda }?.let(SMAPParser::parseOrNull) + ?: SMAP.identityMapping(debugFileName, oldObjectType.internalName, methodsToTransform + listOfNotNull(constructor)) + sourceMapper = SourceMapper(debugFileName, sourceMap) val allCapturedParamBuilder = ParametersBuilder.newBuilder() val constructorParamBuilder = ParametersBuilder.newBuilder() @@ -201,8 +200,8 @@ class AnonymousObjectTransformer( if (GENERATE_SMAP && !inliningContext.isInliningLambda) { classBuilder.visitSMAP(sourceMapper, !state.languageVersionSettings.supportsFeature(LanguageFeature.CorrectSourceMappingSyntax)) - } else if (sourceInfo != null) { - classBuilder.visitSource(sourceInfo!!, debugInfo) + } else if (debugFileName != null) { + classBuilder.visitSource(debugFileName!!, debugInfo) } innerClassNodes.forEach { node -> diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt index b09aa7482d3..cca496dd385 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt @@ -7,9 +7,12 @@ package org.jetbrains.kotlin.codegen.inline import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap import org.jetbrains.kotlin.codegen.SourceInfo +import org.jetbrains.kotlin.codegen.optimization.common.asSequence +import org.jetbrains.org.objectweb.asm.tree.LineNumberNode import org.jetbrains.org.objectweb.asm.tree.MethodNode import java.util.* import kotlin.math.max +import kotlin.math.min const val KOTLIN_STRATA_NAME = "Kotlin" const val KOTLIN_DEBUG_STRATA_NAME = "KotlinDebug" @@ -78,6 +81,8 @@ class SourceMapCopier(val parent: SourceMapper, private val smap: SMAP, val call data class SourcePosition(val line: Int, val file: String, val path: String) 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() @@ -134,6 +139,24 @@ class SMAP(val fileMappings: List) { const val LINE_SECTION = "*L" const val STRATA_SECTION = "*S" const val END = "*E" + + // Create a mapping that simply maps a range of a file to itself, which is equivalent to having no mapping at all. + // The contract is: if `smap` is the return value of this method, then `SourceMapCopier(SourceMapper(name, smap), smap)` + // will not change any line numbers in any of the methods passed as an argument. + fun identityMapping(name: String?, path: String, methods: Collection): SMAP { + if (name.isNullOrEmpty()) return SMAP(emptyList()) + var start = 0 + var end = 0 + for (node in methods) { + for (insn in node.instructions.asSequence()) { + if (insn !is LineNumberNode) continue + start = min(start, insn.line) + end = max(end, insn.line + 1) + } + } + if (start >= end) return SMAP(emptyList()) + return SMAP(listOf(FileMapping(name, path).apply { mapNewInterval(start, start, end - start) })) + } } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt index 3a111d8bde2..d925bbde8ac 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt @@ -17,23 +17,11 @@ package org.jetbrains.kotlin.codegen.inline object SMAPParser { - /*null smap means that there is no any debug info in file (e.g. sourceName)*/ - @JvmStatic - fun parseOrCreateDefault(mappingInfo: String?, source: String?, path: String, methodStartLine: Int, methodEndLine: Int): SMAP { - if (mappingInfo != null && mappingInfo.isNotEmpty()) { - parseOrNull(mappingInfo)?.let { return it } - } - if (source == null || source.isEmpty() || methodStartLine > methodEndLine) { - return SMAP(listOf()) - } - val mapping = FileMapping(source, path).apply { - mapNewInterval(methodStartLine, methodStartLine, methodEndLine - methodStartLine + 1) - } - return SMAP(listOf(mapping)) - } - fun parseOrNull(mappingInfo: String): SMAP? = - parseStratum(mappingInfo, KOTLIN_STRATA_NAME, parseStratum(mappingInfo, KOTLIN_DEBUG_STRATA_NAME, null)) + if (mappingInfo.isNotEmpty()) + parseStratum(mappingInfo, KOTLIN_STRATA_NAME, parseStratum(mappingInfo, KOTLIN_DEBUG_STRATA_NAME, null)) + else + null private class SMAPTokenizer(private val text: String, private val headerString: String) : Iterator { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt index 7353eb0e83c..b210c9ab113 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt @@ -50,7 +50,6 @@ import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor import java.io.PrintWriter import java.io.StringWriter import kotlin.math.max -import kotlin.math.min const val GENERATE_SMAP = true const val NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function" @@ -104,29 +103,16 @@ internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossin } }, ClassReader.SKIP_FRAMES or if (GENERATE_SMAP) 0 else ClassReader.SKIP_DEBUG) - return node?.let{ - val (first, last) = listOfNotNull(it).lineNumberRange() - SMAPAndMethodNode(it, SMAPParser.parseOrCreateDefault(sourceMap, sourceFile, classType.internalName, first, last)) + return node?.let { + val parsedSourceMap = sourceMap?.let(SMAPParser::parseOrNull) + ?: SMAP.identityMapping(sourceFile, classType.internalName, listOfNotNull(it)) + SMAPAndMethodNode(it, parsedSourceMap) } } internal fun getMethodNode(classData: ByteArray, classType: Type, method: Method): SMAPAndMethodNode? = getMethodNode(classData, classType) { it == method } -internal fun Collection.lineNumberRange(): Pair { - var minLine = Int.MAX_VALUE - var maxLine = Int.MIN_VALUE - for (node in this) { - for (insn in node.instructions.asSequence()) { - if (insn is LineNumberNode) { - minLine = min(minLine, insn.line) - maxLine = max(maxLine, insn.line) - } - } - } - return minLine to maxLine -} - internal fun findVirtualFile(state: GenerationState, classId: ClassId): VirtualFile? { return VirtualFileFinder.getInstance(state.project, state.module).findVirtualFileWithHeader(classId) }