[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")
|
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) {
|
override fun visitVariableAccess(expression: IrValueAccessExpression) {
|
||||||
super.visitVariableAccess(expression)
|
super.visitVariableAccess(expression)
|
||||||
|
|
||||||
|
|||||||
@@ -301,6 +301,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
|
|||||||
val jsBoxIntrinsic = getInternalFunction("boxIntrinsic")
|
val jsBoxIntrinsic = getInternalFunction("boxIntrinsic")
|
||||||
val jsUnboxIntrinsic = getInternalFunction("unboxIntrinsic")
|
val jsUnboxIntrinsic = getInternalFunction("unboxIntrinsic")
|
||||||
|
|
||||||
|
val captureStack = getInternalFunction("captureStack")
|
||||||
|
|
||||||
val createSharedBox = defineCreateSharedBox()
|
val createSharedBox = defineCreateSharedBox()
|
||||||
val readSharedBox = defineReadSharedBox()
|
val readSharedBox = defineReadSharedBox()
|
||||||
val writeSharedBox = defineWriteSharedBox()
|
val writeSharedBox = defineWriteSharedBox()
|
||||||
|
|||||||
@@ -624,6 +624,12 @@ private val objectUsageLoweringPhase = makeBodyLoweringPhase(
|
|||||||
description = "Transform IrGetObjectValue into instance generator call"
|
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(
|
private val cleanupLoweringPhase = makeBodyLoweringPhase(
|
||||||
{ CleanupLowering() },
|
{ CleanupLowering() },
|
||||||
name = "CleanupLowering",
|
name = "CleanupLowering",
|
||||||
@@ -704,6 +710,7 @@ val loweringList = listOf<Lowering>(
|
|||||||
constLoweringPhase,
|
constLoweringPhase,
|
||||||
objectDeclarationLoweringPhase,
|
objectDeclarationLoweringPhase,
|
||||||
objectUsageLoweringPhase,
|
objectUsageLoweringPhase,
|
||||||
|
captureStackTraceInThrowablesPhase,
|
||||||
callsLoweringPhase,
|
callsLoweringPhase,
|
||||||
cleanupLoweringPhase,
|
cleanupLoweringPhase,
|
||||||
validateIrAfterLowering
|
validateIrAfterLowering
|
||||||
@@ -784,6 +791,7 @@ val es6LoweringList = listOf<Lowering>(
|
|||||||
constLoweringPhase,
|
constLoweringPhase,
|
||||||
objectDeclarationLoweringPhase,
|
objectDeclarationLoweringPhase,
|
||||||
objectUsageLoweringPhase,
|
objectUsageLoweringPhase,
|
||||||
|
captureStackTraceInThrowablesPhase,
|
||||||
callsLoweringPhase,
|
callsLoweringPhase,
|
||||||
cleanupLoweringPhase,
|
cleanupLoweringPhase,
|
||||||
validateIrAfterLowering
|
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.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
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.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||||
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.symbols.IrFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
||||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||||
import org.jetbrains.kotlin.ir.util.defaultType
|
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.util.parentAsClass
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
@@ -99,9 +96,28 @@ class SecondaryConstructorLowering(val context: JsIrBackendContext) : Declaratio
|
|||||||
|
|
||||||
call.putValueArgument(constructor.valueParameters.size, irCreateCall)
|
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.backend.common.ir.isElseBranch
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
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.IrClass
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
@@ -267,6 +270,15 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
|
|||||||
else -> error("Unexpected operator ${expression.operator}: ${expression.render()}")
|
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) =
|
private fun prefixOperation(operator: JsUnaryOperator, expression: IrDynamicOperatorExpression, data: JsGenerationContext) =
|
||||||
JsPrefixOperation(
|
JsPrefixOperation(
|
||||||
operator,
|
operator,
|
||||||
|
|||||||
+5
@@ -2621,6 +2621,11 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test {
|
|||||||
runTest("js/js.translator/testData/box/expression/misc/safeCallComputesExpressionOnlyOnce.kt");
|
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")
|
@TestMetadata("stringInterpolationEvaluationOrder.kt")
|
||||||
public void testStringInterpolationEvaluationOrder() throws Exception {
|
public void testStringInterpolationEvaluationOrder() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/expression/misc/stringInterpolationEvaluationOrder.kt");
|
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");
|
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")
|
@TestMetadata("stringInterpolationEvaluationOrder.kt")
|
||||||
public void testStringInterpolationEvaluationOrder() throws Exception {
|
public void testStringInterpolationEvaluationOrder() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/expression/misc/stringInterpolationEvaluationOrder.kt");
|
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");
|
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")
|
@TestMetadata("stringInterpolationEvaluationOrder.kt")
|
||||||
public void testStringInterpolationEvaluationOrder() throws Exception {
|
public void testStringInterpolationEvaluationOrder() throws Exception {
|
||||||
runTest("js/js.translator/testData/box/expression/misc/stringInterpolationEvaluationOrder.kt");
|
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 identityHashCode(obj: Any?): Int = getObjectHashCode(obj)
|
||||||
|
|
||||||
internal fun captureStack(instance: Throwable) {
|
internal fun captureStack(instance: Throwable, constructorFunction: Any) {
|
||||||
if (js("Error").captureStackTrace != null) {
|
if (js("Error").captureStackTrace != null) {
|
||||||
// TODO Why we generated get kclass for throwable in original code?
|
js("Error").captureStackTrace(instance, constructorFunction)
|
||||||
js("Error").captureStackTrace(instance, instance.asDynamic().constructor)
|
|
||||||
// js("Error").captureStackTrace(instance, instance::class.js)
|
|
||||||
} else {
|
} else {
|
||||||
instance.asDynamic().stack = js("new Error()").stack
|
instance.asDynamic().stack = js("new Error()").stack
|
||||||
}
|
}
|
||||||
@@ -110,7 +108,6 @@ internal fun setPropertiesToThrowableInstance(this_: dynamic, message: String?,
|
|||||||
this_.cause = cause
|
this_.cause = cause
|
||||||
}
|
}
|
||||||
this_.name = JsObject.getPrototypeOf(this_).constructor.name
|
this_.name = JsObject.getPrototypeOf(this_).constructor.name
|
||||||
captureStack(this_)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsName("Object")
|
@JsName("Object")
|
||||||
|
|||||||
Reference in New Issue
Block a user