Move unhandledExceptionHook to kotlin code (#4704)
This commit is contained in:
committed by
Vasily Levchenko
parent
3ee0dc112b
commit
d5ca285a79
@@ -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,
|
|
||||||
¤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 {
|
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,16 +44,13 @@ 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.
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user