[libc-commits] [libc] [llvm] [libc] Use cpp::function in death test executors and FPExceptMatcher (PR #235777)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Mon Oct 26 06:51:35 PDT 2026


https://github.com/labath created https://github.com/llvm/llvm-project/pull/235777

Instead of the hand-rolled FunctionCaller classes, which had to be heap-allocated and then manually deleted (on every exit path in the case of invoke_in_subprocess), pass the callable as a cpp::function. This lets us remove the createCallable/getFunctionCaller helpers and all the `delete func` statements.

cpp::function does not own the callable, but that's fine here as the callable (a temporary created by the test macro) outlives the call.

Assisted-by: Gemini

>From 8bf672799fd6faa164c2379f04a8e8a953965c98 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 26 Oct 2026 13:50:21 +0000
Subject: [PATCH] [libc] Use cpp::function in death test executors and
 FPExceptMatcher

Instead of the hand-rolled FunctionCaller classes, which had to be
heap-allocated and then manually deleted (on every exit path in the
case of invoke_in_subprocess), pass the callable as a cpp::function.
This lets us remove the createCallable/getFunctionCaller helpers and all
the `delete func` statements.

cpp::function does not own the callable, but that's fine here as the
callable (a temporary created by the test macro) outlives the call.

Assisted-by: Gemini
---
 libc/test/UnitTest/CMakeLists.txt             |  4 +++
 libc/test/UnitTest/ExecuteFunction.h          |  9 ++-----
 libc/test/UnitTest/ExecuteFunctionUnix.cpp    | 22 ++++++----------
 libc/test/UnitTest/FPExceptMatcher.cpp        |  6 ++---
 libc/test/UnitTest/FPExceptMatcher.h          | 25 +++----------------
 libc/test/UnitTest/LibcDeathTestExecutors.cpp |  4 +--
 libc/test/UnitTest/LibcTest.h                 | 19 +++-----------
 .../libc/test/UnitTest/BUILD.bazel            |  2 ++
 8 files changed, 26 insertions(+), 65 deletions(-)

diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index c99666dde0eaf5..de82c2f1362d65 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -102,6 +102,7 @@ add_unittest_framework_library(
     libc.hdr.stdint_proxy
     libc.src.__support.big_int
     libc.src.__support.c_string
+    libc.src.__support.CPP.functional
     libc.src.__support.CPP.string
     libc.src.__support.CPP.string_view
     libc.src.__support.CPP.type_traits
@@ -124,6 +125,7 @@ add_unittest_framework_library(
 if(LIBC_TEST_SUBPROCESS_TESTS)
   set(libc_death_test_deps
     libc.hdr.stdint_proxy
+    libc.src.__support.CPP.functional
     libc.src.__support.CPP.scope
     libc.src.__support.libc_assert
   )
@@ -158,6 +160,7 @@ else()
       ExecuteFunction.h
     DEPENDS
       libc.hdr.stdint_proxy
+      libc.src.__support.CPP.functional
   )
 endif()
 
@@ -229,6 +232,7 @@ add_unittest_framework_library(
     FPExceptMatcher.h
   DEPENDS
     .LibcTest
+    libc.src.__support.CPP.functional
     libc.src.__support.FPUtil.fp_bits
     libc.src.__support.FPUtil.fenv_impl
     libc.hdr.types.fenv_t
diff --git a/libc/test/UnitTest/ExecuteFunction.h b/libc/test/UnitTest/ExecuteFunction.h
index 5844fd55d4e286..4c9dc1387429dd 100644
--- a/libc/test/UnitTest/ExecuteFunction.h
+++ b/libc/test/UnitTest/ExecuteFunction.h
@@ -10,18 +10,13 @@
 #define LLVM_LIBC_TEST_UNITTEST_EXECUTEFUNCTION_H
 
 #include "hdr/stdint_proxy.h"
+#include "src/__support/CPP/functional.h"
 #include "src/__support/CPP/limits.h"
 #include "src/__support/macros/config.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace testutils {
 
-class FunctionCaller {
-public:
-  virtual ~FunctionCaller() {}
-  virtual void operator()() = 0;
-};
-
 struct ProcessStatus {
   int platform_defined;
   const char *failure = nullptr;
@@ -42,7 +37,7 @@ struct ProcessStatus {
   int get_fatal_signal();
 };
 
-ProcessStatus invoke_in_subprocess(FunctionCaller *func,
+ProcessStatus invoke_in_subprocess(cpp::function<void(void)> func,
                                    int timeout_ms = ProcessStatus::TIMEOUT);
 
 const char *signal_as_string(int signum);
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index 919ef0536034c7..8ef03da219bdb9 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -66,29 +66,25 @@ int ProcessStatus::get_fatal_signal() {
   return WTERMSIG(platform_defined);
 }
 
-ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
+ProcessStatus invoke_in_subprocess(cpp::function<void(void)> func,
+                                   int timeout_ms) {
   int pipe_fds[2];
-  if (LIBC_IMPL::pipe(pipe_fds) == -1) {
-    delete func;
+  if (LIBC_IMPL::pipe(pipe_fds) == -1)
     return ProcessStatus::error("pipe(2) failed");
-  }
 
   // Don't copy the buffers into the child process and print twice.
   LIBC_IMPL::fflush(stderr);
   LIBC_IMPL::fflush(stdout);
   pid_t pid = LIBC_IMPL::fork();
-  if (pid == -1) {
-    delete func;
+  if (pid == -1)
     return ProcessStatus::error("fork(2) failed");
-  }
 
   if (!pid) {
     LIBC_IMPL::close(pipe_fds[0]);
 #if defined(__linux__) && defined(PR_SET_DUMPABLE)
     LIBC_IMPL::prctl(PR_SET_DUMPABLE, 0);
 #endif
-    (*func)();
-    delete func;
+    func();
     LIBC_IMPL::exit(0);
   }
   LIBC_IMPL::close(pipe_fds[1]);
@@ -97,14 +93,12 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   pollfd poll_fd{pipe_fds[0], POLLIN, 0};
   // No events requested so this call will only return after the timeout or if
   // the pipes peer was closed, signaling the process exited.
-  if (LIBC_IMPL::poll(&poll_fd, 1, timeout_ms) == -1) {
-    delete func;
+  if (LIBC_IMPL::poll(&poll_fd, 1, timeout_ms) == -1)
     return ProcessStatus::error("poll(2) failed");
-  }
+
   // If the pipe wasn't closed by the child yet then timeout has expired.
   if (!(poll_fd.revents & POLLHUP)) {
     LIBC_IMPL::kill(pid, SIGKILL);
-    delete func;
     return ProcessStatus::timed_out_ps();
   }
 
@@ -113,11 +107,9 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   // and doesn't turn into a zombie.
   pid_t status = LIBC_IMPL::waitpid(pid, &wstatus, 0);
   if (status == -1) {
-    delete func;
     return ProcessStatus::error("waitpid(2) failed");
   }
   LIBC_ASSERT(status == pid);
-  delete func;
   return {wstatus};
 }
 
diff --git a/libc/test/UnitTest/FPExceptMatcher.cpp b/libc/test/UnitTest/FPExceptMatcher.cpp
index 6da21a279ff3de..9b2d909eec145a 100644
--- a/libc/test/UnitTest/FPExceptMatcher.cpp
+++ b/libc/test/UnitTest/FPExceptMatcher.cpp
@@ -14,7 +14,6 @@
 #include "FPExceptMatcher.h"
 
 #include "src/__support/macros/config.h"
-#include "test/UnitTest/ExecuteFunction.h" // FunctionCaller
 #include "test/UnitTest/Test.h"
 
 #include "hdr/types/fenv_t.h"
@@ -48,7 +47,7 @@ static void sigfpeHandler([[maybe_unused]] int sig) {
 
 #endif // TARGET_SUPPORTS_SIGNAL_CATCHING
 
-FPExceptMatcher::FPExceptMatcher(FunctionCaller *func) {
+FPExceptMatcher::FPExceptMatcher(cpp::function<void(void)> func) {
 #if TARGET_SUPPORTS_SIGNAL_CATCHING
   auto *oldSIGFPEHandler = signal(SIGFPE, &sigfpeHandler);
 #endif
@@ -59,8 +58,7 @@ FPExceptMatcher::FPExceptMatcher(FunctionCaller *func) {
 #if TARGET_SUPPORTS_SIGNAL_CATCHING
   if (sigsetjmp(jumpBuffer, 1) == 0)
 #endif
-    func->call();
-  delete func;
+    func();
   // We restore the previous floating point environment after
   // the call to the function which can potentially raise SIGFPE.
   fputil::set_env(&oldEnv);
diff --git a/libc/test/UnitTest/FPExceptMatcher.h b/libc/test/UnitTest/FPExceptMatcher.h
index 9b05e83bc1e3aa..421ece17978b18 100644
--- a/libc/test/UnitTest/FPExceptMatcher.h
+++ b/libc/test/UnitTest/FPExceptMatcher.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_LIBC_TEST_UNITTEST_FPEXCEPTMATCHER_H
 #define LLVM_LIBC_TEST_UNITTEST_FPEXCEPTMATCHER_H
 
+#include "src/__support/CPP/functional.h"
 #include "src/__support/macros/config.h"
 #include "test/UnitTest/Test.h"
 #include "test/UnitTest/TestLogger.h"
@@ -24,24 +25,7 @@ class FPExceptMatcher : public Matcher<bool> {
   bool exceptionRaised;
 
 public:
-  class FunctionCaller {
-  public:
-    virtual ~FunctionCaller() {}
-    virtual void call() = 0;
-  };
-
-  template <typename Func> static FunctionCaller *getFunctionCaller(Func func) {
-    struct Callable : public FunctionCaller {
-      Func f;
-      explicit Callable(Func theFunc) : f(theFunc) {}
-      void call() override { f(); }
-    };
-
-    return new Callable(func);
-  }
-
-  // Takes ownership of func.
-  explicit FPExceptMatcher(FunctionCaller *func);
+  explicit FPExceptMatcher(cpp::function<void(void)> func);
 
   bool match([[maybe_unused]] bool unused) { return exceptionRaised; }
 
@@ -55,10 +39,7 @@ class FPExceptMatcher : public Matcher<bool> {
 } // namespace LIBC_NAMESPACE_DECL
 
 #define ASSERT_RAISES_FP_EXCEPT(func)                                          \
-  ASSERT_THAT(                                                                 \
-      true,                                                                    \
-      LIBC_NAMESPACE::testing::FPExceptMatcher(                                \
-          LIBC_NAMESPACE::testing::FPExceptMatcher::getFunctionCaller(func)))
+  ASSERT_THAT(true, LIBC_NAMESPACE::testing::FPExceptMatcher(func))
 
 #else // !LIBC_TEST_HAS_MATCHERS()
 
diff --git a/libc/test/UnitTest/LibcDeathTestExecutors.cpp b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
index d579ae52e6383e..a50c0ea1ddadc0 100644
--- a/libc/test/UnitTest/LibcDeathTestExecutors.cpp
+++ b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
@@ -20,7 +20,7 @@ constexpr unsigned TIMEOUT_MS = 10000;
 namespace LIBC_NAMESPACE_DECL {
 namespace testing {
 
-bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
+bool Test::testProcessKilled(cpp::function<void(void)> Func, int Signal,
                              const char *LHSStr,
                              [[maybe_unused]] const char *RHSStr,
                              internal::Location Loc) {
@@ -65,7 +65,7 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
   return false;
 }
 
-bool Test::testProcessExits(testutils::FunctionCaller *Func, int ExitCode,
+bool Test::testProcessExits(cpp::function<void(void)> Func, int ExitCode,
                             const char *LHSStr, const char *RHSStr,
                             internal::Location Loc) {
   testutils::ProcessStatus Result =
diff --git a/libc/test/UnitTest/LibcTest.h b/libc/test/UnitTest/LibcTest.h
index be3574c21a7304..cd6ccecc7134ef 100644
--- a/libc/test/UnitTest/LibcTest.h
+++ b/libc/test/UnitTest/LibcTest.h
@@ -24,6 +24,7 @@
 // This file can only include headers from src/__support/ or test/UnitTest. No
 // other headers should be included.
 
+#include "src/__support/CPP/functional.h"
 #include "src/__support/CPP/string.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/CPP/type_traits.h"
@@ -213,24 +214,14 @@ class Test {
     return testMatch(Matcher.match(Value), Matcher, ValueStr, MatcherStr, Loc);
   }
 
-  bool testProcessExits(testutils::FunctionCaller *Func, int ExitCode,
+  bool testProcessExits(cpp::function<void(void)> Func, int ExitCode,
                         const char *LHSStr, const char *RHSStr,
                         internal::Location Loc);
 
-  bool testProcessKilled(testutils::FunctionCaller *Func, int Signal,
+  bool testProcessKilled(cpp::function<void(void)> Func, int Signal,
                          const char *LHSStr, const char *RHSStr,
                          internal::Location Loc);
 
-  template <typename Func> testutils::FunctionCaller *createCallable(Func f) {
-    struct Callable : public testutils::FunctionCaller {
-      Func f;
-      Callable(Func f) : f(f) {}
-      void operator()() override { f(); }
-    };
-
-    return new Callable(f);
-  }
-
 private:
   virtual void Run() = 0;
   virtual const char *getName() const = 0;
@@ -502,9 +493,7 @@ CString libc_make_test_file_path_func(const char *file_name);
 
 #define LIBC_TEST_PROCESS_(TEST_FUNC, FUNC, VALUE, RET_OR_EMPTY)               \
   LIBC_TEST_SCAFFOLDING_(                                                      \
-      TEST_FUNC(LIBC_NAMESPACE::testing::Test::createCallable(FUNC), VALUE,    \
-                #FUNC, #VALUE, LIBC_TEST_LOC_()),                              \
-      RET_OR_EMPTY)
+      TEST_FUNC(FUNC, VALUE, #FUNC, #VALUE, LIBC_TEST_LOC_()), RET_OR_EMPTY)
 
 #define EXPECT_EXITS(FUNC, EXIT)                                               \
   LIBC_TEST_PROCESS_(testProcessExits, FUNC, EXIT, )
diff --git a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
index d594c846957ceb..70c48473105a6b 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
@@ -53,6 +53,7 @@ libc_test_library(
         "//libc:__support_c_string",
         "//libc:__support_cpp_bit",
         "//libc:__support_cpp_bitset",
+        "//libc:__support_cpp_functional",
         "//libc:__support_cpp_limits",
         "//libc:__support_cpp_scope",
         "//libc:__support_cpp_span",
@@ -166,6 +167,7 @@ libc_test_library(
         "//libc:__support_cpp_array",
         "//libc:__support_cpp_bit",
         "//libc:__support_cpp_bitset",
+        "//libc:__support_cpp_functional",
         "//libc:__support_cpp_span",
         "//libc:__support_cpp_type_traits",
         "//libc:__support_cpp_utility",



More information about the libc-commits mailing list