[libcxx-commits] [libcxx] Heres the minimal contracts in libc++ (PR #98187)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 9 09:48:23 PDT 2024


https://github.com/EricWF created https://github.com/llvm/llvm-project/pull/98187

None

>From 843cd2ef0fbb095b933dea677dcb8e515759f140 Mon Sep 17 00:00:00 2001
From: Eric Fiselier <eric at efcs.ca>
Date: Tue, 9 Jul 2024 11:47:35 -0500
Subject: [PATCH] Heres the minimal contracts in libc++

---
 libcxx/include/CMakeLists.txt                 |   1 +
 libcxx/include/__assert                       |  71 +++---------
 libcxx/include/contracts                      |  85 ++++++++++++++
 libcxx/src/CMakeLists.txt                     |   1 +
 libcxx/src/contracts.cpp                      | 104 ++++++++++++++++++
 libcxx/test/CMakeLists.txt                    |   2 +-
 libcxx/test/configs/llvm-libc++-shared.cfg.in |   2 +-
 .../override_with_extensive_mode.pass.cpp     |   2 +-
 .../std/contracts/breathing_test.pass.cpp     |  14 +++
 libcxx/utils/libcxx/test/features.py          |   8 ++
 libcxx/utils/libcxx/test/params.py            |  15 ++-
 11 files changed, 245 insertions(+), 60 deletions(-)
 create mode 100644 libcxx/include/contracts
 create mode 100644 libcxx/src/contracts.cpp
 create mode 100644 libcxx/test/std/contracts/breathing_test.pass.cpp

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 8d0ffd6ed725b..4feb20c8b2e6a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -896,6 +896,7 @@ set(files
   complex.h
   concepts
   condition_variable
+  contracts
   coroutine
   csetjmp
   csignal
diff --git a/libcxx/include/__assert b/libcxx/include/__assert
index 49769fb4d4497..a0c620b40ebb8 100644
--- a/libcxx/include/__assert
+++ b/libcxx/include/__assert
@@ -17,6 +17,8 @@
 #  pragma GCC system_header
 #endif
 
+#define _LIBCPP_CONTRACT_ASSERT(group_str, expression) ([&]() { contract_assert [[clang::contract_group(group_str)]] (expression); }())
+
 #define _LIBCPP_ASSERT(expression, message)                                                                            \
   (__builtin_expect(static_cast<bool>(expression), 1)                                                                  \
        ? (void)0                                                                                                       \
@@ -36,63 +38,20 @@
 
 // clang-format off
 // Fast hardening mode checks.
-
-#if _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_FAST
-
-// Enabled checks.
-#  define _LIBCPP_ASSERT_VALID_INPUT_RANGE(expression, message)       _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message)    _LIBCPP_ASSERT(expression, message)
-// Disabled checks.
-// On most modern platforms, dereferencing a null pointer does not lead to an actual memory access.
-#  define _LIBCPP_ASSERT_NON_NULL(expression, message)                _LIBCPP_ASSUME(expression)
-// Overlapping ranges will make algorithms produce incorrect results but don't directly lead to a security
-// vulnerability.
-#  define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message)  _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message)      _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message)    _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message)  _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_PEDANTIC(expression, message)                _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_SEMANTIC_REQUIREMENT(expression, message)    _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_INTERNAL(expression, message)                _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message)           _LIBCPP_ASSUME(expression)
-
-// Extensive hardening mode checks.
-
-#elif _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_EXTENSIVE
-
-// Enabled checks.
-#  define _LIBCPP_ASSERT_VALID_INPUT_RANGE(expression, message)       _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message)    _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_NON_NULL(expression, message)                _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message)  _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message)      _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message)    _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message)  _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_PEDANTIC(expression, message)                _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message)           _LIBCPP_ASSERT(expression, message)
-// Disabled checks.
-#  define _LIBCPP_ASSERT_SEMANTIC_REQUIREMENT(expression, message)    _LIBCPP_ASSUME(expression)
-#  define _LIBCPP_ASSERT_INTERNAL(expression, message)                _LIBCPP_ASSUME(expression)
-
-// Debug hardening mode checks.
-
-#elif _LIBCPP_HARDENING_MODE == _LIBCPP_HARDENING_MODE_DEBUG
-
+#if __has_keyword(contract_assert)
 // All checks enabled.
-#  define _LIBCPP_ASSERT_VALID_INPUT_RANGE(expression, message)       _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message)    _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_NON_NULL(expression, message)                _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message)  _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message)      _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message)    _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message)  _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_PEDANTIC(expression, message)                _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_SEMANTIC_REQUIREMENT(expression, message)    _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_INTERNAL(expression, message)                _LIBCPP_ASSERT(expression, message)
-#  define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message)           _LIBCPP_ASSERT(expression, message)
+#  define _LIBCPP_ASSERT_VALID_INPUT_RANGE(expression, message)       _LIBCPP_CONTRACT_ASSERT("std.valid_input_range", expression)
+#  define _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(expression, message)    _LIBCPP_CONTRACT_ASSERT("std.valid_element_access", expression)
+#  define _LIBCPP_ASSERT_NON_NULL(expression, message)                _LIBCPP_CONTRACT_ASSERT("std.non_null", expression)
+#  define _LIBCPP_ASSERT_NON_OVERLAPPING_RANGES(expression, message)  _LIBCPP_CONTRACT_ASSERT("std.non_overlapping_ranges", expression)
+#  define _LIBCPP_ASSERT_VALID_DEALLOCATION(expression, message)      _LIBCPP_CONTRACT_ASSERT("std.valid_deallocation", expression)
+#  define _LIBCPP_ASSERT_VALID_EXTERNAL_API_CALL(expression, message) _LIBCPP_CONTRACT_ASSERT("std.valid_external_api_call", expression)
+#  define _LIBCPP_ASSERT_COMPATIBLE_ALLOCATOR(expression, message)    _LIBCPP_CONTRACT_ASSERT("std.compatible_allocator", expression)
+#  define _LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(expression, message)  _LIBCPP_CONTRACT_ASSERT("std.argument_within_domain", expression)
+#  define _LIBCPP_ASSERT_PEDANTIC(expression, message)                _LIBCPP_CONTRACT_ASSERT("std.pedantic", expression)
+#  define _LIBCPP_ASSERT_SEMANTIC_REQUIREMENT(expression, message)    _LIBCPP_CONTRACT_ASSERT("std.semantic_requirement", expression)
+#  define _LIBCPP_ASSERT_INTERNAL(expression, message)                _LIBCPP_CONTRACT_ASSERT("std.internal", expression)
+#  define _LIBCPP_ASSERT_UNCATEGORIZED(expression, message)           _LIBCPP_CONTRACT_ASSERT("std.uncategorized", expression)
 
 // Disable all checks if hardening is not enabled.
 
diff --git a/libcxx/include/contracts b/libcxx/include/contracts
new file mode 100644
index 0000000000000..af22d66359bf6
--- /dev/null
+++ b/libcxx/include/contracts
@@ -0,0 +1,85 @@
+#ifndef _LIBCPP_CONTRACTS
+#define _LIBCPP_CONTRACTS
+
+#include <__config>
+#include <version>
+#include <source_location>
+
+namespace std::contracts {
+
+enum class assertion_kind : unsigned char {
+  __unknown = 0,
+  pre = 1,
+  post = 2,
+  assert = 3
+};
+using _AssertKind = assertion_kind;
+enum class evaluation_semantic : unsigned char {
+  __unknown = 0,
+  enforce = 1,
+  observe = 2
+};
+using _EvaluationSemantic = evaluation_semantic;
+enum class detection_mode : unsigned char {
+  __unknown = 0,
+  predicate_false = 1,
+  evaluation_exception = 2
+};
+using _DetectionMode = detection_mode;
+
+class contract_violation {
+// no user−accessible constructor
+public:
+  // cannot be copied or moved
+  contract_violation(const contract_violation&) = delete;
+  // cannot be assigned to
+  contract_violation& operator=(const contract_violation&) = delete;
+  ~contract_violation() = default;
+  const char* comment() const noexcept { return __info_.__comment_; }
+
+  _DetectionMode detection_mode() const noexcept { return __info_.__detection_mode_; }
+  assertion_kind kind() const noexcept { return __info_.__kind_; }
+  const char* file_name() const noexcept { return __info_.__file_name_; }
+  unsigned line() const noexcept { return __info_.__line_; }
+
+  evaluation_semantic semantic() const noexcept { return __info_.__semantic_;}
+
+
+    struct _Info {
+        _AssertKind __kind_;
+        _EvaluationSemantic __semantic_;
+        _DetectionMode __detection_mode_;
+
+        const char* __comment_;
+        const char* __file_name_;
+        const char* __function_name_;
+        unsigned __line_;
+    };
+
+  explicit contract_violation(_Info __info) noexcept
+      : __info_(__info) {}
+
+
+  _Info __info_;
+};
+
+
+
+_LIBCPP_EXPORTED_FROM_ABI void invoke_default_contract_violation_handler(const contract_violation&) noexcept;
+
+
+} // namespace std::contracts
+
+_LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&);
+
+
+extern "C" {
+_LIBCPP_EXPORTED_FROM_ABI
+void __handle_contract_violation(unsigned kind, unsigned eval_semantic,
+                                  unsigned detection_mode, const char* comment, const char* file, unsigned line);
+_LIBCPP_EXPORTED_FROM_ABI
+void __handle_contract_violation_new(void*) noexcept;
+}
+
+
+#endif // _LIBCPP_CONTRACTS
diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt
index 0ae58a10c879c..0b56059e73c20 100644
--- a/libcxx/src/CMakeLists.txt
+++ b/libcxx/src/CMakeLists.txt
@@ -8,6 +8,7 @@ set(LIBCXX_SOURCES
   call_once.cpp
   charconv.cpp
   chrono.cpp
+  contracts.cpp
   error_category.cpp
   exception.cpp
   expected.cpp
diff --git a/libcxx/src/contracts.cpp b/libcxx/src/contracts.cpp
new file mode 100644
index 0000000000000..958660376d85e
--- /dev/null
+++ b/libcxx/src/contracts.cpp
@@ -0,0 +1,104 @@
+#include <__config>
+#include <contracts>
+#include <exception>
+#include <iostream>
+
+
+
+
+namespace std::contracts {
+
+void invoke_default_contract_violation_handler(const contract_violation& violation) noexcept {
+  ::handle_contract_violation(violation);
+}
+
+static void display_contract_violation(const contract_violation& violation) noexcept {
+  using namespace std::contracts;
+  std::cerr << violation.file_name() << ":" << violation.line() << ": ";
+  auto assert_str = [&]() -> std::pair<const char*, const char*> {
+    switch (violation.kind()) {
+    case _AssertKind::pre:
+      return {"pre(", ")"};
+    case _AssertKind::post:
+      return {"post(", ")"};
+
+    case _AssertKind::assert:
+      return {"contract_assert(", ")"};
+
+    case _AssertKind::__unknown:
+      return {"", ""};
+    }
+  }();
+  std::cerr << assert_str.first << violation.comment() << assert_str.second;
+  if (violation.detection_mode() == _DetectionMode::predicate_false) {
+    std::cerr << " failed" << std::endl;
+  } else {
+    std::cerr << " exited via exception" << std::endl;
+  }
+}
+
+} // namespace std::contracts
+
+struct _BuiltinContractStruct {
+  int32_t version;
+  int32_t contract_kind;
+  int32_t eval_semantic;
+  int32_t detection_mode;
+  int32_t lineno;
+  const char* comment;
+  const char* file;
+};
+
+extern "C" {
+_LIBCPP_EXPORTED_FROM_ABI void __handle_contract_violation(
+    unsigned kind,
+    unsigned eval_semantic,
+    unsigned detection_mode,
+    const char* comment,
+    const char* file,
+    unsigned line) {
+  using namespace std::contracts;
+
+  using _InfoT = std::contracts::contract_violation::_Info;
+  _InfoT info = {.__kind_ = static_cast<_AssertKind>(kind),
+                 .__semantic_ = static_cast<_EvaluationSemantic>(eval_semantic),
+                 .__detection_mode_ = static_cast<_DetectionMode>(detection_mode),
+                 .__comment_ = comment,
+                 .__file_name_ = file,
+                 .__function_name_ = nullptr,
+                 .__line_ = line};
+  contract_violation violation(info);
+  if (::handle_contract_violation)
+    ::handle_contract_violation(violation);
+  else
+    std::contracts::display_contract_violation(violation);
+
+  if (info.__semantic_ == _EvaluationSemantic::enforce) {
+    std::terminate();
+  }
+}
+
+_LIBCPP_EXPORTED_FROM_ABI void __handle_contract_violation_new(void* dataptr) noexcept {
+  using namespace std::contracts;
+  _BuiltinContractStruct* data = static_cast<_BuiltinContractStruct*>(dataptr);
+
+  using _InfoT = std::contracts::contract_violation::_Info;
+  _InfoT info  = {
+       .__kind_           = static_cast<_AssertKind>(data->contract_kind),
+       .__semantic_       = static_cast<_EvaluationSemantic>(data->eval_semantic),
+       .__detection_mode_ = static_cast<_DetectionMode>(data->detection_mode),
+       .__comment_        = data->comment,
+       .__file_name_      = data->file,
+       .__function_name_  = nullptr,
+       .__line_           = unsigned(data->lineno)};
+  contract_violation violation(info);
+  if (::handle_contract_violation)
+    ::handle_contract_violation(violation);
+  else
+    std::contracts::display_contract_violation(violation);
+
+  if (info.__semantic_ == _EvaluationSemantic::enforce) {
+    std::terminate();
+  }
+}
+}
diff --git a/libcxx/test/CMakeLists.txt b/libcxx/test/CMakeLists.txt
index cdd1c2d90fbcf..42f8f1e581ee4 100644
--- a/libcxx/test/CMakeLists.txt
+++ b/libcxx/test/CMakeLists.txt
@@ -1,5 +1,5 @@
 include(HandleLitArguments)
-add_subdirectory(tools)
+
 
 # By default, libcxx and libcxxabi share a library directory.
 if (NOT LIBCXX_CXX_ABI_LIBRARY_PATH)
diff --git a/libcxx/test/configs/llvm-libc++-shared.cfg.in b/libcxx/test/configs/llvm-libc++-shared.cfg.in
index 0c059f0c7ff32..c6b3a722c8861 100644
--- a/libcxx/test/configs/llvm-libc++-shared.cfg.in
+++ b/libcxx/test/configs/llvm-libc++-shared.cfg.in
@@ -7,7 +7,7 @@ config.substitutions.append(('%{flags}',
     '-pthread' + (' -isysroot {}'.format('@CMAKE_OSX_SYSROOT@') if '@CMAKE_OSX_SYSROOT@' else '')
 ))
 config.substitutions.append(('%{compile_flags}',
-    '-nostdinc++ -I %{target-include-dir} -I %{include-dir} -I %{libcxx-dir}/test/support'
+    '-Xclang -fcontracts -nostdinc++ -I %{target-include-dir} -I %{include-dir} -I %{libcxx-dir}/test/support'
 ))
 config.substitutions.append(('%{link_flags}',
     '-nostdlib++ -L %{lib-dir} -Wl,-rpath,%{lib-dir} -lc++'
diff --git a/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp b/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp
index 74fe70feb077c..a17ac80122b98 100644
--- a/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp
+++ b/libcxx/test/libcxx/assertions/modes/override_with_extensive_mode.pass.cpp
@@ -16,7 +16,7 @@
 // XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
 // HWASAN replaces TRAP with abort or error exit code.
 // XFAIL: hwasan
-// ADDITIONAL_COMPILE_FLAGS: -U_LIBCPP_HARDENING_MODE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE
+// ADDITIONAL_COMPILE_FLAGS: -fcontract-group-evaluation-semantic=std.compatible_allocator=quick_enforce,std.internal=ignore
 
 #include <cassert>
 #include "check_assertion.h"
diff --git a/libcxx/test/std/contracts/breathing_test.pass.cpp b/libcxx/test/std/contracts/breathing_test.pass.cpp
new file mode 100644
index 0000000000000..3f14ee7771de9
--- /dev/null
+++ b/libcxx/test/std/contracts/breathing_test.pass.cpp
@@ -0,0 +1,14 @@
+#include <cassert>
+#include <contracts>
+
+using std::contracts::contract_violation;
+
+void test(int x) pre(x != 1) {
+  contract_assert(x != 0);
+}
+
+
+int main() {
+  test(2);
+  test(1);
+}
diff --git a/libcxx/utils/libcxx/test/features.py b/libcxx/utils/libcxx/test/features.py
index 5e708da4f8fbe..1ad63e918f8a2 100644
--- a/libcxx/utils/libcxx/test/features.py
+++ b/libcxx/utils/libcxx/test/features.py
@@ -171,6 +171,14 @@ def _mingwSupportsModules(cfg):
         name="fdelayed-template-parsing",
         when=lambda cfg: hasCompileFlag(cfg, "-fdelayed-template-parsing"),
     ),
+    Feature(
+        name="fcontracts",
+        when=lambda cfg: hasCompileFlag(cfg, "-fcontracts"),
+    ),
+    Feature(
+        name="contract-groups",
+        when=lambda cfg: hasCompileFlag(cfg, "-fcontract-group-evaluation-semantic=std=enforce"),
+    ),
     Feature(
         name="has-fobjc-arc",
         when=lambda cfg: hasCompileFlag(cfg, "-xobjective-c++ -fobjc-arc")
diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py
index ea841acf3a3d4..213db403eacfe 100644
--- a/libcxx/utils/libcxx/test/params.py
+++ b/libcxx/utils/libcxx/test/params.py
@@ -73,7 +73,7 @@
     "-Wno-self-move",
 ]
 
-_allStandards = ["c++03", "c++11", "c++14", "c++17", "c++20", "c++23", "c++26"]
+_allStandards = ["c++03", "c++11", "c++14", "c++17", "c++20", "c++23", "c++26", "c++2c"]
 
 
 def getStdFlag(cfg, std):
@@ -117,6 +117,8 @@ def testClangTidy(cfg, version, executable):
     except ConfigurationRuntimeError:
         return None
 
+def hasContractSupport(cfg):
+    return hasCompileFlag(cfg, "-fcontracts") and hasCompileFlag(cfg, "-fcontract-group-evaluation-semantic=std=enforce")
 
 def getSuitableClangTidy(cfg):
     # If we didn't build the libcxx-tidy plugin via CMake, we can't run the clang-tidy tests.
@@ -429,5 +431,16 @@ def getSuitableClangTidy(cfg):
             AddSubstitution('%{clang-tidy}', exe),
         ]
     ),
+    Parameter(
+        name="use-contracts",
+        type=bool,
+        default=True,
+        help="Whether to enable contracts when compiling the test suite.",
+        actions=lambda use_contracts: [] if not use_contracts else [
+            AddCompileFlag("-fcontracts"),
+            AddFeature("contracts"),
+            AddCompileFlag("-fcontract-group-evaluation-semantic=std=enforce")
+        ],
+    )
 ]
 # fmt: on



More information about the libcxx-commits mailing list