Move unhandledExceptionHook to kotlin code (#4704)

This commit is contained in:
Alexander Shabalin
2021-02-16 12:03:52 +03:00
committed by Vasily Levchenko
parent 3ee0dc112b
commit d5ca285a79
9 changed files with 62 additions and 64 deletions
@@ -2531,12 +2531,20 @@ standaloneTest("kt-37572") {
} }
standaloneTest("custom_hook") { 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" goldValue = "value 42: Error\n"
expectedExitStatus = 1 expectedExitStatus = 1
source = "runtime/exceptions/custom_hook.kt" 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") { standaloneTest("runtime_math_exceptions") {
enabled = (project.testTarget != 'wasm32') enabled = (project.testTarget != 'wasm32')
source = "stdlib_external/numbers/MathExceptionTest.kt" source = "stdlib_external/numbers/MathExceptionTest.kt"
@@ -6,31 +6,17 @@ import kotlin.test.*
import kotlin.native.concurrent.* import kotlin.native.concurrent.*
fun setHookLegacyMM(hook: ReportUnhandledExceptionHook) : ReportUnhandledExceptionHook? { fun main(args : Array<String>) {
assertFailsWith<InvalidMutabilityException> { assertFailsWith<InvalidMutabilityException> {
setUnhandledExceptionHook { _ -> println("wrong") } 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 x = 42
val old = setHook { val old = setUnhandledExceptionHook({
throwable: Throwable -> println("value $x: ${throwable::class.simpleName}") throwable: Throwable -> println("value $x: ${throwable::class.simpleName}")
} }.freeze())
assertNull(old) assertNull(old)
throw Error("an error") throw Error("an error")
} }
@@ -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")
}
@@ -48,14 +48,6 @@
namespace { 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 #if USE_GCC_UNWIND
struct Backtrace { struct Backtrace {
Backtrace(int count, int skip) : index(0), skipCount(skip) { Backtrace(int count, int skip) : index(0), skipCount(skip) {
@@ -220,23 +212,6 @@ void ThrowException(KRef exception) {
#endif #endif
} }
OBJ_GETTER(Kotlin_setUnhandledExceptionHook, KRef hook) {
RETURN_RESULT_OF(SwapHeapRefLocked,
&currentUnhandledExceptionHook, currentUnhandledExceptionHook, hook, &currentUnhandledExceptionHookLock,
&currentUnhandledExceptionHookCookie);
}
void OnUnhandledException(KRef throwable) {
ObjHolder handlerHolder;
auto* handler = SwapHeapRefLocked(&currentUnhandledExceptionHook, currentUnhandledExceptionHook, nullptr,
&currentUnhandledExceptionHookLock, &currentUnhandledExceptionHookCookie, handlerHolder.slot());
if (handler == nullptr) {
ReportUnhandledException(throwable);
} else {
ExceptionReporterLaunchpad(handler, throwable);
}
}
namespace { namespace {
class { class {
@@ -28,11 +28,10 @@ OBJ_GETTER0(Kotlin_getCurrentStackTrace);
OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace); OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace);
OBJ_GETTER(Kotlin_setUnhandledExceptionHook, KRef hook);
// Throws arbitrary exception. // Throws arbitrary exception.
void ThrowException(KRef exception); void ThrowException(KRef exception);
// RuntimeUtils.kt
void OnUnhandledException(KRef throwable); void OnUnhandledException(KRef throwable);
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef exception); RUNTIME_NORETURN void TerminateWithUnhandledException(KRef exception);
@@ -4,9 +4,8 @@
*/ */
package kotlin.native package kotlin.native
import kotlin.native.concurrent.isFrozen
import kotlin.native.concurrent.InvalidMutabilityException 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. * Initializes Kotlin runtime for the current thread, if not inited already.
@@ -45,19 +44,16 @@ public typealias ReportUnhandledExceptionHook = Function1<Throwable, Unit>
* with custom exception hooks. * with custom exception hooks.
*/ */
public fun setUnhandledExceptionHook(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook? { 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") throw InvalidMutabilityException("Unhandled exception hook must be frozen")
} }
return setUnhandledExceptionHook0(hook)
} }
@SymbolName("Kotlin_setUnhandledExceptionHook")
@Escapes(0b01) // <hook> escapes
external private fun setUnhandledExceptionHook0(hook: ReportUnhandledExceptionHook): ReportUnhandledExceptionHook?
/** /**
* 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.
*/ */
@SymbolName("Kotlin_Any_hashCode") @SymbolName("Kotlin_Any_hashCode")
public external fun Any?.identityHashCode(): Int public external fun Any?.identityHashCode(): Int
@@ -283,6 +283,19 @@ public class AtomicReference<T> {
public override fun toString(): String = public override fun toString(): String =
"${debugString(this)} -> ${debugString(value)}" "${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. // Implementation details.
@SymbolName("Kotlin_AtomicReference_set") @SymbolName("Kotlin_AtomicReference_set")
private external fun setImpl(new: Any?): Unit private external fun setImpl(new: Any?): Unit
@@ -7,6 +7,7 @@ package kotlin.native.internal
import kotlin.internal.getProgressionLastElement import kotlin.internal.getProgressionLastElement
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.native.concurrent.AtomicReference
@ExportForCppRuntime @ExportForCppRuntime
fun ThrowNullPointerException(): Nothing { fun ThrowNullPointerException(): Nothing {
@@ -118,10 +119,23 @@ internal fun ReportUnhandledException(throwable: Throwable) {
@SymbolName("TerminateWithUnhandledException") @SymbolName("TerminateWithUnhandledException")
internal external fun TerminateWithUnhandledException(throwable: Throwable) 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<ReportUnhandledExceptionHook?> = AtomicReference(null)
}
@PublishedApi
@ExportForCppRuntime @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 { try {
reporter(throwable) handler(throwable)
} catch (t: Throwable) { } catch (t: Throwable) {
ReportUnhandledException(t) ReportUnhandledException(t)
} }
@@ -210,8 +224,3 @@ internal fun <T> listOfInternal(vararg elements: T): List<T> {
result.add(elements[i]) result.add(elements[i])
return result return result
} }
@PublishedApi
@SymbolName("OnUnhandledException")
external internal fun OnUnhandledException(throwable: Throwable)
@@ -170,7 +170,7 @@ RUNTIME_NORETURN OBJ_GETTER(DescribeObjectForDebugging, KConstNativePtr typeInfo
throw std::runtime_error("Not implemented for tests"); 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"); throw std::runtime_error("Not implemented for tests");
} }