[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)) {
+        return DeathTestResult(Outcome::Success, cause);
+      }
+      auto failure_description = std::string("Child died, but with a different verbose abort message\n") + maybe_error;
----------------
ldionne wrote:

```suggestion
      auto failure_description = std::string("Child died, but with a different error message\n") + maybe_error;
```

https://github.com/llvm/llvm-project/pull/78561


More information about the libcxx-commits mailing list