[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