Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some core utilities. #7

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions cmake/iwyu.imp
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
{ include: [ "@<__utility/.*>", private, "<utility>", public ] },
{ include: [ "@<__config>", private, "<version>", public ] },

# POSIX headers.
{ include: [ "@<sys/signal.h>", private, "<csignal>", public ] },

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
]
7 changes: 5 additions & 2 deletions source/tit/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions source/tit/core/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 94 additions & 0 deletions source/tit/core/posix.cpp
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <csignal>
#include <functional>
#include <tuple>
#include <vector>

#include "tit/core/assert.hpp"
#include "tit/core/config.hpp"
#include "tit/core/posix.hpp"

namespace tit::posix {

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

std::vector<const SignalHandler*> SignalHandler::handlers_{};

#if !TIT_IWYU
SignalHandler::SignalHandler()
: SignalHandler({SIGINT, SIGTERM, SIGABRT, SIGSEGV, SIGILL, SIGFPE}) {}
#endif

SignalHandler::SignalHandler(std::initializer_list<int> 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
80 changes: 80 additions & 0 deletions source/tit/core/posix.hpp
Original file line number Diff line number Diff line change
@@ -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 <csignal> // IWYU pragma: keep
#include <initializer_list>
#include <ranges>
#include <tuple>
#include <vector>

#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<int> 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<std::tuple<int, sigaction_t>> prev_actions_;
#else
using sighandler_t = void (*)(int);
std::vector<std::tuple<int, sighandler_t>> prev_handlers_;
#endif

static std::vector<const SignalHandler*> handlers_;
static void handle_signal_(int signal_number) noexcept;

}; // class SignalHandler

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

} // namespace tit::posix
49 changes: 49 additions & 0 deletions source/tit/core/string_utils.hpp
Original file line number Diff line number Diff line change
@@ -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 <concepts>
#include <ranges>
#include <string>
#include <string_view>

#include "tit/core/misc.hpp"

namespace tit::string_utils {

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

/** A string type: `const char*`, `std::string`, `std::string_view`, etc. */
template<class String>
concept string = std::constructible_from<std::string_view, String>;

/** A range of strings. */
template<class Strings>
concept string_range =
std::ranges::range<Strings> && string<std::ranges::range_value_t<Strings>>;

/** An input range of strings. */
template<class Strings>
concept input_string_range =
string_range<Strings> && std::ranges::input_range<Strings>;

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

/** Join the string with delimiter. */
template<input_string_range Strings>
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
6 changes: 4 additions & 2 deletions tests/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ set(
CXX_SOURCES
"math_tests.cpp"
"mdvector_tests.cpp"
"posix_tests.cpp"
"string_utils_tests.cpp"
"vec_tests.cpp")

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
Expand All @@ -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)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
2 changes: 1 addition & 1 deletion tests/core/mdvector_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
80 changes: 80 additions & 0 deletions tests/core/posix_tests.cpp
Original file line number Diff line number Diff line change
@@ -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 <csignal>
#include <initializer_list>

#include <doctest/doctest.h>

#include "tit/core/posix.hpp"

namespace {

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

class MyHandler final : public tit::posix::SignalHandler {
public:

MyHandler(int& handled_signal_number,
std::initializer_list<int> 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
Loading