blob: 850134cf22bba4a1865edfc535378cf954a8cdb6 [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 <zircon/errors.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/object.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#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_));
}
~TestThread() {
if (thread_) {
// It's OK if an ExceptionCatcher already killed this thread,
// killing a task multiple times has no effect.
EXPECT_OK(thread_.kill());
}
}
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());
EXPECT_OK(catcher.ExpectException());
}
TEST(ExceptionCatcher, CatchThreadException) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
EXPECT_OK(catcher.ExpectException(thread.get()));
}
TEST(ExceptionCatcher, CatchProcessException) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
EXPECT_OK(catcher.ExpectException(*zx::process::self()));
}
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) {
EXPECT_OK(catcher.ExpectException());
}
}
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) {
EXPECT_OK(catcher.ExpectException(thread.get()));
}
}
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) {
EXPECT_OK(catcher.ExpectException(*zx::process::self()));
}
}
TEST(ExceptionCatcher, CatchMultipleThreadExceptionsAnyOrder) {
ExceptionCatcher catcher(*zx::process::self());
TestThread threads[4];
for (auto& thread : threads) {
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
}
EXPECT_OK(catcher.ExpectException(threads[1].get()));
EXPECT_OK(catcher.ExpectException(threads[3].get()));
EXPECT_OK(catcher.ExpectException(threads[0].get()));
EXPECT_OK(catcher.ExpectException(threads[2].get()));
}
TEST(ExceptionCatcher, CatchExceptionFromKilledThread) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(thread.WaitUntilInException());
ASSERT_OK(thread.get().kill());
ASSERT_OK(thread.get().wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), nullptr));
// Exception should still be handled properly even if the exception
// channel has since been closed.
EXPECT_OK(catcher.ExpectException());
}
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());
EXPECT_OK(process_catcher.ExpectException(thread.get()));
}
TEST(ExceptionCatcher, ThreadTerminatedFailure) {
TestThread thread;
ExceptionCatcher catcher(thread.get());
ASSERT_OK(thread.StartAndCrash());
ASSERT_OK(catcher.ExpectException(thread.get()));
EXPECT_EQ(ZX_ERR_PEER_CLOSED, catcher.ExpectException(thread.get()));
}
} // namespace
} // namespace test_exceptions