[libc-commits] [libc] [llvm] [libc] Port process utilities to hermetic mode and enable some tests (PR #209999)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Thu Jul 16 00:49:40 PDT 2026


https://github.com/labath updated https://github.com/llvm/llvm-project/pull/209999

>From 0a5eeafc754f359aa082cbd3dd136aa0a6968b49 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 14 Jul 2026 09:15:08 +0000
Subject: [PATCH 1/2] [libc] Port process utilities to hermetic mode and enable
 some tests

This is achieved by calling internal libc functions in hermetic mode. In
the overlay mode I keep calling the system functions so that the tests
work even on systems which don't have these implemented.

I also needed to implement the delete operators as both the libc proper
and the test framework uses them nowadays.

This is sufficient to enable all stdlib tests in hermetic mode, except
for one, which actually exposes a bug in the implementation. I'll deal
with that in a separate patch.
---
 libc/cmake/modules/LLVMLibCTestRules.cmake    | 13 +++++
 libc/test/UnitTest/CMakeLists.txt             | 15 +++++-
 libc/test/UnitTest/ExecuteFunctionUnix.cpp    | 54 ++++++++++++++-----
 libc/test/UnitTest/HermeticTestUtils.cpp      | 19 ++-----
 libc/test/UnitTest/LibcDeathTestExecutors.cpp |  5 +-
 libc/test/src/stdlib/CMakeLists.txt           | 12 +----
 6 files changed, 75 insertions(+), 43 deletions(-)

diff --git a/libc/cmake/modules/LLVMLibCTestRules.cmake b/libc/cmake/modules/LLVMLibCTestRules.cmake
index b7d3a98059723..3f0f059475710 100644
--- a/libc/cmake/modules/LLVMLibCTestRules.cmake
+++ b/libc/cmake/modules/LLVMLibCTestRules.cmake
@@ -778,6 +778,18 @@ function(add_libc_hermetic test_name)
       libc.src.string.memset
       libc.src.strings.bcmp
       libc.src.strings.bzero
+      # Syscalls used by death tests.
+      libc.src.poll.poll
+      libc.src.signal.kill
+      libc.src.stdio.fflush
+      libc.src.stdio.stderr
+      libc.src.stdio.stdout
+      libc.src.stdlib.exit
+      libc.src.string.strsignal
+      libc.src.sys.wait.waitpid
+      libc.src.unistd.close
+      libc.src.unistd.fork
+      libc.src.unistd.pipe
   )
 
   if(libc.src.compiler.__stack_chk_fail IN_LIST TARGET_LLVMLIBC_ENTRYPOINTS)
@@ -1007,6 +1019,7 @@ function(add_libc_test test_name)
       ${test_name}.__hermetic__
       LINK_LIBRARIES
         LibcTest.hermetic
+        LibcDeathTestExecutors.hermetic
       ${LIBC_TEST_UNPARSED_ARGUMENTS}
     )
     get_fq_target_name(${test_name} fq_test_name)
diff --git a/libc/test/UnitTest/CMakeLists.txt b/libc/test/UnitTest/CMakeLists.txt
index 4a47597ae3a45..0ce0ec3c54a31 100644
--- a/libc/test/UnitTest/CMakeLists.txt
+++ b/libc/test/UnitTest/CMakeLists.txt
@@ -40,7 +40,7 @@ function(add_unittest_framework_library name)
   endif()
   target_compile_options(${name}.unit PRIVATE ${compile_options})
 
-    _get_hermetic_test_compile_options(compile_options "")
+  _get_hermetic_test_compile_options(compile_options "")
   target_include_directories(${name}.hermetic PRIVATE ${LIBC_INCLUDE_DIR})
   target_compile_options(${name}.hermetic PRIVATE ${compile_options} -nostdinc++)
 
@@ -100,6 +100,17 @@ set(libc_death_test_srcs LibcDeathTestExecutors.cpp)
 if(${LIBC_TARGET_OS} STREQUAL "linux" OR ${LIBC_TARGET_OS} STREQUAL "darwin")
   list(APPEND libc_death_test_srcs ExecuteFunctionUnix.cpp)
 endif()
+set(libc_death_tess_deps libc.hdr.stdint_proxy)
+if (LLVM_LIBC_FULL_BUILD)
+  list(APPEND libc_death_tess_deps
+    libc.include.llvm-libc-macros.poll-macros
+    libc.include.llvm-libc-macros.signal_macros
+    libc.include.llvm-libc-macros.sys_wait_macros
+    libc.include.llvm-libc-types.pid_t
+    libc.include.llvm-libc-types.struct_pollfd
+    libc.src.__support.libc_assert
+  )
+endif()
 
 add_unittest_framework_library(
   LibcDeathTestExecutors
@@ -108,7 +119,7 @@ add_unittest_framework_library(
   HDRS
     ExecuteFunction.h
   DEPENDS
-    libc.hdr.stdint_proxy
+    ${libc_death_tess_deps}
 )
 
 add_unittest_framework_library(
diff --git a/libc/test/UnitTest/ExecuteFunctionUnix.cpp b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
index ab18f7a2ebf52..6ca867d5e65df 100644
--- a/libc/test/UnitTest/ExecuteFunctionUnix.cpp
+++ b/libc/test/UnitTest/ExecuteFunctionUnix.cpp
@@ -7,9 +7,32 @@
 //===----------------------------------------------------------------------===//
 
 #include "ExecuteFunction.h"
+#include "src/__support/libc_assert.h"
 #include "src/__support/macros/config.h"
 #include "test/UnitTest/ExecuteFunction.h" // FunctionCaller
-#include <assert.h>
+
+#ifdef LIBC_FULL_BUILD
+#include "include/llvm-libc-macros/poll-macros.h"
+#include "include/llvm-libc-macros/signal-macros.h"
+#include "include/llvm-libc-macros/sys-wait-macros.h"
+#include "include/llvm-libc-types/pid_t.h"
+#include "include/llvm-libc-types/struct_pollfd.h"
+#include "src/__support/libc_assert.h"
+#include "src/poll/poll.h"
+#include "src/signal/kill.h"
+#include "src/stdio/fflush.h"
+#include "src/stdio/stderr.h"
+#include "src/stdio/stdout.h"
+#include "src/stdlib/exit.h"
+#include "src/string/strsignal.h"
+#include "src/sys/wait/waitpid.h"
+#include "src/unistd/close.h"
+#include "src/unistd/fork.h"
+#include "src/unistd/pipe.h"
+
+#define LIBC_IMPL LIBC_NAMESPACE
+
+#else // Overlay mode
 #include <poll.h>
 #include <signal.h>
 #include <stdio.h>
@@ -18,13 +41,16 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#define LIBC_IMPL
+#endif
+
 namespace LIBC_NAMESPACE_DECL {
 namespace testutils {
 
 bool ProcessStatus::exited_normally() { return WIFEXITED(platform_defined); }
 
 int ProcessStatus::get_exit_code() {
-  assert(exited_normally() && "Abnormal termination, no exit code");
+  LIBC_ASSERT(exited_normally() && "Abnormal termination, no exit code");
   return WEXITSTATUS(platform_defined);
 }
 
@@ -36,15 +62,15 @@ int ProcessStatus::get_fatal_signal() {
 
 ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   int pipe_fds[2];
-  if (::pipe(pipe_fds) == -1) {
+  if (LIBC_IMPL::pipe(pipe_fds) == -1) {
     delete func;
     return ProcessStatus::error("pipe(2) failed");
   }
 
   // Don't copy the buffers into the child process and print twice.
-  ::fflush(stderr);
-  ::fflush(stdout);
-  pid_t pid = ::fork();
+  LIBC_IMPL::fflush(stderr);
+  LIBC_IMPL::fflush(stdout);
+  pid_t pid = LIBC_IMPL::fork();
   if (pid == -1) {
     delete func;
     return ProcessStatus::error("fork(2) failed");
@@ -53,20 +79,20 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   if (!pid) {
     (*func)();
     delete func;
-    ::exit(0);
+    LIBC_IMPL::exit(0);
   }
-  ::close(pipe_fds[1]);
+  LIBC_IMPL::close(pipe_fds[1]);
 
   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 (::poll(&poll_fd, 1, timeout_ms) == -1) {
+  if (LIBC_IMPL::poll(&poll_fd, 1, timeout_ms) == -1) {
     delete func;
     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)) {
-    ::kill(pid, SIGKILL);
+    LIBC_IMPL::kill(pid, SIGKILL);
     delete func;
     return ProcessStatus::timed_out_ps();
   }
@@ -74,17 +100,19 @@ ProcessStatus invoke_in_subprocess(FunctionCaller *func, int timeout_ms) {
   int wstatus = 0;
   // Wait on the pid of the subprocess here so it gets collected by the system
   // and doesn't turn into a zombie.
-  pid_t status = ::waitpid(pid, &wstatus, 0);
+  pid_t status = LIBC_IMPL::waitpid(pid, &wstatus, 0);
   if (status == -1) {
     delete func;
     return ProcessStatus::error("waitpid(2) failed");
   }
-  assert(status == pid);
+  LIBC_ASSERT(status == pid);
   delete func;
   return {wstatus};
 }
 
-const char *signal_as_string(int signum) { return ::strsignal(signum); }
+const char *signal_as_string(int signum) {
+  return LIBC_IMPL::strsignal(signum);
+}
 
 } // namespace testutils
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/UnitTest/HermeticTestUtils.cpp b/libc/test/UnitTest/HermeticTestUtils.cpp
index 3c82d1e963643..b79ca68e13c3a 100644
--- a/libc/test/UnitTest/HermeticTestUtils.cpp
+++ b/libc/test/UnitTest/HermeticTestUtils.cpp
@@ -132,15 +132,9 @@ void *operator new(size_t size) { return malloc(size); }
 
 void *operator new[](size_t size) { return malloc(size); }
 
-void operator delete(void *) {
-  // The libc runtime should not use the global delete operator. Hence,
-  // we just trap here to catch any such accidental usages.
-  __builtin_trap();
-}
+void operator delete(void *ptr) { free(ptr); }
 
-void operator delete([[maybe_unused]] void *ptr, [[maybe_unused]] size_t size) {
-  __builtin_trap();
-}
+void operator delete(void *ptr, [[maybe_unused]] size_t size) { free(ptr); }
 
 // Defining members in the std namespace is not preferred. But, we do it here
 // so that we can use it to define the operator new which takes std::align_val_t
@@ -149,11 +143,8 @@ namespace std {
 enum class align_val_t : size_t {};
 } // namespace std
 
-void operator delete([[maybe_unused]] void *mem, std::align_val_t) noexcept {
-  __builtin_trap();
-}
+void operator delete(void *ptr, std::align_val_t) noexcept { free(ptr); }
 
-void operator delete([[maybe_unused]] void *mem, unsigned int,
-                     std::align_val_t) noexcept {
-  __builtin_trap();
+void operator delete(void *ptr, unsigned int, std::align_val_t) noexcept {
+  free(ptr);
 }
diff --git a/libc/test/UnitTest/LibcDeathTestExecutors.cpp b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
index 5f8d7b86a5187..9a0a749a3404a 100644
--- a/libc/test/UnitTest/LibcDeathTestExecutors.cpp
+++ b/libc/test/UnitTest/LibcDeathTestExecutors.cpp
@@ -8,12 +8,11 @@
 
 #include "LibcTest.h"
 
+#include "src/__support/libc_assert.h"
 #include "src/__support/macros/config.h"
 #include "test/UnitTest/ExecuteFunction.h"
 #include "test/UnitTest/TestLogger.h"
 
-#include <assert.h>
-
 namespace {
 constexpr unsigned TIMEOUT_MS = 10000;
 } // Anonymous namespace
@@ -51,7 +50,7 @@ bool Test::testProcessKilled(testutils::FunctionCaller *Func, int Signal,
   }
 
   int KilledBy = Result.get_fatal_signal();
-  assert(KilledBy != 0 && "Not killed by any signal");
+  LIBC_ASSERT(KilledBy != 0 && "Not killed by any signal");
   if (Signal == -1 || KilledBy == Signal)
     return true;
 
diff --git a/libc/test/src/stdlib/CMakeLists.txt b/libc/test/src/stdlib/CMakeLists.txt
index 0101386f5b2fc..f4cb50f7fe4c2 100644
--- a/libc/test/src/stdlib/CMakeLists.txt
+++ b/libc/test/src/stdlib/CMakeLists.txt
@@ -493,8 +493,6 @@ if(LLVM_LIBC_FULL_BUILD)
 
   add_libc_test(
     _Exit_test
-    # The EXPECT_EXITS test is only availible for unit tests.
-    UNIT_TEST_ONLY
     SUITE
       libc-stdlib-tests
     SRCS
@@ -506,8 +504,6 @@ if(LLVM_LIBC_FULL_BUILD)
 
   add_libc_test(
     exit_test
-    # The EXPECT_EXITS test is only availible for unit tests.
-    UNIT_TEST_ONLY
     SUITE
       libc-stdlib-tests
     SRCS
@@ -519,7 +515,7 @@ if(LLVM_LIBC_FULL_BUILD)
 
   add_libc_test(
     atexit_test
-    # The EXPECT_EXITS test is only availible for unit tests.
+    # TODO: Fix this test in hermetic mode.
     UNIT_TEST_ONLY
     SUITE
       libc-stdlib-tests
@@ -534,8 +530,6 @@ if(LLVM_LIBC_FULL_BUILD)
 
   add_libc_test(
     at_quick_exit_test
-    # The EXPECT_EXITS test is only availible for unit tests.
-    UNIT_TEST_ONLY
     SUITE
       libc-stdlib-tests
     SRCS
@@ -549,8 +543,6 @@ if(LLVM_LIBC_FULL_BUILD)
 
   add_libc_test(
     abort_test
-    # The EXPECT_DEATH test is only availible for unit tests.
-    UNIT_TEST_ONLY
     SUITE
       libc-stdlib-tests
     SRCS
@@ -564,8 +556,6 @@ if(LLVM_LIBC_FULL_BUILD)
 
   add_libc_test(
     quick_exit_test
-    # The EXPECT_EXITS test is only availible for unit tests.
-    UNIT_TEST_ONLY
     SUITE
       libc-stdlib-tests
     SRCS

>From a492a1fc3cf159613efbfef486e8df9f3a0a370b Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Thu, 16 Jul 2026 07:49:19 +0000
Subject: [PATCH 2/2] fix bazel

---
 utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel | 1 +
 1 file changed, 1 insertion(+)

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 9eacb93043352..2f62ab26bca97 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
@@ -61,6 +61,7 @@ libc_test_library(
         "//libc:__support_fputil_fp_bits",
         "//libc:__support_fputil_fpbits_str",
         "//libc:__support_fputil_rounding_mode",
+        "//libc:__support_libc_assert",
         "//libc:__support_libc_errno",
         "//libc:__support_macros_config",
         "//libc:__support_macros_properties_architectures",



More information about the libc-commits mailing list