Skip to content
Open
1 change: 1 addition & 0 deletions libs/core/algorithms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ add_hpx_module(
hpx_concepts
hpx_concurrency
hpx_config
hpx_contracts
hpx_coroutines
hpx_datastructures
hpx_errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ namespace hpx {
#include <hpx/config.hpp>
#include <hpx/algorithms/traits/projected.hpp>
#include <hpx/assert.hpp>
#include <hpx/contracts.hpp>
#include <hpx/modules/async_local.hpp>
#include <hpx/modules/concepts.hpp>
#include <hpx/modules/execution.hpp>
Expand Down Expand Up @@ -392,8 +393,8 @@ namespace hpx {
>
)
// clang-format on
friend void tag_fallback_invoke(
hpx::sort_t, RandomIt first, RandomIt last, Comp comp = Comp())
friend void tag_fallback_invoke(hpx::sort_t, RandomIt first,
RandomIt last, Comp comp = Comp()) HPX_PRE(first <= last)
{
static_assert(std::random_access_iterator<RandomIt>,
"Requires a random access iterator.");
Expand All @@ -416,7 +417,7 @@ namespace hpx {
// clang-format on
friend parallel::util::detail::algorithm_result_t<ExPolicy>
tag_fallback_invoke(hpx::sort_t, ExPolicy&& policy, RandomIt first,
RandomIt last, Comp comp = Comp())
RandomIt last, Comp comp = Comp()) HPX_PRE(first <= last)
{
static_assert(std::random_access_iterator<RandomIt>,
"Requires a random access iterator.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ namespace hpx {

#include <hpx/config.hpp>
#include <hpx/algorithms/traits/projected.hpp>
#include <hpx/contracts.hpp>
#include <hpx/modules/concepts.hpp>
#include <hpx/modules/execution.hpp>
#include <hpx/modules/executors.hpp>
Expand Down Expand Up @@ -285,7 +286,7 @@ namespace hpx {
)
// clang-format on
friend void tag_fallback_invoke(hpx::stable_sort_t, RandomIt first,
RandomIt last, Comp comp = Comp())
RandomIt last, Comp comp = Comp()) HPX_PRE(first <= last)
{
static_assert(std::random_access_iterator<RandomIt>,
"Requires a random access iterator.");
Expand All @@ -310,6 +311,7 @@ namespace hpx {
friend hpx::parallel::util::detail::algorithm_result_t<ExPolicy>
tag_fallback_invoke(hpx::stable_sort_t, ExPolicy&& policy,
RandomIt first, RandomIt last, Comp comp = Comp())
HPX_PRE(first <= last)
{
static_assert(std::random_access_iterator<RandomIt>,
"Requires a random access iterator.");
Expand Down
36 changes: 34 additions & 2 deletions libs/core/contracts/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,46 @@ if(HPX_WITH_CXX26_CONTRACTS)
DEFINE HPX_CONTRACTS_HAVE_ASSERTS_AS_CONTRACT_ASSERTS NAMESPACE CONTRACTS
)
endif()
else()
hpx_option(
HPX_WITH_CONTRACTS_MODE
STRING
"Contract evaluation mode for pre-C++26 compilers, options are: ENFORCE, OBSERVE, IGNORE"
"ENFORCE"
STRINGS "ENFORCE;OBSERVE;IGNORE"
)

# Note: values start at 1 because hpx_add_config_define_namespace treats a
# numeric "0" as falsy and would emit a valueless #define.
if(HPX_WITH_CONTRACTS_MODE STREQUAL "ENFORCE")
hpx_add_config_define_namespace(
DEFINE HPX_HAVE_CONTRACTS_MODE
VALUE 1
NAMESPACE CONTRACTS
)
elseif(HPX_WITH_CONTRACTS_MODE STREQUAL "OBSERVE")
hpx_add_config_define_namespace(
DEFINE HPX_HAVE_CONTRACTS_MODE
VALUE 2
NAMESPACE CONTRACTS
)
elseif(HPX_WITH_CONTRACTS_MODE STREQUAL "IGNORE")
hpx_add_config_define_namespace(
DEFINE HPX_HAVE_CONTRACTS_MODE
VALUE 3
NAMESPACE CONTRACTS
)
endif()
endif()

# Default location is $HPX_ROOT/libs/contracts/include
set(contracts_headers hpx/contracts.hpp hpx/contracts/macros.hpp)
set(contracts_headers hpx/contracts.hpp hpx/contracts/macros.hpp
hpx/contracts/violation_handler.hpp
)
set(contracts_macro_headers hpx/contracts/macros.hpp)

# Default location is $HPX_ROOT/libs/contracts/src
set(contracts_sources)
set(contracts_sources violation_handler.cpp)

include(HPX_AddModule)
add_hpx_module(
Expand Down
48 changes: 36 additions & 12 deletions libs/core/contracts/include/hpx/contracts/macros.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2025 The STE||AR-Group
// Copyright (c) 2025-2026 The STE||AR-Group
// Copyright (c) 2025 Alexandros Papadakis
//
// SPDX-License-Identifier: BSL-1.0
Expand All @@ -13,22 +13,25 @@
/// behavior when contracts are not available.
///
/// ## API Reference:
/// - **HPX_PRE(condition)**: Precondition contracts (no-op in fallback mode)
/// - **HPX_POST(condition)**: Postcondition contracts (no-op in fallback mode)
/// - **HPX_CONTRACT_ASSERT(condition)**: Contract assertions (always available, maps to HPX_ASSERT)
/// - **HPX_PRE(condition)**: Precondition contracts (declaration specifier in
/// C++26; no-op in fallback mode)
/// - **HPX_POST(condition)**: Postcondition contracts (declaration specifier
/// in C++26; no-op in fallback mode)
/// - **HPX_CONTRACT_ASSERT(condition)**: Contract assertions (always active;
/// dispatches on HPX_WITH_CONTRACTS_MODE in fallback mode)
///
/// ## Configuration:
/// Enable with: `cmake -DHPX_WITH_CONTRACTS=ON -DCMAKE_CXX_STANDARD=26`
/// Enable native contracts with: `cmake -DHPX_WITH_CONTRACTS=ON -DCMAKE_CXX_STANDARD=26`
/// Set fallback mode with: `cmake -DHPX_WITH_CONTRACTS_MODE=ENFORCE|OBSERVE|IGNORE`
///
/// See docs/index.rst for comprehensive usage guide.

#pragma once

#include <hpx/config.hpp>
#include <hpx/contracts/config/defines.hpp>
#include <hpx/assert.hpp>

// Contract implementation: automatically selects native C++26 contracts
// or provides appropriate fallback behavior based on compiler capabilities
#if defined(HPX_HAVE_CXX26_CONTRACTS)

// Native C++26 contracts mode
Expand All @@ -42,12 +45,33 @@
#define HPX_ASSERT(x) contract_assert(x)
#endif

#else
#else // fallback mode

// Fallback mode: PRE/POST become no-ops for forward compatibility,
// CONTRACT_ASSERT maps to HPX_ASSERT for runtime validation
// HPX_PRE and HPX_POST are declaration specifiers in C++26 and cannot be
// replicated as statement macros without changing call sites. Keep them as
// no-ops in fallback regardless of mode.
#define HPX_PRE(x)
#define HPX_CONTRACT_ASSERT(x) HPX_ASSERT((x))
#define HPX_POST(x)

#endif
#if defined(HPX_HAVE_CONTRACTS_MODE) && \
HPX_HAVE_CONTRACTS_MODE == 3 // IGNORE

#define HPX_CONTRACT_ASSERT(x)

#else // ENFORCE (0) or OBSERVE (1): runtime checking via violation handler

#include <hpx/contracts/violation_handler.hpp>

// clang-format off
#define HPX_CONTRACT_ASSERT(x) \
do { \
if (!(x)) \
::hpx::contracts::invoke_violation_handler( \
{::hpx::contracts::contract_kind::assertion, #x, \
HPX_CURRENT_SOURCE_LOCATION()}); \
} while (false)
// clang-format on

#endif // HPX_HAVE_CONTRACTS_MODE

#endif // HPX_HAVE_CXX26_CONTRACTS
36 changes: 36 additions & 0 deletions libs/core/contracts/include/hpx/contracts/violation_handler.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2026 The STE||AR-Group
//
// SPDX-License-Identifier: BSL-1.0
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#pragma once

#include <hpx/config.hpp>
#include <hpx/assertion/source_location.hpp>

namespace hpx::contracts {

HPX_CXX_CORE_EXPORT enum class contract_kind { pre, post, assertion };

HPX_CXX_CORE_EXPORT struct violation_info
{
contract_kind kind;
char const* condition;
hpx::source_location location;
};

HPX_CXX_CORE_EXPORT using violation_handler_t =
void (*)(violation_info const&);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better if this was accepting any callable of the given signature:

Suggested change
void (*)(violation_info const&);
hpx::unique_function<void(violation_info const&)>

?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to confirm , when you said hpx::unique_function , did you mean hpx::move_only_function? That's what I found in the codebase. Also switching from a raw function pointer to a move-only wrapper would change the internal storage in violation_handler.cpp ; since we can't hold it in a plain static pointer anymore.
Makes sense to me if that's the direction, just wanted to confirm before changing it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant move_only_function, sorry for the confusion.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sanchit2662 Are you still interested in moving this PR forward?


HPX_CXX_CORE_EXPORT HPX_CORE_EXPORT violation_handler_t
set_violation_handler(violation_handler_t handler) noexcept;
HPX_CXX_CORE_EXPORT HPX_CORE_EXPORT violation_handler_t
get_violation_handler() noexcept;

HPX_CXX_CORE_EXPORT HPX_CORE_EXPORT void default_violation_handler(
violation_info const& info);
HPX_CXX_CORE_EXPORT HPX_CORE_EXPORT void invoke_violation_handler(
violation_info const& info);

} // namespace hpx::contracts
73 changes: 73 additions & 0 deletions libs/core/contracts/src/violation_handler.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2026 The STE||AR-Group
//
// SPDX-License-Identifier: BSL-1.0
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <hpx/config.hpp>
#include <hpx/contracts/config/defines.hpp>
Comment thread
hkaiser marked this conversation as resolved.
#include <hpx/contracts/violation_handler.hpp>

#include <cstdlib>
#include <iostream>

namespace hpx::contracts {

namespace detail {

[[nodiscard]] violation_handler_t& get_handler() noexcept
{
static violation_handler_t handler = nullptr;
return handler;
}

} // namespace detail

violation_handler_t set_violation_handler(
violation_handler_t handler) noexcept
{
violation_handler_t old = detail::get_handler();
detail::get_handler() = handler;
return old;
}

violation_handler_t get_violation_handler() noexcept
{
return detail::get_handler();
}

void default_violation_handler(violation_info const& info)
{
char const* kind_str = nullptr;
switch (info.kind)
{
case contract_kind::pre:
kind_str = "precondition";
break;
case contract_kind::post:
kind_str = "postcondition";
break;
case contract_kind::assertion:
kind_str = "assertion";
break;
}

std::cerr << info.location << ": Contract " << kind_str << " '"
<< info.condition << "' violated\n";

#if !defined(HPX_HAVE_CONTRACTS_MODE) || HPX_HAVE_CONTRACTS_MODE != 2
// abort in ENFORCE (and by default); continue in OBSERVE
std::abort();
#endif
}

void invoke_violation_handler(violation_info const& info)
{
violation_handler_t handler = detail::get_handler();
if (handler == nullptr)
default_violation_handler(info);
else
handler(info);
}

} // namespace hpx::contracts
39 changes: 28 additions & 11 deletions libs/core/contracts/tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright (c) 2025 Alexandros Papadakis
# Copyright (c) 2026 The STE||AR-Group
# HPX Contracts Module Unit Tests
#
# SPDX-License-Identifier: BSL-1.0
Expand All @@ -15,6 +16,9 @@ set(contract_tests
fallback_contracts_succeed
fallback_contracts_fail
disabled_contracts
violation_handler_invoked
default_handler_aborts
handler_receives_correct_info
)

foreach(test ${contract_tests})
Expand All @@ -29,16 +33,8 @@ foreach(test ${contract_tests})
add_hpx_unit_test("modules.contracts" ${test})
endforeach()

# Set failure expectation for fallback failure test Fallback only fails in Debug
# mode (HPX_ASSERT or HPX_CONTRACT_ASSERT behavior)
set_tests_properties(
tests.unit.modules.contracts.declaration_contracts_fail_contract_assert
PROPERTIES WILL_FAIL $<$<CONFIG:Debug>:ON>
)

if(HPX_HAVE_CXX26_CONTRACTS)
# Set failure expectations for declaration contract failure tests Native
# contracts should fail when violated
# Native C++26 contracts always fail when violated, regardless of build config
set_tests_properties(
tests.unit.modules.contracts.declaration_contracts_fail_pre
PROPERTIES WILL_FAIL ON
Expand All @@ -47,9 +43,30 @@ if(HPX_HAVE_CXX26_CONTRACTS)
tests.unit.modules.contracts.declaration_contracts_fail_post
PROPERTIES WILL_FAIL ON
)
# HPX_CONTRACT_ASSERT still routes through HPX_ASSERT in native mode — Debug
# only
set_tests_properties(
tests.unit.modules.contracts.declaration_contracts_fail_contract_assert
PROPERTIES WILL_FAIL $<$<CONFIG:Debug>:ON>
)
else()
# Set failure expectation for fallback failure test Fallback only fails in
# Debug mode (HPX_ASSERT behavior)
# Fallback mode: HPX_PRE / HPX_POST are no-ops so fail_pre / fail_post
# succeed. HPX_CONTRACT_ASSERT routes through invoke_violation_handler, whose
# default handler aborts unless HPX_WITH_CONTRACTS_MODE=OBSERVE.
if(NOT HPX_WITH_CONTRACTS_MODE STREQUAL "OBSERVE"
AND NOT HPX_WITH_CONTRACTS_MODE STREQUAL "IGNORE"
)
set_tests_properties(
tests.unit.modules.contracts.declaration_contracts_fail_contract_assert
PROPERTIES WILL_FAIL ON
)
set_tests_properties(
tests.unit.modules.contracts.default_handler_aborts PROPERTIES WILL_FAIL
ON
)
endif()

# fallback_contracts_fail uses HPX_ASSERT directly, so it only fails in Debug
set_tests_properties(
tests.unit.modules.contracts.fallback_contracts_fail
PROPERTIES WILL_FAIL $<$<CONFIG:Debug>:ON>
Expand Down
13 changes: 13 additions & 0 deletions libs/core/contracts/tests/unit/default_handler_aborts.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) 2026 The STE||AR-Group
//
// SPDX-License-Identifier: BSL-1.0
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <hpx/contracts.hpp>

int main()
{
HPX_CONTRACT_ASSERT(false); // default handler should abort here
return 0;
}
Loading
Loading