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

Protect against uninitialized services #666

Merged
merged 2 commits into from
Jan 2, 2025
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
3 changes: 3 additions & 0 deletions include/cib/builder_meta.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

#include <concepts>

namespace cib {
template <typename T>
concept builder_meta = requires {
typename T::builder_t;
typename T::interface_t;
{ T::uninitialized() } -> std::same_as<typename T::interface_t>;
};

template <builder_meta T> using builder_t = typename T::builder_t;
Expand Down
3 changes: 2 additions & 1 deletion include/cib/built.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
#include <cib/builder_meta.hpp>

namespace cib {
template <builder_meta ServiceMeta> interface_t<ServiceMeta> service;
template <builder_meta ServiceMeta>
constinit auto service = ServiceMeta::uninitialized();
} // namespace cib
8 changes: 8 additions & 0 deletions include/cib/callback.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cib/builder_meta.hpp>

#include <stdx/compiler.hpp>
#include <stdx/panic.hpp>

#include <array>
#include <concepts>
Expand Down Expand Up @@ -119,5 +120,12 @@ template <int NumFuncs = 0, typename... ArgTypes> struct builder {
template <typename... ArgTypes> struct service {
using builder_t = builder<0, ArgTypes...>;
using interface_t = void (*)(ArgTypes...);

CONSTEVAL static auto uninitialized() -> interface_t {
return [](ArgTypes...) {
stdx::panic<
"Attempting to run callback before it is initialized">();
};
}
};
} // namespace callback
9 changes: 3 additions & 6 deletions include/cib/nexus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,22 @@ namespace cib {
*
* @see cib::config
*/
template <typename Config> struct nexus {
private:
using this_t = nexus<Config>;

template <typename Config> struct nexus {
// Workaround unfortunate bug in clang where it can't deduce "auto" sometimes
#define CIB_BUILD_SERVICE \
initialized<Config, Tag>::value.template build<initialized<Config, Tag>>()

public:
template <typename Tag>
constexpr static decltype(CIB_BUILD_SERVICE) service = CIB_BUILD_SERVICE;
#undef CIB_BUILD_SERVICE

static void init() {
auto const service = []<typename T> {
using from_t = std::remove_cvref_t<decltype(this_t::service<T>)>;
using from_t = std::remove_cvref_t<decltype(nexus::service<T>)>;
using to_t = std::remove_cvref_t<decltype(cib::service<T>)>;

auto &service_impl = this_t::service<T>;
auto &service_impl = nexus::service<T>;
if constexpr (std::is_convertible_v<from_t, to_t>) {
cib::service<T> = service_impl;
} else {
Expand Down
10 changes: 10 additions & 0 deletions include/flow/builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#include <flow/graph_builder.hpp>
#include <flow/impl.hpp>

#include <stdx/compiler.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/panic.hpp>

namespace flow {
template <stdx::ct_string Name = "">
Expand All @@ -13,5 +15,13 @@ using builder = graph<Name, graph_builder<Name, impl>>;
template <stdx::ct_string Name = ""> struct service {
using builder_t = builder<Name>;
using interface_t = FunctionPtr;

CONSTEVAL static auto uninitialized() -> interface_t {
return [] {
using namespace stdx::literals;
stdx::panic<"Attempting to run flow ("_cts + Name +
") before it is initialized"_cts>();
};
}
};
} // namespace flow
35 changes: 35 additions & 0 deletions include/msg/handler_interface.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#pragma once

#include <stdx/compiler.hpp>
#include <stdx/ct_conversions.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/panic.hpp>
#include <stdx/type_traits.hpp>

namespace msg {
template <typename MsgBase, typename... ExtraCallbackArgs>
struct handler_interface {
Expand All @@ -8,4 +14,33 @@ struct handler_interface {
virtual auto handle(MsgBase const &msg,
ExtraCallbackArgs... extra_args) const -> bool = 0;
};

namespace detail {
template <typename M>
concept named_msg_base =
stdx::is_specialization_of<decltype(M::name), stdx::ct_string>().value;

template <typename M> CONSTEVAL auto name_for_msg() {
if constexpr (detail::named_msg_base<M>) {
return M::name;
} else {
constexpr auto name = stdx::type_as_string<M>();
return stdx::ct_string<name.size() + 1>{name};
}
}
} // namespace detail

template <typename MsgBase, typename... ExtraCallbackArgs>
struct uninitialized_handler_t
: handler_interface<MsgBase, ExtraCallbackArgs...> {
auto is_match(MsgBase const &) const -> bool override { return false; }

auto handle(MsgBase const &, ExtraCallbackArgs...) const -> bool override {
using namespace stdx::literals;
stdx::panic<"Attempting to handle msg ("_cts +
detail::name_for_msg<MsgBase>() +
") before service is initialized"_cts>();
return false;
}
};
} // namespace msg
7 changes: 7 additions & 0 deletions include/msg/indexed_service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <msg/handler_interface.hpp>
#include <msg/indexed_builder.hpp>

#include <stdx/compiler.hpp>
#include <stdx/tuple.hpp>

namespace msg {
Expand All @@ -12,5 +13,11 @@ struct indexed_service {
ExtraCallbackArgs...>;
using interface_t =
handler_interface<MsgBase, ExtraCallbackArgs...> const *;

constexpr static auto uninitialized_v =
uninitialized_handler_t<MsgBase, ExtraCallbackArgs...>{};
CONSTEVAL static auto uninitialized() -> interface_t {
return &uninitialized_v;
}
};
} // namespace msg
2 changes: 2 additions & 0 deletions include/msg/message.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ template <stdx::ct_string Name, typename... Fields> struct message {
(... and Fields::template fits_inside<S>());

template <typename T> struct base {
constexpr static auto name = Name;

constexpr auto as_derived() const -> T const & {
return static_cast<T const &>(*this);
}
Expand Down
8 changes: 7 additions & 1 deletion include/msg/service.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
#include <msg/handler_builder.hpp>
#include <msg/handler_interface.hpp>

#include <stdx/tuple.hpp>
#include <stdx/compiler.hpp>

namespace msg {
template <typename MsgBase, typename... ExtraCallbackArgs> struct service {
using builder_t =
handler_builder<stdx::tuple<>, MsgBase, ExtraCallbackArgs...>;
using interface_t =
handler_interface<MsgBase, ExtraCallbackArgs...> const *;

constexpr static auto uninitialized_v =
uninitialized_handler_t<MsgBase, ExtraCallbackArgs...>{};
CONSTEVAL static auto uninitialized() -> interface_t {
return &uninitialized_v;
}
};
} // namespace msg
1 change: 1 addition & 0 deletions test/cib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ add_tests(
FILES
builder_meta
callback
callback_uninit
nexus
readme_hello_world
LIBRARIES
Expand Down
15 changes: 9 additions & 6 deletions test/cib/builder_meta.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
#include <cib/builder_meta.hpp>

#include <stdx/compiler.hpp>

#include <catch2/catch_test_macros.hpp>

#include <type_traits>

namespace {
struct TestBuilderTag;
struct TestInterfaceTag;
struct TestBuilder;
struct TestInterface {};

struct test_builder_meta {
using builder_t = TestBuilderTag;
using interface_t = TestInterfaceTag;
using builder_t = TestBuilder;
using interface_t = TestInterface;
CONSTEVAL static auto uninitialized() -> interface_t;
};
} // namespace

Expand All @@ -21,7 +24,7 @@ TEST_CASE("builder_meta concept") {
TEST_CASE(
"builder_meta builder and interface type traits return correct values") {
static_assert(
std::is_same_v<TestBuilderTag, cib::builder_t<test_builder_meta>>);
std::is_same_v<TestBuilder, cib::builder_t<test_builder_meta>>);
static_assert(
std::is_same_v<TestInterfaceTag, cib::interface_t<test_builder_meta>>);
std::is_same_v<TestInterface, cib::interface_t<test_builder_meta>>);
}
35 changes: 35 additions & 0 deletions test/cib/callback_uninit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <cib/built.hpp>
#include <cib/callback.hpp>

#include <stdx/panic.hpp>

#include <catch2/catch_test_macros.hpp>

#include <string>
#include <string_view>

namespace {
template <int Id, typename... Args>
struct TestCallback : public callback::service<Args...> {};

std::string panic_string = {};
int panics{};

struct test_panic_handler {
template <stdx::ct_string Why, typename... Ts>
static auto panic(Ts &&...) -> void {
panic_string = std::string_view{Why};
++panics;
}
};
} // namespace

template <> inline auto stdx::panic_handler<> = test_panic_handler{};

TEST_CASE("run callback service when uninitialized", "[callback]") {
panics = 0;
cib::service<TestCallback<0>>();
CHECK(panics == 1);
CHECK(panic_string ==
"Attempting to run callback before it is initialized");
}
1 change: 1 addition & 0 deletions test/flow/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ add_unit_test(
add_tests(
FILES
flow
flow_uninit
graph
graph_builder
logging
Expand Down
1 change: 1 addition & 0 deletions test/flow/flow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ using alt_builder = flow::graph<Name, flow::graphviz_builder>;
template <stdx::ct_string Name = ""> struct alt_flow_service {
using builder_t = alt_builder<Name>;
using interface_t = flow::VizFunctionPtr;
constexpr static auto uninitialized() -> interface_t { return {}; }
};

struct VizFlow : public alt_flow_service<"debug"> {};
Expand Down
34 changes: 34 additions & 0 deletions test/flow/flow_uninit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <cib/built.hpp>
#include <flow/builder.hpp>

#include <stdx/panic.hpp>

#include <catch2/catch_test_macros.hpp>

#include <string>
#include <string_view>

namespace {
struct TestFlow : flow::service<"test"> {};

std::string panic_string = {};
int panics{};

struct test_panic_handler {
template <stdx::ct_string Why, typename... Ts>
static auto panic(Ts &&...) -> void {
panic_string = std::string_view{Why};
++panics;
}
};
} // namespace

template <> inline auto stdx::panic_handler<> = test_panic_handler{};

TEST_CASE("run flow when uninitialized", "[flow]") {
panics = 0;
cib::service<TestFlow>();
CHECK(panics == 1);
CHECK(panic_string ==
"Attempting to run flow (test) before it is initialized");
}
2 changes: 2 additions & 0 deletions test/msg/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ add_tests(
field_matchers
handler
handler_builder
handler_uninit
indexed_builder
indexed_callback
indexed_handler
indexed_handler_uninit
message
send
LIBRARIES
Expand Down
54 changes: 54 additions & 0 deletions test/msg/handler_uninit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include <cib/built.hpp>
#include <msg/message.hpp>
#include <msg/service.hpp>

#include <stdx/panic.hpp>

#include <catch2/catch_test_macros.hpp>

#include <cstdint>
#include <string>
#include <string_view>

struct raw_msg_t {};

namespace {
using namespace msg;

using msg_defn = message<"msg">;
using test_msg_t = msg::owning<msg_defn>;
using msg_view_t = msg::const_view<msg_defn>;
struct test_service : msg::service<msg_view_t> {};
struct raw_service : msg::service<raw_msg_t> {};

std::string panic_string = {};
int panics{};

struct test_panic_handler {
template <stdx::ct_string Why, typename... Ts>
static auto panic(Ts &&...) -> void {
panic_string = std::string_view{Why};
++panics;
}
};
} // namespace

template <> inline auto stdx::panic_handler<> = test_panic_handler{};

TEST_CASE("invoke handle on service when uninitialized (named msg type)",
"[handler]") {
panics = 0;
cib::service<test_service>->handle(test_msg_t{});
CHECK(panics == 1);
CHECK(panic_string ==
"Attempting to handle msg (msg) before service is initialized");
}

TEST_CASE("invoke handle on service when uninitialized (raw msg type)",
"[handler]") {
panics = 0;
cib::service<raw_service>->handle(raw_msg_t{});
CHECK(panics == 1);
CHECK(panic_string ==
"Attempting to handle msg (raw_msg_t) before service is initialized");
}
Loading
Loading