Files
kotlin/kotlin-native/runtime/src/main/cpp/Exceptions.cpp
Alexander Shabalin 7e04bb4bf1 [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.
2021-08-10 08:22:55 +00:00

196 lines
6.7 KiB
C++

/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <exception>
#include <unistd.h>
#include "KAssert.h"
#include "Exceptions.h"
#include "ExecFormat.h"
#include "Memory.h"
#include "Mutex.hpp"
#include "Types.h"
#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");
#if KONAN_NO_EXCEPTIONS
PrintThrowable(exception);
RuntimeCheck(false, "Exceptions unsupported");
#else
ExceptionObjHolder::Throw(exception);
#endif
}
namespace {
class {
/**
* Timeout 5 sec for concurrent (second) terminate attempt to give a chance the first one to finish.
* If the terminate handler hangs for 5 sec it is probably fatally broken, so let's do abnormal _Exit in that case.
*/
unsigned int timeoutSec = 5;
int terminatingFlag = 0;
public:
template <class Fun> RUNTIME_NORETURN void operator()(Fun block) {
if (compareAndSet(&terminatingFlag, 0, 1)) {
block();
// block() is supposed to be NORETURN, otherwise go to normal abort()
konan::abort();
} else {
sleep(timeoutSec);
// We come here when another terminate handler hangs for 5 sec, that looks fatally broken. Go to forced exit now.
}
_Exit(EXIT_FAILURE); // force exit
}
} concurrentTerminateWrapper;
void RUNTIME_NORETURN terminateWithUnhandledException(KRef exception) {
kotlin::AssertThreadState(kotlin::ThreadState::kRunnable);
concurrentTerminateWrapper([exception]() {
ReportUnhandledException(exception);
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
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
ALWAYS_INLINE RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder) {
#if !KONAN_NO_EXCEPTIONS
RETURN_OBJ(static_cast<ExceptionObjHolder*>(holder)->GetExceptionObject());
#else
RETURN_OBJ(nullptr);
#endif
}
#if !KONAN_NO_EXCEPTIONS
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() {
if (auto currentException = std::current_exception()) {
try {
std::rethrow_exception(currentException);
} catch (ExceptionObjHolder& e) {
// 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
queuedHandler();
}
}
// Come here in case of direct terminate() call or unknown exception - go to default terminate handler.
queuedHandler();
}
using QH = __attribute__((noreturn)) void(*)();
QH queuedHandler_;
/// Use machinery like Meyers singleton to provide thread safety
TerminateHandler()
: queuedHandler_((QH)std::set_terminate(kotlinHandler)) {}
static TerminateHandler& instance() {
static TerminateHandler singleton [[clang::no_destroy]];
return singleton;
}
// Dtor might be in use to restore original handler. However, consequent install
// will not reconstruct handler anyway, so let's keep dtor deleted to avoid confusion.
~TerminateHandler() = delete;
public:
/// First call will do the job, all consequent will do nothing.
static void install() {
instance(); // Use side effect of warming up
}
};
} // anon namespace
// Use one public function to limit access to the class declaration
void SetKonanTerminateHandler() {
TerminateHandler::install();
}
#else // !KONAN_NO_EXCEPTIONS
void SetKonanTerminateHandler() {
// Nothing to do.
}
#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);
}