From e5cf4883253476500a9b62f0935c87ce40579ec2 Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Sun, 21 Apr 2024 21:57:03 +0200 Subject: fn: wrapper hook with deducing noexcept --- fn_hook.cc | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 fn_hook.cc diff --git a/fn_hook.cc b/fn_hook.cc new file mode 100644 index 0000000..1c70eab --- /dev/null +++ b/fn_hook.cc @@ -0,0 +1,99 @@ +#if 0 +set -xe +trap 'rm -f a.out' EXIT +g++ -std=c++17 -rdynamic fn_hook.cc -Wno-ignored-attributes && ./a.out +clang++ -std=c++17 -rdynamic fn_hook.cc && ./a.out +g++ -std=c++20 -rdynamic fn_hook.cc -Wno-ignored-attributes && ./a.out +clang++ -std=c++20 -rdynamic fn_hook.cc && ./a.out +exit 0 +#endif + +#include + +// Function wrapper hook with deducing noexcept. +// +// This is an experiment providing a function wrapper which automatically +// deduces if the wrapped function is marked noexcept. The information is then +// generated through the whole wrapper chain. +// +// To achieve this, a template specialization for function pointers is defined +// over a non implemented primary template. + +// -- FN HOOK ------------------------------------------------------------------ + +namespace detail { +template +struct hook; + +template +struct hook { + // Function pointer type. + using fn_t = Ret (*)(Args...); + + // Remember whether this hook was noexcept, to be used for the outer wrapper. + static constexpr bool is_noexcept = IsNoexcept; + + constexpr hook() noexcept = default; + + // Forwarding wrapper implementation. This also initializes the function + // pointer to the next function to call, on the first invocation (non-thread + // safe). Alternatively, the function pointer could be retrieved during + // construction, but then the construction and first invocation of the wrapped + // function must be coordinated. + Ret operator()(Args... args) noexcept(IsNoexcept) { + if (ptr == nullptr) { + init(); + } + return ptr(args...); + } + + // Initialize function pointer to next function to call. + void init() noexcept { + ptr = reinterpret_cast(::dlsym(RTLD_NEXT, Derived::sym)); + } + +private: + fn_t ptr = nullptr; +}; +} // namespace detail + +// Used to ensure that global function hook objects are initialized during +// compile time; constant initialization, which however allows mutation during +// runtime, compared to constexpr object, which is non-mutable. +#if defined(__cpp_constinit) +#define CONSTINIT constinit +#else +#define CONSTINIT +#endif + +// Helper macro to generate boilerplate code for function wrapper hook. +#define WRAP(ret, fn, ...) \ + namespace detail { \ + static CONSTINIT struct hook_##fn : hook { \ + static constexpr const char* sym = #fn; \ + } fn; \ + } \ + \ + extern "C" ret fn(__VA_ARGS__) noexcept(detail::hook_##fn::is_noexcept) + +// -- EXAMPLE ------------------------------------------------------------------ + +#include +#include + +WRAP(ssize_t, write, int fd, const void* buf, size_t sz) { + puts("wrapper write(..)"); + return detail::write(fd, buf, sz); +} + +WRAP(pid_t, getpid) { + puts("wrapper getpid()"); + return detail::getpid(); +} + +int main() { + const char msg[] = "hello moose\n"; + write(STDOUT_FILENO, msg, sizeof(msg)); + + printf("pid = %d\n", getpid()); +} -- cgit v1.2.3