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 b48929efbaa..dbd20fe9336 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt @@ -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() diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index cd1fd49caec..4da2253bb61 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -208,7 +208,7 @@ abstract class InlineCodegen( 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)) { 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 9d7aa5ff0db..1743984e83a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt @@ -21,46 +21,41 @@ object SMAPBuilder { if (realMappings.isEmpty()) { return null } - val defaultStrata = generateDefaultStrata(realMappings) - val debugStrata = generateDebugStrata(realMappings) + + val debugMappings = linkedMapOf, 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): 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.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): 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, FileMapping> = linkedMapOf() - var callSiteMarker: CallSiteMarker? = null + var callSiteMarker: Int? = null val resultMappings: List 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() - 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 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 21b5bc8a731..b3023bbaa43 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt @@ -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() + 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() + // #,:, + 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()) diff --git a/compiler/testData/codegen/boxInline/smap/anonymous/severalMappingsForDefaultFile.kt b/compiler/testData/codegen/boxInline/smap/anonymous/severalMappingsForDefaultFile.kt index 8e096858a78..e47c4fd18e8 100644 --- a/compiler/testData/codegen/boxInline/smap/anonymous/severalMappingsForDefaultFile.kt +++ b/compiler/testData/codegen/boxInline/smap/anonymous/severalMappingsForDefaultFile.kt @@ -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 diff --git a/compiler/testData/codegen/boxInline/smap/smapWithNewSyntax.kt b/compiler/testData/codegen/boxInline/smap/smapWithNewSyntax.kt index 5a894ea1730..990c428c0bc 100644 --- a/compiler/testData/codegen/boxInline/smap/smapWithNewSyntax.kt +++ b/compiler/testData/codegen/boxInline/smap/smapWithNewSyntax.kt @@ -58,5 +58,5 @@ test/_1Kt _2Kt *L 5#1:9 -5#1,6:10 +5#1:10,6 *E diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt index f3fb6a7a70e..74921b05daf 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/codegen/SMAPTestUtil.kt @@ -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 diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt index 08b634707bc..ee650af506a 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt @@ -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>>) +class BytecodeDebugInfo(val smapData: SMAP?, val lineTableMapping: Map>>) 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) } diff --git a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt index 2b480faa8e2..d2a426b1625 100644 --- a/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt +++ b/idea/jvm-debugger/jvm-debugger-util/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt @@ -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? { - 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) } diff --git a/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/inlineFun.kt b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/inlineFun.kt new file mode 100644 index 00000000000..c2e239c75cf --- /dev/null +++ b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/inlineFun.kt @@ -0,0 +1,14 @@ +inline fun foo(crossinline x: () -> Unit) = object { + fun run() { + barNonThrowing() + x() + } +}.run() + +inline fun barNonThrowing() { + null +} + +inline fun bar() { + null!! +} diff --git a/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/inlineFunCallSiteInLambdaInlinedIntoObject.kt b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/inlineFunCallSiteInLambdaInlinedIntoObject.kt new file mode 100644 index 00000000000..65f4491023c --- /dev/null +++ b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInLambdaInlinedIntoObject/inlineFunCallSiteInLambdaInlinedIntoObject.kt @@ -0,0 +1,9 @@ +fun box() { + foo { + bar() + } +} + +// NAVIGATE_TO_CALL_SITE +// FILE: inlineFunCallSiteInLambdaInlinedIntoObject.kt +// LINE: 3 diff --git a/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/inlineFun.kt b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/inlineFun.kt new file mode 100644 index 00000000000..3b9e9e90b8c --- /dev/null +++ b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/inlineFun.kt @@ -0,0 +1,14 @@ +inline fun foo(crossinline x: () -> Unit) = object { + fun run() { + bar() + x() + } +}.run() + +inline fun barNonThrowing() { + null +} + +inline fun bar() { + null!! +} diff --git a/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/inlineFunCallSiteInRegeneratedObject.kt b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/inlineFunCallSiteInRegeneratedObject.kt new file mode 100644 index 00000000000..6d61a96af8e --- /dev/null +++ b/idea/testData/debugger/exceptionFilter/inlineFunCallSiteInRegeneratedObject/inlineFunCallSiteInRegeneratedObject.kt @@ -0,0 +1,9 @@ +fun box() { + foo { + barNonThrowing() + } +} + +// NAVIGATE_TO_CALL_SITE +// FILE: inlineFun.kt +// LINE: 3 diff --git a/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java index 58018471591..3c4a6a58767 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilterTestGenerated.java @@ -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/");