From 62755590a5a5fe7d29ce1c8372779c11b6377776 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 5 Sep 2017 11:19:28 +0300 Subject: [PATCH] Add uncaught exception handler when Objective-C interop is enabled UIApplicationMain doesn't forward exceptions to the caller, so catching them in Kotlin main doesn't have any effect. --- runtime/src/main/cpp/Exceptions.cpp | 47 +++++++++++++++++++ runtime/src/main/cpp/Exceptions.h | 2 + runtime/src/main/cpp/Runtime.cpp | 2 + .../kotlin/konan/internal/RuntimeUtils.kt | 6 +++ 4 files changed, 57 insertions(+) diff --git a/runtime/src/main/cpp/Exceptions.cpp b/runtime/src/main/cpp/Exceptions.cpp index 6603bf3ba06..d31947b5032 100644 --- a/runtime/src/main/cpp/Exceptions.cpp +++ b/runtime/src/main/cpp/Exceptions.cpp @@ -18,6 +18,8 @@ #include #include +#include + #if KONAN_NO_EXCEPTIONS #define OMIT_BACKTRACE 1 #endif @@ -38,6 +40,7 @@ #include "Natives.h" #include "KString.h" #include "Types.h" +#include "Utils.h" namespace { @@ -175,6 +178,50 @@ void ThrowException(KRef exception) { #endif } +#if KONAN_OBJC_INTEROP + +void ReportUnhandledException(KRef e); + +static void (*oldTerminateHandler)() = nullptr; + +static void KonanTerminateHandler() { + auto currentException = std::current_exception(); + if (!currentException) { + // No current exception. + oldTerminateHandler(); + } else { + try { + std::rethrow_exception(currentException); + } catch (ObjHolder& e) { + ReportUnhandledException(e.obj()); + konan::abort(); + } catch (...) { + // Not a Kotlin exception. + oldTerminateHandler(); + } + } +} + +static SimpleMutex konanTerminateHandlerInitializationMutex; + +void SetKonanTerminateHandler() { + if (oldTerminateHandler != nullptr) return; // Already initialized. + + LockGuard lockGuard(konanTerminateHandlerInitializationMutex); + + if (oldTerminateHandler != nullptr) return; // Already initialized. + + oldTerminateHandler = std::get_terminate(); // If termination happens between `set_terminate` and assignment. + oldTerminateHandler = std::set_terminate(&KonanTerminateHandler); +} + +#else // KONAN_OBJC_INTEROP + +void SetKonanTerminateHandler() { + // Nothing to do. +} + +#endif // KONAN_OBJC_INTEROP #ifdef __cplusplus } // extern "C" diff --git a/runtime/src/main/cpp/Exceptions.h b/runtime/src/main/cpp/Exceptions.h index 3b38f130c1e..8ed2a5f71dc 100644 --- a/runtime/src/main/cpp/Exceptions.h +++ b/runtime/src/main/cpp/Exceptions.h @@ -29,6 +29,8 @@ OBJ_GETTER0(GetCurrentStackTrace); // Throws arbitrary exception. void ThrowException(KRef exception); +void SetKonanTerminateHandler(); + // The functions below are implemented in Kotlin (at package konan.internal). // Throws null pointer exception. Context is evaluated from caller's address. diff --git a/runtime/src/main/cpp/Runtime.cpp b/runtime/src/main/cpp/Runtime.cpp index 6a90d9aa3fe..8b958813784 100644 --- a/runtime/src/main/cpp/Runtime.cpp +++ b/runtime/src/main/cpp/Runtime.cpp @@ -17,6 +17,7 @@ #include #include "Alloc.h" +#include "Exceptions.h" #include "Memory.h" #include "Porting.h" #include "Runtime.h" @@ -60,6 +61,7 @@ void AppendToInitializersTail(InitNode *next) { // TODO: properly use RuntimeState. RuntimeState* InitRuntime() { + SetKonanTerminateHandler(); RuntimeState* result = konanConstructInstance(); result->memoryState = InitMemory(); // Keep global variables in state as well. diff --git a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt index a4f64fd6bd3..7c9d634c963 100644 --- a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt @@ -66,6 +66,12 @@ fun PrintThrowable(throwable: Throwable) { println(throwable) } +@ExportForCppRuntime +fun ReportUnhandledException(e: Throwable) { + print("Uncaught Kotlin exception: ") + e.printStackTrace() +} + @ExportForCppRuntime internal fun TheEmptyString() = ""