mirror of
https://github.com/jlengrand/kotlin.git
synced 2026-03-10 08:31:29 +00:00
[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(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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +320,14 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
|
||||
@Argument(value="-Xruntime-asserts-mode", valueDescription = "<mode>", description = "Enable asserts in runtime. Possible values: 'ignore', 'log', 'panic'")
|
||||
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> =
|
||||
super.configureAnalysisFlags(collector, languageVersion).also {
|
||||
val useExperimental = it[AnalysisFlags.useExperimental] as List<*>
|
||||
|
||||
@@ -465,7 +465,7 @@ private class ExportedElement(val kind: ElementKind,
|
||||
"result", cfunction[0], Direction.KOTLIN_TO_C, builder)
|
||||
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")
|
||||
|
||||
@@ -902,6 +902,8 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi
|
||||
// Include header into C++ source.
|
||||
headerFile.forEachLine { it -> output(it) }
|
||||
|
||||
output("#include <exception>")
|
||||
|
||||
output("""
|
||||
|struct KObjHeader;
|
||||
|typedef struct KObjHeader KObjHeader;
|
||||
@@ -924,7 +926,6 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi
|
||||
|void Kotlin_initRuntimeIfNeeded();
|
||||
|void Kotlin_mm_switchThreadStateRunnable() RUNTIME_NOTHROW;
|
||||
|void Kotlin_mm_switchThreadStateNative() RUNTIME_NOTHROW;
|
||||
|void TerminateWithUnhandledException(KObjHeader*) RUNTIME_NORETURN;
|
||||
|
|
||||
|KObjHeader* CreateStringFromCString(const char*, KObjHeader**);
|
||||
|char* CreateCStringFromString(const KObjHeader*);
|
||||
|
||||
@@ -79,10 +79,12 @@ internal fun makeEntryPoint(context: Context): IrFunction {
|
||||
}
|
||||
catches += irCatch(context.irBuiltIns.throwableType).apply {
|
||||
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))
|
||||
}
|
||||
+irReturn(irInt(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
val gc: GC get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR)!!
|
||||
val gcAggressive: Boolean get() = configuration.get(KonanConfigKeys.GARBAGE_COLLECTOR_AGRESSIVE)!!
|
||||
val runtimeAssertsMode: RuntimeAssertsMode get() = configuration.get(KonanConfigKeys.RUNTIME_ASSERTS_MODE)!!
|
||||
val workerExceptionHandling: WorkerExceptionHandling get() = configuration.get(KonanConfigKeys.WORKER_EXCEPTION_HANDLING)!!
|
||||
|
||||
val needVerifyIr: Boolean
|
||||
get() = configuration.get(KonanConfigKeys.VERIFY_IR) == true
|
||||
|
||||
@@ -164,6 +164,7 @@ class KonanConfigKeys {
|
||||
val RUNTIME_ASSERTS_MODE: CompilerConfigurationKey<RuntimeAssertsMode> = CompilerConfigurationKey.create("enable runtime asserts")
|
||||
val PROPERTY_LAZY_INITIALIZATION: CompilerConfigurationKey<Boolean>
|
||||
= CompilerConfigurationKey.create("lazy top level properties initialization")
|
||||
val WORKER_EXCEPTION_HANDLING: CompilerConfigurationKey<WorkerExceptionHandling> = CompilerConfigurationKey.create("unhandled exception processing in Worker.executeAfter")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
@@ -117,7 +117,8 @@ internal class KonanSymbols(
|
||||
|
||||
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 =
|
||||
symbolTable.referenceSimpleFunction(context.interopBuiltIns.nativePointedGetRawPointer)
|
||||
|
||||
@@ -2631,6 +2631,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
|
||||
overrideRuntimeGlobal("Kotlin_destroyRuntimeMode", Int32(context.config.destroyRuntimeMode.value))
|
||||
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"
|
||||
}
|
||||
|
||||
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") {
|
||||
disabled = (project.testTarget == 'wasm32') // Needs pthreads.
|
||||
source = "runtime/workers/worker_threadlocal_no_leak.kt"
|
||||
@@ -2548,11 +2640,68 @@ standaloneTest("kt-37572") {
|
||||
|
||||
standaloneTest("custom_hook") {
|
||||
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||
goldValue = "value 42: Error\n"
|
||||
expectedExitStatus = 1
|
||||
outputChecker = {
|
||||
it.contains("value 42: Error") && it.contains("Uncaught Kotlin exception: kotlin.Error: an error")
|
||||
}
|
||||
expectedExitStatusChecker = { it != 0 }
|
||||
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") {
|
||||
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
|
||||
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"
|
||||
}
|
||||
|
||||
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") {
|
||||
enabled = (project.testTarget != 'wasm32')
|
||||
source = "stdlib_external/numbers/MathExceptionTest.kt"
|
||||
@@ -2789,7 +2953,7 @@ task runtime_basic_standard(type: KonanLocalTest) {
|
||||
}
|
||||
|
||||
standaloneTest("runtime_basic_assert_failed") {
|
||||
expectedExitStatus = 1
|
||||
expectedExitStatusChecker = { it != 0 }
|
||||
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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
RUNTIME_WEAK int32_t Kotlin_destroyRuntimeMode = 1;
|
||||
RUNTIME_WEAK int32_t Kotiln_gcAggressive = 0;
|
||||
RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0;
|
||||
|
||||
ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexcept {
|
||||
return static_cast<compiler::DestroyRuntimeMode>(Kotlin_destroyRuntimeMode);
|
||||
@@ -20,3 +21,7 @@ ALWAYS_INLINE compiler::DestroyRuntimeMode compiler::destroyRuntimeMode() noexce
|
||||
ALWAYS_INLINE bool compiler::gcAggressive() noexcept {
|
||||
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,
|
||||
};
|
||||
|
||||
// Must match WorkerExceptionHandling in WorkerExceptionHandling.kt
|
||||
enum class WorkerExceptionHandling : int32_t {
|
||||
kLegacy = 0,
|
||||
kUseHook = 1,
|
||||
};
|
||||
|
||||
DestroyRuntimeMode destroyRuntimeMode() noexcept;
|
||||
|
||||
bool gcAggressive() noexcept;
|
||||
@@ -44,6 +50,8 @@ ALWAYS_INLINE inline RuntimeAssertsMode runtimeAssertsMode() noexcept {
|
||||
return static_cast<RuntimeAssertsMode>(Kotlin_runtimeAssertsMode);
|
||||
}
|
||||
|
||||
WorkerExceptionHandling workerExceptionHandling() noexcept;
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace kotlin
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
#include "Utils.hpp"
|
||||
#include "ObjCExceptions.h"
|
||||
|
||||
// Defined in RuntimeUtils.kt
|
||||
extern "C" void Kotlin_runUnhandledExceptionHook(KRef exception);
|
||||
extern "C" void ReportUnhandledException(KRef exception);
|
||||
|
||||
void ThrowException(KRef exception) {
|
||||
RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo),
|
||||
"Throwing something non-throwable");
|
||||
@@ -64,27 +68,32 @@ class {
|
||||
}
|
||||
} concurrentTerminateWrapper;
|
||||
|
||||
//! Process exception hook (if any) or just printStackTrace + write crash log
|
||||
void processUnhandledKotlinException(KRef throwable) {
|
||||
// Use the reentrant switch because both states are possible here:
|
||||
// - 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);
|
||||
OnUnhandledException(throwable);
|
||||
void RUNTIME_NORETURN terminateWithUnhandledException(KRef exception) {
|
||||
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
|
||||
concurrentTerminateWrapper([exception]() {
|
||||
ReportUnhandledException(exception);
|
||||
#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
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
|
||||
concurrentTerminateWrapper([=]() {
|
||||
processUnhandledKotlinException(throwable);
|
||||
konan::abort();
|
||||
});
|
||||
}
|
||||
|
||||
ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder) {
|
||||
#if !KONAN_NO_EXCEPTIONS
|
||||
RETURN_OBJ(static_cast<ExceptionObjHolder*>(holder)->GetExceptionObject());
|
||||
@@ -98,25 +107,33 @@ ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder
|
||||
namespace {
|
||||
// Copy, move and assign would be safe, but not much useful, so let's delete all (rule of 5)
|
||||
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,
|
||||
// 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() {
|
||||
concurrentTerminateWrapper([]() {
|
||||
if (auto currentException = std::current_exception()) {
|
||||
try {
|
||||
std::rethrow_exception(currentException);
|
||||
} catch (ExceptionObjHolder& e) {
|
||||
processUnhandledKotlinException(e.GetExceptionObject());
|
||||
konan::abort();
|
||||
// Use the reentrant switch because both states are possible here:
|
||||
// - 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 (...) {
|
||||
// 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.
|
||||
instance().queuedHandler_();
|
||||
});
|
||||
queuedHandler();
|
||||
}
|
||||
|
||||
using QH = __attribute__((noreturn)) void(*)();
|
||||
@@ -154,3 +171,25 @@ void SetKonanTerminateHandler() {
|
||||
}
|
||||
|
||||
#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.
|
||||
void ThrowException(KRef exception);
|
||||
|
||||
// RuntimeUtils.kt
|
||||
void OnUnhandledException(KRef throwable);
|
||||
|
||||
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef exception);
|
||||
|
||||
void SetKonanTerminateHandler();
|
||||
|
||||
RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder);
|
||||
@@ -68,4 +63,11 @@ void PrintThrowable(KRef);
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
namespace kotlin {
|
||||
|
||||
void ProcessUnhandledException(KRef exception) noexcept;
|
||||
void RUNTIME_NORETURN TerminateWithUnhandledException(KRef exception) noexcept;
|
||||
|
||||
} // namespace kotlin
|
||||
|
||||
#endif // RUNTIME_NAMES_H
|
||||
|
||||
287
kotlin-native/runtime/src/main/cpp/ExceptionsTest.cpp
Normal file
287
kotlin-native/runtime/src/main/cpp/ExceptionsTest.cpp
Normal file
@@ -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"
|
||||
"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;
|
||||
@@ -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 "
|
||||
"from Kotlin to Objective-C/Swift as NSError.\n"
|
||||
"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);
|
||||
|
||||
@@ -28,6 +28,7 @@ private:
|
||||
int32_t instanceSize_ = 0;
|
||||
KStdVector<int32_t> objOffsets_;
|
||||
int32_t flags_ = 0;
|
||||
const TypeInfo* superType_ = nullptr;
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -45,6 +46,11 @@ public:
|
||||
flags_ &= ~flag;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
ObjectBuilder&& setSuperType(const TypeInfo* superType) noexcept {
|
||||
superType_ = superType;
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Payload>
|
||||
@@ -75,6 +81,7 @@ public:
|
||||
typeInfo_.objOffsetsCount_ = objOffsets_.size();
|
||||
}
|
||||
typeInfo_.flags_ = builder.flags_;
|
||||
typeInfo_.superType_ = builder.superType_;
|
||||
}
|
||||
|
||||
TypeInfo* typeInfo() noexcept { return &typeInfo_; }
|
||||
|
||||
@@ -106,7 +106,7 @@ RuntimeState* initRuntime() {
|
||||
// Switch thread state because worker and globals inits require the runnable state.
|
||||
// This call may block if GC requested suspending threads.
|
||||
stateGuard = kotlin::ThreadStateGuard(result->memoryState, kotlin::ThreadState::kRunnable);
|
||||
result->worker = WorkerInit(result->memoryState, true);
|
||||
result->worker = WorkerInit(result->memoryState);
|
||||
firstRuntime = atomicAdd(&aliveRuntimesCount, 1) == 1;
|
||||
if (!kotlin::kSupportsMultipleMutators && !firstRuntime) {
|
||||
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.
|
||||
// This call may block if GC requested suspending threads.
|
||||
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);
|
||||
|
||||
@@ -60,3 +60,5 @@ private:
|
||||
|
||||
ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock();
|
||||
ScopedStrictMockFunction<void(KInt, bool)> ScopedShutdownCleanerWorkerMock();
|
||||
ScopedStrictMockFunction<void(KRef)> ScopedReportUnhandledExceptionMock();
|
||||
ScopedStrictMockFunction<void(KRef)> ScopedKotlin_runUnhandledExceptionHookMock();
|
||||
|
||||
@@ -49,6 +49,25 @@ OBJ_GETTER(WorkerLaunchpad, KRef);
|
||||
|
||||
} // 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
|
||||
|
||||
namespace {
|
||||
@@ -117,10 +136,10 @@ typedef KStdOrderedSet<Job, JobCompare> DelayedJobSet;
|
||||
|
||||
class Worker {
|
||||
public:
|
||||
Worker(KInt id, bool errorReporting, KRef customName, WorkerKind kind)
|
||||
Worker(KInt id, WorkerExceptionHandling exceptionHandling, KRef customName, WorkerKind kind)
|
||||
: id_(id),
|
||||
kind_(kind),
|
||||
errorReporting_(errorReporting) {
|
||||
exceptionHandling_(exceptionHandling) {
|
||||
name_ = customName != nullptr ? CreateStablePointer(customName) : nullptr;
|
||||
pthread_mutex_init(&lock_, nullptr);
|
||||
pthread_cond_init(&cond_, nullptr);
|
||||
@@ -147,7 +166,7 @@ class Worker {
|
||||
|
||||
KInt id() const { return id_; }
|
||||
|
||||
bool errorReporting() const { return errorReporting_; }
|
||||
WorkerExceptionHandling exceptionHandling() const { return exceptionHandling_; }
|
||||
|
||||
KNativePtr name() const { return name_; }
|
||||
|
||||
@@ -173,7 +192,7 @@ class Worker {
|
||||
memoryState_ = state;
|
||||
}
|
||||
|
||||
friend Worker* WorkerInit(MemoryState* memoryState, KBoolean errorReporting);
|
||||
friend Worker* WorkerInit(MemoryState* memoryState);
|
||||
|
||||
KInt id_;
|
||||
WorkerKind kind_;
|
||||
@@ -184,8 +203,7 @@ class Worker {
|
||||
// Lock and condition for waiting on the queue.
|
||||
pthread_mutex_t lock_;
|
||||
pthread_cond_t cond_;
|
||||
// If errors to be reported on console.
|
||||
bool errorReporting_;
|
||||
WorkerExceptionHandling exceptionHandling_;
|
||||
bool terminated_ = false;
|
||||
pthread_t thread_ = 0;
|
||||
// MemoryState for worker's thread.
|
||||
@@ -319,11 +337,11 @@ class State {
|
||||
pthread_cond_destroy(&cond_);
|
||||
}
|
||||
|
||||
Worker* addWorkerUnlocked(bool errorReporting, KRef customName, WorkerKind kind) {
|
||||
Worker* addWorkerUnlocked(WorkerExceptionHandling exceptionHandling, KRef customName, WorkerKind kind) {
|
||||
Worker* worker = nullptr;
|
||||
{
|
||||
Locker locker(&lock_);
|
||||
worker = konanConstructInstance<Worker>(nextWorkerId(), errorReporting, customName, kind);
|
||||
worker = konanConstructInstance<Worker>(nextWorkerId(), exceptionHandling, customName, kind);
|
||||
if (worker == nullptr) return nullptr;
|
||||
workers_[worker->id()] = worker;
|
||||
}
|
||||
@@ -642,8 +660,8 @@ void Future::cancelUnlocked(MemoryState* memoryState) {
|
||||
// Defined in RuntimeUtils.kt.
|
||||
extern "C" void ReportUnhandledException(KRef e);
|
||||
|
||||
KInt startWorker(KBoolean errorReporting, KRef customName) {
|
||||
Worker* worker = theState()->addWorkerUnlocked(errorReporting != 0, customName, WorkerKind::kNative);
|
||||
KInt startWorker(WorkerExceptionHandling exceptionHandling, KRef customName) {
|
||||
Worker* worker = theState()->addWorkerUnlocked(exceptionHandling, customName, WorkerKind::kNative);
|
||||
if (worker == nullptr) return -1;
|
||||
worker->startEventLoop();
|
||||
return worker->id();
|
||||
@@ -719,7 +737,7 @@ KNativePtr detachObjectGraphInternal(KInt transferMode, KRef producer) {
|
||||
|
||||
#else
|
||||
|
||||
KInt startWorker(KBoolean errorReporting, KRef customName) {
|
||||
KInt startWorker(WorkerExceptionHandling exceptionHandling, KRef customName) {
|
||||
ThrowWorkerUnsupported();
|
||||
}
|
||||
|
||||
@@ -787,13 +805,13 @@ KInt GetWorkerId(Worker* worker) {
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
Worker* WorkerInit(MemoryState* memoryState, KBoolean errorReporting) {
|
||||
Worker* WorkerInit(MemoryState* memoryState) {
|
||||
#if WITH_WORKERS
|
||||
Worker* worker;
|
||||
if (::g_worker != nullptr) {
|
||||
worker = ::g_worker;
|
||||
} else {
|
||||
worker = theState()->addWorkerUnlocked(errorReporting != 0, nullptr, WorkerKind::kOther);
|
||||
worker = theState()->addWorkerUnlocked(workerExceptionHandling(), nullptr, WorkerKind::kOther);
|
||||
::g_worker = worker;
|
||||
}
|
||||
worker->setThread(pthread_self());
|
||||
@@ -1039,8 +1057,15 @@ JobKind Worker::processQueueElement(bool blocking) {
|
||||
#endif
|
||||
WorkerLaunchpad(obj, dummyHolder.slot());
|
||||
} catch (ExceptionObjHolder& e) {
|
||||
if (errorReporting())
|
||||
ReportUnhandledException(e.GetExceptionObject());
|
||||
switch (exceptionHandling()) {
|
||||
case WorkerExceptionHandling::kIgnore: break;
|
||||
case WorkerExceptionHandling::kDefault:
|
||||
kotlin::ProcessUnhandledException(e.GetExceptionObject());
|
||||
break;
|
||||
case WorkerExceptionHandling::kLog:
|
||||
ReportUnhandledException(e.GetExceptionObject());
|
||||
break;
|
||||
}
|
||||
}
|
||||
DisposeStablePointer(job.executeAfter.operation);
|
||||
break;
|
||||
@@ -1061,8 +1086,14 @@ JobKind Worker::processQueueElement(bool blocking) {
|
||||
result = transfer(&resultHolder, job.regularJob.transferMode);
|
||||
} catch (ExceptionObjHolder& e) {
|
||||
ok = false;
|
||||
if (errorReporting())
|
||||
ReportUnhandledException(e.GetExceptionObject());
|
||||
switch (exceptionHandling()) {
|
||||
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.
|
||||
job.regularJob.future->storeResultUnlocked(result, ok);
|
||||
@@ -1079,8 +1110,8 @@ JobKind Worker::processQueueElement(bool blocking) {
|
||||
|
||||
extern "C" {
|
||||
|
||||
KInt Kotlin_Worker_startInternal(KBoolean noErrorReporting, KRef customName) {
|
||||
return startWorker(noErrorReporting, customName);
|
||||
KInt Kotlin_Worker_startInternal(KBoolean errorReporting, KRef customName) {
|
||||
return startWorker(errorReporting ? workerExceptionHandling() : WorkerExceptionHandling::kIgnore, customName);
|
||||
}
|
||||
|
||||
KInt Kotlin_Worker_currentInternal() {
|
||||
|
||||
@@ -8,7 +8,7 @@ class Worker;
|
||||
|
||||
KInt GetWorkerId(Worker* worker);
|
||||
|
||||
Worker* WorkerInit(MemoryState* memoryState, KBoolean errorReporting);
|
||||
Worker* WorkerInit(MemoryState* memoryState);
|
||||
void WorkerDeinit(Worker* worker);
|
||||
// Clean up all associated thread state, if this was a native worker.
|
||||
void WorkerDestroyThreadDataIfNeeded(KInt id);
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
package kotlin.native
|
||||
|
||||
import kotlin.native.concurrent.InvalidMutabilityException
|
||||
import kotlin.native.internal.ExportForCppRuntime
|
||||
import kotlin.native.internal.GCUnsafeCall
|
||||
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.
|
||||
@@ -51,8 +54,6 @@ public typealias ReportUnhandledExceptionHook = Function1<Throwable, Unit>
|
||||
* 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.
|
||||
* 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? {
|
||||
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.
|
||||
* @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
|
||||
* 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.
|
||||
* @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
|
||||
* 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,
|
||||
* @throws [IllegalArgumentException] on negative values of [afterMicroseconds].
|
||||
|
||||
@@ -29,10 +29,13 @@ private object EmptyCompletion : Continuation<Any?> {
|
||||
override val context: CoroutineContext
|
||||
get() = EmptyCoroutineContext
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun resumeWith(result: Result<Any?>) {
|
||||
val exception = result.exceptionOrNull() ?: return
|
||||
TerminateWithUnhandledException(exception)
|
||||
// Throwing the exception from [resumeWith] is not generally expected.
|
||||
processUnhandledException(exception)
|
||||
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.
|
||||
}
|
||||
}
|
||||
@@ -63,4 +66,4 @@ private external fun runCompletionSuccess(completionHolder: Any, result: Any?)
|
||||
|
||||
@FilterExceptions
|
||||
@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()
|
||||
}
|
||||
|
||||
@GCUnsafeCall("TerminateWithUnhandledException")
|
||||
internal external fun TerminateWithUnhandledException(throwable: Throwable)
|
||||
|
||||
// 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
|
||||
// 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
|
||||
@ExportForCppRuntime
|
||||
internal fun OnUnhandledException(throwable: Throwable) {
|
||||
val handler = UnhandledExceptionHookHolder.hook.swap(null)
|
||||
val handler = UnhandledExceptionHookHolder.hook.value
|
||||
if (handler == null) {
|
||||
ReportUnhandledException(throwable);
|
||||
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
|
||||
internal fun TheEmptyString() = ""
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ struct KBox {
|
||||
|
||||
testing::StrictMock<testing::MockFunction<KInt()>>* createCleanerWorkerMock = 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
|
||||
|
||||
@@ -190,15 +192,19 @@ void RUNTIME_NORETURN ThrowFreezingException(KRef toFreeze, KRef blocker) {
|
||||
}
|
||||
|
||||
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) {
|
||||
throw std::runtime_error("Not implemented for tests");
|
||||
}
|
||||
|
||||
void OnUnhandledException(KRef throwable) {
|
||||
throw std::runtime_error("Not implemented for tests");
|
||||
void Kotlin_runUnhandledExceptionHook(KRef throwable) {
|
||||
if (!Kotlin_runUnhandledExceptionHookMock) throw std::runtime_error("Not implemented for tests");
|
||||
|
||||
return Kotlin_runUnhandledExceptionHookMock->Call(throwable);
|
||||
}
|
||||
|
||||
void Kotlin_WorkerBoundReference_freezeHook(KRef thiz) {
|
||||
@@ -285,3 +291,11 @@ ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock() {
|
||||
ScopedStrictMockFunction<void(KInt, bool)> ScopedShutdownCleanerWorkerMock() {
|
||||
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