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
@@ -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();
}