[JS IR] Fix stack trace capturing in secondary constructors (KT-37563)
Call captureStack in primary constructors and generated factories
This commit is contained in:
@@ -270,6 +270,12 @@ fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendConte
|
||||
expression.symbol.owner.enqueue("function access")
|
||||
}
|
||||
|
||||
override fun visitRawFunctionReference(expression: IrRawFunctionReference) {
|
||||
super.visitRawFunctionReference(expression)
|
||||
|
||||
expression.symbol.owner.enqueue("raw function access")
|
||||
}
|
||||
|
||||
override fun visitVariableAccess(expression: IrValueAccessExpression) {
|
||||
super.visitVariableAccess(expression)
|
||||
|
||||
|
||||
@@ -301,6 +301,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
||||
val jsBoxIntrinsic = getInternalFunction("boxIntrinsic")
|
||||
val jsUnboxIntrinsic = getInternalFunction("unboxIntrinsic")
|
||||
|
||||
val captureStack = getInternalFunction("captureStack")
|
||||
|
||||
val createSharedBox = defineCreateSharedBox()
|
||||
val readSharedBox = defineReadSharedBox()
|
||||
val writeSharedBox = defineWriteSharedBox()
|
||||
|
||||
@@ -624,6 +624,12 @@ private val objectUsageLoweringPhase = makeBodyLoweringPhase(
|
||||
description = "Transform IrGetObjectValue into instance generator call"
|
||||
)
|
||||
|
||||
private val captureStackTraceInThrowablesPhase = makeBodyLoweringPhase(
|
||||
::CaptureStackTraceInThrowables,
|
||||
name = "CaptureStackTraceInThrowables",
|
||||
description = "Capture stack trace in Throwable constructors"
|
||||
)
|
||||
|
||||
private val cleanupLoweringPhase = makeBodyLoweringPhase(
|
||||
{ CleanupLowering() },
|
||||
name = "CleanupLowering",
|
||||
@@ -704,6 +710,7 @@ val loweringList = listOf<Lowering>(
|
||||
constLoweringPhase,
|
||||
objectDeclarationLoweringPhase,
|
||||
objectUsageLoweringPhase,
|
||||
captureStackTraceInThrowablesPhase,
|
||||
callsLoweringPhase,
|
||||
cleanupLoweringPhase,
|
||||
validateIrAfterLowering
|
||||
@@ -784,6 +791,7 @@ val es6LoweringList = listOf<Lowering>(
|
||||
constLoweringPhase,
|
||||
objectDeclarationLoweringPhase,
|
||||
objectUsageLoweringPhase,
|
||||
captureStackTraceInThrowablesPhase,
|
||||
callsLoweringPhase,
|
||||
cleanupLoweringPhase,
|
||||
validateIrAfterLowering
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.lower
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBody
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrRawFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.util.isSubclassOf
|
||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||
|
||||
/**
|
||||
* Capture stack trace in primary constructors of Throwable
|
||||
*/
|
||||
class CaptureStackTraceInThrowables(val context: JsIrBackendContext) : BodyLoweringPass {
|
||||
override fun lower(irBody: IrBody, container: IrDeclaration) {
|
||||
if (container !is IrConstructor || !container.isPrimary)
|
||||
return
|
||||
|
||||
val klass = container.parentAsClass
|
||||
|
||||
if (!klass.isSubclassOf(context.irBuiltIns.throwableClass.owner))
|
||||
return
|
||||
|
||||
(irBody as IrBlockBodyImpl).statements += JsIrBuilder.buildCall(context.intrinsics.captureStack).also { call ->
|
||||
call.putValueArgument(0, JsIrBuilder.buildGetValue(klass.thisReceiver!!.symbol))
|
||||
call.putValueArgument(
|
||||
1,
|
||||
IrRawFunctionReferenceImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.anyType, container.symbol)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-7
@@ -17,18 +17,15 @@ import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.isSubclassOf
|
||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
@@ -99,9 +96,28 @@ class SecondaryConstructorLowering(val context: JsIrBackendContext) : Declaratio
|
||||
|
||||
call.putValueArgument(constructor.valueParameters.size, irCreateCall)
|
||||
}
|
||||
val irReturn = JsIrBuilder.buildReturn(stub.symbol, irDelegateCall, context.irBuiltIns.nothingType)
|
||||
|
||||
statements += irReturn
|
||||
if (irClass.isSubclassOf(context.irBuiltIns.throwableClass.owner)) {
|
||||
val tmp = JsIrBuilder.buildVar(
|
||||
type = irDelegateCall.type,
|
||||
parent = stub,
|
||||
initializer = irDelegateCall
|
||||
)
|
||||
|
||||
statements += tmp
|
||||
statements += JsIrBuilder.buildCall(context.intrinsics.captureStack).also { call ->
|
||||
call.putValueArgument(0, JsIrBuilder.buildGetValue(tmp.symbol))
|
||||
call.putValueArgument(
|
||||
1,
|
||||
IrRawFunctionReferenceImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.anyType, stub.symbol)
|
||||
)
|
||||
}
|
||||
statements += JsIrBuilder.buildReturn(stub.symbol, JsIrBuilder.buildGetValue(tmp.symbol), context.irBuiltIns.nothingType)
|
||||
} else {
|
||||
val irReturn = JsIrBuilder.buildReturn(stub.symbol, irDelegateCall, context.irBuiltIns.nothingType)
|
||||
statements += irReturn
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -7,7 +7,10 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.ir.isElseBranch
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
@@ -267,6 +270,15 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
||||
else -> error("Unexpected operator ${expression.operator}: ${expression.render()}")
|
||||
}
|
||||
|
||||
override fun visitRawFunctionReference(expression: IrRawFunctionReference, data: JsGenerationContext): JsExpression {
|
||||
val name = when (val function = expression.symbol.owner) {
|
||||
is IrConstructor -> data.getNameForConstructor(function)
|
||||
is IrSimpleFunction -> data.getNameForStaticFunction(function)
|
||||
else -> error("Unexpected function kind")
|
||||
}
|
||||
return JsNameRef(name)
|
||||
}
|
||||
|
||||
private fun prefixOperation(operator: JsUnaryOperator, expression: IrDynamicOperatorExpression, data: JsGenerationContext) =
|
||||
JsPrefixOperation(
|
||||
operator,
|
||||
|
||||
+5
@@ -2621,6 +2621,11 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
||||
runTest("js/js.translator/testData/box/expression/misc/safeCallComputesExpressionOnlyOnce.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stackTraceCapturing.kt")
|
||||
public void testStackTraceCapturing() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/misc/stackTraceCapturing.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringInterpolationEvaluationOrder.kt")
|
||||
public void testStringInterpolationEvaluationOrder() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/misc/stringInterpolationEvaluationOrder.kt");
|
||||
|
||||
+5
@@ -2621,6 +2621,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/expression/misc/safeCallComputesExpressionOnlyOnce.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stackTraceCapturing.kt")
|
||||
public void testStackTraceCapturing() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/misc/stackTraceCapturing.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringInterpolationEvaluationOrder.kt")
|
||||
public void testStringInterpolationEvaluationOrder() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/misc/stringInterpolationEvaluationOrder.kt");
|
||||
|
||||
+5
@@ -2631,6 +2631,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
runTest("js/js.translator/testData/box/expression/misc/safeCallComputesExpressionOnlyOnce.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stackTraceCapturing.kt")
|
||||
public void testStackTraceCapturing() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/misc/stackTraceCapturing.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringInterpolationEvaluationOrder.kt")
|
||||
public void testStringInterpolationEvaluationOrder() throws Exception {
|
||||
runTest("js/js.translator/testData/box/expression/misc/stringInterpolationEvaluationOrder.kt");
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1281
|
||||
// KJS_WITH_FULL_RUNTIME
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
// Reproduction of KT-37563
|
||||
// Test stack trace capturing in various kinds of constructors
|
||||
|
||||
public open class MyExceptionPrimary(message: String) : Exception(message, null) {
|
||||
constructor() : this("empty")
|
||||
}
|
||||
|
||||
public open class MyExceptionSecondary : MyExceptionPrimary {
|
||||
constructor(message: String) : super(message)
|
||||
}
|
||||
|
||||
@JsName("foo__0") // Need a stable name to test stack trace text.
|
||||
fun foo__0() {
|
||||
throw Exception("msg", null)
|
||||
}
|
||||
|
||||
@JsName("foo__1")
|
||||
fun foo__1() {
|
||||
throw MyExceptionPrimary("primary")
|
||||
}
|
||||
|
||||
@JsName("foo__2")
|
||||
fun foo__2(): Throwable {
|
||||
throw MyExceptionPrimary()
|
||||
}
|
||||
|
||||
@JsName("foo__3")
|
||||
fun foo__3(): Throwable {
|
||||
throw MyExceptionSecondary("secondaryOnly")
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
val functions = listOf(
|
||||
::foo__0,
|
||||
::foo__1,
|
||||
::foo__2,
|
||||
::foo__3
|
||||
)
|
||||
|
||||
var count = 0
|
||||
for ((i, f) in functions.withIndex()) {
|
||||
try {
|
||||
f()
|
||||
} catch (e: Throwable) {
|
||||
count++
|
||||
val stack = e.asDynamic().stack as String
|
||||
|
||||
// Even though stack trace format is not stadard,
|
||||
// it should contain names of the functions.
|
||||
if (!stack.contains("foo__$i")) return "fail $i"
|
||||
}
|
||||
}
|
||||
|
||||
if (count != functions.size) return "fail count"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -79,11 +79,9 @@ internal fun getStringHashCode(str: String): Int {
|
||||
|
||||
internal fun identityHashCode(obj: Any?): Int = getObjectHashCode(obj)
|
||||
|
||||
internal fun captureStack(instance: Throwable) {
|
||||
internal fun captureStack(instance: Throwable, constructorFunction: Any) {
|
||||
if (js("Error").captureStackTrace != null) {
|
||||
// TODO Why we generated get kclass for throwable in original code?
|
||||
js("Error").captureStackTrace(instance, instance.asDynamic().constructor)
|
||||
// js("Error").captureStackTrace(instance, instance::class.js)
|
||||
js("Error").captureStackTrace(instance, constructorFunction)
|
||||
} else {
|
||||
instance.asDynamic().stack = js("new Error()").stack
|
||||
}
|
||||
@@ -110,7 +108,6 @@ internal fun setPropertiesToThrowableInstance(this_: dynamic, message: String?,
|
||||
this_.cause = cause
|
||||
}
|
||||
this_.name = JsObject.getPrototypeOf(this_).constructor.name
|
||||
captureStack(this_)
|
||||
}
|
||||
|
||||
@JsName("Object")
|
||||
|
||||
Reference in New Issue
Block a user