From 679277928103f4df4d1aa20794e40c420a3cc9a4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Kuzmich Date: Wed, 10 Jun 2020 11:59:17 +0300 Subject: [PATCH] [JS IR] Fix stack trace capturing in secondary constructors (KT-37563) Call captureStack in primary constructors and generated factories --- .../org/jetbrains/kotlin/ir/backend/js/Dce.kt | 6 ++ .../kotlin/ir/backend/js/JsIntrinsics.kt | 2 + .../kotlin/ir/backend/js/JsLoweringPhases.kt | 8 +++ .../js/lower/CaptureStackTraceInThrowables.kt | 41 +++++++++++++ .../backend/js/lower/SecondaryCtorLowering.kt | 30 ++++++--- .../IrElementToJsExpressionTransformer.kt | 14 ++++- .../semantics/IrBoxJsES6TestGenerated.java | 5 ++ .../ir/semantics/IrBoxJsTestGenerated.java | 5 ++ .../js/test/semantics/BoxJsTestGenerated.java | 5 ++ .../expression/misc/stackTraceCapturing.kt | 61 +++++++++++++++++++ libraries/stdlib/js-ir/runtime/coreRuntime.kt | 7 +-- 11 files changed, 171 insertions(+), 13 deletions(-) create mode 100644 compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CaptureStackTraceInThrowables.kt create mode 100644 js/js.translator/testData/box/expression/misc/stackTraceCapturing.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 159bd191033..33ddef7d381 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -270,6 +270,12 @@ fun usefulDeclarations(roots: Iterable, 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) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 8a97d1e94f1..768e99e7eae 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -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() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 7a5f04e2c56..6a337443682 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -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( constLoweringPhase, objectDeclarationLoweringPhase, objectUsageLoweringPhase, + captureStackTraceInThrowablesPhase, callsLoweringPhase, cleanupLoweringPhase, validateIrAfterLowering @@ -784,6 +791,7 @@ val es6LoweringList = listOf( constLoweringPhase, objectDeclarationLoweringPhase, objectUsageLoweringPhase, + captureStackTraceInThrowablesPhase, callsLoweringPhase, cleanupLoweringPhase, validateIrAfterLowering diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CaptureStackTraceInThrowables.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CaptureStackTraceInThrowables.kt new file mode 100644 index 00000000000..a1daafd9019 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/CaptureStackTraceInThrowables.kt @@ -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) + ) + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt index 33c10f30337..52bae86fd67 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt @@ -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 + } + } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt index 393189a7421..0c48aafb463 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrElementToJsExpressionTransformer.kt @@ -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 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, diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index 4bf2e74a02d..63a4f25daeb 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -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"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index e38a6cec649..047e9eaf67f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -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"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index e56c443975c..a038be1d7ab 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -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"); diff --git a/js/js.translator/testData/box/expression/misc/stackTraceCapturing.kt b/js/js.translator/testData/box/expression/misc/stackTraceCapturing.kt new file mode 100644 index 00000000000..f85acb100fc --- /dev/null +++ b/js/js.translator/testData/box/expression/misc/stackTraceCapturing.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" +} \ No newline at end of file diff --git a/libraries/stdlib/js-ir/runtime/coreRuntime.kt b/libraries/stdlib/js-ir/runtime/coreRuntime.kt index 0909b9ae8e9..445e5ed0827 100644 --- a/libraries/stdlib/js-ir/runtime/coreRuntime.kt +++ b/libraries/stdlib/js-ir/runtime/coreRuntime.kt @@ -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")