diff options
author | Johannes Stoelp <johannes.stoelp@gmail.com> | 2024-04-21 21:57:03 +0200 |
---|---|---|
committer | Johannes Stoelp <johannes.stoelp@gmail.com> | 2024-04-21 21:57:03 +0200 |
commit | e5cf4883253476500a9b62f0935c87ce40579ec2 (patch) | |
tree | 1e79f01acfced504c41e7096df6d18e3537b941a /fn_hook.cc | |
parent | 9f94e8c8e80623372b450352f7a24dc8623f4712 (diff) | |
download | cpp-templates-e5cf4883253476500a9b62f0935c87ce40579ec2.tar.gz cpp-templates-e5cf4883253476500a9b62f0935c87ce40579ec2.zip |
fn: wrapper hook with deducing noexcept
Diffstat (limited to 'fn_hook.cc')
-rw-r--r-- | fn_hook.cc | 99 |
1 files changed, 99 insertions, 0 deletions
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 <dlfcn.h> + +// 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 <typename Derived, typename FnPtr> +struct hook; + +template <typename Derived, typename Ret, typename... Args, bool IsNoexcept> +struct hook<Derived, Ret (*)(Args...) noexcept(IsNoexcept)> { + // 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<fn_t>(::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<hook_##fn, decltype(&::fn)> { \ + static constexpr const char* sym = #fn; \ + } fn; \ + } \ + \ + extern "C" ret fn(__VA_ARGS__) noexcept(detail::hook_##fn::is_noexcept) + +// -- EXAMPLE ------------------------------------------------------------------ + +#include <stdio.h> +#include <unistd.h> + +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()); +} |