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 6401b4c1b0a..2036c6490bb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt @@ -335,8 +335,7 @@ class AnonymousObjectTransformer( inliningContext.callSiteInfo.inlineScopeVisibility, inliningContext.callSiteInfo.file, inliningContext.callSiteInfo.lineNumber - ), - null + ) ).doInline(deferringVisitor, LocalVarRemapper(parameters, 0), false, mapOf()) reifiedTypeParametersUsages?.let(result.reifiedTypeParametersUsages::mergeAll) deferringVisitor.visitMaxs(-1, -1) 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 641b3fef6c4..960614f3d4e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -106,7 +106,7 @@ abstract class InlineCodegen( node, parameters, info, FieldRemapper(null, null, parameters), sourceCompiler.isCallInsideSameModuleAsCallee, "Method inlining " + sourceCompiler.callElementText, SourceMapCopier(sourceMapper, nodeAndSmap.classSMAP, callSite), - info.callSiteInfo, if (isInlineOnly) InlineOnlySmapSkipper(codegen) else null, + info.callSiteInfo, isInlineOnly, !isInlinedToInlineFunInKotlinRuntime() ) //with captured diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt index c4508b04ba3..fd3ac1f8449 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt @@ -48,7 +48,7 @@ class MethodInliner( private val errorPrefix: String, private val sourceMapper: SourceMapCopier, private val inlineCallSiteInfo: InlineCallSiteInfo, - private val inlineOnlySmapSkipper: InlineOnlySmapSkipper?, //non null only for root + private val overrideLineNumber: Boolean = false, private val shouldPreprocessApiVersionCalls: Boolean = false ) { private val languageVersionSettings = inliningContext.state.languageVersionSettings @@ -154,9 +154,17 @@ class MethodInliner( val fakeContinuationName = CoroutineTransformer.findFakeContinuationConstructorClassName(node) val markerShift = calcMarkerShift(parameters, node) + var currentLineNumber = if (overrideLineNumber) sourceMapper.callSite!!.line else -1 val lambdaInliner = object : InlineAdapter(remappingMethodAdapter, parameters.argsSizeOnStack, sourceMapper) { private var transformationInfo: TransformationInfo? = null + override fun visitLineNumber(line: Int, start: Label) { + if (!overrideLineNumber) { + currentLineNumber = line + } + super.visitLineNumber(line, start) + } + private fun handleAnonymousObjectRegeneration() { transformationInfo = iterator.next() @@ -250,10 +258,29 @@ class MethodInliner( store(valueParamShift, type) } if (expectedParameters.isEmpty()) { - nop() // add something for a line number to bind onto + nop() // add something for a line number to bind onto (TODO what line number?) + } + + val firstLine = info.node.node.instructions.asSequence().mapNotNull { it as? LineNumberNode }.firstOrNull()?.line ?: -1 + if (currentLineNumber >= 0 && firstLine == currentLineNumber) { + // This can happen in two cases: + // 1. `someInlineOnlyFunction { singleLineLambda }`: in this case line numbers are removed + // from the inline function, so the entirety of its bytecode has the line number of + // the call site; + // 2. `inline fun someFunction(defaultLambda: ... = { ... }) = singleLineExpression`: + // all of `someFunction`, including `defaultLambda` if no value is provided at call site, + // has the line number of the declaration. + // In those cases the debugger is unable to observe the boundary between the body of the function + // and the inline lambda call, as they have the exact same line number. So to force a JDI + // event we insert a fake line number separating those two real stretches. The event corresponding + // to the fake line number itself should be ignored by the debugger though. + val label = Label() + val fakeLineNumber = + sourceMapper.parent.mapSyntheticLineNumber(SourceMapper.LOCAL_VARIABLE_INLINE_ARGUMENT_SYNTHETIC_LINE_NUMBER) + mv.visitLabel(label) + mv.visitLineNumber(fakeLineNumber, label) } - inlineOnlySmapSkipper?.onInlineLambdaStart(remappingMethodAdapter, info.node.node, sourceMapper.parent) addInlineMarker(this, true) val lambdaParameters = info.addAllParameters(nodeRemapper) @@ -270,7 +297,7 @@ class MethodInliner( newCapturedRemapper, if (info is DefaultLambda) isSameModule else true /*cause all nested objects in same module as lambda*/, "Lambda inlining " + info.lambdaClassType.internalName, - SourceMapCopier(sourceMapper.parent, info.node.classSMAP, callSite), inlineCallSiteInfo, null + SourceMapCopier(sourceMapper.parent, info.node.classSMAP, callSite), inlineCallSiteInfo ) val varRemapper = LocalVarRemapper(lambdaParameters, valueParamShift) @@ -284,7 +311,12 @@ class MethodInliner( StackValue.coerce(info.invokeMethod.returnType, info.invokeMethodReturnType, OBJECT_TYPE, nullableAnyType, this) setLambdaInlining(false) addInlineMarker(this, false) - inlineOnlySmapSkipper?.onInlineLambdaEnd(remappingMethodAdapter) + + if (overrideLineNumber) { + val endLabel = Label() + mv.visitLabel(endLabel) + mv.visitLineNumber(currentLineNumber, endLabel) + } } else if (isAnonymousConstructorCall(owner, name)) { //TODO add method //TODO add proper message val newInfo = transformationInfo as? AnonymousObjectTransformationInfo ?: throw AssertionError( @@ -371,7 +403,7 @@ class MethodInliner( ) val transformationVisitor = object : InlineMethodInstructionAdapter(transformedNode) { - private val GENERATE_DEBUG_INFO = GENERATE_SMAP && inlineOnlySmapSkipper == null + private val GENERATE_DEBUG_INFO = GENERATE_SMAP && !overrideLineNumber private val isInliningLambda = nodeRemapper.isInsideInliningLambda 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 fd1620a1e4c..7a14aa603c0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAP.kt @@ -87,6 +87,7 @@ class SourceMapper(val sourceInfo: SourceInfo?) { 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 { 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 0aa0bc65e35..6173f49ebe5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.vfs.VirtualFile import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.codegen.ASSERTIONS_DISABLED_FIELD_NAME import org.jetbrains.kotlin.codegen.AsmUtil -import org.jetbrains.kotlin.codegen.BaseExpressionCodegen import org.jetbrains.kotlin.codegen.SamWrapperCodegen.SAM_WRAPPER_SUFFIX import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.`when`.WhenByEnumsMapping @@ -695,37 +694,6 @@ fun isFakeLocalVariableForInline(name: String): Boolean { internal fun isThis0(name: String): Boolean = AsmUtil.CAPTURED_THIS_FIELD == name -class InlineOnlySmapSkipper(codegen: BaseExpressionCodegen) { - private val callLineNumber = codegen.lastLineNumber - - companion object { - const val LOCAL_VARIABLE_INLINE_ARGUMENT_SYNTHETIC_LINE_NUMBER = 1 - } - - fun onInlineLambdaStart(mv: MethodVisitor, lambda: MethodNode, smap: SourceMapper) { - val firstLine = lambda.instructions.asSequence().mapNotNull { it as? LineNumberNode }.firstOrNull()?.line ?: -1 - if (callLineNumber >= 0 && firstLine == callLineNumber) { - // We want the debugger to be able to break both on the inline call itself, plus on each - // invocation of the inline lambda passed to it. For that to happen there needs to be at least - // one different line number in between those breakpoints for the VM to emit a locatable event. - // @InlineOnly functions, however, contain no line numbers, so if the lambda is single-line, - // the entire call will "meld" into a single region. To break it up, we insert a different line - // number that is remapped by the SMAP to a line that does not exist. - val label = Label() - mv.visitLabel(label) - mv.visitLineNumber(smap.mapSyntheticLineNumber(LOCAL_VARIABLE_INLINE_ARGUMENT_SYNTHETIC_LINE_NUMBER), label) - } - } - - fun onInlineLambdaEnd(mv: MethodVisitor) { - if (callLineNumber >= 0) { - val label = Label() - mv.visitLabel(label) - mv.visitLineNumber(callLineNumber, label) - } - } -} - fun MethodNode.preprocessSuspendMarkers(forInline: Boolean, keepFakeContinuation: Boolean = true) { if (instructions.first == null) return if (!keepFakeContinuation) {