[K/N] Refactor unhandled exception handling API
* Do not reset unhandled exception hook * Add processUnhandledException to perform default unhandled exception processing * Add terminateWithUnhandledException to report the unhandled exception and terminate the program * Use the default unhandled exception processing in entrypoint, interop boundaries and in Worker.executeAfter * Add -Xworker-exception-handling to control exception processing of Worker.executeAfter. By default its the old behaviour with the old MM, and new behaviour with the new MM.
This commit is contained in:
committed by
Space
parent
766857881a
commit
7e04bb4bf1
@@ -329,6 +329,15 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
put(PROPERTY_LAZY_INITIALIZATION, arguments.propertyLazyInitialization)
|
put(PROPERTY_LAZY_INITIALIZATION, arguments.propertyLazyInitialization)
|
||||||
|
put(WORKER_EXCEPTION_HANDLING, when (arguments.workerExceptionHandling) {
|
||||||
|
null -> if (memoryModel == MemoryModel.EXPERIMENTAL) WorkerExceptionHandling.USE_HOOK else WorkerExceptionHandling.LEGACY
|
||||||
|
"legacy" -> WorkerExceptionHandling.LEGACY
|
||||||
|
"use-hook" -> WorkerExceptionHandling.USE_HOOK
|
||||||
|
else -> {
|
||||||
|
configuration.report(ERROR, "Unsupported worker exception handling mode ${arguments.workerExceptionHandling}")
|
||||||
|
WorkerExceptionHandling.LEGACY
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
@@ -320,6 +320,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
|
|||||||
@Argument(value="-Xruntime-asserts-mode", valueDescription = "<mode>", description = "Enable asserts in runtime. Possible values: 'ignore', 'log', 'panic'")
|
@Argument(value="-Xruntime-asserts-mode", valueDescription = "<mode>", description = "Enable asserts in runtime. Possible values: 'ignore', 'log', 'panic'")
|
||||||
var runtimeAssertsMode: String? = "ignore"
|
var runtimeAssertsMode: String? = "ignore"
|
||||||
|
|
||||||
|
// TODO: Remove when legacy MM is gone.
|
||||||
|
@Argument(
|
||||||
|
value = "-Xworker-exception-handling",
|
||||||
|
valueDescription = "<mode>",
|
||||||
|
description = "Unhandled exception processing in Worker.executeAfter. Possible values: 'legacy', 'use-hook'. The default value is 'legacy', for -memory-model experimental the default value is 'use-hook'"
|
||||||
|
)
|
||||||
|
var workerExceptionHandling: String? = null
|
||||||
|
|
||||||
override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap<AnalysisFlag<*>, Any> =
|
override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap<AnalysisFlag<*>, Any> =
|
||||||
super.configureAnalysisFlags(collector, languageVersion).also {
|
super.configureAnalysisFlags(collector, languageVersion).also {
|
||||||
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
|
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
|
||||||
|
|||||||
+3
-2
@@ -465,7 +465,7 @@ private class ExportedElement(val kind: ElementKind,
|
|||||||
"result", cfunction[0], Direction.KOTLIN_TO_C, builder)
|
"result", cfunction[0], Direction.KOTLIN_TO_C, builder)
|
||||||
builder.append(" return $result;\n")
|
builder.append(" return $result;\n")
|
||||||
}
|
}
|
||||||
builder.append(" } catch (ExceptionObjHolder& e) { TerminateWithUnhandledException(e.GetExceptionObject()); } \n")
|
builder.append(" } catch (ExceptionObjHolder& e) { std::terminate(); } \n")
|
||||||
|
|
||||||
builder.append("}\n")
|
builder.append("}\n")
|
||||||
|
|
||||||
@@ -902,6 +902,8 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi
|
|||||||
// Include header into C++ source.
|
// Include header into C++ source.
|
||||||
headerFile.forEachLine { it -> output(it) }
|
headerFile.forEachLine { it -> output(it) }
|
||||||
|
|
||||||
|
output("#include <exception>")
|
||||||
|
|
||||||
output("""
|
output("""
|
||||||
|struct KObjHeader;
|
|struct KObjHeader;
|
||||||
|typedef struct KObjHeader KObjHeader;
|
|typedef struct KObjHeader KObjHeader;
|
||||||
@@ -924,7 +926,6 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi
|
|||||||
|void Kotlin_initRuntimeIfNeeded();
|
|void Kotlin_initRuntimeIfNeeded();
|
||||||
|void Kotlin_mm_switchThreadStateRunnable() RUNTIME_NOTHROW;
|
|void Kotlin_mm_switchThreadStateRunnable() RUNTIME_NOTHROW;
|
||||||
|void Kotlin_mm_switchThreadStateNative() RUNTIME_NOTHROW;
|
|void Kotlin_mm_switchThreadStateNative() RUNTIME_NOTHROW;
|
||||||
|void TerminateWithUnhandledException(KObjHeader*) RUNTIME_NORETURN;
|
|
||||||
|
|
|
|
||||||
|KObjHeader* CreateStringFromCString(const char*, KObjHeader**);
|
|KObjHeader* CreateStringFromCString(const char*, KObjHeader**);
|
||||||
|char* CreateCStringFromString(const KObjHeader*);
|
|char* CreateCStringFromString(const KObjHeader*);
|
||||||
|
|||||||
+4
-2
@@ -79,10 +79,12 @@ internal fun makeEntryPoint(context: Context): IrFunction {
|
|||||||
}
|
}
|
||||||
catches += irCatch(context.irBuiltIns.throwableType).apply {
|
catches += irCatch(context.irBuiltIns.throwableType).apply {
|
||||||
result = irBlock {
|
result = irBlock {
|
||||||
+irCall(context.ir.symbols.onUnhandledException).apply {
|
+irCall(context.ir.symbols.processUnhandledException).apply {
|
||||||
|
putValueArgument(0, irGet(catchParameter))
|
||||||
|
}
|
||||||
|
+irCall(context.ir.symbols.terminateWithUnhandledException).apply {
|
||||||
putValueArgument(0, irGet(catchParameter))
|
putValueArgument(0, irGet(catchParameter))
|
||||||
}
|
}
|
||||||
+irReturn(irInt(1))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -50,6 +50,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
|||||||
val gc: GC get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR)!!
|
val gc: GC get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR)!!
|
||||||
val gcAggressive: Boolean get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR_AGRESSIVE)!!
|
val gcAggressive: Boolean get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR_AGRESSIVE)!!
|
||||||
val runtimeAssertsMode: RuntimeAssertsMode get() = configuration.get(KonanConfigKeys.RUNTIME_ASSERTS_MODE)!!
|
val runtimeAssertsMode: RuntimeAssertsMode get() = configuration.get(KonanConfigKeys.RUNTIME_ASSERTS_MODE)!!
|
||||||
|
val workerExceptionHandling: WorkerExceptionHandling get() = configuration.get(KonanConfigKeys.WORKER_EXCEPTION_HANDLING)!!
|
||||||
|
|
||||||
val needVerifyIr: Boolean
|
val needVerifyIr: Boolean
|
||||||
get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true
|
get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true
|
||||||
|
|||||||
+1
@@ -164,6 +164,7 @@ class KonanConfigKeys {
|
|||||||
val RUNTIME_ASSERTS_MODE: CompilerConfigurationKey<RuntimeAssertsMode> = CompilerConfigurationKey.create("enable runtime asserts")
|
val RUNTIME_ASSERTS_MODE: CompilerConfigurationKey<RuntimeAssertsMode> = CompilerConfigurationKey.create("enable runtime asserts")
|
||||||
val PROPERTY_LAZY_INITIALIZATION: CompilerConfigurationKey<Boolean>
|
val PROPERTY_LAZY_INITIALIZATION: CompilerConfigurationKey<Boolean>
|
||||||
= CompilerConfigurationKey.create("lazy top level properties initialization")
|
= CompilerConfigurationKey.create("lazy top level properties initialization")
|
||||||
|
val WORKER_EXCEPTION_HANDLING: CompilerConfigurationKey<WorkerExceptionHandling> = CompilerConfigurationKey.create("unhandled exception processing in Worker.executeAfter")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
package org.jetbrains.kotlin.backend.konan
|
||||||
|
|
||||||
|
// Must match `WorkerExceptionHandling` in CompilerConstants.hpp
|
||||||
|
enum class WorkerExceptionHandling(val value: Int) {
|
||||||
|
LEGACY(0),
|
||||||
|
USE_HOOK(1),
|
||||||
|
}
|
||||||
+2
-1
@@ -117,7 +117,8 @@ internal class KonanSymbols(
|
|||||||
|
|
||||||
val objCMethodImp = symbolTable.referenceClass(context.interopBuiltIns.objCMethodImp)
|
val objCMethodImp = symbolTable.referenceClass(context.interopBuiltIns.objCMethodImp)
|
||||||
|
|
||||||
val onUnhandledException = internalFunction("OnUnhandledException")
|
val processUnhandledException = irBuiltIns.findFunctions(Name.identifier("processUnhandledException"), "kotlin", "native").single()
|
||||||
|
val terminateWithUnhandledException = irBuiltIns.findFunctions(Name.identifier("terminateWithUnhandledException"), "kotlin", "native").single()
|
||||||
|
|
||||||
val interopNativePointedGetRawPointer =
|
val interopNativePointedGetRawPointer =
|
||||||
symbolTable.referenceSimpleFunction(context.interopBuiltIns.nativePointedGetRawPointer)
|
symbolTable.referenceSimpleFunction(context.interopBuiltIns.nativePointedGetRawPointer)
|
||||||
|
|||||||
+1
@@ -2631,6 +2631,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
|
|
||||||
overrideRuntimeGlobal("Kotlin_destroyRuntimeMode", Int32(context.config.destroyRuntimeMode.value))
|
overrideRuntimeGlobal("Kotlin_destroyRuntimeMode", Int32(context.config.destroyRuntimeMode.value))
|
||||||
overrideRuntimeGlobal("Kotlin_gcAggressive", Int32(if (context.config.gcAggressive) 1 else 0))
|
overrideRuntimeGlobal("Kotlin_gcAggressive", Int32(if (context.config.gcAggressive) 1 else 0))
|
||||||
|
overrideRuntimeGlobal("Kotlin_workerExceptionHandling", Int32(context.config.workerExceptionHandling.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
//-------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------//
|
||||||
|
|||||||
@@ -1008,6 +1008,98 @@ task worker_exception_messages(type: KonanLocalTest) {
|
|||||||
source = "runtime/workers/worker_exception_messages.kt"
|
source = "runtime/workers/worker_exception_messages.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions") {
|
||||||
|
flags = ["-tr", "-Xworker-exception-handling=use-hook"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
!it.contains("testExecuteAfterStartQuiet error") && it.contains("testExecuteStart error") && !it.contains("testExecuteStartQuiet error")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_legacy") {
|
||||||
|
flags = ["-tr", "-Xworker-exception-handling=legacy"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("testExecuteAfterStartLegacy error") && it.contains("testExecuteStartLegacy error")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_legacy.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate") {
|
||||||
|
flags = ["-Xworker-exception-handling=use-hook"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("some error") && !it.contains("Will not happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_legacy") {
|
||||||
|
flags = ["-Xworker-exception-handling=legacy"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("some error") && it.contains("Will not happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_hook") {
|
||||||
|
flags = ["-Xworker-exception-handling=use-hook"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("hook called") && !it.contains("some error") && it.contains("Will happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate_hook.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_hook_legacy") {
|
||||||
|
flags = ["-Xworker-exception-handling=legacy"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
!it.contains("hook called") && it.contains("some error") && it.contains("Will happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate_hook.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_current") {
|
||||||
|
flags = ["-Xworker-exception-handling=use-hook"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("some error") && !it.contains("Will not happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate_current.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_current_legacy") {
|
||||||
|
flags = ["-Xworker-exception-handling=legacy"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("some error") && it.contains("Will not happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate_current.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_hook_current") {
|
||||||
|
flags = ["-Xworker-exception-handling=use-hook"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("hook called") && !it.contains("some error") && it.contains("Will happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate_hook_current.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("worker_exceptions_terminate_hook_current_legacy") {
|
||||||
|
flags = ["-Xworker-exception-handling=legacy"]
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Workers need pthreads.
|
||||||
|
outputChecker = {
|
||||||
|
!it.contains("hook called") && it.contains("some error") && it.contains("Will happen")
|
||||||
|
}
|
||||||
|
source = "runtime/workers/worker_exceptions_terminate_hook_current.kt"
|
||||||
|
}
|
||||||
|
|
||||||
standaloneTest("worker_threadlocal_no_leak") {
|
standaloneTest("worker_threadlocal_no_leak") {
|
||||||
disabled = (project.testTarget == 'wasm32') // Needs pthreads.
|
disabled = (project.testTarget == 'wasm32') // Needs pthreads.
|
||||||
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
||||||
@@ -2548,11 +2640,68 @@ standaloneTest("kt-37572") {
|
|||||||
|
|
||||||
standaloneTest("custom_hook") {
|
standaloneTest("custom_hook") {
|
||||||
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
goldValue = "value 42: Error\n"
|
outputChecker = {
|
||||||
expectedExitStatus = 1
|
it.contains("value 42: Error") && it.contains("Uncaught Kotlin exception: kotlin.Error: an error")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
source = "runtime/exceptions/custom_hook.kt"
|
source = "runtime/exceptions/custom_hook.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_memory_leak") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("Hook 42") && it.contains("Uncaught Kotlin exception: kotlin.Error: an error")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/custom_hook_memory_leak.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_get") {
|
||||||
|
source = "runtime/exceptions/custom_hook_get.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_no_reset") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("Hook called") && it.contains("Uncaught Kotlin exception: kotlin.Error: some error")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/custom_hook_no_reset.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_throws") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("Hook called") && it.contains("Uncaught Kotlin exception: kotlin.Error: another error") && !it.contains("some error")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/custom_hook_throws.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_unhandled_exception") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
goldValue = ""
|
||||||
|
source = "runtime/exceptions/custom_hook_unhandled_exception.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_terminate") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("Hook called")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/custom_hook_terminate.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("custom_hook_terminate_unhandled_exception") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("Hook called") && it.contains("Uncaught Kotlin exception: kotlin.Error: some error") && !it.contains("Not going to happen")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/custom_hook_terminate_unhandled_exception.kt"
|
||||||
|
}
|
||||||
|
|
||||||
standaloneTest("exception_in_global_init") {
|
standaloneTest("exception_in_global_init") {
|
||||||
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
source = "runtime/exceptions/exception_in_global_init.kt"
|
source = "runtime/exceptions/exception_in_global_init.kt"
|
||||||
@@ -2570,6 +2719,21 @@ task throw_from_catch(type: KonanLocalTest) {
|
|||||||
source = "runtime/exceptions/throw_from_catch.kt"
|
source = "runtime/exceptions/throw_from_catch.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standaloneTest("terminate") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/terminate.kt"
|
||||||
|
}
|
||||||
|
|
||||||
|
standaloneTest("unhandled_exception") {
|
||||||
|
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||||
|
outputChecker = {
|
||||||
|
it.contains("Uncaught Kotlin exception: kotlin.Error: some error")
|
||||||
|
}
|
||||||
|
expectedExitStatusChecker = { it != 0 }
|
||||||
|
source = "runtime/exceptions/unhandled_exception.kt"
|
||||||
|
}
|
||||||
|
|
||||||
standaloneTest("runtime_math_exceptions") {
|
standaloneTest("runtime_math_exceptions") {
|
||||||
enabled = (project.testTarget != 'wasm32')
|
enabled = (project.testTarget != 'wasm32')
|
||||||
source = "stdlib_external/numbers/MathExceptionTest.kt"
|
source = "stdlib_external/numbers/MathExceptionTest.kt"
|
||||||
@@ -2789,7 +2953,7 @@ task runtime_basic_standard(type: KonanLocalTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
standaloneTest("runtime_basic_assert_failed") {
|
standaloneTest("runtime_basic_assert_failed") {
|
||||||
expectedExitStatus = 1
|
expectedExitStatusChecker = { it != 0 }
|
||||||
source = "runtime/basic/assert_failed.kt"
|
source = "runtime/basic/assert_failed.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val exceptionHook = { _: Throwable ->
|
||||||
|
println("Hook")
|
||||||
|
}.freeze()
|
||||||
|
|
||||||
|
val oldHook = setUnhandledExceptionHook(exceptionHook)
|
||||||
|
assertNull(oldHook)
|
||||||
|
val hook1 = getUnhandledExceptionHook()
|
||||||
|
assertEquals(exceptionHook, hook1)
|
||||||
|
val hook2 = getUnhandledExceptionHook()
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
data class C(val x: Int)
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Platform.isMemoryLeakCheckerActive = true
|
||||||
|
|
||||||
|
val c = C(42)
|
||||||
|
setUnhandledExceptionHook({ _: Throwable ->
|
||||||
|
println("Hook ${c.x}")
|
||||||
|
}.freeze())
|
||||||
|
|
||||||
|
throw Error("an error")
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun customExceptionHook(throwable: Throwable) {
|
||||||
|
println("Hook called")
|
||||||
|
assertEquals(::customExceptionHook, getUnhandledExceptionHook())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
setUnhandledExceptionHook((::customExceptionHook).freeze())
|
||||||
|
|
||||||
|
throw Error("some error")
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
setUnhandledExceptionHook({ t: Throwable ->
|
||||||
|
println("Hook called")
|
||||||
|
terminateWithUnhandledException(t)
|
||||||
|
}.freeze())
|
||||||
|
|
||||||
|
throw Error("some error")
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
setUnhandledExceptionHook({ t: Throwable ->
|
||||||
|
println("Hook called")
|
||||||
|
terminateWithUnhandledException(t)
|
||||||
|
}.freeze())
|
||||||
|
|
||||||
|
val exception = Error("some error")
|
||||||
|
processUnhandledException(exception)
|
||||||
|
println("Not going to happen")
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun customExceptionHook(throwable: Throwable) {
|
||||||
|
println("Hook called")
|
||||||
|
throw Error("another error")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
setUnhandledExceptionHook((::customExceptionHook).freeze())
|
||||||
|
|
||||||
|
throw Error("some error")
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val called = AtomicInt(0)
|
||||||
|
setUnhandledExceptionHook({ _: Throwable ->
|
||||||
|
called.value = 1
|
||||||
|
}.freeze())
|
||||||
|
|
||||||
|
val exception = Error("some error")
|
||||||
|
processUnhandledException(exception)
|
||||||
|
assertEquals(1, called.value)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val exception = Error("some error")
|
||||||
|
terminateWithUnhandledException(exception)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
@file:OptIn(ExperimentalStdlibApi::class)
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val exception = Error("some error")
|
||||||
|
processUnhandledException(exception)
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package runtime.workers.worker_exceptions
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExecuteAfterStartQuiet() {
|
||||||
|
val worker = Worker.start(errorReporting = false)
|
||||||
|
worker.executeAfter(0L, {
|
||||||
|
throw Error("testExecuteAfterStartQuiet error")
|
||||||
|
}.freeze())
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExecuteStart() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
|
throw Error("testExecuteStart error")
|
||||||
|
}
|
||||||
|
assertFailsWith<Throwable> {
|
||||||
|
future.result
|
||||||
|
}
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExecuteStartQuiet() {
|
||||||
|
val worker = Worker.start(errorReporting = false)
|
||||||
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
|
throw Error("testExecuteStartQuiet error")
|
||||||
|
}
|
||||||
|
assertFailsWith<Throwable> {
|
||||||
|
future.result
|
||||||
|
}
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package runtime.workers.worker_exceptions_legacy
|
||||||
|
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExecuteAfterStartLegacy() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
worker.executeAfter(0L, {
|
||||||
|
throw Error("testExecuteAfterStartLegacy error")
|
||||||
|
}.freeze())
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExecuteStartLegacy() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
val future = worker.execute(TransferMode.SAFE, {}) {
|
||||||
|
throw Error("testExecuteStartLegacy error")
|
||||||
|
}
|
||||||
|
assertFailsWith<Throwable> {
|
||||||
|
future.result
|
||||||
|
}
|
||||||
|
worker.requestTermination().result
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val worker = Worker.start()
|
||||||
|
worker.executeAfter(0L, {
|
||||||
|
throw Error("some error")
|
||||||
|
}.freeze())
|
||||||
|
worker.requestTermination().result
|
||||||
|
println("Will not happen")
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
Worker.current.executeAfter(0L, {
|
||||||
|
throw Error("some error")
|
||||||
|
}.freeze())
|
||||||
|
Worker.current.processQueue()
|
||||||
|
println("Will not happen")
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
setUnhandledExceptionHook({ _: Throwable ->
|
||||||
|
println("hook called")
|
||||||
|
}.freeze())
|
||||||
|
|
||||||
|
|
||||||
|
val worker = Worker.start()
|
||||||
|
worker.executeAfter(0L, {
|
||||||
|
throw Error("some error")
|
||||||
|
}.freeze())
|
||||||
|
worker.requestTermination().result
|
||||||
|
println("Will happen")
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
import kotlin.native.concurrent.*
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
setUnhandledExceptionHook({ _: Throwable ->
|
||||||
|
println("hook called")
|
||||||
|
}.freeze())
|
||||||
|
|
||||||
|
Worker.current.executeAfter(0L, {
|
||||||
|
throw Error("some error")
|
||||||
|
}.freeze())
|
||||||
|
Worker.current.processQueue()
|
||||||
|
println("Will happen")
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ using namespace kotlin;
|
|||||||
// These are defined by overrideRuntimeGlobals in IrToBitcode.kt
|
// These are defined by overrideRuntimeGlobals in IrToBitcode.kt
|
||||||
RUNTIME_WEAK int32_t Kotlin_destroyRuntimeMode = 1;
|
RUNTIME_WEAK int32_t Kotlin_destroyRuntimeMode = 1;
|
||||||
RUNTIME_WEAK int32_t Kotiln_gcAggressive = 0;
|
RUNTIME_WEAK int32_t Kotiln_gcAggressive = 0;
|
||||||
|
RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0;
|
||||||
|
|
||||||
ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept {
|
ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept {
|
||||||
return static_cast<compiler::DestroyRuntimeMode>(Kotlin_destroyRuntimeMode);
|
return static_cast<compiler::DestroyRuntimeMode>(Kotlin_destroyRuntimeMode);
|
||||||
@@ -20,3 +21,7 @@ ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexce
|
|||||||
ALWAYS_INLINE bool compiler::gcAggressive() noexcept {
|
ALWAYS_INLINE bool compiler::gcAggressive() noexcept {
|
||||||
return Kotiln_gcAggressive != 0;
|
return Kotiln_gcAggressive != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE compiler::WorkerExceptionHandling compiler::workerExceptionHandling() noexcept {
|
||||||
|
return static_cast<compiler::WorkerExceptionHandling>(Kotlin_workerExceptionHandling);
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ enum class RuntimeAssertsMode : int32_t {
|
|||||||
kPanic = 2,
|
kPanic = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Must match WorkerExceptionHandling in WorkerExceptionHandling.kt
|
||||||
|
enum class WorkerExceptionHandling : int32_t {
|
||||||
|
kLegacy = 0,
|
||||||
|
kUseHook = 1,
|
||||||
|
};
|
||||||
|
|
||||||
DestroyRuntimeMode destroyRuntimeMode() noexcept;
|
DestroyRuntimeMode destroyRuntimeMode() noexcept;
|
||||||
|
|
||||||
bool gcAggressive() noexcept;
|
bool gcAggressive() noexcept;
|
||||||
@@ -44,6 +50,8 @@ ALWAYS_INLINE inline RuntimeAssertsMode runtimeAssertsMode() noexcept {
|
|||||||
return static_cast<RuntimeAssertsMode>(Kotlin_runtimeAssertsMode);
|
return static_cast<RuntimeAssertsMode>(Kotlin_runtimeAssertsMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorkerExceptionHandling workerExceptionHandling() noexcept;
|
||||||
|
|
||||||
} // namespace compiler
|
} // namespace compiler
|
||||||
} // namespace kotlin
|
} // namespace kotlin
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
#include "ObjCExceptions.h"
|
#include "ObjCExceptions.h"
|
||||||
|
|
||||||
|
// Defined in RuntimeUtils.kt
|
||||||
|
extern "C" void Kotlin_runUnhandledExceptionHook(KRef exception);
|
||||||
|
extern "C" void ReportUnhandledException(KRef exception);
|
||||||
|
|
||||||
void ThrowException(KRef exception) {
|
void ThrowException(KRef exception) {
|
||||||
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
||||||
"Throwing something non-throwable");
|
"Throwing something non-throwable");
|
||||||
@@ -64,27 +68,32 @@ class {
|
|||||||
}
|
}
|
||||||
} concurrentTerminateWrapper;
|
} concurrentTerminateWrapper;
|
||||||
|
|
||||||
//! Process exception hook (if any) or just printStackTrace + write crash log
|
void RUNTIME_NORETURN terminateWithUnhandledException(KRef exception) {
|
||||||
void processUnhandledKotlinException(KRef throwable) {
|
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
|
||||||
// Use the reentrant switch because both states are possible here:
|
concurrentTerminateWrapper([exception]() {
|
||||||
// - runnable, if the exception occured in a pure Kotlin thread (except initialization of globals).
|
ReportUnhandledException(exception);
|
||||||
// - native, if the throwing code was called from ObjC/Swift or if the exception occured during initialization of globals.
|
|
||||||
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable, /* reentrant = */ true);
|
|
||||||
OnUnhandledException(throwable);
|
|
||||||
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
|
||||||
ReportBacktraceToIosCrashLog(throwable);
|
ReportBacktraceToIosCrashLog(exception);
|
||||||
|
#endif
|
||||||
|
konan::abort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void processUnhandledException(KRef exception) noexcept {
|
||||||
|
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
|
||||||
|
#if KONAN_NO_EXCEPTIONS
|
||||||
|
terminateWithUnhandledException(exception);
|
||||||
|
#else
|
||||||
|
try {
|
||||||
|
Kotlin_runUnhandledExceptionHook(exception);
|
||||||
|
} catch (ExceptionObjHolder& e) {
|
||||||
|
terminateWithUnhandledException(e.GetExceptionObject());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
|
|
||||||
concurrentTerminateWrapper([=]() {
|
|
||||||
processUnhandledKotlinException(throwable);
|
|
||||||
konan::abort();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder) {
|
ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder) {
|
||||||
#if !KONAN_NO_EXCEPTIONS
|
#if !KONAN_NO_EXCEPTIONS
|
||||||
RETURN_OBJ(static_cast<ExceptionObjHolder*>(holder)->GetExceptionObject());
|
RETURN_OBJ(static_cast<ExceptionObjHolder*>(holder)->GetExceptionObject());
|
||||||
@@ -98,25 +107,33 @@ ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder
|
|||||||
namespace {
|
namespace {
|
||||||
// Copy, move and assign would be safe, but not much useful, so let's delete all (rule of 5)
|
// Copy, move and assign would be safe, but not much useful, so let's delete all (rule of 5)
|
||||||
class TerminateHandler : private kotlin::Pinned {
|
class TerminateHandler : private kotlin::Pinned {
|
||||||
|
RUNTIME_NORETURN static void queuedHandler() {
|
||||||
|
concurrentTerminateWrapper([]() {
|
||||||
|
// Not a Kotlin exception - call default handler
|
||||||
|
instance().queuedHandler_();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// In fact, it's safe to call my_handler directly from outside: it will do the job and then invoke original handler,
|
// In fact, it's safe to call my_handler directly from outside: it will do the job and then invoke original handler,
|
||||||
// even if it has not been initialized yet. So one may want to make it public and/or not the class member
|
// even if it has not been initialized yet. So one may want to make it public and/or not the class member
|
||||||
RUNTIME_NORETURN static void kotlinHandler() {
|
RUNTIME_NORETURN static void kotlinHandler() {
|
||||||
concurrentTerminateWrapper([]() {
|
|
||||||
if (auto currentException = std::current_exception()) {
|
if (auto currentException = std::current_exception()) {
|
||||||
try {
|
try {
|
||||||
std::rethrow_exception(currentException);
|
std::rethrow_exception(currentException);
|
||||||
} catch (ExceptionObjHolder& e) {
|
} catch (ExceptionObjHolder& e) {
|
||||||
processUnhandledKotlinException(e.GetExceptionObject());
|
// Use the reentrant switch because both states are possible here:
|
||||||
konan::abort();
|
// - runnable, if the exception occured in a pure Kotlin thread (except initialization of globals).
|
||||||
|
// - native, if the throwing code was called from ObjC/Swift or if the exception occured during initialization of globals.
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable, /* reentrant = */ true);
|
||||||
|
processUnhandledException(e.GetExceptionObject());
|
||||||
|
terminateWithUnhandledException(e.GetExceptionObject());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
// Not a Kotlin exception - call default handler
|
// Not a Kotlin exception - call default handler
|
||||||
instance().queuedHandler_();
|
queuedHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Come here in case of direct terminate() call or unknown exception - go to default terminate handler.
|
// Come here in case of direct terminate() call or unknown exception - go to default terminate handler.
|
||||||
instance().queuedHandler_();
|
queuedHandler();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
using QH = __attribute__((noreturn)) void(*)();
|
using QH = __attribute__((noreturn)) void(*)();
|
||||||
@@ -154,3 +171,25 @@ void SetKonanTerminateHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif // !KONAN_NO_EXCEPTIONS
|
#endif // !KONAN_NO_EXCEPTIONS
|
||||||
|
|
||||||
|
extern "C" void RUNTIME_NORETURN Kotlin_terminateWithUnhandledException(KRef exception) {
|
||||||
|
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
|
||||||
|
terminateWithUnhandledException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void Kotlin_processUnhandledException(KRef exception) {
|
||||||
|
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
|
||||||
|
processUnhandledException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
void kotlin::ProcessUnhandledException(KRef exception) noexcept {
|
||||||
|
// This may be called from any state, do reentrant state switch to runnable.
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable, /* reentrant = */ true);
|
||||||
|
processUnhandledException(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RUNTIME_NORETURN kotlin::TerminateWithUnhandledException(KRef exception) noexcept {
|
||||||
|
// This may be called from any state, do reentrant state switch to runnable.
|
||||||
|
kotlin::ThreadStateGuard guard(kotlin::ThreadState::kRunnable, /* reentrant = */ true);
|
||||||
|
terminateWithUnhandledException(exception);
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ extern "C" {
|
|||||||
// Throws arbitrary exception.
|
// Throws arbitrary exception.
|
||||||
void ThrowException(KRef exception);
|
void ThrowException(KRef exception);
|
||||||
|
|
||||||
// RuntimeUtils.kt
|
|
||||||
void OnUnhandledException(KRef throwable);
|
|
||||||
|
|
||||||
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef exception);
|
|
||||||
|
|
||||||
void SetKonanTerminateHandler();
|
void SetKonanTerminateHandler();
|
||||||
|
|
||||||
RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder);
|
RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder);
|
||||||
@@ -68,4 +63,11 @@ void PrintThrowable(KRef);
|
|||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace kotlin {
|
||||||
|
|
||||||
|
void ProcessUnhandledException(KRef exception) noexcept;
|
||||||
|
void RUNTIME_NORETURN TerminateWithUnhandledException(KRef exception) noexcept;
|
||||||
|
|
||||||
|
} // namespace kotlin
|
||||||
|
|
||||||
#endif // RUNTIME_NAMES_H
|
#endif // RUNTIME_NAMES_H
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
|
* that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Exceptions.h"
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
|
||||||
|
#include "ObjectTestSupport.hpp"
|
||||||
|
#include "TestSupportCompilerGenerated.hpp"
|
||||||
|
#include "TestSupport.hpp"
|
||||||
|
|
||||||
|
using namespace kotlin;
|
||||||
|
|
||||||
|
using ::testing::_;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Payload {
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
using Field = ObjHeader* Payload::*;
|
||||||
|
static constexpr std::array<Field, 0> kFields{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using Object = test_support::Object<Payload>;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(ExceptionTest, ProcessUnhandledException_WithHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
EXPECT_CALL(*reportUnhandledExceptionMock, Call(_)).Times(0);
|
||||||
|
EXPECT_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillOnce([](KRef exception) {
|
||||||
|
EXPECT_THAT(Object::FromObjHeader(exception)->value, 42);
|
||||||
|
});
|
||||||
|
kotlin::ProcessUnhandledException(exception.header());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, ProcessUnhandledException_NoHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
// Kotlin_runUnhandledExceptionHookMock rethrows original exception when hook is unset.
|
||||||
|
ThrowException(exception);
|
||||||
|
});
|
||||||
|
EXPECT_DEATH({ kotlin::ProcessUnhandledException(exception.header()); }, "Hook 42\nReporting 42\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, ProcessUnhandledException_WithFailingHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
Object hookException(typeHolder.typeInfo());
|
||||||
|
hookException.header()->typeInfoOrMeta_ = setPointerBits(hookException.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
hookException->value = 13;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([&hookException](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
ThrowException(hookException.header());
|
||||||
|
});
|
||||||
|
EXPECT_DEATH({ kotlin::ProcessUnhandledException(exception.header()); }, "Hook 42\nReporting 13\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, ProcessUnhandledException_WithTerminatingFailingHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
Object hookException(typeHolder.typeInfo());
|
||||||
|
hookException.header()->typeInfoOrMeta_ = setPointerBits(hookException.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
hookException->value = 13;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([&hookException](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
kotlin::TerminateWithUnhandledException(hookException.header());
|
||||||
|
});
|
||||||
|
EXPECT_DEATH({ kotlin::ProcessUnhandledException(exception.header()); }, "Hook 42\nReporting 13\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, TerminateWithUnhandledException) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
EXPECT_DEATH({ kotlin::TerminateWithUnhandledException(exception.header()); }, "Reporting 42\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, TerminateHandler_WithHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
std::set_terminate([]() {
|
||||||
|
konan::consoleErrorf("Custom terminate\n");
|
||||||
|
if (auto exception = std::current_exception()) {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(exception);
|
||||||
|
} catch (int i) {
|
||||||
|
konan::consoleErrorf("Exception %d\n", i);
|
||||||
|
} catch (...) {
|
||||||
|
konan::consoleErrorf("Unknown Exception\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SetKonanTerminateHandler();
|
||||||
|
try {
|
||||||
|
ThrowException(exception.header());
|
||||||
|
} catch (...) {
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Hook 42\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, TerminateHandler_NoHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
// Kotlin_runUnhandledExceptionHookMock rethrows original exception when hook is unset.
|
||||||
|
ThrowException(exception);
|
||||||
|
});
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
std::set_terminate([]() {
|
||||||
|
konan::consoleErrorf("Custom terminate\n");
|
||||||
|
if (auto exception = std::current_exception()) {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(exception);
|
||||||
|
} catch (int i) {
|
||||||
|
konan::consoleErrorf("Exception %d\n", i);
|
||||||
|
} catch (...) {
|
||||||
|
konan::consoleErrorf("Unknown Exception\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SetKonanTerminateHandler();
|
||||||
|
try {
|
||||||
|
ThrowException(exception.header());
|
||||||
|
} catch (...) {
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Hook 42\nReporting 42\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, TerminateHandler_WithFailingHook) {
|
||||||
|
test_support::TypeInfoHolder typeHolder{test_support::TypeInfoHolder::ObjectBuilder<Payload>().setSuperType(theThrowableTypeInfo)};
|
||||||
|
kotlin::RunInNewThread([&typeHolder]() {
|
||||||
|
Object exception(typeHolder.typeInfo());
|
||||||
|
exception.header()->typeInfoOrMeta_ = setPointerBits(exception.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
exception->value = 42;
|
||||||
|
Object hookException(typeHolder.typeInfo());
|
||||||
|
hookException.header()->typeInfoOrMeta_ = setPointerBits(hookException.header()->typeInfoOrMeta_, OBJECT_TAG_PERMANENT_CONTAINER);
|
||||||
|
hookException->value = 13;
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([&hookException](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
ThrowException(hookException.header());
|
||||||
|
});
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
std::set_terminate([]() {
|
||||||
|
konan::consoleErrorf("Custom terminate\n");
|
||||||
|
if (auto exception = std::current_exception()) {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(exception);
|
||||||
|
} catch (int i) {
|
||||||
|
konan::consoleErrorf("Exception %d\n", i);
|
||||||
|
} catch (...) {
|
||||||
|
konan::consoleErrorf("Unknown Exception\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SetKonanTerminateHandler();
|
||||||
|
try {
|
||||||
|
ThrowException(exception.header());
|
||||||
|
} catch (...) {
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Hook 42\nReporting 13\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ExceptionDeathTest, TerminateHandler_IgnoreHooks) {
|
||||||
|
kotlin::RunInNewThread([]() {
|
||||||
|
auto reportUnhandledExceptionMock = ScopedReportUnhandledExceptionMock();
|
||||||
|
auto Kotlin_runUnhandledExceptionHookMock = ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
ON_CALL(*reportUnhandledExceptionMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Reporting %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
});
|
||||||
|
ON_CALL(*Kotlin_runUnhandledExceptionHookMock, Call(_)).WillByDefault([](KRef exception) {
|
||||||
|
konan::consoleErrorf("Hook %d\n", Object::FromObjHeader(exception)->value);
|
||||||
|
ThrowException(exception);
|
||||||
|
});
|
||||||
|
EXPECT_DEATH(
|
||||||
|
{
|
||||||
|
std::set_terminate([]() {
|
||||||
|
konan::consoleErrorf("Custom terminate\n");
|
||||||
|
if (auto exception = std::current_exception()) {
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(exception);
|
||||||
|
} catch (int i) {
|
||||||
|
konan::consoleErrorf("Exception %d\n", i);
|
||||||
|
} catch (...) {
|
||||||
|
konan::consoleErrorf("Unknown Exception\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SetKonanTerminateHandler();
|
||||||
|
try {
|
||||||
|
throw 3;
|
||||||
|
} catch (...) {
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Custom terminate\nException 3\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -40,7 +40,9 @@ extern "C" RUNTIME_NORETURN void Kotlin_ObjCExport_trapOnUndeclaredException(KRe
|
|||||||
"from Kotlin to Objective-C/Swift as NSError.\n"
|
"from Kotlin to Objective-C/Swift as NSError.\n"
|
||||||
"It is considered unexpected and unhandled instead. Program will be terminated.");
|
"It is considered unexpected and unhandled instead. Program will be terminated.");
|
||||||
|
|
||||||
TerminateWithUnhandledException(exception);
|
kotlin::ProcessUnhandledException(exception);
|
||||||
|
// Cannot safely continue, must terminate.
|
||||||
|
kotlin::TerminateWithUnhandledException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char kotlinExceptionOriginChar;
|
static char kotlinExceptionOriginChar;
|
||||||
@@ -67,7 +69,9 @@ extern "C" id Kotlin_ObjCExport_ExceptionAsNSError(KRef exception, const TypeInf
|
|||||||
printlnMessage("Exception doesn't match @Throws-specified class list and thus isn't propagated "
|
printlnMessage("Exception doesn't match @Throws-specified class list and thus isn't propagated "
|
||||||
"from Kotlin to Objective-C/Swift as NSError.\n"
|
"from Kotlin to Objective-C/Swift as NSError.\n"
|
||||||
"It is considered unexpected and unhandled instead. Program will be terminated.");
|
"It is considered unexpected and unhandled instead. Program will be terminated.");
|
||||||
TerminateWithUnhandledException(exception);
|
kotlin::ProcessUnhandledException(exception);
|
||||||
|
// Cannot safely continue, must terminate.
|
||||||
|
kotlin::TerminateWithUnhandledException(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Kotlin_ObjCExport_WrapExceptionToNSError(exception);
|
return Kotlin_ObjCExport_WrapExceptionToNSError(exception);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ private:
|
|||||||
int32_t instanceSize_ = 0;
|
int32_t instanceSize_ = 0;
|
||||||
KStdVector<int32_t> objOffsets_;
|
KStdVector<int32_t> objOffsets_;
|
||||||
int32_t flags_ = 0;
|
int32_t flags_ = 0;
|
||||||
|
const TypeInfo* superType_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -45,6 +46,11 @@ public:
|
|||||||
flags_ &= ~flag;
|
flags_ &= ~flag;
|
||||||
return std::move(*this);
|
return std::move(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjectBuilder&& setSuperType(const TypeInfo* superType) noexcept {
|
||||||
|
superType_ = superType;
|
||||||
|
return std::move(*this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Payload>
|
template <typename Payload>
|
||||||
@@ -75,6 +81,7 @@ public:
|
|||||||
typeInfo_.objOffsetsCount_ = objOffsets_.size();
|
typeInfo_.objOffsetsCount_ = objOffsets_.size();
|
||||||
}
|
}
|
||||||
typeInfo_.flags_ = builder.flags_;
|
typeInfo_.flags_ = builder.flags_;
|
||||||
|
typeInfo_.superType_ = builder.superType_;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeInfo* typeInfo() noexcept { return &typeInfo_; }
|
TypeInfo* typeInfo() noexcept { return &typeInfo_; }
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ RuntimeState* initRuntime() {
|
|||||||
// Switch thread state because worker and globals inits require the runnable state.
|
// Switch thread state because worker and globals inits require the runnable state.
|
||||||
// This call may block if GC requested suspending threads.
|
// This call may block if GC requested suspending threads.
|
||||||
stateGuard = kotlin::ThreadStateGuard(result->memoryState, kotlin::ThreadState::kRunnable);
|
stateGuard = kotlin::ThreadStateGuard(result->memoryState, kotlin::ThreadState::kRunnable);
|
||||||
result->worker = WorkerInit(result->memoryState, true);
|
result->worker = WorkerInit(result->memoryState);
|
||||||
firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
|
firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
|
||||||
if (!kotlin::kSupportsMultipleMutators && !firstRuntime) {
|
if (!kotlin::kSupportsMultipleMutators && !firstRuntime) {
|
||||||
konan::consoleErrorf("This GC implementation does not support multiple mutator threads.");
|
konan::consoleErrorf("This GC implementation does not support multiple mutator threads.");
|
||||||
@@ -130,7 +130,7 @@ RuntimeState* initRuntime() {
|
|||||||
// Switch thread state because worker and globals inits require the runnable state.
|
// Switch thread state because worker and globals inits require the runnable state.
|
||||||
// This call may block if GC requested suspending threads.
|
// This call may block if GC requested suspending threads.
|
||||||
stateGuard = kotlin::ThreadStateGuard(result->memoryState, kotlin::ThreadState::kRunnable);
|
stateGuard = kotlin::ThreadStateGuard(result->memoryState, kotlin::ThreadState::kRunnable);
|
||||||
result->worker = WorkerInit(result->memoryState, true);
|
result->worker = WorkerInit(result->memoryState);
|
||||||
}
|
}
|
||||||
|
|
||||||
InitOrDeinitGlobalVariables(ALLOC_THREAD_LOCAL_GLOBALS, result->memoryState);
|
InitOrDeinitGlobalVariables(ALLOC_THREAD_LOCAL_GLOBALS, result->memoryState);
|
||||||
|
|||||||
@@ -60,3 +60,5 @@ private:
|
|||||||
|
|
||||||
ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock();
|
ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock();
|
||||||
ScopedStrictMockFunction<void(KInt, bool)> ScopedShutdownCleanerWorkerMock();
|
ScopedStrictMockFunction<void(KInt, bool)> ScopedShutdownCleanerWorkerMock();
|
||||||
|
ScopedStrictMockFunction<void(KRef)> ScopedReportUnhandledExceptionMock();
|
||||||
|
ScopedStrictMockFunction<void(KRef)> ScopedKotlin_runUnhandledExceptionHookMock();
|
||||||
|
|||||||
@@ -49,6 +49,25 @@ OBJ_GETTER(WorkerLaunchpad, KRef);
|
|||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum class WorkerExceptionHandling {
|
||||||
|
kDefault, // Perform the default processing of unhandled exception.
|
||||||
|
kIgnore, // Do nothing on exception escaping job unit.
|
||||||
|
kLog, // Deprecated.
|
||||||
|
};
|
||||||
|
|
||||||
|
WorkerExceptionHandling workerExceptionHandling() noexcept {
|
||||||
|
switch (compiler::workerExceptionHandling()) {
|
||||||
|
case compiler::WorkerExceptionHandling::kLegacy:
|
||||||
|
return WorkerExceptionHandling::kLog;
|
||||||
|
case compiler::WorkerExceptionHandling::kUseHook:
|
||||||
|
return WorkerExceptionHandling::kDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
#if WITH_WORKERS
|
#if WITH_WORKERS
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -117,10 +136,10 @@ typedef KStdOrderedSet<Job, JobCompare> DelayedJobSet;
|
|||||||
|
|
||||||
class Worker {
|
class Worker {
|
||||||
public:
|
public:
|
||||||
Worker(KInt id, bool errorReporting, KRef customName, WorkerKind kind)
|
Worker(KInt id, WorkerExceptionHandling exceptionHandling, KRef customName, WorkerKind kind)
|
||||||
: id_(id),
|
: id_(id),
|
||||||
kind_(kind),
|
kind_(kind),
|
||||||
errorReporting_(errorReporting) {
|
exceptionHandling_(exceptionHandling) {
|
||||||
name_ = customName != nullptr ? CreateStablePointer(customName) : nullptr;
|
name_ = customName != nullptr ? CreateStablePointer(customName) : nullptr;
|
||||||
pthread_mutex_init(&lock_, nullptr);
|
pthread_mutex_init(&lock_, nullptr);
|
||||||
pthread_cond_init(&cond_, nullptr);
|
pthread_cond_init(&cond_, nullptr);
|
||||||
@@ -147,7 +166,7 @@ class Worker {
|
|||||||
|
|
||||||
KInt id() const { return id_; }
|
KInt id() const { return id_; }
|
||||||
|
|
||||||
bool errorReporting() const { return errorReporting_; }
|
WorkerExceptionHandling exceptionHandling() const { return exceptionHandling_; }
|
||||||
|
|
||||||
KNativePtr name() const { return name_; }
|
KNativePtr name() const { return name_; }
|
||||||
|
|
||||||
@@ -173,7 +192,7 @@ class Worker {
|
|||||||
memoryState_ = state;
|
memoryState_ = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend Worker* WorkerInit(MemoryState* memoryState, KBoolean errorReporting);
|
friend Worker* WorkerInit(MemoryState* memoryState);
|
||||||
|
|
||||||
KInt id_;
|
KInt id_;
|
||||||
WorkerKind kind_;
|
WorkerKind kind_;
|
||||||
@@ -184,8 +203,7 @@ class Worker {
|
|||||||
// Lock and condition for waiting on the queue.
|
// Lock and condition for waiting on the queue.
|
||||||
pthread_mutex_t lock_;
|
pthread_mutex_t lock_;
|
||||||
pthread_cond_t cond_;
|
pthread_cond_t cond_;
|
||||||
// If errors to be reported on console.
|
WorkerExceptionHandling exceptionHandling_;
|
||||||
bool errorReporting_;
|
|
||||||
bool terminated_ = false;
|
bool terminated_ = false;
|
||||||
pthread_t thread_ = 0;
|
pthread_t thread_ = 0;
|
||||||
// MemoryState for worker's thread.
|
// MemoryState for worker's thread.
|
||||||
@@ -319,11 +337,11 @@ class State {
|
|||||||
pthread_cond_destroy(&cond_);
|
pthread_cond_destroy(&cond_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Worker* addWorkerUnlocked(bool errorReporting, KRef customName, WorkerKind kind) {
|
Worker* addWorkerUnlocked(WorkerExceptionHandling exceptionHandling, KRef customName, WorkerKind kind) {
|
||||||
Worker* worker = nullptr;
|
Worker* worker = nullptr;
|
||||||
{
|
{
|
||||||
Locker locker(&lock_);
|
Locker locker(&lock_);
|
||||||
worker = konanConstructInstance<Worker>(nextWorkerId(), errorReporting, customName, kind);
|
worker = konanConstructInstance<Worker>(nextWorkerId(), exceptionHandling, customName, kind);
|
||||||
if (worker == nullptr) return nullptr;
|
if (worker == nullptr) return nullptr;
|
||||||
workers_[worker->id()] = worker;
|
workers_[worker->id()] = worker;
|
||||||
}
|
}
|
||||||
@@ -642,8 +660,8 @@ void Future::cancelUnlocked(MemoryState* memoryState) {
|
|||||||
// Defined in RuntimeUtils.kt.
|
// Defined in RuntimeUtils.kt.
|
||||||
extern "C" void ReportUnhandledException(KRef e);
|
extern "C" void ReportUnhandledException(KRef e);
|
||||||
|
|
||||||
KInt startWorker(KBoolean errorReporting, KRef customName) {
|
KInt startWorker(WorkerExceptionHandling exceptionHandling, KRef customName) {
|
||||||
Worker* worker = theState()->addWorkerUnlocked(errorReporting != 0, customName, WorkerKind::kNative);
|
Worker* worker = theState()->addWorkerUnlocked(exceptionHandling, customName, WorkerKind::kNative);
|
||||||
if (worker == nullptr) return -1;
|
if (worker == nullptr) return -1;
|
||||||
worker->startEventLoop();
|
worker->startEventLoop();
|
||||||
return worker->id();
|
return worker->id();
|
||||||
@@ -719,7 +737,7 @@ KNativePtr detachObjectGraphInternal(KInt transferMode, KRef producer) {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
KInt startWorker(KBoolean errorReporting, KRef customName) {
|
KInt startWorker(WorkerExceptionHandling exceptionHandling, KRef customName) {
|
||||||
ThrowWorkerUnsupported();
|
ThrowWorkerUnsupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,13 +805,13 @@ KInt GetWorkerId(Worker* worker) {
|
|||||||
#endif // WITH_WORKERS
|
#endif // WITH_WORKERS
|
||||||
}
|
}
|
||||||
|
|
||||||
Worker* WorkerInit(MemoryState* memoryState, KBoolean errorReporting) {
|
Worker* WorkerInit(MemoryState* memoryState) {
|
||||||
#if WITH_WORKERS
|
#if WITH_WORKERS
|
||||||
Worker* worker;
|
Worker* worker;
|
||||||
if (::g_worker != nullptr) {
|
if (::g_worker != nullptr) {
|
||||||
worker = ::g_worker;
|
worker = ::g_worker;
|
||||||
} else {
|
} else {
|
||||||
worker = theState()->addWorkerUnlocked(errorReporting != 0, nullptr, WorkerKind::kOther);
|
worker = theState()->addWorkerUnlocked(workerExceptionHandling(), nullptr, WorkerKind::kOther);
|
||||||
::g_worker = worker;
|
::g_worker = worker;
|
||||||
}
|
}
|
||||||
worker->setThread(pthread_self());
|
worker->setThread(pthread_self());
|
||||||
@@ -1039,8 +1057,15 @@ JobKind Worker::processQueueElement(bool blocking) {
|
|||||||
#endif
|
#endif
|
||||||
WorkerLaunchpad(obj, dummyHolder.slot());
|
WorkerLaunchpad(obj, dummyHolder.slot());
|
||||||
} catch (ExceptionObjHolder& e) {
|
} catch (ExceptionObjHolder& e) {
|
||||||
if (errorReporting())
|
switch (exceptionHandling()) {
|
||||||
ReportUnhandledException(e.GetExceptionObject());
|
case WorkerExceptionHandling::kIgnore: break;
|
||||||
|
case WorkerExceptionHandling::kDefault:
|
||||||
|
kotlin::ProcessUnhandledException(e.GetExceptionObject());
|
||||||
|
break;
|
||||||
|
case WorkerExceptionHandling::kLog:
|
||||||
|
ReportUnhandledException(e.GetExceptionObject());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DisposeStablePointer(job.executeAfter.operation);
|
DisposeStablePointer(job.executeAfter.operation);
|
||||||
break;
|
break;
|
||||||
@@ -1061,8 +1086,14 @@ JobKind Worker::processQueueElement(bool blocking) {
|
|||||||
result = transfer(&resultHolder, job.regularJob.transferMode);
|
result = transfer(&resultHolder, job.regularJob.transferMode);
|
||||||
} catch (ExceptionObjHolder& e) {
|
} catch (ExceptionObjHolder& e) {
|
||||||
ok = false;
|
ok = false;
|
||||||
if (errorReporting())
|
switch (exceptionHandling()) {
|
||||||
ReportUnhandledException(e.GetExceptionObject());
|
case WorkerExceptionHandling::kIgnore:
|
||||||
|
break;
|
||||||
|
case WorkerExceptionHandling::kDefault: // TODO: Pass exception object into the future and do nothing in the default case.
|
||||||
|
case WorkerExceptionHandling::kLog:
|
||||||
|
ReportUnhandledException(e.GetExceptionObject());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Notify the future.
|
// Notify the future.
|
||||||
job.regularJob.future->storeResultUnlocked(result, ok);
|
job.regularJob.future->storeResultUnlocked(result, ok);
|
||||||
@@ -1079,8 +1110,8 @@ JobKind Worker::processQueueElement(bool blocking) {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
KInt Kotlin_Worker_startInternal(KBoolean noErrorReporting, KRef customName) {
|
KInt Kotlin_Worker_startInternal(KBoolean errorReporting, KRef customName) {
|
||||||
return startWorker(noErrorReporting, customName);
|
return startWorker(errorReporting ? workerExceptionHandling() : WorkerExceptionHandling::kIgnore, customName);
|
||||||
}
|
}
|
||||||
|
|
||||||
KInt Kotlin_Worker_currentInternal() {
|
KInt Kotlin_Worker_currentInternal() {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class Worker;
|
|||||||
|
|
||||||
KInt GetWorkerId(Worker* worker);
|
KInt GetWorkerId(Worker* worker);
|
||||||
|
|
||||||
Worker* WorkerInit(MemoryState* memoryState, KBoolean errorReporting);
|
Worker* WorkerInit(MemoryState* memoryState);
|
||||||
void WorkerDeinit(Worker* worker);
|
void WorkerDeinit(Worker* worker);
|
||||||
// Clean up all associated thread state, if this was a native worker.
|
// Clean up all associated thread state, if this was a native worker.
|
||||||
void WorkerDestroyThreadDataIfNeeded(KInt id);
|
void WorkerDestroyThreadDataIfNeeded(KInt id);
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
package kotlin.native
|
package kotlin.native
|
||||||
|
|
||||||
import kotlin.native.concurrent.InvalidMutabilityException
|
import kotlin.native.concurrent.InvalidMutabilityException
|
||||||
|
import kotlin.native.internal.ExportForCppRuntime
|
||||||
import kotlin.native.internal.GCUnsafeCall
|
import kotlin.native.internal.GCUnsafeCall
|
||||||
import kotlin.native.internal.UnhandledExceptionHookHolder
|
import kotlin.native.internal.UnhandledExceptionHookHolder
|
||||||
|
import kotlin.native.internal.runUnhandledExceptionHook
|
||||||
|
import kotlin.native.internal.ReportUnhandledException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes Kotlin runtime for the current thread, if not inited already.
|
* Initializes Kotlin runtime for the current thread, if not inited already.
|
||||||
@@ -51,8 +54,6 @@ public typealias ReportUnhandledExceptionHook = Function1<Throwable, Unit>
|
|||||||
* Hook is invoked whenever there's uncaught exception reaching boundaries of the Kotlin world,
|
* Hook is invoked whenever there's uncaught exception reaching boundaries of the Kotlin world,
|
||||||
* i.e. top level main(), or when Objective-C to Kotlin call not marked with @Throws throws an exception.
|
* i.e. top level main(), or when Objective-C to Kotlin call not marked with @Throws throws an exception.
|
||||||
* Hook must be a frozen lambda, so that it could be called from any thread/worker.
|
* Hook must be a frozen lambda, so that it could be called from any thread/worker.
|
||||||
* Hook is invoked once, and is cleared afterwards, so that memory leak detection works as expected even
|
|
||||||
* with custom exception hooks.
|
|
||||||
*/
|
*/
|
||||||
public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook? {
|
public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook? {
|
||||||
try {
|
try {
|
||||||
@@ -62,6 +63,38 @@ public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): Report
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a user-defined uncaught exception handler set by [setUnhandledExceptionHook] or `null` if no user-defined handlers were set.
|
||||||
|
*/
|
||||||
|
@ExperimentalStdlibApi
|
||||||
|
@SinceKotlin("1.6")
|
||||||
|
public fun getUnhandledExceptionHook(): ReportUnhandledExceptionHook? {
|
||||||
|
return UnhandledExceptionHookHolder.hook.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the default processing of unhandled exception.
|
||||||
|
*
|
||||||
|
* If user-defined hook set by [setUnhandledExceptionHook] is present, calls it and returns.
|
||||||
|
* If the hook is not present, calls [terminateWithUnhandledException] with [throwable].
|
||||||
|
* If the hook fails with exception, calls [terminateWithUnhandledException] with exception from the hook.
|
||||||
|
*/
|
||||||
|
@ExperimentalStdlibApi
|
||||||
|
@SinceKotlin("1.6")
|
||||||
|
@GCUnsafeCall("Kotlin_processUnhandledException")
|
||||||
|
public external fun processUnhandledException(throwable: Throwable): Unit
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Terminates the program with the given [throwable] as an unhandled exception.
|
||||||
|
* User-defined hooks installed with [setUnhandledExceptionHook] are not invoked.
|
||||||
|
*
|
||||||
|
* `terminateWithUnhandledException` can be used to emulate an abrupt termination of the application with an uncaught exception.
|
||||||
|
*/
|
||||||
|
@ExperimentalStdlibApi
|
||||||
|
@SinceKotlin("1.6")
|
||||||
|
@GCUnsafeCall("Kotlin_terminateWithUnhandledException")
|
||||||
|
public external fun terminateWithUnhandledException(throwable: Throwable): Nothing
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute stable wrt potential object relocations by the memory manager identity hash code.
|
* Compute stable wrt potential object relocations by the memory manager identity hash code.
|
||||||
* @return 0 for `null` object, identity hash code otherwise.
|
* @return 0 for `null` object, identity hash code otherwise.
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public inline class Worker @PublishedApi internal constructor(val id: Int) {
|
|||||||
* Typically new worker may be needed for computations offload to another core, for IO it may be
|
* Typically new worker may be needed for computations offload to another core, for IO it may be
|
||||||
* better to use non-blocking IO combined with more lightweight coroutines.
|
* better to use non-blocking IO combined with more lightweight coroutines.
|
||||||
*
|
*
|
||||||
* @param errorReporting controls if an uncaught exceptions in the worker will be printed out
|
* @param errorReporting controls if an uncaught exceptions in the worker will be reported.
|
||||||
* @param name defines the optional name of this worker, if none - default naming is used.
|
* @param name defines the optional name of this worker, if none - default naming is used.
|
||||||
* @return worker object, usable across multiple concurrent contexts.
|
* @return worker object, usable across multiple concurrent contexts.
|
||||||
*/
|
*/
|
||||||
@@ -99,6 +99,8 @@ public inline class Worker @PublishedApi internal constructor(val id: Int) {
|
|||||||
/**
|
/**
|
||||||
* Plan job for further execution in the worker. [operation] parameter must be either frozen, or execution to be
|
* Plan job for further execution in the worker. [operation] parameter must be either frozen, or execution to be
|
||||||
* planned on the current worker. Otherwise [IllegalStateException] will be thrown.
|
* planned on the current worker. Otherwise [IllegalStateException] will be thrown.
|
||||||
|
* With -Xworker-exception-handling=use-hook, if the worker was created with `errorReporting` set to true, any exception escaping from [operation] will
|
||||||
|
* be handled by [processUnhandledException].
|
||||||
*
|
*
|
||||||
* @param afterMicroseconds defines after how many microseconds delay execution shall happen, 0 means immediately,
|
* @param afterMicroseconds defines after how many microseconds delay execution shall happen, 0 means immediately,
|
||||||
* @throws [IllegalArgumentException] on negative values of [afterMicroseconds].
|
* @throws [IllegalArgumentException] on negative values of [afterMicroseconds].
|
||||||
|
|||||||
+6
-3
@@ -29,10 +29,13 @@ private object EmptyCompletion : Continuation<Any?> {
|
|||||||
override val context: CoroutineContext
|
override val context: CoroutineContext
|
||||||
get() = EmptyCoroutineContext
|
get() = EmptyCoroutineContext
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
override fun resumeWith(result: Result<Any?>) {
|
override fun resumeWith(result: Result<Any?>) {
|
||||||
val exception = result.exceptionOrNull() ?: return
|
val exception = result.exceptionOrNull() ?: return
|
||||||
TerminateWithUnhandledException(exception)
|
processUnhandledException(exception)
|
||||||
// Throwing the exception from [resumeWith] is not generally expected.
|
terminateWithUnhandledException(exception)
|
||||||
|
// Terminate even if unhandled exception hook has finished successfully, because
|
||||||
|
// throwing the exception from [resumeWith] is not generally expected.
|
||||||
// Also terminating is consistent with other pieces of ObjCExport machinery.
|
// Also terminating is consistent with other pieces of ObjCExport machinery.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,4 +66,4 @@ private external fun runCompletionSuccess(completionHolder: Any, result: Any?)
|
|||||||
|
|
||||||
@FilterExceptions
|
@FilterExceptions
|
||||||
@SymbolName("Kotlin_ObjCExport_runCompletionFailure")
|
@SymbolName("Kotlin_ObjCExport_runCompletionFailure")
|
||||||
private external fun runCompletionFailure(completionHolder: Any, exception: Throwable, exceptionTypes: NativePtr)
|
private external fun runCompletionFailure(completionHolder: Any, exception: Throwable, exceptionTypes: NativePtr)
|
||||||
|
|||||||
@@ -123,9 +123,6 @@ internal fun ReportUnhandledException(throwable: Throwable) {
|
|||||||
throwable.printStackTrace()
|
throwable.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
@GCUnsafeCall("TerminateWithUnhandledException")
|
|
||||||
internal external fun TerminateWithUnhandledException(throwable: Throwable)
|
|
||||||
|
|
||||||
// Using object to make sure that `hook` is initialized when it's needed instead of
|
// Using object to make sure that `hook` is initialized when it's needed instead of
|
||||||
// in a normal global initialization flow. This is important if some global happens
|
// in a normal global initialization flow. This is important if some global happens
|
||||||
// to throw an exception during it's initialization before this hook would've been initialized.
|
// to throw an exception during it's initialization before this hook would've been initialized.
|
||||||
@@ -138,10 +135,11 @@ internal object UnhandledExceptionHookHolder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Can be removed only when native-mt coroutines stop using it.
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
@ExportForCppRuntime
|
@ExportForCppRuntime
|
||||||
internal fun OnUnhandledException(throwable: Throwable) {
|
internal fun OnUnhandledException(throwable: Throwable) {
|
||||||
val handler = UnhandledExceptionHookHolder.hook.swap(null)
|
val handler = UnhandledExceptionHookHolder.hook.value
|
||||||
if (handler == null) {
|
if (handler == null) {
|
||||||
ReportUnhandledException(throwable);
|
ReportUnhandledException(throwable);
|
||||||
return
|
return
|
||||||
@@ -153,6 +151,12 @@ internal fun OnUnhandledException(throwable: Throwable) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_runUnhandledExceptionHook")
|
||||||
|
internal fun runUnhandledExceptionHook(throwable: Throwable) {
|
||||||
|
val handler = UnhandledExceptionHookHolder.hook.value ?: throw throwable
|
||||||
|
handler(throwable)
|
||||||
|
}
|
||||||
|
|
||||||
@ExportForCppRuntime
|
@ExportForCppRuntime
|
||||||
internal fun TheEmptyString() = ""
|
internal fun TheEmptyString() = ""
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ struct KBox {
|
|||||||
|
|
||||||
testing::StrictMock<testing::MockFunction<KInt()>>* createCleanerWorkerMock = nullptr;
|
testing::StrictMock<testing::MockFunction<KInt()>>* createCleanerWorkerMock = nullptr;
|
||||||
testing::StrictMock<testing::MockFunction<void(KInt, bool)>>* shutdownCleanerWorkerMock = nullptr;
|
testing::StrictMock<testing::MockFunction<void(KInt, bool)>>* shutdownCleanerWorkerMock = nullptr;
|
||||||
|
testing::StrictMock<testing::MockFunction<void(KRef)>>* reportUnhandledExceptionMock = nullptr;
|
||||||
|
testing::StrictMock<testing::MockFunction<void(KRef)>>* Kotlin_runUnhandledExceptionHookMock = nullptr;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -190,15 +192,19 @@ void RUNTIME_NORETURN ThrowFreezingException(KRef toFreeze, KRef blocker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ReportUnhandledException(KRef throwable) {
|
void ReportUnhandledException(KRef throwable) {
|
||||||
konan::consolePrintf("Uncaught Kotlin exception.");
|
if (!reportUnhandledExceptionMock) throw std::runtime_error("Not implemented for tests");
|
||||||
|
|
||||||
|
return reportUnhandledExceptionMock->Call(throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
RUNTIME_NORETURN OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo, KConstNativePtr address) {
|
RUNTIME_NORETURN OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo, KConstNativePtr address) {
|
||||||
throw std::runtime_error("Not implemented for tests");
|
throw std::runtime_error("Not implemented for tests");
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnUnhandledException(KRef throwable) {
|
void Kotlin_runUnhandledExceptionHook(KRef throwable) {
|
||||||
throw std::runtime_error("Not implemented for tests");
|
if (!Kotlin_runUnhandledExceptionHookMock) throw std::runtime_error("Not implemented for tests");
|
||||||
|
|
||||||
|
return Kotlin_runUnhandledExceptionHookMock->Call(throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kotlin_WorkerBoundReference_freezeHook(KRef thiz) {
|
void Kotlin_WorkerBoundReference_freezeHook(KRef thiz) {
|
||||||
@@ -285,3 +291,11 @@ ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock() {
|
|||||||
ScopedStrictMockFunction<void(KInt, bool)> ScopedShutdownCleanerWorkerMock() {
|
ScopedStrictMockFunction<void(KInt, bool)> ScopedShutdownCleanerWorkerMock() {
|
||||||
return ScopedStrictMockFunction<void(KInt, bool)>(&shutdownCleanerWorkerMock);
|
return ScopedStrictMockFunction<void(KInt, bool)>(&shutdownCleanerWorkerMock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScopedStrictMockFunction<void(KRef)> ScopedReportUnhandledExceptionMock() {
|
||||||
|
return ScopedStrictMockFunction<void(KRef)>(&reportUnhandledExceptionMock);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedStrictMockFunction<void(KRef)> ScopedKotlin_runUnhandledExceptionHookMock() {
|
||||||
|
return ScopedStrictMockFunction<void(KRef)>(&Kotlin_runUnhandledExceptionHookMock);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user