JVM: parse KotlinDebug when regenerating anonymous objects

This commit is contained in:
pyos
2020-04-08 13:53:17 +02:00
committed by max-kammerer
parent 1fe7ef6521
commit 5feadd56ef
14 changed files with 167 additions and 158 deletions
@@ -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())
@@ -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
@@ -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) }
@@ -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) }
@@ -0,0 +1,14 @@
inline fun foo(crossinline x: () -> Unit) = object {
fun run() {
barNonThrowing()
x()
}
}.run()
inline fun barNonThrowing() {
null
}
inline fun bar() {
null!!
}
@@ -0,0 +1,9 @@
fun box() {
foo {
bar()
}
}
// NAVIGATE_TO_CALL_SITE
// FILE: inlineFunCallSiteInLambdaInlinedIntoObject.kt
// LINE: 3
@@ -0,0 +1,14 @@
inline fun foo(crossinline x: () -> Unit) = object {
fun run() {
bar()
x()
}
}.run()
inline fun barNonThrowing() {
null
}
inline fun bar() {
null!!
}
@@ -0,0 +1,9 @@
fun box() {
foo {
barNonThrowing()
}
}
// NAVIGATE_TO_CALL_SITE
// FILE: inlineFun.kt
// LINE: 3
@@ -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/");