Files
kotlin/kotlin-native/runtime/src/main/cpp/Exceptions.cpp
2021-02-24 10:53:42 +01:00

322 lines
10 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>
#if KONAN_NO_EXCEPTIONS
#define OMIT_BACKTRACE 1
#endif
#ifndef OMIT_BACKTRACE
#if USE_GCC_UNWIND
// GCC unwinder for backtrace.
#include <unwind.h>
#else
// Glibc backtrace() function.
#include <execinfo.h>
#endif
#endif // OMIT_BACKTRACE
#include "KAssert.h"
#include "Exceptions.h"
#include "ExecFormat.h"
#include "Memory.h"
#include "Mutex.hpp"
#include "Natives.h"
#include "KString.h"
#include "SourceInfo.h"
#include "Types.h"
#include "Utils.hpp"
#include "ObjCExceptions.h"
namespace {
#if USE_GCC_UNWIND
struct Backtrace {
Backtrace(int count, int skip) : index(0), skipCount(skip) {
uint32_t size = count - skipCount;
if (size < 0) {
size = 0;
}
auto result = AllocArrayInstance(theNativePtrArrayTypeInfo, size, arrayHolder.slot());
// TODO: throw cached OOME?
RuntimeCheck(result != nullptr, "Cannot create backtrace array");
}
void setNextElement(_Unwind_Ptr element) {
Kotlin_NativePtrArray_set(obj(), index++, (KNativePtr) element);
}
ObjHeader* obj() { return arrayHolder.obj(); }
int index;
int skipCount;
ObjHolder arrayHolder;
};
_Unwind_Reason_Code depthCountCallback(
struct _Unwind_Context * context, void* arg) {
int* result = reinterpret_cast<int*>(arg);
(*result)++;
return _URC_NO_REASON;
}
_Unwind_Reason_Code unwindCallback(
struct _Unwind_Context* context, void* arg) {
Backtrace* backtrace = reinterpret_cast<Backtrace*>(arg);
if (backtrace->skipCount > 0) {
backtrace->skipCount--;
return _URC_NO_REASON;
}
#if (__MINGW32__ || __MINGW64__)
_Unwind_Ptr address = _Unwind_GetRegionStart(context);
#else
_Unwind_Ptr address = _Unwind_GetIP(context);
#endif
backtrace->setNextElement(address);
return _URC_NO_REASON;
}
#endif
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
#if !OMIT_BACKTRACE && !USE_GCC_UNWIND
SourceInfo getSourceInfo(KConstRef stackTrace, int index) {
return disallowSourceInfo
? SourceInfo { .fileName = nullptr, .lineNumber = -1, .column = -1 }
: Kotlin_getSourceInfo(*PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), index));
}
#endif
} // namespace
// TODO: this implementation is just a hack, e.g. the result is inexact;
// however it is better to have an inexact stacktrace than not to have any.
NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) {
#if OMIT_BACKTRACE
return AllocArrayInstance(theNativePtrArrayTypeInfo, 0, OBJ_RESULT);
#else
// Skips first 2 elements as irrelevant: this function and primary Throwable constructor.
constexpr int kSkipFrames = 2;
#if USE_GCC_UNWIND
int depth = 0;
_Unwind_Backtrace(depthCountCallback, &depth);
Backtrace result(depth, kSkipFrames);
if (result.obj()->array()->count_ > 0) {
_Unwind_Backtrace(unwindCallback, &result);
}
RETURN_OBJ(result.obj());
#else
const int maxSize = 32;
void* buffer[maxSize];
int size = backtrace(buffer, maxSize);
if (size < kSkipFrames)
return AllocArrayInstance(theNativePtrArrayTypeInfo, 0, OBJ_RESULT);
ObjHolder resultHolder;
ObjHeader* result = AllocArrayInstance(theNativePtrArrayTypeInfo, size - kSkipFrames, resultHolder.slot());
for (int index = kSkipFrames; index < size; ++index) {
Kotlin_NativePtrArray_set(result, index - kSkipFrames, buffer[index]);
}
RETURN_OBJ(result);
#endif
#endif // !OMIT_BACKTRACE
}
OBJ_GETTER(GetStackTraceStrings, KConstRef stackTrace) {
#if OMIT_BACKTRACE
ObjHeader* result = AllocArrayInstance(theArrayTypeInfo, 1, OBJ_RESULT);
ObjHolder holder;
CreateStringFromCString("<UNIMPLEMENTED>", holder.slot());
UpdateHeapRef(ArrayAddressOfElementAt(result->array(), 0), holder.obj());
return result;
#else
uint32_t size = stackTrace->array()->count_;
ObjHolder resultHolder;
ObjHeader* strings = AllocArrayInstance(theArrayTypeInfo, size, resultHolder.slot());
#if USE_GCC_UNWIND
for (uint32_t index = 0; index < size; ++index) {
KNativePtr address = Kotlin_NativePtrArray_get(stackTrace, index);
char symbol[512];
if (!AddressToSymbol((const void*) address, symbol, sizeof(symbol))) {
// Make empty string:
symbol[0] = '\0';
}
char line[512];
konan::snprintf(line, sizeof(line) - 1, "%s (%p)", symbol, (void*)(intptr_t)address);
ObjHolder holder;
CreateStringFromCString(line, holder.slot());
UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
}
#else
if (size > 0) {
char **symbols = backtrace_symbols(PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), 0), size);
RuntimeCheck(symbols != nullptr, "Not enough memory to retrieve the stacktrace");
for (uint32_t index = 0; index < size; ++index) {
auto sourceInfo = getSourceInfo(stackTrace, index);
const char* symbol = symbols[index];
const char* result;
char line[1024];
if (sourceInfo.fileName != nullptr) {
if (sourceInfo.lineNumber != -1) {
konan::snprintf(line, sizeof(line) - 1, "%s (%s:%d:%d)",
symbol, sourceInfo.fileName, sourceInfo.lineNumber, sourceInfo.column);
} else {
konan::snprintf(line, sizeof(line) - 1, "%s (%s:<unknown>)", symbol, sourceInfo.fileName);
}
result = line;
} else {
result = symbol;
}
ObjHolder holder;
CreateStringFromCString(result, holder.slot());
UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
}
// Not konan::free. Used to free memory allocated in backtrace_symbols where malloc is used.
free(symbols);
}
#endif
RETURN_OBJ(strings);
#endif // !OMIT_BACKTRACE
}
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
throw ExceptionObjHolder(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;
//! Process exception hook (if any) or just printStackTrace + write crash log
void processUnhandledKotlinException(KRef throwable) {
OnUnhandledException(throwable);
#if KONAN_REPORT_BACKTRACE_TO_IOS_CRASH_LOG
ReportBacktraceToIosCrashLog(throwable);
#endif
}
} // namespace
RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
concurrentTerminateWrapper([=]() {
processUnhandledKotlinException(throwable);
konan::abort();
});
}
// Some libstdc++-based targets has limited support for std::current_exception and other C++11 functions.
// This restriction can be lifted later when toolchains will be updated.
#if KONAN_HAS_CXX11_EXCEPTION_FUNCTIONS
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 {
// 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() {
concurrentTerminateWrapper([]() {
if (auto currentException = std::current_exception()) {
try {
std::rethrow_exception(currentException);
} catch (ExceptionObjHolder& e) {
processUnhandledKotlinException(e.obj());
konan::abort();
} catch (...) {
// Not a Kotlin exception - call default handler
instance().queuedHandler_();
}
}
// Come here in case of direct terminate() call or unknown exception - go to default terminate handler.
instance().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_OBJC_INTEROP
void SetKonanTerminateHandler() {
// Nothing to do.
}
#endif // KONAN_OBJC_INTEROP
void DisallowSourceInfo() {
disallowSourceInfo = true;
}