[libcxx-commits] [libcxx] [libc++][hardening] In production hardening modes, trap rather than abort (PR #78561)
Konstantin Varlamov via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jan 18 02:42:11 PST 2024
https://github.com/var-const created https://github.com/llvm/llvm-project/pull/78561
TODO
>From 9308d0520ea99f806682b62b1659f4c327da1ca6 Mon Sep 17 00:00:00 2001
From: Konstantin Varlamov <varconsteq at gmail.com>
Date: Thu, 18 Jan 2024 02:38:55 -0800
Subject: [PATCH 1/2] [libc++][hardening] In production hardening modes, trap
instead of abort
TODO
---
...tomize_verbose_abort.compile-time.pass.cpp | 1 +
...customize_verbose_abort.link-time.pass.cpp | 1 +
.../assertions/default_verbose_abort.pass.cpp | 1 +
libcxx/test/support/check_assertion.h | 401 ++++++++++++------
.../test_check_assertion.pass.cpp | 111 +++--
.../vendor/llvm/default_assertion_handler.in | 11 +-
6 files changed, 357 insertions(+), 169 deletions(-)
diff --git a/libcxx/test/libcxx/assertions/customize_verbose_abort.compile-time.pass.cpp b/libcxx/test/libcxx/assertions/customize_verbose_abort.compile-time.pass.cpp
index d09881e8cecb9f..d176d8a12875e6 100644
--- a/libcxx/test/libcxx/assertions/customize_verbose_abort.compile-time.pass.cpp
+++ b/libcxx/test/libcxx/assertions/customize_verbose_abort.compile-time.pass.cpp
@@ -8,6 +8,7 @@
// This compile-time customization requires cross-file macros, which doesn't work with modules.
// UNSUPPORTED: clang-modules-build
+// REQUIRES: libcpp-hardening-mode=debug
// Make sure that we can customize the verbose termination function at compile-time by
// defining _LIBCPP_VERBOSE_ABORT ourselves. Note that this does not have any
diff --git a/libcxx/test/libcxx/assertions/customize_verbose_abort.link-time.pass.cpp b/libcxx/test/libcxx/assertions/customize_verbose_abort.link-time.pass.cpp
index 219c43874e77db..c82aaea6ba30df 100644
--- a/libcxx/test/libcxx/assertions/customize_verbose_abort.link-time.pass.cpp
+++ b/libcxx/test/libcxx/assertions/customize_verbose_abort.link-time.pass.cpp
@@ -11,6 +11,7 @@
// We flag uses of the verbose termination function in older dylibs at compile-time to avoid runtime
// failures when back-deploying.
// XFAIL: availability-verbose_abort-missing
+// REQUIRES: libcpp-hardening-mode=debug
#include <cstdlib>
diff --git a/libcxx/test/libcxx/assertions/default_verbose_abort.pass.cpp b/libcxx/test/libcxx/assertions/default_verbose_abort.pass.cpp
index 870f43da4b8f02..29ef10016c1a37 100644
--- a/libcxx/test/libcxx/assertions/default_verbose_abort.pass.cpp
+++ b/libcxx/test/libcxx/assertions/default_verbose_abort.pass.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
// Test that the default verbose termination function aborts the program.
+// REQUIRES: libcpp-hardening-mode=debug
#include <csignal>
#include <cstdlib>
diff --git a/libcxx/test/support/check_assertion.h b/libcxx/test/support/check_assertion.h
index 83a46548fa9250..8e1493b70a66a6 100644
--- a/libcxx/test/support/check_assertion.h
+++ b/libcxx/test/support/check_assertion.h
@@ -16,6 +16,7 @@
#include <cstdlib>
#include <exception>
#include <regex>
+#include <sstream>
#include <string>
#include <string_view>
#include <utility>
@@ -27,37 +28,70 @@
#include "test_allocator.h"
#if TEST_STD_VER < 11
-# error "C++11 or greater is required to use this header"
+# error "C++11 or greater is required to use this header"
#endif
struct AssertionInfoMatcher {
- static const int any_line = -1;
+ // When printing the assertion message to `stderr`, delimit it with a marker to make it easier to match the message
+ // later.
+ static constexpr const char* Marker = "###";
+
+ static const int any_line = -1;
static constexpr const char* any_file = "*";
- static constexpr const char* any_msg = "*";
+ static constexpr const char* any_msg = "*";
- constexpr AssertionInfoMatcher() : is_empty_(true), msg_(any_msg, __builtin_strlen(any_msg)), file_(any_file, __builtin_strlen(any_file)), line_(any_line) { }
+ constexpr AssertionInfoMatcher()
+ : msg_(any_msg, __builtin_strlen(any_msg)), file_(any_file, __builtin_strlen(any_file)), line_(any_line) {}
constexpr AssertionInfoMatcher(const char* msg, const char* file = any_file, int line = any_line)
- : is_empty_(false), msg_(msg, __builtin_strlen(msg)), file_(file, __builtin_strlen(file)), line_(line) {}
+ : is_empty_(false), msg_(msg, __builtin_strlen(msg)), file_(file, __builtin_strlen(file)), line_(line) {}
+
+ bool CheckMatchInOutput(const std::string& output, std::string& error) const {
+ // Extract information from the error message. This has to stay synchronized with how we format assertions in the
+ // library.
+ std::regex message_format(".*###\\n(.*):(\\d+): assertion (.*) failed: (.*)\\n###");
+
+ std::smatch match_result;
+ bool has_match = std::regex_match(output, match_result, message_format);
+ assert(has_match);
+ assert(match_result.size() == 5);
+
+ const std::string& file = match_result[1];
+ int line = std::stoi(match_result[2]);
+ // Omitting `expression` in `match_result[3]`
+ const std::string& failure_reason = match_result[4];
+
+ bool result = Matches(file, line, failure_reason);
+ if (!result) {
+ error = FormatMatchingError(file, line, failure_reason);
+ }
+ return result;
+ }
- bool Matches(char const* file, int line, char const* message) const {
- assert(!empty() && "empty matcher");
+ bool Matches(const std::string& file, int line, const std::string& message) const {
+ assert(!empty() && "Empty matcher");
+ return CheckLineMatches(line) && CheckFileMatches(file) && CheckMessageMatches(message);
+ }
- if (CheckLineMatches(line) && CheckFileMatches(file) && CheckMessageMatches(message))
- return true;
- // Write to stdout because that's the file descriptor captured by the parent
- // process.
- std::printf("Failed to match assertion info!\n%s\nVS\n%s:%d (%s)\n", ToString().data(), file, line, message);
- return false;
+ std::string FormatMatchingError(const std::string& file, int line, const std::string& message) const {
+ std::stringstream output;
+ output //
+ << "Expected message: '" << msg_.data() << "'\n" //
+ << "Actual message: '" << message.c_str() << "'\n" //
+ << "Expected location: " << FormatLocation(file_, line_) << "\n" //
+ << "Actual location: " << FormatLocation(file, line) << "\n";
+ return output.str();
}
- std::string ToString() const {
- std::string result = "msg = \""; result += msg_; result += "\"\n";
- result += "line = " + (line_ == any_line ? "'*'" : std::to_string(line_)) + "\n";
- result += "file = " + (file_ == any_file ? "'*'" : std::string(file_));
+ static std::string FormatLocation(std::string_view file, int line) {
+ std::string result;
+ result += (file == any_file ? "*" : std::string(file)) + ":";
+ result += (line == any_line ? "*" : std::to_string(line));
return result;
}
bool empty() const { return is_empty_; }
+ bool IsAnyMatcher() const { return msg_ == any_msg && file_ == any_file && line_ == any_line; }
+
private:
bool CheckLineMatches(int got_line) const {
if (line_ == any_line)
@@ -90,10 +124,13 @@ struct AssertionInfoMatcher {
std::size_t found_at = got_msg.find(msg_);
if (found_at == std::string_view::npos)
return false;
- return found_at == 0 && got_msg.size() == msg_.size();
+ // Allow any match
+ return true;
}
+
private:
- bool is_empty_;
+ bool is_empty_ = true;
+ ;
std::string_view msg_;
std::string_view file_;
int line_;
@@ -101,44 +138,175 @@ struct AssertionInfoMatcher {
static constexpr AssertionInfoMatcher AnyMatcher(AssertionInfoMatcher::any_msg);
-inline AssertionInfoMatcher& GlobalMatcher() {
- static AssertionInfoMatcher GMatch;
- return GMatch;
+enum class DeathCause {
+ // Valid causes
+ VerboseAbort = 1,
+ StdTerminate,
+ Trap,
+ // Invalid causes
+ DidNotDie,
+ SetupFailure,
+ Unknown
+};
+
+bool IsValidCause(DeathCause cause) {
+ switch (cause) {
+ case DeathCause::VerboseAbort:
+ case DeathCause::StdTerminate:
+ case DeathCause::Trap:
+ return true;
+ default:
+ return false;
+ }
}
-struct DeathTest {
- enum ResultKind {
- RK_DidNotDie, RK_MatchFound, RK_MatchFailure, RK_Terminate, RK_SetupFailure, RK_Unknown
- };
+std::string ToString(DeathCause cause) {
+ switch (cause) {
+ case DeathCause::VerboseAbort:
+ return "verbose abort";
+ case DeathCause::StdTerminate:
+ return "`std::terminate`";
+ case DeathCause::Trap:
+ return "trap";
+ case DeathCause::DidNotDie:
+ return "<invalid cause (did not die)>";
+ case DeathCause::SetupFailure:
+ return "<invalid cause (setup failure)>";
+ case DeathCause::Unknown:
+ return "<invalid cause (unknown)>";
+ }
+}
- static const char* ResultKindToString(ResultKind RK) {
-#define CASE(K) case K: return #K
- switch (RK) {
- CASE(RK_MatchFailure);
- CASE(RK_DidNotDie);
- CASE(RK_SetupFailure);
- CASE(RK_MatchFound);
- CASE(RK_Unknown);
- CASE(RK_Terminate);
- }
- return "not a result kind";
+TEST_NORETURN void StopChildProcess(DeathCause cause) { std::exit(static_cast<int>(cause)); }
+
+DeathCause ConvertToDeathCause(int val) {
+ if (val < static_cast<int>(DeathCause::VerboseAbort) || val > static_cast<int>(DeathCause::Unknown)) {
+ return DeathCause::Unknown;
}
+ return static_cast<DeathCause>(val);
+}
+
+enum class Outcome {
+ Success,
+ UnexpectedCause,
+ UnexpectedAbortMessage,
+ InvalidCause,
+};
- static bool IsValidResultKind(int val) {
- return val >= RK_DidNotDie && val <= RK_Unknown;
+std::string ToString(Outcome outcome) {
+ switch (outcome) {
+ case Outcome::Success:
+ return "success";
+ case Outcome::UnexpectedCause:
+ return "unexpected death cause";
+ case Outcome::UnexpectedAbortMessage:
+ return "unexpected verbose abort message";
+ case Outcome::InvalidCause:
+ return "invalid death cause";
}
+}
+
+class DeathTestResult {
+ public:
+ DeathTestResult() = default;
+ DeathTestResult(Outcome set_outcome, DeathCause set_cause, const std::string& set_failure_description = "")
+ : outcome_(set_outcome), cause_(set_cause), failure_description_(set_failure_description) {}
+
+ bool success() const { return outcome() == Outcome::Success; }
+ Outcome outcome() const { return outcome_; }
+ DeathCause cause() const { return cause_; }
+ const std::string& failure_description() const { return failure_description_; }
+
+ private:
+ Outcome outcome_ = Outcome::Success;
+ DeathCause cause_ = DeathCause::Unknown;
+ std::string failure_description_;
+};
- DeathTest(AssertionInfoMatcher const& Matcher) : matcher_(Matcher) {}
+class DeathTest {
+ public:
+ DeathTest() = default;
+ DeathTest(DeathTest const&) = delete;
+ DeathTest& operator=(DeathTest const&) = delete;
template <class Func>
- ResultKind Run(Func&& f) {
+ DeathTestResult Run(DeathCause expected_cause, Func&& func, const AssertionInfoMatcher& matcher) {
+ std::set_terminate([] {
+ StopChildProcess(DeathCause::StdTerminate);
+ });
+
+ DeathCause cause = Run(func);
+
+ auto DescribeDeathCauseMismatch = [](DeathCause expected, DeathCause actual) {
+ std::stringstream output;
+ output //
+ << "Child died, but with a different death cause\n" //
+ << "Expected cause: " << ToString(expected) << "\n" //
+ << "Actual cause: " << ToString(actual) << "\n";
+ return output.str();
+ };
+
+ switch (cause) {
+ case DeathCause::StdTerminate:
+ case DeathCause::Trap:
+ if (expected_cause != cause) {
+ auto failure_description = DescribeDeathCauseMismatch(expected_cause, cause);
+ return DeathTestResult(Outcome::UnexpectedCause, cause, failure_description);
+ }
+ return DeathTestResult(Outcome::Success, cause);
+
+ case DeathCause::VerboseAbort: {
+ if (expected_cause != cause) {
+ auto failure_description = DescribeDeathCauseMismatch(expected_cause, cause);
+ return DeathTestResult(Outcome::UnexpectedCause, cause, failure_description);
+ }
+
+ std::string maybe_error;
+ if (matcher.CheckMatchInOutput(getChildStdErr(), maybe_error)) {
+ return DeathTestResult(Outcome::Success, cause);
+ }
+ auto failure_description = std::string("Child died, but with a different verbose abort message\n") + maybe_error;
+ return DeathTestResult(Outcome::UnexpectedAbortMessage, cause, failure_description);
+ }
+
+ // Invalid causes.
+ case DeathCause::DidNotDie:
+ return DeathTestResult(Outcome::InvalidCause, cause, "Child did not die");
+ case DeathCause::SetupFailure:
+ return DeathTestResult(Outcome::InvalidCause, cause, "Child failed to set up test environment");
+ case DeathCause::Unknown:
+ return DeathTestResult(Outcome::InvalidCause, cause, "Cause unknown");
+ }
+
+ assert(false && "Unreachable");
+ }
+
+ void PrintFailureDetails(std::string_view failure_description, std::string_view stmt, DeathCause cause) const {
+ std::fprintf(
+ stderr, "Failure: EXPECT_DEATH( %s ) failed!\n(reason: %s)\n\n", stmt.data(), failure_description.data());
+
+ if (cause != DeathCause::Unknown) {
+ std::fprintf(stderr, "child exit code: %d\n", getChildExitCode());
+ }
+ std::fprintf(stderr, "---------- standard err ----------\n%s", getChildStdErr().c_str());
+ std::fprintf(stderr, "\n----------------------------------\n");
+ std::fprintf(stderr, "---------- standard out ----------\n%s", getChildStdOut().c_str());
+ std::fprintf(stderr, "\n----------------------------------\n");
+ };
+
+ int getChildExitCode() const { return exit_code_; }
+ std::string const& getChildStdOut() const { return stdout_from_child_; }
+ std::string const& getChildStdErr() const { return stderr_from_child_; }
+
+private:
+ template <class Func>
+ DeathCause Run(Func&& f) {
int pipe_res = pipe(stdout_pipe_fd_);
assert(pipe_res != -1 && "failed to create pipe");
pipe_res = pipe(stderr_pipe_fd_);
assert(pipe_res != -1 && "failed to create pipe");
pid_t child_pid = fork();
- assert(child_pid != -1 &&
- "failed to fork a process to perform a death test");
+ assert(child_pid != -1 && "failed to fork a process to perform a death test");
child_pid_ = child_pid;
if (child_pid_ == 0) {
RunForChild(std::forward<Func>(f));
@@ -147,10 +315,6 @@ struct DeathTest {
return RunForParent();
}
- int getChildExitCode() const { return exit_code_; }
- std::string const& getChildStdOut() const { return stdout_from_child_; }
- std::string const& getChildStdErr() const { return stderr_from_child_; }
-private:
template <class Func>
TEST_NORETURN void RunForChild(Func&& f) {
close(GetStdOutReadFD()); // don't need to read from the pipe in the child.
@@ -158,14 +322,13 @@ struct DeathTest {
auto DupFD = [](int DestFD, int TargetFD) {
int dup_result = dup2(DestFD, TargetFD);
if (dup_result == -1)
- std::exit(RK_SetupFailure);
+ StopChildProcess(DeathCause::SetupFailure);
};
DupFD(GetStdOutWriteFD(), STDOUT_FILENO);
DupFD(GetStdErrWriteFD(), STDERR_FILENO);
- GlobalMatcher() = matcher_;
f();
- std::exit(RK_DidNotDie);
+ StopChildProcess(DeathCause::DidNotDie);
}
static std::string ReadChildIOUntilEnd(int FD) {
@@ -190,7 +353,7 @@ struct DeathTest {
close(GetStdErrReadFD());
}
- ResultKind RunForParent() {
+ DeathCause RunForParent() {
CaptureIOFromChild();
int status_value;
@@ -199,35 +362,29 @@ struct DeathTest {
if (WIFEXITED(status_value)) {
exit_code_ = WEXITSTATUS(status_value);
- if (!IsValidResultKind(exit_code_))
- return RK_Unknown;
- return static_cast<ResultKind>(exit_code_);
+ return ConvertToDeathCause(exit_code_);
}
- return RK_Unknown;
- }
- DeathTest(DeathTest const&) = delete;
- DeathTest& operator=(DeathTest const&) = delete;
+ if (WIFSIGNALED(status_value)) {
+ exit_code_ = WTERMSIG(status_value);
+ if (exit_code_ == SIGILL || exit_code_ == SIGTRAP) {
+ return DeathCause::Trap;
+ }
+ }
- int GetStdOutReadFD() const {
- return stdout_pipe_fd_[0];
+ return DeathCause::Unknown;
}
- int GetStdOutWriteFD() const {
- return stdout_pipe_fd_[1];
- }
+ int GetStdOutReadFD() const { return stdout_pipe_fd_[0]; }
- int GetStdErrReadFD() const {
- return stderr_pipe_fd_[0];
- }
+ int GetStdOutWriteFD() const { return stdout_pipe_fd_[1]; }
+
+ int GetStdErrReadFD() const { return stderr_pipe_fd_[0]; }
+
+ int GetStdErrWriteFD() const { return stderr_pipe_fd_[1]; }
- int GetStdErrWriteFD() const {
- return stderr_pipe_fd_[1];
- }
-private:
- AssertionInfoMatcher matcher_;
pid_t child_pid_ = -1;
- int exit_code_ = -1;
+ int exit_code_ = -1;
int stdout_pipe_fd_[2];
int stderr_pipe_fd_[2];
std::string stdout_from_child_;
@@ -235,82 +392,56 @@ struct DeathTest {
};
#ifdef _LIBCPP_VERSION
-void std::__libcpp_verbose_abort(char const* printf_format, ...) {
- // Extract information from the error message. This has to stay synchronized with how we format assertions in the
- // library.
+void std::__libcpp_verbose_abort(char const* format, ...) {
va_list args;
- va_start(args, printf_format);
- char const* message = va_arg(args, char const*);
-
- std::regex message_format("(.*):(\\d+): assertion (.*) failed: (.*)\\n");
+ va_start(args, format);
- std::cmatch match_result;
- bool has_match = std::regex_match(message, match_result, message_format);
- assert(has_match);
- assert(match_result.size() == 5);
+ std::fprintf(stderr, "%s\n", AssertionInfoMatcher::Marker);
+ std::vfprintf(stderr, format, args);
+ std::fprintf(stderr, "%s", AssertionInfoMatcher::Marker);
- std::string file = match_result[1];
- int line = std::stoi(match_result[2]);
- // Omitting `expression` in `match_result[3]`
- std::string failure_reason = match_result[4];
+ va_end(args);
- if (GlobalMatcher().Matches(file.c_str(), line, failure_reason.c_str())) {
- std::exit(DeathTest::RK_MatchFound);
- }
- std::exit(DeathTest::RK_MatchFailure);
+ StopChildProcess(DeathCause::VerboseAbort);
}
#endif // _LIBCPP_VERSION
-[[noreturn]] inline void terminate_handler() {
- std::exit(DeathTest::RK_Terminate);
-}
-
template <class Func>
-inline bool ExpectDeath(const char* stmt, Func&& func, AssertionInfoMatcher Matcher) {
- std::set_terminate(terminate_handler);
- DeathTest DT(Matcher);
- DeathTest::ResultKind RK = DT.Run(func);
- auto OnFailure = [&](const char* msg) {
- std::fprintf(stderr, "EXPECT_DEATH( %s ) failed! (%s)\n\n", stmt, msg);
- if (RK != DeathTest::RK_Unknown) {
- std::fprintf(stderr, "child exit code: %d\n", DT.getChildExitCode());
- }
- if (!DT.getChildStdErr().empty()) {
- std::fprintf(stderr, "---------- standard err ----------\n%s\n", DT.getChildStdErr().c_str());
- }
- if (!DT.getChildStdOut().empty()) {
- std::fprintf(stderr, "---------- standard out ----------\n%s\n", DT.getChildStdOut().c_str());
- }
- return false;
- };
- switch (RK) {
- case DeathTest::RK_MatchFound:
- case DeathTest::RK_Terminate:
- return true;
- case DeathTest::RK_SetupFailure:
- return OnFailure("child failed to setup test environment");
- case DeathTest::RK_Unknown:
- return OnFailure("reason unknown");
- case DeathTest::RK_DidNotDie:
- return OnFailure("child did not die");
- case DeathTest::RK_MatchFailure:
- return OnFailure("matcher failed");
+inline bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func, AssertionInfoMatcher matcher) {
+ assert(IsValidCause(expected_cause));
+
+ DeathTest test_case;
+ DeathTestResult test_result = test_case.Run(expected_cause, func, matcher);
+ if (!test_result.success()) {
+ test_case.PrintFailureDetails(test_result.failure_description(), stmt, test_result.cause());
}
- assert(false && "unreachable");
+
+ return test_result.success();
}
template <class Func>
-inline bool ExpectDeath(const char* stmt, Func&& func) {
- return ExpectDeath(stmt, func, AnyMatcher);
+inline bool ExpectDeath(DeathCause expected_cause, const char* stmt, Func&& func) {
+ return ExpectDeath(expected_cause, stmt, func, AnyMatcher);
}
-/// Assert that the specified expression throws a libc++ debug exception.
-#define EXPECT_DEATH(...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; } )))
-
-#define EXPECT_STD_TERMINATE(...) assert(ExpectDeath(#__VA_ARGS__, __VA_ARGS__))
-
-#define EXPECT_DEATH_MATCHES(Matcher, ...) assert((ExpectDeath(#__VA_ARGS__, [&]() { __VA_ARGS__; }, Matcher)))
-
-#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) assert((ExpectDeath(#expr, [&]() { (void)(expr); }, AssertionInfoMatcher(message))))
+// clang-format off
+
+/// Assert that the specified expression aborts with the expected cause and, optionally, error message.
+#define EXPECT_DEATH(...) \
+ assert(( ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; } ) ))
+#define EXPECT_DEATH_MATCHES(matcher, ...) \
+ assert(( ExpectDeath(DeathCause::VerboseAbort, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher) ))
+#define EXPECT_STD_TERMINATE(...) \
+ assert( ExpectDeath(DeathCause::StdTerminate, #__VA_ARGS__, __VA_ARGS__) )
+
+#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
+ assert(( ExpectDeath(DeathCause::VerboseAbort, #expr, [&]() { (void)(expr); }, AssertionInfoMatcher(message)) ))
+#else
+#define TEST_LIBCPP_ASSERT_FAILURE(expr, message) \
+ assert(( ExpectDeath(DeathCause::Trap, #expr, [&]() { (void)(expr); }) ))
+#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+
+// clang-format on
#endif // TEST_SUPPORT_CHECK_ASSERTION_H
diff --git a/libcxx/test/support/test.support/test_check_assertion.pass.cpp b/libcxx/test/support/test.support/test_check_assertion.pass.cpp
index 7cf0e0966ce89d..b45c101f567cda 100644
--- a/libcxx/test/support/test.support/test_check_assertion.pass.cpp
+++ b/libcxx/test/support/test.support/test_check_assertion.pass.cpp
@@ -18,47 +18,94 @@
#include "check_assertion.h"
template <class Func>
-inline bool TestDeathTest(const char* stmt, Func&& func, DeathTest::ResultKind ExpectResult, AssertionInfoMatcher Matcher = AnyMatcher) {
- DeathTest DT(Matcher);
- DeathTest::ResultKind RK = DT.Run(func);
- auto OnFailure = [&](std::string msg) {
- std::fprintf(stderr, "EXPECT_DEATH( %s ) failed! (%s)\n\n", stmt, msg.c_str());
- if (!DT.getChildStdErr().empty()) {
- std::fprintf(stderr, "---------- standard err ----------\n%s\n", DT.getChildStdErr().c_str());
- }
- if (!DT.getChildStdOut().empty()) {
- std::fprintf(stderr, "---------- standard out ----------\n%s\n", DT.getChildStdOut().c_str());
+inline bool TestDeathTest(
+ Outcome expected_outcome, DeathCause expected_cause, const char* stmt, Func&& func, AssertionInfoMatcher matcher) {
+ DeathTest test_case;
+ DeathTestResult test_result = test_case.Run(expected_cause, func, matcher);
+ std::string maybe_failure_description;
+
+ Outcome outcome = test_result.outcome();
+ if (expected_outcome != outcome) {
+ maybe_failure_description +=
+ std::string("Test outcome was different from expected; expected ") + ToString(expected_outcome) +
+ ", got: " + ToString(outcome);
+ }
+
+ DeathCause cause = test_result.cause();
+ if (expected_cause != cause) {
+ auto failure_description =
+ std::string("Cause of death was different from expected; expected ") + ToString(expected_cause) +
+ ", got: " + ToString(cause);
+ if (maybe_failure_description.empty()) {
+ maybe_failure_description = failure_description;
+ } else {
+ maybe_failure_description += std::string("; ") + failure_description;
}
+ }
+
+ if (!maybe_failure_description.empty()) {
+ test_case.PrintFailureDetails(maybe_failure_description, stmt, test_result.cause());
return false;
- };
- if (RK != ExpectResult)
- return OnFailure(std::string("expected result did not occur: expected ") + DeathTest::ResultKindToString(ExpectResult) + " got: " + DeathTest::ResultKindToString(RK));
+ }
+
return true;
}
-#define TEST_DEATH_TEST(RK, ...) assert((TestDeathTest(#__VA_ARGS__, [&]() { __VA_ARGS__; }, RK, AnyMatcher )))
-#define TEST_DEATH_TEST_MATCHES(RK, Matcher, ...) assert((TestDeathTest(#__VA_ARGS__, [&]() { __VA_ARGS__; }, RK, Matcher)))
+// clang-format off
-void my_libcpp_assert() {
- _LIBCPP_ASSERT(false, "other");
-}
+#define TEST_DEATH_TEST(outcome, cause, ...) \
+ assert(( TestDeathTest(outcome, cause, #__VA_ARGS__, [&]() { __VA_ARGS__; }, AnyMatcher) ))
+#define TEST_DEATH_TEST_MATCHES(outcome, cause, matcher, ...) \
+ assert(( TestDeathTest(outcome, cause, #__VA_ARGS__, [&]() { __VA_ARGS__; }, matcher) ))
-void test_no_match_found() {
- AssertionInfoMatcher ExpectMatch("my message");
- TEST_DEATH_TEST_MATCHES(DeathTest::RK_MatchFailure, ExpectMatch, my_libcpp_assert());
-}
+// clang-format on
-void test_did_not_die() {
- TEST_DEATH_TEST(DeathTest::RK_DidNotDie, ((void)0));
-}
+int main(int, char**) {
+ { // Success -- verbose abort with any matcher.
+ auto fail_assert = [] { _LIBCPP_ASSERT(false, "Some message"); };
+#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+ TEST_DEATH_TEST_MATCHES(Outcome::Success, DeathCause::VerboseAbort, AnyMatcher, fail_assert());
+#else
+ TEST_DEATH_TEST_MATCHES(Outcome::Success, DeathCause::Trap, AnyMatcher, fail_assert());
+#endif
+ }
-void test_unknown() {
- TEST_DEATH_TEST(DeathTest::RK_Unknown, std::exit(13));
-}
+ { // Success -- verbose abort with a specific matcher.
+ auto fail_assert = [] { _LIBCPP_ASSERT(false, "Some message"); };
+ AssertionInfoMatcher matcher("Some message");
+#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+ TEST_DEATH_TEST_MATCHES(Outcome::Success, DeathCause::VerboseAbort, matcher, fail_assert());
+#else
+ TEST_DEATH_TEST_MATCHES(Outcome::Success, DeathCause::Trap, matcher, fail_assert());
+#endif
+ }
+
+ { // Success -- `std::terminate`.
+ TEST_DEATH_TEST(Outcome::Success, DeathCause::StdTerminate, std::terminate());
+ }
+
+ { // Success -- trapping.
+ TEST_DEATH_TEST(Outcome::Success, DeathCause::Trap, __builtin_trap());
+ }
+
+ { // Error message doesn't match.
+ auto fail_assert = [] { _LIBCPP_ASSERT(false, "Actual message doesn't match"); };
+ AssertionInfoMatcher matcher("Bad expected message");
+#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+ TEST_DEATH_TEST_MATCHES(Outcome::UnexpectedAbortMessage, DeathCause::VerboseAbort, matcher, fail_assert());
+#else
+ TEST_DEATH_TEST_MATCHES(Outcome::Success, DeathCause::Trap, matcher, fail_assert());
+#endif
+ }
+
+
+ { // Invalid cause -- child did not die.
+ TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::DidNotDie, ((void)0));
+ }
+
+ { // Invalid cause -- unknown.
+ TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::Unknown, std::exit(13));
+ }
-int main(int, char**) {
- test_no_match_found();
- test_did_not_die();
- test_unknown();
return 0;
}
diff --git a/libcxx/vendor/llvm/default_assertion_handler.in b/libcxx/vendor/llvm/default_assertion_handler.in
index 111d305a16f7c4..60bbf27489097b 100644
--- a/libcxx/vendor/llvm/default_assertion_handler.in
+++ b/libcxx/vendor/llvm/default_assertion_handler.in
@@ -17,7 +17,14 @@
# pragma GCC system_header
#endif
-// TODO(hardening): in production, trap rather than abort.
-#define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT("%s", message)
+#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
+
+# define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT("%s", message)
+
+#else
+
+# define _LIBCPP_ASSERTION_HANDLER(message) ((void)message, __builtin_trap())
+
+#endif // _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
#endif // _LIBCPP___ASSERTION_HANDLER
>From 522100d40be08e67894416fb485e8be467160884 Mon Sep 17 00:00:00 2001
From: Konstantin Varlamov <varconsteq at gmail.com>
Date: Thu, 18 Jan 2024 02:40:42 -0800
Subject: [PATCH 2/2] Format
---
libcxx/test/support/check_assertion.h | 34 +++++++++----------
.../test_check_assertion.pass.cpp | 1 -
2 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/libcxx/test/support/check_assertion.h b/libcxx/test/support/check_assertion.h
index 8e1493b70a66a6..229cd250e66f1b 100644
--- a/libcxx/test/support/check_assertion.h
+++ b/libcxx/test/support/check_assertion.h
@@ -207,33 +207,31 @@ std::string ToString(Outcome outcome) {
}
class DeathTestResult {
- public:
- DeathTestResult() = default;
- DeathTestResult(Outcome set_outcome, DeathCause set_cause, const std::string& set_failure_description = "")
- : outcome_(set_outcome), cause_(set_cause), failure_description_(set_failure_description) {}
-
- bool success() const { return outcome() == Outcome::Success; }
- Outcome outcome() const { return outcome_; }
- DeathCause cause() const { return cause_; }
- const std::string& failure_description() const { return failure_description_; }
-
- private:
- Outcome outcome_ = Outcome::Success;
- DeathCause cause_ = DeathCause::Unknown;
- std::string failure_description_;
+public:
+ DeathTestResult() = default;
+ DeathTestResult(Outcome set_outcome, DeathCause set_cause, const std::string& set_failure_description = "")
+ : outcome_(set_outcome), cause_(set_cause), failure_description_(set_failure_description) {}
+
+ bool success() const { return outcome() == Outcome::Success; }
+ Outcome outcome() const { return outcome_; }
+ DeathCause cause() const { return cause_; }
+ const std::string& failure_description() const { return failure_description_; }
+
+private:
+ Outcome outcome_ = Outcome::Success;
+ DeathCause cause_ = DeathCause::Unknown;
+ std::string failure_description_;
};
class DeathTest {
- public:
+public:
DeathTest() = default;
DeathTest(DeathTest const&) = delete;
DeathTest& operator=(DeathTest const&) = delete;
template <class Func>
DeathTestResult Run(DeathCause expected_cause, Func&& func, const AssertionInfoMatcher& matcher) {
- std::set_terminate([] {
- StopChildProcess(DeathCause::StdTerminate);
- });
+ std::set_terminate([] { StopChildProcess(DeathCause::StdTerminate); });
DeathCause cause = Run(func);
diff --git a/libcxx/test/support/test.support/test_check_assertion.pass.cpp b/libcxx/test/support/test.support/test_check_assertion.pass.cpp
index b45c101f567cda..a078f66cf0a19b 100644
--- a/libcxx/test/support/test.support/test_check_assertion.pass.cpp
+++ b/libcxx/test/support/test.support/test_check_assertion.pass.cpp
@@ -98,7 +98,6 @@ int main(int, char**) {
#endif
}
-
{ // Invalid cause -- child did not die.
TEST_DEATH_TEST(Outcome::InvalidCause, DeathCause::DidNotDie, ((void)0));
}
More information about the libcxx-commits
mailing list