From d5ca285a7984d9bb21c841d3a890f4a967e26791 Mon Sep 17 00:00:00 2001 From: Alexander Shabalin Date: Tue, 16 Feb 2021 12:03:52 +0300 Subject: [PATCH] Move unhandledExceptionHook to kotlin code (#4704) --- .../backend.native/tests/build.gradle | 10 +++++++- .../tests/runtime/exceptions/custom_hook.kt | 24 ++++-------------- .../exceptions/exception_in_global_init.kt | 12 +++++++++ .../runtime/src/main/cpp/Exceptions.cpp | 25 ------------------- .../runtime/src/main/cpp/Exceptions.h | 3 +-- .../src/main/kotlin/kotlin/native/Runtime.kt | 14 ++++------- .../kotlin/native/concurrent/Atomics.kt | 13 ++++++++++ .../kotlin/native/internal/RuntimeUtils.kt | 23 +++++++++++------ .../test_support/cpp/CompilerGenerated.cpp | 2 +- 9 files changed, 62 insertions(+), 64 deletions(-) create mode 100644 kotlin-native/backend.native/tests/runtime/exceptions/exception_in_global_init.kt diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index f2d6430e992..fb5363322d1 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -2531,12 +2531,20 @@ standaloneTest("kt-37572") { } standaloneTest("custom_hook") { - enabled = (project.testTarget != 'wasm32') // Uses exceptions. + enabled = (project.testTarget != 'wasm32') && // Uses exceptions. + !isExperimentalMM // Experimental MM does not support freezing yet. goldValue = "value 42: Error\n" expectedExitStatus = 1 source = "runtime/exceptions/custom_hook.kt" } +standaloneTest("exception_in_global_init") { + enabled = (project.testTarget != 'wasm32') // Uses exceptions. + source = "runtime/exceptions/exception_in_global_init.kt" + expectedExitStatusChecker = { it != 0 } + outputChecker = { s -> s.contains("Uncaught Kotlin exception: kotlin.IllegalStateException: FAIL") && !s.contains("in kotlin main") } +} + standaloneTest("runtime_math_exceptions") { enabled = (project.testTarget != 'wasm32') source = "stdlib_external/numbers/MathExceptionTest.kt" diff --git a/kotlin-native/backend.native/tests/runtime/exceptions/custom_hook.kt b/kotlin-native/backend.native/tests/runtime/exceptions/custom_hook.kt index 8ce6fe326aa..edb2c682a6e 100644 --- a/kotlin-native/backend.native/tests/runtime/exceptions/custom_hook.kt +++ b/kotlin-native/backend.native/tests/runtime/exceptions/custom_hook.kt @@ -6,31 +6,17 @@ import kotlin.test.* import kotlin.native.concurrent.* -fun setHookLegacyMM(hook: ReportUnhandledExceptionHook) : ReportUnhandledExceptionHook? { +fun main(args : Array) { assertFailsWith { setUnhandledExceptionHook { _ -> println("wrong") } } - return setUnhandledExceptionHook(hook.freeze()) -} - -fun setHookNewMM(hook: ReportUnhandledExceptionHook) : ReportUnhandledExceptionHook? { - return setUnhandledExceptionHook(hook) -} - -fun setHook(hook: ReportUnhandledExceptionHook) : ReportUnhandledExceptionHook? { - return when (kotlin.native.Platform.memoryModel) { - kotlin.native.MemoryModel.EXPERIMENTAL -> setHookNewMM(hook) - else -> setHookLegacyMM(hook) - } -} - -fun main() { val x = 42 - val old = setHook { + val old = setUnhandledExceptionHook({ throwable: Throwable -> println("value $x: ${throwable::class.simpleName}") - } + }.freeze()) assertNull(old) + throw Error("an error") -} +} \ No newline at end of file diff --git a/kotlin-native/backend.native/tests/runtime/exceptions/exception_in_global_init.kt b/kotlin-native/backend.native/tests/runtime/exceptions/exception_in_global_init.kt new file mode 100644 index 00000000000..0e67064b833 --- /dev/null +++ b/kotlin-native/backend.native/tests/runtime/exceptions/exception_in_global_init.kt @@ -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. + */ + +import kotlin.test.* + +val p: Nothing = error("FAIL") + +fun main() { + println("in kotlin main") +} diff --git a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp b/kotlin-native/runtime/src/main/cpp/Exceptions.cpp index 8fd6c4088c8..a4cf68769d5 100644 --- a/kotlin-native/runtime/src/main/cpp/Exceptions.cpp +++ b/kotlin-native/runtime/src/main/cpp/Exceptions.cpp @@ -48,14 +48,6 @@ namespace { -// RuntimeUtils.kt -extern "C" void ReportUnhandledException(KRef throwable); -extern "C" void ExceptionReporterLaunchpad(KRef reporter, KRef throwable); - -KRef currentUnhandledExceptionHook = nullptr; -int32_t currentUnhandledExceptionHookLock = 0; -int32_t currentUnhandledExceptionHookCookie = 0; - #if USE_GCC_UNWIND struct Backtrace { Backtrace(int count, int skip) : index(0), skipCount(skip) { @@ -220,23 +212,6 @@ void ThrowException(KRef exception) { #endif } -OBJ_GETTER(Kotlin_setUnhandledExceptionHook, KRef hook) { - RETURN_RESULT_OF(SwapHeapRefLocked, - ¤tUnhandledExceptionHook, currentUnhandledExceptionHook, hook, ¤tUnhandledExceptionHookLock, - ¤tUnhandledExceptionHookCookie); -} - -void OnUnhandledException(KRef throwable) { - ObjHolder handlerHolder; - auto* handler = SwapHeapRefLocked(¤tUnhandledExceptionHook, currentUnhandledExceptionHook, nullptr, - ¤tUnhandledExceptionHookLock, ¤tUnhandledExceptionHookCookie, handlerHolder.slot()); - if (handler == nullptr) { - ReportUnhandledException(throwable); - } else { - ExceptionReporterLaunchpad(handler, throwable); - } -} - namespace { class { diff --git a/kotlin-native/runtime/src/main/cpp/Exceptions.h b/kotlin-native/runtime/src/main/cpp/Exceptions.h index 551b2d89bcf..f115c5f9d42 100644 --- a/kotlin-native/runtime/src/main/cpp/Exceptions.h +++ b/kotlin-native/runtime/src/main/cpp/Exceptions.h @@ -28,11 +28,10 @@ OBJ_GETTER0(Kotlin_getCurrentStackTrace); OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace); -OBJ_GETTER(Kotlin_setUnhandledExceptionHook, KRef hook); - // Throws arbitrary exception. void ThrowException(KRef exception); +// RuntimeUtils.kt void OnUnhandledException(KRef throwable); RUNTIME_NORETURN void TerminateWithUnhandledException(KRef exception); diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt index 28e4c74872d..331a7ce90bd 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/Runtime.kt @@ -4,9 +4,8 @@ */ package kotlin.native -import kotlin.native.concurrent.isFrozen import kotlin.native.concurrent.InvalidMutabilityException -import kotlin.native.internal.Escapes +import kotlin.native.internal.UnhandledExceptionHookHolder /** * Initializes Kotlin runtime for the current thread, if not inited already. @@ -45,19 +44,16 @@ public typealias ReportUnhandledExceptionHook = Function1 * with custom exception hooks. */ public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook? { - if (Platform.memoryModel != MemoryModel.EXPERIMENTAL && !hook.isFrozen) { + try { + return UnhandledExceptionHookHolder.hook.swap(hook) + } catch (e: InvalidMutabilityException) { throw InvalidMutabilityException("Unhandled exception hook must be frozen") } - return setUnhandledExceptionHook0(hook) } -@SymbolName("Kotlin_setUnhandledExceptionHook") -@Escapes(0b01) // escapes -external private fun setUnhandledExceptionHook0(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook? - /** * Compute stable wrt potential object relocations by the memory manager identity hash code. * @return 0 for `null` object, identity hash code otherwise. */ @SymbolName("Kotlin_Any_hashCode") -public external fun Any?.identityHashCode(): Int \ No newline at end of file +public external fun Any?.identityHashCode(): Int diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt index ed6ff354af8..1b28d5cfc49 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/concurrent/Atomics.kt @@ -283,6 +283,19 @@ public class AtomicReference { public override fun toString(): String = "${debugString(this)} -> ${debugString(value)}" + // TODO: Consider making this public. + internal fun swap(new: T): T { + while (true) { + val old = value + if (old === new) { + return old + } + if (compareAndSet(old, new)) { + return old + } + } + } + // Implementation details. @SymbolName("Kotlin_AtomicReference_set") private external fun setImpl(new: Any?): Unit diff --git a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt index f4cdde2d06d..2913c1f39b0 100644 --- a/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt +++ b/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt @@ -7,6 +7,7 @@ package kotlin.native.internal import kotlin.internal.getProgressionLastElement import kotlin.reflect.KClass +import kotlin.native.concurrent.AtomicReference @ExportForCppRuntime fun ThrowNullPointerException(): Nothing { @@ -118,10 +119,23 @@ internal fun ReportUnhandledException(throwable: Throwable) { @SymbolName("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. +internal object UnhandledExceptionHookHolder { + internal val hook: AtomicReference = AtomicReference(null) +} + +@PublishedApi @ExportForCppRuntime -internal fun ExceptionReporterLaunchpad(reporter: (Throwable) -> Unit, throwable: Throwable) { +internal fun OnUnhandledException(throwable: Throwable) { + val handler = UnhandledExceptionHookHolder.hook.swap(null) + if (handler == null) { + ReportUnhandledException(throwable); + return + } try { - reporter(throwable) + handler(throwable) } catch (t: Throwable) { ReportUnhandledException(t) } @@ -210,8 +224,3 @@ internal fun listOfInternal(vararg elements: T): List { result.add(elements[i]) return result } - - -@PublishedApi -@SymbolName("OnUnhandledException") -external internal fun OnUnhandledException(throwable: Throwable) diff --git a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp index 16d087cf208..05bab08ed91 100644 --- a/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp +++ b/kotlin-native/runtime/src/test_support/cpp/CompilerGenerated.cpp @@ -170,7 +170,7 @@ RUNTIME_NORETURN OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo throw std::runtime_error("Not implemented for tests"); } -void ExceptionReporterLaunchpad(KRef reporter, KRef throwable) { +void OnUnhandledException(KRef throwable) { throw std::runtime_error("Not implemented for tests"); }