[libcxx-commits] [libcxx] DRAFT [libc++][hardening] In production hardening modes, trap rather than abort (PR #78561)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Jan 18 13:05:53 PST 2024
================
@@ -90,55 +124,187 @@ 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_;
};
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,
+};
+
+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_;
+};
+
+class DeathTest {
+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); });
+
+ 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)) {
----------------
ldionne wrote:
We could instead pass a function object that does the matching -- it seems that this is all that `AssertionInfoMatcher` is. That would make it easier to match arbitrary error messages. It would also remove the need for a special "any" matcher by passing a function object that always returns true, as you suggested during live review.
`AssertionInfoMatcher` could become e.g. `MatchAssertionMessage` or something like that.
https://github.com/llvm/llvm-project/pull/78561
More information about the libcxx-commits
mailing list