[JS IR] Improve debug info precision for blocks

Namely:
- Generate debug info for closing braces, which allows the breakpoints
  set on closing braces to be hit
- Generate debug info for 'if' and 'try/catch' statements.

KT-46276
This commit is contained in:
Sergej Jaskiewicz
2022-10-13 19:15:49 +02:00
committed by Space Team
parent a1c61bb9a0
commit a939f9ccd0
129 changed files with 423 additions and 160 deletions
@@ -353,11 +353,13 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
lambdaContextMapping: Map<IrFieldSymbol, IrValueSymbol>,
outerReceiverMapping: Map<IrFieldSymbol, IrGetField> = emptyMap()
): IrBlockBody {
val body = invokeFun.body
val oldBody = invokeFun.body as? IrBlockBody
?: compilationException(
"invoke() method has to have a body",
invokeFun
)
// Don't use offsets from oldBody, use offsets from invokeFun instead. This is more precise.
val body = context.irFactory.createBlockBody(invokeFun.startOffset, invokeFun.endOffset, oldBody.statements)
fun IrExpression.getValue(d: IrValueSymbol): IrExpression = IrGetValueImpl(startOffset, endOffset, d)
fun IrExpression.getCastedValue(d: IrValueSymbol, toType: IrType): IrExpression =
@@ -406,10 +408,20 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
if (invokeFun.returnType.isUnit()) {
val unitValue = JsIrBuilder.buildGetObjectValue(context.irBuiltIns.unitType, context.irBuiltIns.unitClass)
(body as IrBlockBody).statements.add(IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.nothingType, lambdaDeclaration.symbol, unitValue))
// Set both offsets of the IrReturn to body.endOffset - 1 so that a breakpoint set at the closing brace of a lambda expression
// could be hit.
body.statements.add(
IrReturnImpl(
body.endOffset - 1,
body.endOffset - 1,
context.irBuiltIns.nothingType,
lambdaDeclaration.symbol,
unitValue
)
)
}
return body as IrBlockBody
return body
}
private fun buildLambdaBody(instance: IrVariable, lambdaDeclaration: IrSimpleFunction, invokeFun: IrSimpleFunction): IrBlockBody {
@@ -32,7 +32,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
override fun visitBlockBody(body: IrBlockBody, context: JsGenerationContext): JsStatement {
return JsBlock(body.statements.map { it.accept(this, context) })
return JsBlock(body.statements.map { it.accept(this, context) }).withSource(body, context, container = context.currentFunction)
}
override fun visitBlock(expression: IrBlock, context: JsGenerationContext): JsStatement {
@@ -53,7 +53,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
} else {
JsBlock(statements)
}
}.withSource(expression, context)
}
private fun List<JsStatement>.wrapInCommentsInlineFunctionCall(expression: IrReturnableBlock): List<JsStatement> {
@@ -65,7 +65,7 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
override fun visitComposite(expression: IrComposite, context: JsGenerationContext): JsStatement {
return JsBlock(expression.statements.map { it.accept(this, context) })
return JsBlock(expression.statements.map { it.accept(this, context) }).withSource(expression, context)
}
override fun visitExpression(expression: IrExpression, context: JsGenerationContext): JsStatement {
@@ -186,12 +186,12 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
val jsCatch = aTry.catches.singleOrNull()?.let {
val name = context.getNameForValueDeclaration(it.catchParameter)
val jsCatchBlock = it.result.accept(this, context)
JsCatch(emptyScope, name.ident, jsCatchBlock)
JsCatch(emptyScope, name.ident, jsCatchBlock).withSource(it, context)
}
val jsFinallyBlock = aTry.finallyExpression?.accept(this, context)?.asBlock()
return JsTry(jsTryBlock, jsCatch, jsFinallyBlock)
return JsTry(jsTryBlock, jsCatch, jsFinallyBlock).withSource(aTry, context)
}
override fun visitWhen(expression: IrWhen, context: JsGenerationContext): JsStatement {
@@ -45,16 +45,16 @@ fun jsVar(name: JsName, initializer: IrExpression?, context: JsGenerationContext
fun <T : JsNode> IrWhen.toJsNode(
tr: BaseIrElementToJsNodeTransformer<T, JsGenerationContext>,
data: JsGenerationContext,
context: JsGenerationContext,
node: (JsExpression, T, T?) -> T,
implicitElse: T? = null
): T? =
branches.foldRight(implicitElse) { br, n ->
val body = br.result.accept(tr, data)
val body = br.result.accept(tr, context)
if (isElseBranch(br)) body
else {
val condition = br.condition.accept(IrElementToJsExpressionTransformer(), data)
node(condition, body, n)
val condition = br.condition.accept(IrElementToJsExpressionTransformer(), context)
node(condition, body, n).withSource(br, context)
}
}
@@ -521,25 +521,46 @@ object JsAstUtils {
}
}
internal fun <T : JsNode> T.withSource(node: IrElement, context: JsGenerationContext, useNameOf: IrDeclarationWithName? = null): T {
addSourceInfoIfNeed(node, context, useNameOf)
internal fun <T : JsNode> T.withSource(
node: IrElement,
context: JsGenerationContext,
useNameOf: IrDeclarationWithName? = null,
container: IrDeclaration? = null
): T {
addSourceInfoIfNeed(node, context, useNameOf, container)
return this
}
@Suppress("NOTHING_TO_INLINE")
private inline fun <T : JsNode> T.addSourceInfoIfNeed(node: IrElement, context: JsGenerationContext, useNameOf: IrDeclarationWithName?) {
private inline fun <T : JsNode> T.addSourceInfoIfNeed(
node: IrElement,
context: JsGenerationContext,
useNameOf: IrDeclarationWithName?,
container: IrDeclaration?
) {
val sourceMapsInfo = context.staticContext.backendContext.sourceMapsInfo ?: return
val originalName = useNameOf?.originalNameForUseInSourceMap(sourceMapsInfo.namesPolicy)
val location = context.getLocationForIrElement(node, originalName) ?: return
val location = context.getStartLocationForIrElement(node, originalName) ?: return
val isNodeFromCurrentModule = context.currentFile.module.descriptor == context.staticContext.backendContext.module
// TODO maybe it's better to fix in JsExpressionStatement
val locationTarget = if (this is JsExpressionStatement) this.expression else this
if (locationTarget is JsBlock && (node is IrBlockBody || node is IrBlock)) {
locationTarget.closingBraceSource = if (container is IrConstructor) {
// This is a hack. Without this special case, the closing brace in the generated code for constructors would always be mapped
// to the closing brace of the Kotlin class declaration.
context.getStartLocationForIrElement(node)
} else {
context.getEndLocationForIrElement(node)?.run {
// Assuming that endOffset for IrBlock and IrBlockBody points to the character after the closing brace.
// TODO: This doesn't produce good results if the node originates from an expression body
// (meaning, in the source code; not to be confused with IrExpressionBody)
if (startChar > 0) copy(startChar = startChar - 1) else null
}
}
}
locationTarget.source = when (sourceMapsInfo.sourceMapContentEmbedding) {
SourceMapSourceEmbedding.NEVER -> location
SourceMapSourceEmbedding.INLINING -> if (isNodeFromCurrentModule) location else location.withEmbeddedSource(context)
@@ -568,16 +589,20 @@ private fun JsLocation.withEmbeddedSource(
}
}
fun IrElement.getSourceInfo(container: IrDeclaration): JsLocation? {
fun IrElement.getStartSourceLocation(container: IrDeclaration): JsLocation? {
val fileEntry = container.fileOrNull?.fileEntry ?: return null
return getSourceInfo(fileEntry)
return getStartSourceLocation(fileEntry)
}
fun IrElement.getSourceInfo(fileEntry: IrFileEntry): JsLocation? {
fun IrElement.getStartSourceLocation(fileEntry: IrFileEntry) =
getSourceLocation(fileEntry) { startOffset }
inline fun IrElement.getSourceLocation(fileEntry: IrFileEntry, offsetSelector: IrElement.() -> Int): JsLocation? {
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET) return null
val path = fileEntry.name
val startLine = fileEntry.getLineNumber(startOffset)
val startColumn = fileEntry.getColumnNumber(startOffset)
val offset = offsetSelector()
val startLine = fileEntry.getLineNumber(offset)
val startColumn = fileEntry.getColumnNumber(offset)
return JsLocation(path, startLine, startColumn)
}
@@ -28,14 +28,14 @@ fun translateJsCodeIntoStatementList(code: IrExpression, context: JsIrBackendCon
translateJsCodeIntoStatementList(
code,
context,
code.getSourceInfo(container) ?: container.fileOrNull?.fileEntry?.let { JsLocation(it.name, 0, 0) }
code.getStartSourceLocation(container) ?: container.fileOrNull?.fileEntry?.let { JsLocation(it.name, 0, 0) }
)
/**
* Returns null if constant expression could not be parsed.
*/
fun translateJsCodeIntoStatementList(code: IrExpression, context: JsIrBackendContext?, fileEntry: IrFileEntry) =
translateJsCodeIntoStatementList(code, context, code.getSourceInfo(fileEntry) ?: JsLocation(fileEntry.name, 0, 0))
translateJsCodeIntoStatementList(code, context, code.getStartSourceLocation(fileEntry) ?: JsLocation(fileEntry.name, 0, 0))
private fun translateJsCodeIntoStatementList(
code: IrExpression,
@@ -6,7 +6,8 @@
package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getSourceInfo
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getSourceLocation
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.getStartSourceLocation
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrLoop
import org.jetbrains.kotlin.ir.expressions.IrReturnableBlock
@@ -33,7 +34,8 @@ class JsGenerationContext(
private val nameCache: MutableMap<IrElement, JsName> = mutableMapOf(),
private val useBareParameterNames: Boolean = false,
) : IrNamer by staticContext {
private val locationCache = mutableMapOf<Int, JsLocation>()
private val startLocationCache = mutableMapOf<Int, JsLocation>()
private val endLocationCache = mutableMapOf<Int, JsLocation>()
fun newFile(file: IrFile, func: IrFunction? = null, localNames: LocalNameGenerator? = null): JsGenerationContext {
return JsGenerationContext(
@@ -87,9 +89,18 @@ class JsGenerationContext(
fun checkIfHasAssociatedJsCode(symbol: IrFunctionSymbol): Boolean = staticContext.backendContext.getJsCodeForFunction(symbol) != null
fun getLocationForIrElement(irElement: IrElement, originalName: String? = null): JsLocation? {
return locationCache.getOrPut(irElement.startOffset) {
irElement.getSourceInfo(currentFile.fileEntry) ?: return null
}.copy(name = originalName)
}
fun getStartLocationForIrElement(irElement: IrElement, originalName: String? = null) =
getLocationForIrElement(irElement, originalName, startLocationCache) { startOffset }
fun getEndLocationForIrElement(irElement: IrElement, originalName: String? = null) =
getLocationForIrElement(irElement, originalName, endLocationCache) { endOffset }
private inline fun getLocationForIrElement(
irElement: IrElement,
originalName: String?,
cache: MutableMap<Int, JsLocation>,
offsetSelector: IrElement.() -> Int,
): JsLocation? = cache.getOrPut(irElement.offsetSelector()) {
irElement.getSourceLocation(currentFile.fileEntry, offsetSelector) ?: return null
}.copy(name = originalName)
}