summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2024-04-21 21:57:03 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2024-04-21 21:57:03 +0200
commite5cf4883253476500a9b62f0935c87ce40579ec2 (patch)
tree1e79f01acfced504c41e7096df6d18e3537b941a
parent9f94e8c8e80623372b450352f7a24dc8623f4712 (diff)
downloadcpp-templates-e5cf4883253476500a9b62f0935c87ce40579ec2.tar.gz
cpp-templates-e5cf4883253476500a9b62f0935c87ce40579ec2.zip
fn: wrapper hook with deducing noexcept
-rw-r--r--fn_hook.cc99
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());
+}