diff --git a/.clang-tidy b/.clang-tidy index 4dbed2fe1..d38778f93 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,7 +12,9 @@ Checks: | cert-* concurrency-* cppcoreguidelines-* + -cppcoreguidelines-avoid-do-while -cppcoreguidelines-avoid-magic-numbers + -cppcoreguidelines-avoid-non-const-global-variables -cppcoreguidelines-macro-usage -cppcoreguidelines-misleading-capture-default-by-value -cppcoreguidelines-pro-bounds-constant-array-index diff --git a/cmake/iwyu.imp b/cmake/iwyu.imp index 208d91d30..1a19f2751 100644 --- a/cmake/iwyu.imp +++ b/cmake/iwyu.imp @@ -28,5 +28,8 @@ { include: [ "@<__utility/.*>", private, "", public ] }, { include: [ "@<__config>", private, "", public ] }, + # POSIX headers. + { include: [ "@", private, "", public ] }, + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ] diff --git a/source/tit/core/CMakeLists.txt b/source/tit/core/CMakeLists.txt index c5acb0eff..3d421c723 100644 --- a/source/tit/core/CMakeLists.txt +++ b/source/tit/core/CMakeLists.txt @@ -16,7 +16,10 @@ set( "meta.hpp" "misc.hpp" "multivector.hpp" + "posix.cpp" + "posix.hpp" "simd.hpp" + "string_utils.hpp" "types.hpp" "vec_avx.hpp" "vec_neon.hpp" @@ -26,11 +29,11 @@ set( # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # Create the library. -add_library(tit_core INTERFACE ${CXX_SOURCES}) +add_library(tit_core STATIC ${CXX_SOURCES}) add_library(tit::core ALIAS tit_core) # Link with the dependent libraries. -target_link_libraries(tit_core INTERFACE tit::base) +target_link_libraries(tit_core PUBLIC tit::base) # Enable static analysis. check_includes(tit::core) diff --git a/source/tit/core/config.hpp b/source/tit/core/config.hpp index d11d60b47..9c40d35ab 100644 --- a/source/tit/core/config.hpp +++ b/source/tit/core/config.hpp @@ -28,6 +28,12 @@ #define TIT_LIBCPP 0 #endif +#ifdef _WIN32 +#define TIT_HAVE_SIGACTION 0 +#else +#define TIT_HAVE_SIGACTION 1 +#endif + /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ #ifndef TIT_ENABLE_TBB diff --git a/source/tit/core/posix.cpp b/source/tit/core/posix.cpp new file mode 100644 index 000000000..fcb00fe75 --- /dev/null +++ b/source/tit/core/posix.cpp @@ -0,0 +1,94 @@ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\ + * Part of the Tit Solver project, under the MIT License + * See /LICENSE.md for license information. + * SPDX-License-Identifier: MIT +\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#include +#include +#include +#include +#include + +#include "tit/core/assert.hpp" +#include "tit/core/config.hpp" +#include "tit/core/posix.hpp" + +namespace tit::posix { + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +std::vector SignalHandler::handlers_{}; + +#if !TIT_IWYU +SignalHandler::SignalHandler() + : SignalHandler({SIGINT, SIGTERM, SIGABRT, SIGSEGV, SIGILL, SIGFPE}) {} +#endif + +SignalHandler::SignalHandler(std::initializer_list signal_numbers) { + // Register the current handler object. + handlers_.push_back(this); + // Register the new signal actions (or handlers). +#if TIT_HAVE_SIGACTION + prev_actions_.reserve(signal_numbers.size()); + for (const auto signal_number : signal_numbers) { + // Prepare the new action. + sigaction_t signal_action{}; + signal_action.sa_flags = 0; + signal_action.sa_handler = &handle_signal_; + sigemptyset(&signal_action.sa_mask); + // Register the new action and store the previous one. + sigaction_t prev_signal_action{}; + const auto status = + sigaction(signal_number, &signal_action, &prev_signal_action); + TIT_ENSURE(status == 0, "Unable to set the signal action!"); + prev_actions_.emplace_back(signal_number, prev_signal_action); + } +#else + prev_handlers_.reserve(signal_numbers.size()); + for (const auto signal_number : signal_numbers) { + // Register the new handler and store the previous one. + const auto prev_signal_handler = signal(signal_number, &handle_signal_); + TIT_ENSURE(prev_signal_handler != SIG_ERR, + "Unable to set the signal handler!"); + prev_handlers_.emplace_back(signal_number, prev_signal_handler); + } +#endif +} + +SignalHandler::~SignalHandler() noexcept { + // Restore the old signal handlers or actions. +#if TIT_HAVE_SIGACTION + for (auto& [signal_number, prev_signal_action] : prev_actions_) { + const auto status = sigaction(signal_number, &prev_signal_action, nullptr); + TIT_ENSURE(status == 0, "Unable to reset the signal action!"); + } +#else + for (const auto& [signal_number, prev_signal_handler] : prev_handlers_) { + const auto signal_handler = signal(signal_number, prev_signal_handler); + TIT_ENSURE(signal_handler != SIG_ERR, + "Unable to reset the signal handler!"); + } +#endif + // Unregister the current signal handler. + TIT_ASSERT(handlers_.back() == this, "Signal handler was not registered!"); + handlers_.pop_back(); +} + +void SignalHandler::handle_signal_(int signal_number) noexcept { + // Traverse the registered handler and find the one that handles the signal + // that we've intercepted. + for (const auto* handler : handlers_ | std::views::reverse) { + TIT_ASSERT(handler != nullptr, "Invalid handler was registered."); + const auto iter = std::ranges::find(handler->signals(), signal_number); + if (iter != handler->signals().end()) { + handler->on_signal(signal_number); + return; + } + } + TIT_ASSERT(false, "Interpected a signal that has no handler!"); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +} // namespace tit::posix diff --git a/source/tit/core/posix.hpp b/source/tit/core/posix.hpp new file mode 100644 index 000000000..abe0d02a2 --- /dev/null +++ b/source/tit/core/posix.hpp @@ -0,0 +1,80 @@ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\ + * Part of the Tit Solver project, under the MIT License + * See /LICENSE.md for license information. + * SPDX-License-Identifier: MIT +\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#pragma once + +#include // IWYU pragma: keep +#include +#include +#include +#include + +#include "tit/core/config.hpp" + +namespace tit::posix { + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/******************************************************************************\ + ** POSIX signal handler. +\******************************************************************************/ +class SignalHandler { +public: + + /** Initialize signal handling for the common signals. */ + SignalHandler() +#if TIT_IWYU + = default +#endif + ; + /** Initialize signal handling for the specified signals. */ + SignalHandler(std::initializer_list signal_numbers); + + /** Signal handler is not move-constructible. */ + SignalHandler(SignalHandler&&) = delete; + /** Signal handler is not movable. */ + auto operator=(SignalHandler&&) -> SignalHandler& = delete; + + /** Signal handler is not copy-constructible. */ + SignalHandler(const SignalHandler&) = delete; + /** Signal handler is not copyable. */ + auto operator=(const SignalHandler&) -> SignalHandler& = delete; + + /** Reset signal handling. */ + virtual ~SignalHandler() noexcept; + + /** A range of handled signals. */ + auto signals() const noexcept { +#if TIT_HAVE_SIGACTION + return prev_actions_ | std::views::keys; +#else + return prev_handlers_ | std::views::keys; +#endif + } + +protected: + + /** Signal interception callback. */ + virtual void on_signal(int signal_number) const = 0; + +private: + +#if TIT_HAVE_SIGACTION + using sigaction_t = struct sigaction; + std::vector> prev_actions_; +#else + using sighandler_t = void (*)(int); + std::vector> prev_handlers_; +#endif + + static std::vector handlers_; + static void handle_signal_(int signal_number) noexcept; + +}; // class SignalHandler + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +} // namespace tit::posix diff --git a/source/tit/core/string_utils.hpp b/source/tit/core/string_utils.hpp new file mode 100644 index 000000000..61a4e3657 --- /dev/null +++ b/source/tit/core/string_utils.hpp @@ -0,0 +1,49 @@ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\ + * Part of the Tit Solver project, under the MIT License + * See /LICENSE.md for license information. + * SPDX-License-Identifier: MIT +\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#include +#include +#include +#include + +#include "tit/core/misc.hpp" + +namespace tit::string_utils { + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/** A string type: `const char*`, `std::string`, `std::string_view`, etc. */ +template +concept string = std::constructible_from; + +/** A range of strings. */ +template +concept string_range = + std::ranges::range && string>; + +/** An input range of strings. */ +template +concept input_string_range = + string_range && std::ranges::input_range; + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +/** Join the string with delimiter. */ +template +constexpr auto join(std::string_view with, Strings&& strings) -> std::string { + TIT_ASSUME_UNIVERSAL(Strings, strings); + if (std::ranges::empty(strings)) return ""; + std::string result(*strings.begin()); + for (const auto& string : strings | std::views::drop(1)) { + result += with; + result += string; + } + return result; +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +} // namespace tit::string_utils diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 663119d7f..2874200d7 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -9,6 +9,8 @@ set( CXX_SOURCES "math_tests.cpp" "mdvector_tests.cpp" + "posix_tests.cpp" + "string_utils_tests.cpp" "vec_tests.cpp") # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # @@ -20,7 +22,7 @@ add_executable(tit::core_tests ALIAS tit_core_tests) # Link with the dependent libraries. target_link_libraries(tit_core_tests PRIVATE tit::core) -# # Enable static analysis. -# check_includes(tit::core_tests) +# Enable static analysis. +check_includes(tit::core_tests) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # diff --git a/tests/core/mdvector_tests.cpp b/tests/core/mdvector_tests.cpp index 6bbf8046c..b1504dd7e 100644 --- a/tests/core/mdvector_tests.cpp +++ b/tests/core/mdvector_tests.cpp @@ -41,7 +41,7 @@ TEST_CASE("tit::core::Mdspan") { CHECK(mdspan[1][0] == 4); CHECK(mdspan[2][1] == 8); // Check data access using iterators. - const auto iter = std::ranges::find(mdspan, 7); + const auto* iter = std::ranges::find(mdspan, 7); CHECK(iter - mdspan.begin() == 6); } diff --git a/tests/core/posix_tests.cpp b/tests/core/posix_tests.cpp new file mode 100644 index 000000000..a8d837e81 --- /dev/null +++ b/tests/core/posix_tests.cpp @@ -0,0 +1,80 @@ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\ + * Part of the Tit Solver project, under the MIT License + * See /LICENSE.md for license information. + * SPDX-License-Identifier: MIT +\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#include +#include + +#include + +#include "tit/core/posix.hpp" + +namespace { + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +class MyHandler final : public tit::posix::SignalHandler { +public: + + MyHandler(int& handled_signal_number, + std::initializer_list signal_numbers) + : tit::posix::SignalHandler(signal_numbers), + handled_signal_number_{&handled_signal_number} {} + +protected: + + void on_signal(int signal_number) const final { + *handled_signal_number_ = signal_number; + } + +private: + + int* handled_signal_number_; + +}; // class MyHandler + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +// NOLINTBEGIN(cert-err33-c) + +TEST_CASE("tit::core::posix::SignalHandler") { + int handled_1 = 0, handled_2 = 0; + const auto reset = [&] { handled_1 = handled_2 = 0; }; + { // Create the first handler for two signals. + const MyHandler handler_1(handled_1, {SIGUSR1, SIGUSR2}); + { + // Create the second handler for the two signals. + const MyHandler handler_2(handled_2, {SIGUSR2, SIGCHLD}); + { + // Raise the signal that shall be intercepted by the first handler. + reset(); + raise(SIGUSR1); + CHECK(handled_1 == SIGUSR1); + CHECK(handled_2 == 0); + // Raise the signal that it shall be intercepted by the second handler. + reset(); + raise(SIGUSR2); + CHECK(handled_1 == 0); + CHECK(handled_2 == SIGUSR2); + } + } + // Raise the signal that shall be intercepted by the first handler. + reset(); + raise(SIGUSR2); + CHECK(handled_1 == SIGUSR2); + CHECK(handled_2 == 0); + // Raise the signal that shall not be intercepted by any handlers. + reset(); + raise(SIGCHLD); + CHECK(handled_1 == 0); + CHECK(handled_2 == 0); + } +} + +// NOLINTEND(cert-err33-c) + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +} // namespace diff --git a/tests/core/string_utils_tests.cpp b/tests/core/string_utils_tests.cpp new file mode 100644 index 000000000..6cf70ad1d --- /dev/null +++ b/tests/core/string_utils_tests.cpp @@ -0,0 +1,32 @@ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *\ + * Part of the Tit Solver project, under the MIT License + * See /LICENSE.md for license information. + * SPDX-License-Identifier: MIT +\* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#include +#include + +#include + +#include "tit/core/string_utils.hpp" + +namespace { + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +TEST_CASE("tit::core::string_utils::join") { + // Join a non-empty string list. + const std::vector strings{ + "budgie", "cockatiel", "cockatoo", "macao", "ringneck", + }; + CHECK(tit::string_utils::join(", ", strings) == + "budgie, cockatiel, cockatoo, macao, ringneck"); + // Join an empty string list. + const std::vector empty_strings{}; + CHECK(tit::string_utils::join("...", empty_strings) == ""); +} + +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +} // namespace