Track current exception (#4701)

This commit is contained in:
Alexander Shabalin
2021-02-21 11:35:03 +03:00
committed by Vasily Levchenko
parent a38e64a7c8
commit 3a5c6480a3
17 changed files with 319 additions and 64 deletions
@@ -461,7 +461,7 @@ private class ExportedElement(val kind: ElementKind,
"result", cfunction[0], Direction.KOTLIN_TO_C, builder)
builder.append(" return $result;\n")
}
builder.append(" } catch (ExceptionObjHolder& e) { TerminateWithUnhandledException(e.obj()); } \n")
builder.append(" } catch (ExceptionObjHolder& e) { TerminateWithUnhandledException(e.GetExceptionObject()); } \n")
builder.append("}\n")
@@ -905,7 +905,6 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi
|#define RUNTIME_NORETURN __attribute__((noreturn))
|
|extern "C" {
|void UpdateHeapRef(KObjHeader**, const KObjHeader*) RUNTIME_NOTHROW;
|void UpdateStackRef(KObjHeader**, const KObjHeader*) RUNTIME_NOTHROW;
|KObjHeader* AllocInstance(const KTypeInfo*, KObjHeader**) RUNTIME_NOTHROW;
|KObjHeader* DerefStablePointer(void*, KObjHeader**) RUNTIME_NOTHROW;
@@ -951,16 +950,10 @@ internal class CAdapterGenerator(val context: Context) : DeclarationDescriptorVi
|};
|
|class ExceptionObjHolder {
| public:
| explicit ExceptionObjHolder(const KObjHeader* obj): obj_(nullptr) {
| ::UpdateHeapRef(&obj_, obj);
| }
| ~ExceptionObjHolder() {
| UpdateHeapRef(&obj_, nullptr);
| }
| KObjHeader* obj() { return obj_; }
| private:
| KObjHeader* obj_;
|public:
| virtual ~ExceptionObjHolder() = default;
|
| KObjHeader* GetExceptionObject() noexcept;
|};
|
|static void DisposeStablePointerImpl(${prefix}_KNativePtr ptr) {
@@ -862,14 +862,10 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
val beginCatch = context.llvm.cxaBeginCatchFunction
val exceptionRawPtr = call(beginCatch, listOf(exceptionRecord))
// Pointer to KotlinException instance:
val exceptionPtrPtr = bitcast(codegen.kObjHeaderPtrPtr, exceptionRawPtr, "")
// Pointer to Kotlin exception object:
// We do need a slot here, as otherwise exception instance could be freed by _cxa_end_catch.
val exceptionPtr = loadSlot(exceptionPtrPtr, true, "exception")
val exceptionPtr = call(context.llvm.Kotlin_getExceptionObject, listOf(exceptionRawPtr), Lifetime.GLOBAL)
// __cxa_end_catch performs some C++ cleanup, including calling `KotlinException` class destructor.
// __cxa_end_catch performs some C++ cleanup, including calling `ExceptionObjHolder` class destructor.
val endCatch = context.llvm.cxaEndCatchFunction
call(endCatch, listOf())
@@ -516,6 +516,7 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val checkLifetimesConstraint = importRtFunction("CheckLifetimesConstraint")
val freezeSubgraph = importRtFunction("FreezeSubgraph")
val checkGlobalsAccessible = importRtFunction("CheckGlobalsAccessible")
val Kotlin_getExceptionObject = importRtFunction("Kotlin_getExceptionObject")
val kRefSharedHolderInitLocal = importRtFunction("KRefSharedHolder_initLocal")
val kRefSharedHolderInit = importRtFunction("KRefSharedHolder_init")
@@ -2545,6 +2545,16 @@ standaloneTest("exception_in_global_init") {
outputChecker = { s -> s.contains("Uncaught Kotlin exception: kotlin.IllegalStateException: FAIL") && !s.contains("in kotlin main") }
}
task rethrow_exception(type: KonanLocalTest) {
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
source = "runtime/exceptions/rethrow.kt"
}
task throw_from_catch(type: KonanLocalTest) {
enabled = (project.testTarget != 'wasm32') // Uses exceptions.
source = "runtime/exceptions/throw_from_catch.kt"
}
standaloneTest("runtime_math_exceptions") {
enabled = (project.testTarget != 'wasm32')
source = "stdlib_external/numbers/MathExceptionTest.kt"
@@ -0,0 +1,19 @@
/*
* 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.
*/
package runtime.exceptions.rethtow
import kotlin.test.*
@Test
fun runTest() {
assertFailsWith<IllegalStateException>("My error") {
try {
error("My error")
} catch (e: Throwable) {
throw e
}
}
}
@@ -0,0 +1,19 @@
/*
* 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.
*/
package runtime.exceptions.throw_from_catch
import kotlin.test.*
@Test
fun runTest() {
assertFailsWith<IllegalStateException>("My another error") {
try {
error("My error")
} catch (e: Throwable) {
error("My another error")
}
}
}
@@ -440,6 +440,20 @@ void setContainerFor(ObjHeader* obj, ContainerHeader* container) {
obj->typeInfoOrMeta_ = setPointerBits(obj->typeInfoOrMeta_, OBJECT_TAG_NONTRIVIAL_CONTAINER);
}
#if !KONAN_NO_EXCEPTIONS
class ExceptionObjHolderImpl : public ExceptionObjHolder {
public:
explicit ExceptionObjHolderImpl(ObjHeader* obj) noexcept { ::SetHeapRef(&obj_, obj); }
~ExceptionObjHolderImpl() override { ZeroHeapRef(&obj_); }
ObjHeader* obj() noexcept { return obj_; }
private:
ObjHeader* obj_;
};
#endif
} // namespace
ContainerHeader* containerFor(const ObjHeader* obj) {
@@ -3691,3 +3705,14 @@ ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_safePointExceptionUnwind() {
}
} // extern "C"
#if !KONAN_NO_EXCEPTIONS
// static
ALWAYS_INLINE RUNTIME_NORETURN void ExceptionObjHolder::Throw(ObjHeader* exception) {
throw ExceptionObjHolderImpl(exception);
}
ALWAYS_INLINE ObjHeader* ExceptionObjHolder::GetExceptionObject() noexcept {
return static_cast<ExceptionObjHolderImpl*>(this)->obj();
}
#endif
@@ -208,7 +208,7 @@ void ThrowException(KRef exception) {
PrintThrowable(exception);
RuntimeCheck(false, "Exceptions unsupported");
#else
throw ExceptionObjHolder(exception);
ExceptionObjHolder::Throw(exception);
#endif
}
@@ -252,6 +252,14 @@ RUNTIME_NORETURN void TerminateWithUnhandledException(KRef throwable) {
});
}
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 {
@@ -266,7 +274,7 @@ class TerminateHandler : private kotlin::Pinned {
try {
std::rethrow_exception(currentException);
} catch (ExceptionObjHolder& e) {
processUnhandledKotlinException(e.obj());
processUnhandledKotlinException(e.GetExceptionObject());
konan::abort();
} catch (...) {
// Not a Kotlin exception - call default handler
@@ -38,6 +38,8 @@ RUNTIME_NORETURN void TerminateWithUnhandledException(KRef exception);
void SetKonanTerminateHandler();
RUNTIME_NOTHROW OBJ_GETTER(Kotlin_getExceptionObject, void* holder);
// The functions below are implemented in Kotlin (at package kotlin.native.internal).
// Throws null pointer exception. Context is evaluated from caller's address.
+8 -14
View File
@@ -22,6 +22,7 @@
#include "TypeInfo.h"
#include "Atomic.h"
#include "PointerBits.h"
#include "Utils.hpp"
typedef enum {
// Must match to permTag() in Kotlin.
@@ -356,23 +357,16 @@ class ObjHolder {
ObjHeader* obj_;
};
//! TODO Follow the Rule of Zero to prevent dangling on unintented copy ctor
class ExceptionObjHolder {
public:
explicit ExceptionObjHolder(const ObjHeader* obj) {
::SetHeapRef(&obj_, obj);
}
public:
#if !KONAN_NO_EXCEPTIONS
static void Throw(ObjHeader* exception) RUNTIME_NORETURN;
~ExceptionObjHolder() {
ZeroHeapRef(&obj_);
}
ObjHeader* GetExceptionObject() noexcept;
#endif
ObjHeader* obj() { return obj_; }
const ObjHeader* obj() const { return obj_; }
private:
ObjHeader* obj_;
// Exceptions are not on a hot path, so having virtual dispatch is fine.
virtual ~ExceptionObjHolder() = default;
};
#endif // RUNTIME_MEMORY_H
@@ -144,6 +144,11 @@ public:
deletionQueue_ = std::move(remainingDeletions);
}
void ClearForTests() noexcept {
queue_.clear();
deletionQueue_.clear();
}
private:
// Using `KStdList` as it allows to implement `Collect` without memory allocations,
// which is important for GC mark phase.
@@ -959,7 +959,7 @@ JobKind Worker::processQueueElement(bool blocking) {
WorkerLaunchpad(obj, dummyHolder.slot());
} catch (ExceptionObjHolder& e) {
if (errorReporting())
ReportUnhandledException(e.obj());
ReportUnhandledException(e.GetExceptionObject());
}
DisposeStablePointer(job.executeAfter.operation);
break;
@@ -979,7 +979,7 @@ JobKind Worker::processQueueElement(bool blocking) {
} catch (ExceptionObjHolder& e) {
ok = false;
if (errorReporting())
ReportUnhandledException(e.obj());
ReportUnhandledException(e.GetExceptionObject());
}
// Notify the future.
job.regularJob.future->storeResultUnlocked(result, ok);
@@ -0,0 +1,47 @@
/*
* 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.
*/
#include "Memory.h"
#include "StableRefRegistry.hpp"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
using namespace kotlin;
namespace {
#if !KONAN_NO_EXCEPTIONS
class ExceptionObjHolderImpl : public ExceptionObjHolder {
public:
explicit ExceptionObjHolderImpl(ObjHeader* obj) noexcept {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
stableRef_ = threadData->stableRefThreadQueue().Insert(obj);
}
~ExceptionObjHolderImpl() override {
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->stableRefThreadQueue().Erase(stableRef_);
}
ObjHeader* obj() noexcept { return **stableRef_; }
private:
mm::StableRefRegistry::Node* stableRef_;
};
#endif
} // namespace
#if !KONAN_NO_EXCEPTIONS
// static
RUNTIME_NORETURN void ExceptionObjHolder::Throw(ObjHeader* exception) {
throw ExceptionObjHolderImpl(exception);
}
ObjHeader* ExceptionObjHolder::GetExceptionObject() noexcept {
return static_cast<ExceptionObjHolderImpl*>(this)->obj();
}
#endif
@@ -0,0 +1,109 @@
/*
* 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.
*/
#include <thread>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "Memory.h"
#include "TestSupport.hpp"
#include "ThreadData.hpp"
#include "Types.h"
using namespace kotlin;
namespace {
class ExceptionObjHolderTest : public ::testing::Test {
public:
~ExceptionObjHolderTest() {
auto& stableRefs = mm::StableRefRegistry::Instance();
stableRefs.ClearForTests();
}
static KStdVector<ObjHeader*> Collect(mm::ThreadData& threadData) {
auto& stableRefs = mm::StableRefRegistry::Instance();
stableRefs.ProcessThread(&threadData);
stableRefs.ProcessDeletions();
KStdVector<ObjHeader*> result;
for (const auto& obj : stableRefs.Iter()) {
result.push_back(obj);
}
return result;
}
private:
};
} // namespace
TEST_F(ExceptionObjHolderTest, NothingByDefault) {
mm::RunInNewThread([](mm::ThreadData& threadData) { EXPECT_THAT(Collect(threadData), testing::IsEmpty()); });
}
TEST_F(ExceptionObjHolderTest, Throw) {
mm::RunInNewThread([](mm::ThreadData& threadData) {
ASSERT_THAT(Collect(threadData), testing::IsEmpty());
ObjHeader exception;
try {
ExceptionObjHolder::Throw(&exception);
} catch (...) {
EXPECT_THAT(Collect(threadData), testing::ElementsAre(&exception));
}
EXPECT_THAT(Collect(threadData), testing::IsEmpty());
});
}
TEST_F(ExceptionObjHolderTest, ThrowInsideCatch) {
mm::RunInNewThread([](mm::ThreadData& threadData) {
ASSERT_THAT(Collect(threadData), testing::IsEmpty());
ObjHeader exception1;
try {
ExceptionObjHolder::Throw(&exception1);
} catch (...) {
ObjHeader exception2;
try {
ExceptionObjHolder::Throw(&exception2);
} catch (...) {
EXPECT_THAT(Collect(threadData), testing::ElementsAre(&exception1, &exception2));
}
EXPECT_THAT(Collect(threadData), testing::ElementsAre(&exception1));
}
EXPECT_THAT(Collect(threadData), testing::IsEmpty());
});
}
TEST_F(ExceptionObjHolderTest, StoreException) {
mm::RunInNewThread([](mm::ThreadData& threadData) {
ASSERT_THAT(Collect(threadData), testing::IsEmpty());
ObjHeader exception1;
std::exception_ptr storedException1;
try {
ExceptionObjHolder::Throw(&exception1);
} catch (...) {
storedException1 = std::current_exception();
}
EXPECT_THAT(Collect(threadData), testing::ElementsAre(&exception1));
ObjHeader exception2;
std::exception_ptr storedException2;
try {
ExceptionObjHolder::Throw(&exception2);
} catch (...) {
storedException2 = std::current_exception();
}
EXPECT_THAT(Collect(threadData), testing::ElementsAre(&exception1, &exception2));
storedException1 = std::exception_ptr();
EXPECT_THAT(Collect(threadData), testing::ElementsAre(&exception2));
storedException2 = std::exception_ptr();
EXPECT_THAT(Collect(threadData), testing::IsEmpty());
});
}
@@ -48,6 +48,8 @@ public:
// much of a problem is it.
Iterable Iter() noexcept { return stableRefs_.Iter(); }
void ClearForTests() noexcept { stableRefs_.ClearForTests(); }
private:
// Current approach optimizes for creating and disposing of stable refs:
// * creation just enqueues ref, disposing either queues or deletes the ref immediately (if it still resides in the current queue).
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2020 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.
*/
#include "../../main/cpp/TestSupport.hpp"
#include <thread>
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
namespace kotlin {
namespace mm {
template <typename F>
void RunInNewThread(F f) {
std::thread([&f]() {
class ScopedRegistration : private kotlin::Pinned {
public:
ScopedRegistration() : node_(mm::ThreadRegistry::Instance().RegisterCurrentThread()) {}
~ScopedRegistration() { mm::ThreadRegistry::Instance().Unregister(node_); }
mm::ThreadData& threadData() { return *node_->Get(); }
private:
mm::ThreadRegistry::Node* node_;
} registration;
f(registration.threadData());
}).join();
}
} // namespace mm
} // namespace kotlin
@@ -7,69 +7,58 @@
#include "gtest/gtest.h"
#include "TestSupport.hpp"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
#include "ThreadState.hpp"
using namespace kotlin;
TEST(ThreadStateTest, StateSwitch) {
std::thread t([]() {
mm::ThreadRegistry::Instance().RegisterCurrentThread();
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
auto initialState = threadData->state();
mm::RunInNewThread([](mm::ThreadData& threadData) {
auto initialState = threadData.state();
EXPECT_EQ(mm::ThreadState::kRunnable, initialState);
mm::ThreadState oldState = mm::SwitchThreadState(threadData, mm::ThreadState::kNative);
mm::ThreadState oldState = mm::SwitchThreadState(&threadData, mm::ThreadState::kNative);
EXPECT_EQ(initialState, oldState);
EXPECT_EQ(mm::ThreadState::kNative, threadData->state());
EXPECT_EQ(mm::ThreadState::kNative, threadData.state());
// Check functions exported for the compiler too.
Kotlin_mm_switchThreadStateRunnable();
EXPECT_EQ(mm::ThreadState::kRunnable, threadData->state());
EXPECT_EQ(mm::ThreadState::kRunnable, threadData.state());
Kotlin_mm_switchThreadStateNative();
EXPECT_EQ(mm::ThreadState::kNative, threadData->state());
EXPECT_EQ(mm::ThreadState::kNative, threadData.state());
});
t.join();
}
TEST(ThreadStateTest, StateGuard) {
std::thread t([]() {
mm::ThreadRegistry::Instance().RegisterCurrentThread();
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
auto initialState = threadData->state();
mm::RunInNewThread([](mm::ThreadData& threadData) {
auto initialState = threadData.state();
EXPECT_EQ(mm::ThreadState::kRunnable, initialState);
{
mm::ThreadStateGuard guard(threadData, mm::ThreadState::kNative);
EXPECT_EQ(mm::ThreadState::kNative, threadData->state());
mm::ThreadStateGuard guard(&threadData, mm::ThreadState::kNative);
EXPECT_EQ(mm::ThreadState::kNative, threadData.state());
}
EXPECT_EQ(initialState, threadData->state());
EXPECT_EQ(initialState, threadData.state());
});
t.join();
}
TEST(ThreadStateDeathTest, StateAsserts) {
std::thread t([]() {
auto* threadData = mm::ThreadRegistry::Instance().RegisterCurrentThread()->Get();
EXPECT_DEATH(mm::AssertThreadState(threadData, mm::ThreadState::kNative),
mm::RunInNewThread([](mm::ThreadData& threadData) {
EXPECT_DEATH(mm::AssertThreadState(&threadData, mm::ThreadState::kNative),
"runtime assert: Unexpected thread state. Expected: NATIVE. Actual: RUNNABLE");
});
t.join();
}
TEST(ThreadStateDeathTest, IncorrectStateSwitch) {
std::thread t([]() {
auto* threadData = mm::ThreadRegistry::Instance().RegisterCurrentThread()->Get();
EXPECT_DEATH(mm::SwitchThreadState(threadData, kotlin::mm::ThreadState::kRunnable),
mm::RunInNewThread([](mm::ThreadData& threadData) {
EXPECT_DEATH(mm::SwitchThreadState(&threadData, kotlin::mm::ThreadState::kRunnable),
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
EXPECT_DEATH(Kotlin_mm_switchThreadStateRunnable(),
"runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE");
mm::SwitchThreadState(threadData, kotlin::mm::ThreadState::kNative);
mm::SwitchThreadState(&threadData, kotlin::mm::ThreadState::kNative);
EXPECT_DEATH(Kotlin_mm_switchThreadStateNative(),
"runtime assert: Illegal thread state switch. Old state: NATIVE. New state: NATIVE");
});
t.join();
}