blob: 2c3bc7f06fbe89c0cb8c67c4a65282d2e2f0c717 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/test-exceptions/exception-catcher.h>
#include <lib/test-exceptions/exception-handling.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <threads.h>
#include <zircon/errors.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/object.h>
#include <thread>
#include <zxtest/zxtest.h>
namespace test_exceptions {
namespace {
// Helper to easily create and kill threads to reduce boilerplate.
class TestThread {
public:
TestThread() {
EXPECT_OK(zx::thread::create(*zx::process::self(), "test", strlen("test"), 0, &thread_));
}
const zx::thread& get() const { return thread_; }
zx_status_t StartAndCrash() {
// Passing 0 for sp and pc crashes the thread immediately.
return thread_.start(nullptr, 0, 0, 0);
}
// Blocks until the thread is in an exception.
zx_status_t WaitUntilInException() {
while (1) {
zx_info_thread_t info;
zx_status_t status = thread_.get_info(ZX_INFO_THREAD, &info, sizeof(info), nullptr, nullptr);
if (status != ZX_OK) {
return status;
}
if (info.wait_exception_channel_type == ZX_EXCEPTION_CHANNEL_TYPE_NONE) {
zx::nanosleep(zx::deadline_after(zx::msec(1)));
} else {
return ZX_OK;
}
}
}
private:
zx::thread thread_;
};
TEST(ExceptionCatcher, NoExceptions) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
}
TEST(ExceptionCatcher, NoExceptionsManualStartStop) {
TestThread thread;
ExceptionCatcher catcher;
EXPECT_OK(catcher.Start(thread.get()));
EXPECT_OK(catcher.Stop());
}
TEST(ExceptionCatcher, MultipleStartFailure) {
TestThread thread, thread2;
ExceptionCatcher catcher;
EXPECT_OK(catcher.Start(thread.get()));
EXPECT_NOT_OK(catcher.Start(thread2.get()));
}
TEST(ExceptionCatcher, ChannelInUseFailure) {
TestThread thread;
ExceptionCatcher catcher, catcher2;
EXPECT_OK(catcher.Start(thread.get()));
EXPECT_NOT_OK(catcher2.Start(thread.get()));
}
TEST(ExceptionCatcher, CatchException) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
zx::result<zx::exception> result = catcher.ExpectException();
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
TEST(ExceptionCatcher, CatchThreadException) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
zx::result<zx::exception> result = catcher.ExpectException(thread.get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
TEST(ExceptionCatcher, CatchProcessException) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
zx::result<zx::exception> result = catcher.ExpectException(*zx::process::self());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
TEST(ExceptionCatcher, CatchMultipleExceptions) {
ExceptionCatcher catcher(*zx::process::self());
TestThread threads[4];
for (auto& thread : threads) {
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
}
for ([[maybe_unused]] auto& thread : threads) {
zx::result<zx::exception> result = catcher.ExpectException();
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
}
TEST(ExceptionCatcher, CatchMultipleThreadExceptions) {
ExceptionCatcher catcher(*zx::process::self());
TestThread threads[4];
for (auto& thread : threads) {
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
}
for (auto& thread : threads) {
zx::result<zx::exception> result = catcher.ExpectException(thread.get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
}
TEST(ExceptionCatcher, CatchMultipleProcessExceptions) {
ExceptionCatcher catcher(*zx::process::self());
TestThread threads[4];
for (auto& thread : threads) {
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
}
for ([[maybe_unused]] auto& thread : threads) {
zx::result<zx::exception> result = catcher.ExpectException(*zx::process::self());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
}
TEST(ExceptionCatcher, CatchMultipleThreadExceptionsAnyOrder) {
ExceptionCatcher catcher(*zx::process::self());
TestThread threads[4];
for (auto& thread : threads) {
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
}
{
zx::result<zx::exception> result = catcher.ExpectException(threads[1].get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
{
zx::result<zx::exception> result = catcher.ExpectException(threads[3].get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
{
zx::result<zx::exception> result = catcher.ExpectException(threads[0].get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
{
zx::result<zx::exception> result = catcher.ExpectException(threads[2].get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
}
TEST(ExceptionCatcher, UncaughtExceptionFailure) {
// Catch the exception again at the process level so it doesn't filter
// up to the system crash handler and kill our whole process.
ExceptionCatcher process_catcher(*zx::process::self());
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
EXPECT_EQ(ZX_ERR_CANCELED, catcher.Stop());
zx::result<zx::exception> result = process_catcher.ExpectException(thread.get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
TEST(ExceptionCatcher, ThreadTerminatedFailure) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
{
zx::result<zx::exception> result = catcher.ExpectException(thread.get());
ASSERT_TRUE(result.is_ok());
ASSERT_OK(ExitExceptionZxThread(std::move(result.value())));
}
zx::result<zx::exception> result = catcher.ExpectException(thread.get());
ASSERT_EQ(result.status_value(), ZX_ERR_PEER_CLOSED);
}
void crash_function() {
volatile int* bad_address = nullptr;
*bad_address = 5;
}
int thrd_crash_function(void* arg) {
crash_function();
return 0;
}
void* pthread_crash_function(void* arg) {
crash_function();
return nullptr;
}
TEST(ExceptionCatcher, CThreadExit) {
zx::unowned_process process = zx::process::self();
ExceptionCatcher catcher(*process);
thrd_t thread;
ASSERT_EQ(thrd_create(&thread, thrd_crash_function, nullptr), thrd_success);
auto result = catcher.ExpectException();
ASSERT_TRUE(result.is_ok());
ASSERT_OK(catcher.Stop());
ASSERT_OK(ExitExceptionCThread(std::move(result.value())));
ASSERT_EQ(thrd_join(thread, nullptr), thrd_success);
}
TEST(ExceptionCatcher, PThreadExit) {
zx::unowned_process process = zx::process::self();
ExceptionCatcher catcher(*process);
pthread_t thread;
ASSERT_EQ(pthread_create(&thread, nullptr, &pthread_crash_function, nullptr), 0);
auto result = catcher.ExpectException();
ASSERT_TRUE(result.is_ok());
ASSERT_OK(catcher.Stop());
ASSERT_OK(ExitExceptionPThread(std::move(result.value())));
ASSERT_EQ(pthread_join(thread, nullptr), 0);
}
} // namespace
} // namespace test_exceptions