[libc-commits] [libc] [libc][tsearch] add tsearch functions (PR #172625)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Wed Mar 11 08:50:52 PDT 2026


https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/172625

>From 6622a513669065c4897cfb9d862539b184bffec2 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Wed, 17 Dec 2025 22:13:25 -0500
Subject: [PATCH 1/7] [libc][tsearch] add tsearch functions

---
 libc/config/linux/aarch64/entrypoints.txt     |   6 +
 libc/config/linux/x86_64/entrypoints.txt      |   6 +
 libc/hdr/types/CMakeLists.txt                 |  18 +++
 libc/hdr/types/VISIT.h                        |  22 +++
 libc/hdr/types/posix_tnode.h                  |  28 ++++
 libc/include/CMakeLists.txt                   |   4 +
 libc/include/llvm-libc-types/CMakeLists.txt   |  16 ++
 .../llvm-libc-types/__action_closure_fn_t.h   |  17 +++
 libc/include/llvm-libc-types/__action_fn_t.h  |  17 +++
 libc/include/llvm-libc-types/__free_fn_t.h    |  14 ++
 libc/include/llvm-libc-types/posix_tnode.h    |  17 +++
 libc/include/search.yaml                      |  48 ++++++
 libc/src/__support/weak_avl.h                 |  30 ++--
 libc/src/search/CMakeLists.txt                |  68 +++++++++
 libc/src/search/tdelete.cpp                   |  36 +++++
 libc/src/search/tdelete.h                     |  20 +++
 libc/src/search/tdestroy.cpp                  |  28 ++++
 libc/src/search/tdestroy.h                    |  19 +++
 libc/src/search/tfind.cpp                     |  27 ++++
 libc/src/search/tfind.h                       |  20 +++
 libc/src/search/tsearch.cpp                   |  27 ++++
 libc/src/search/tsearch.h                     |  20 +++
 libc/src/search/twalk.cpp                     |  34 +++++
 libc/src/search/twalk.h                       |  21 +++
 libc/src/search/twalk_r.cpp                   |  34 +++++
 libc/src/search/twalk_r.h                     |  22 +++
 libc/test/src/search/CMakeLists.txt           |  15 ++
 libc/test/src/search/tsearch_test.cpp         | 137 ++++++++++++++++++
 28 files changed, 762 insertions(+), 9 deletions(-)
 create mode 100644 libc/hdr/types/VISIT.h
 create mode 100644 libc/hdr/types/posix_tnode.h
 create mode 100644 libc/include/llvm-libc-types/__action_closure_fn_t.h
 create mode 100644 libc/include/llvm-libc-types/__action_fn_t.h
 create mode 100644 libc/include/llvm-libc-types/__free_fn_t.h
 create mode 100644 libc/include/llvm-libc-types/posix_tnode.h
 create mode 100644 libc/src/search/tdelete.cpp
 create mode 100644 libc/src/search/tdelete.h
 create mode 100644 libc/src/search/tdestroy.cpp
 create mode 100644 libc/src/search/tdestroy.h
 create mode 100644 libc/src/search/tfind.cpp
 create mode 100644 libc/src/search/tfind.h
 create mode 100644 libc/src/search/tsearch.cpp
 create mode 100644 libc/src/search/tsearch.h
 create mode 100644 libc/src/search/twalk.cpp
 create mode 100644 libc/src/search/twalk.h
 create mode 100644 libc/src/search/twalk_r.cpp
 create mode 100644 libc/src/search/twalk_r.h
 create mode 100644 libc/test/src/search/tsearch_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 970c825bbfc96..f2a634ea4afa1 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1116,6 +1116,12 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.search.lfind
     libc.src.search.lsearch
     libc.src.search.remque
+    libc.src.search.tdelete
+    libc.src.search.tdestroy
+    libc.src.search.tfind
+    libc.src.search.tsearch
+    libc.src.search.twalk
+    libc.src.search.twalk_r
 
     # threads.h entrypoints
     libc.src.threads.call_once
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 9399b284fa2da..a6ce8a3715d51 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1298,6 +1298,12 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.search.lfind
     libc.src.search.lsearch
     libc.src.search.remque
+    libc.src.search.tdelete
+    libc.src.search.tdestroy
+    libc.src.search.tfind
+    libc.src.search.tsearch
+    libc.src.search.twalk
+    libc.src.search.twalk_r
 
     # threads.h entrypoints
     libc.src.threads.call_once
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 362feefa67783..8301635c2d500 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -823,3 +823,21 @@ add_proxy_header_library(
   FULL_BUILD_DEPENDS
     libc.include.llvm-libc-types.Elf64_Versym
 )
+
+add_proxy_header_library(
+  posix_tnode
+  HDRS
+    posix_tnode.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.posix_tnode
+    libc.include.search
+)
+
+add_proxy_header_library(
+  VISIT
+  HDRS
+    VISIT.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.VISIT
+    libc.include.search
+)
diff --git a/libc/hdr/types/VISIT.h b/libc/hdr/types/VISIT.h
new file mode 100644
index 0000000000000..e8377c233267c
--- /dev/null
+++ b/libc/hdr/types/VISIT.h
@@ -0,0 +1,22 @@
+//===-- Definition of macros from VISIT -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_VISIT_H
+#define LLVM_LIBC_HDR_TYPES_VISIT_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/VISIT.h"
+
+#else // Overlay mode
+
+#include <search.h>
+
+#endif // LLVM_LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_VISIT_H
diff --git a/libc/hdr/types/posix_tnode.h b/libc/hdr/types/posix_tnode.h
new file mode 100644
index 0000000000000..0b2ecfd488217
--- /dev/null
+++ b/libc/hdr/types/posix_tnode.h
@@ -0,0 +1,28 @@
+//===-- Definition of macros from posix_tnode -----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_POSIX_TNODE_H
+#define LLVM_LIBC_HDR_TYPES_POSIX_TNODE_H
+
+// posix_tnode is introduced in POSIX.1-2024.
+// this may result in problems when overlaying on older systems.
+// internal code should use __llvm_libc_tnode.
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/posix_tnode.h"
+#define __llvm_libc_tnode posix_tnode
+
+#else // Overlay mode
+
+#include <search.h>
+typedef void __llvm_libc_tnode;
+
+#endif // LLVM_LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_POSIX_TNODE_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 1bc9c2ca1a51d..f67f338764114 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -273,9 +273,13 @@ add_header_macro(
     .llvm-libc-types.ACTION
     .llvm-libc-types.ENTRY
     .llvm-libc-types.VISIT
+    .llvm-libc-types.posix_tnode
     .llvm-libc-types.__search_compare_t
     .llvm-libc-types.size_t
     .llvm-libc-types.struct_hsearch_data
+    .llvm-libc-types.__action_closure_fn_t
+    .llvm-libc-types.__action_fn_t
+    .llvm-libc-types.__free_fn_t
     .llvm_libc_common_h
 )
 
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 3124fdc33fbc2..9d32bac79337f 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -52,6 +52,7 @@ add_header(off_t HDR off_t.h)
 add_header(once_flag HDR once_flag.h DEPENDS .__futex_word)
 add_header(posix_spawn_file_actions_t HDR posix_spawn_file_actions_t.h)
 add_header(posix_spawnattr_t HDR posix_spawnattr_t.h)
+add_header(posix_tnode HDR posix_tnode.h)
 add_header(pthread_attr_t HDR pthread_attr_t.h DEPENDS .size_t)
 add_header(pthread_condattr_t HDR pthread_condattr_t.h DEPENDS .clockid_t)
 add_header(pthread_key_t HDR pthread_key_t.h)
@@ -550,3 +551,18 @@ add_header(EFI_SYSTEM_TABLE
     .EFI_TABLE_HEADER
     .char16_t
 )
+add_header(__action_closure_fn_t 
+  HDR 
+    __action_closure_fn_t.h 
+  DEPENDS 
+    .posix_tnode 
+    .VISIT
+)
+add_header(__action_fn_t 
+  HDR 
+    __action_fn_t.h 
+  DEPENDS 
+    .posix_tnode 
+    .VISIT
+)
+add_header(__free_fn_t HDR __free_fn_t.h)
diff --git a/libc/include/llvm-libc-types/__action_closure_fn_t.h b/libc/include/llvm-libc-types/__action_closure_fn_t.h
new file mode 100644
index 0000000000000..18d91a3f53925
--- /dev/null
+++ b/libc/include/llvm-libc-types/__action_closure_fn_t.h
@@ -0,0 +1,17 @@
+//===-- Definition of type __action_closure_fn_t --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES___ACTION_CLOSURE_FN_T_H
+#define LLVM_LIBC_TYPES___ACTION_CLOSURE_FN_T_H
+
+#include "VISIT.h"
+#include "posix_tnode.h"
+
+typedef void (*__action_closure_fn_t)(const posix_tnode *, VISIT, void *);
+
+#endif // LLVM_LIBC_TYPES___ACTION_CLOSURE_FN_T_H
diff --git a/libc/include/llvm-libc-types/__action_fn_t.h b/libc/include/llvm-libc-types/__action_fn_t.h
new file mode 100644
index 0000000000000..de3dc02e4eacd
--- /dev/null
+++ b/libc/include/llvm-libc-types/__action_fn_t.h
@@ -0,0 +1,17 @@
+//===-- Definition of type __action_fn_t ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES___ACTION_FN_T_H
+#define LLVM_LIBC_TYPES___ACTION_FN_T_H
+
+#include "VISIT.h"
+#include "posix_tnode.h"
+
+typedef void (*__action_fn_t)(const posix_tnode *, VISIT, int);
+
+#endif // LLVM_LIBC_TYPES___ACTION_FN_T_H
diff --git a/libc/include/llvm-libc-types/__free_fn_t.h b/libc/include/llvm-libc-types/__free_fn_t.h
new file mode 100644
index 0000000000000..72f00f3df5796
--- /dev/null
+++ b/libc/include/llvm-libc-types/__free_fn_t.h
@@ -0,0 +1,14 @@
+//===-- Definition of type __free_fn_t ------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES___FREE_FN_T_H
+#define LLVM_LIBC_TYPES___FREE_FN_T_H
+
+typedef void (*__free_fn_t)(void *);
+
+#endif // LLVM_LIBC_TYPES___FREE_FN_T_H
diff --git a/libc/include/llvm-libc-types/posix_tnode.h b/libc/include/llvm-libc-types/posix_tnode.h
new file mode 100644
index 0000000000000..0f7336ac2ad89
--- /dev/null
+++ b/libc/include/llvm-libc-types/posix_tnode.h
@@ -0,0 +1,17 @@
+//===-- Definition of type posix_tnode--------------- ---------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_POSIX_TNODE_H
+#define LLVM_LIBC_TYPES_POSIX_TNODE_H
+
+// Following POSIX.1-2024 and Austin Group Defect Report 1011:
+// https://austingroupbugs.net/view.php?id=1011
+// Define posix_tnode as void to provide both type safety and compatibility.
+typedef void posix_tnode;
+
+#endif // LLVM_LIBC_TYPES_POSIX_TNODE_H
diff --git a/libc/include/search.yaml b/libc/include/search.yaml
index 8a3a0c50af60f..50f317820e1b5 100644
--- a/libc/include/search.yaml
+++ b/libc/include/search.yaml
@@ -7,6 +7,10 @@ types:
   - type_name: VISIT
   - type_name: __search_compare_t
   - type_name: struct_hsearch_data
+  - type_name: posix_tnode
+  - type_name: __action_closure_fn_t
+  - type_name: __action_fn_t
+  - type_name: __free_fn_t
 enums: []
 objects: []
 functions:
@@ -80,3 +84,47 @@ functions:
       - type: size_t *
       - type: size_t
       - type: __search_compare_t
+  - name: tdelete
+    standards:
+      - posix
+    return_type: void *
+    arguments:
+      - type: const void *__restrict
+      - type: posix_tnode **__restrict
+      - type: __search_compare_t
+  - name: tfind
+    standards:
+      - posix
+    return_type: posix_tnode *
+    arguments:
+      - type: const void *
+      - type: posix_tnode * const *
+      - type: __search_compare_t
+  - name: tsearch
+    standards:
+      - posix
+    return_type: posix_tnode *
+    arguments:
+      - type: const void *
+      - type: posix_tnode **
+      - type: __search_compare_t
+  - name: twalk
+    standards:
+      - posix
+    return_type: void
+    arguments:
+      - type: const posix_tnode *
+      - type: __action_fn_t
+  - name: twalk_r
+    standards: gnu
+    return_type: void
+    arguments:
+      - type: const posix_tnode *
+      - type: __action_closure_fn_t
+      - type: void *
+  - name: tdestroy
+    standards: gnu
+    return_type: void
+    arguments:
+      - type: posix_tnode *
+      - type: __free_fn_t
diff --git a/libc/src/__support/weak_avl.h b/libc/src/__support/weak_avl.h
index 31c7e31a19c6e..40222f44a38e2 100644
--- a/libc/src/__support/weak_avl.h
+++ b/libc/src/__support/weak_avl.h
@@ -82,7 +82,7 @@ template <typename T> class WeakAVLNode {
   unsigned char left_rank_diff_2 : 1;
   unsigned char right_rank_diff_2 : 1;
 
-  LIBC_INLINE bool is_leaf() {
+  LIBC_INLINE bool is_leaf() const {
     return (children[0] == nullptr) && (children[1] == nullptr);
   }
 
@@ -214,6 +214,7 @@ template <typename T> class WeakAVLNode {
 
   LIBC_INLINE const WeakAVLNode *get_left() const { return children[0]; }
   LIBC_INLINE const WeakAVLNode *get_right() const { return children[1]; }
+  LIBC_INLINE const WeakAVLNode *get_parent() const { return parent; }
   LIBC_INLINE const T &get_data() const { return data; }
   LIBC_INLINE bool has_rank_diff_2(bool is_right) const {
     return is_right ? right_rank_diff_2 : left_rank_diff_2;
@@ -227,6 +228,17 @@ template <typename T> class WeakAVLNode {
     destroy(node->children[1]);
     delete node;
   }
+
+  // Destroy the subtree rooted at node with finalizer
+  template <typename Finalizer>
+  LIBC_INLINE static void destroy(WeakAVLNode *node, Finalizer finalizer) {
+    if (!node)
+      return;
+    destroy(node->children[0], finalizer);
+    destroy(node->children[1], finalizer);
+    finalizer(node->data);
+    ::delete node;
+  }
   // Rotate the subtree rooted at node in the given direction.
   //
   // Illustration for is_right = true (Left Rotation):
@@ -568,25 +580,25 @@ template <typename T> class WeakAVLNode {
     Leaf,
   };
   template <typename Func>
-  LIBC_INLINE static void walk(WeakAVLNode *node, Func func) {
+  LIBC_INLINE static void walk(const WeakAVLNode *node, Func func,
+                               int depth = 0) {
     if (!node)
       return;
 
     if (node->is_leaf()) {
-      func(node, WalkType::Leaf);
+      func(node, WalkType::Leaf, depth);
       return;
     }
 
-    func(node, WalkType::PreOrder);
-
+    func(node, WalkType::PreOrder, depth);
     if (node->children[0])
-      walk(node->children[0], func);
+      walk(node->children[0], func, depth + 1);
 
-    func(node, WalkType::InOrder);
+    func(node, WalkType::InOrder, depth);
 
     if (node->children[1])
-      walk(node->children[1], func);
-    func(node, WalkType::PostOrder);
+      walk(node->children[1], func, depth + 1);
+    func(node, WalkType::PostOrder, depth);
   }
 };
 
diff --git a/libc/src/search/CMakeLists.txt b/libc/src/search/CMakeLists.txt
index 0ed513e648ed1..2cdab57396bd1 100644
--- a/libc/src/search/CMakeLists.txt
+++ b/libc/src/search/CMakeLists.txt
@@ -125,3 +125,71 @@ add_entrypoint_object(
     libc.src.__support.memory_size
     libc.src.string.memory_utils.inline_memcpy
 )
+
+add_entrypoint_object(
+  tdelete
+  SRCS
+    tdelete.cpp
+  HDRS
+    tdelete.h
+  DEPENDS
+    libc.hdr.types.posix_tnode
+    libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+  tfind
+  SRCS
+    tfind.cpp
+  HDRS
+    tfind.h
+  DEPENDS
+    libc.hdr.types.posix_tnode
+    libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+  tsearch
+  SRCS
+    tsearch.cpp
+  HDRS
+    tsearch.h
+  DEPENDS
+    libc.hdr.types.posix_tnode
+    libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+  twalk
+  SRCS
+    twalk.cpp
+  HDRS
+    twalk.h
+  DEPENDS
+    libc.hdr.types.posix_tnode
+    libc.hdr.types.VISIT
+    libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+  twalk_r
+  SRCS
+    twalk_r.cpp
+  HDRS
+    twalk_r.h
+  DEPENDS
+    libc.hdr.types.posix_tnode
+    libc.hdr.types.VISIT
+    libc.src.__support.weak_avl
+)
+
+add_entrypoint_object(
+  tdestroy
+  SRCS
+    tdestroy.cpp
+  HDRS
+    tdestroy.h
+  DEPENDS
+    libc.hdr.types.posix_tnode
+    libc.src.__support.weak_avl
+)
diff --git a/libc/src/search/tdelete.cpp b/libc/src/search/tdelete.cpp
new file mode 100644
index 0000000000000..3fd5fc76bead4
--- /dev/null
+++ b/libc/src/search/tdelete.cpp
@@ -0,0 +1,36 @@
+//===-- Implementation of tdelete -------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/search/tdelete.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+// The tdelete() function shall return a pointer to the parent of the deleted
+// node, or an unspecified non-null pointer if the deleted node was the root
+// node, or a null pointer if the node is not found.
+LLVM_LIBC_FUNCTION(void *, tdelete,
+                   (const void *key, posix_tnode **rootp,
+                    int (*compar)(const void *, const void *))) {
+  if (!rootp)
+    return nullptr;
+  using Node = WeakAVLNode<const void *>;
+  Node *&root = *reinterpret_cast<Node **>(rootp);
+  Node::OptionalNodePtr node = Node::find(root, key, compar);
+  if (!node)
+    return nullptr;
+  void *result = const_cast<Node *>(node.value()->get_parent());
+  if (!result)
+    result = cpp::bit_cast<void *>(uintptr_t(-1));
+  Node::erase(root, *node);
+  return result;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tdelete.h b/libc/src/search/tdelete.h
new file mode 100644
index 0000000000000..168860304dbca
--- /dev/null
+++ b/libc/src/search/tdelete.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for tdelete -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SEARCH_TDELETE_H
+#define LLVM_LIBC_SRC_SEARCH_TDELETE_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void *tdelete(const void *key, __llvm_libc_tnode **rootp,
+              int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TDELETE_H
diff --git a/libc/src/search/tdestroy.cpp b/libc/src/search/tdestroy.cpp
new file mode 100644
index 0000000000000..3b93313879ae0
--- /dev/null
+++ b/libc/src/search/tdestroy.cpp
@@ -0,0 +1,28 @@
+//===-- Implementation of tdestroy ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/search/tdestroy.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, tdestroy,
+                   (__llvm_libc_tnode * root, void (*free_node)(void *))) {
+  if (!root)
+    return;
+  using Node = WeakAVLNode<const void *>;
+  Node *node = reinterpret_cast<Node *>(root);
+  Node::destroy(node, [free_node](const void *&data) {
+    if (free_node)
+      free_node(const_cast<void *>(data));
+  });
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tdestroy.h b/libc/src/search/tdestroy.h
new file mode 100644
index 0000000000000..fd2305b815079
--- /dev/null
+++ b/libc/src/search/tdestroy.h
@@ -0,0 +1,19 @@
+//===-- Implementation header for tdestroy ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SEARCH_TDESTROY_H
+#define LLVM_LIBC_SRC_SEARCH_TDESTROY_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void tdestroy(__llvm_libc_tnode *root, void (*free_node)(void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TDESTROY_H
diff --git a/libc/src/search/tfind.cpp b/libc/src/search/tfind.cpp
new file mode 100644
index 0000000000000..640b8fe853d12
--- /dev/null
+++ b/libc/src/search/tfind.cpp
@@ -0,0 +1,27 @@
+//===-- Implementation of tfind ---------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/search/tfind.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(__llvm_libc_tnode *, tfind,
+                   (const void *key, __llvm_libc_tnode *const *rootp,
+                    int (*compar)(const void *, const void *))) {
+  if (!rootp)
+    return nullptr;
+  using Node = WeakAVLNode<const void *>;
+  Node *root = reinterpret_cast<Node *>(*rootp);
+  Node::OptionalNodePtr node = Node::find(root, key, compar);
+  return node ? reinterpret_cast<__llvm_libc_tnode *>(*node) : nullptr;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tfind.h b/libc/src/search/tfind.h
new file mode 100644
index 0000000000000..f44ec83c701dc
--- /dev/null
+++ b/libc/src/search/tfind.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for tfind -------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SEARCH_TFIND_H
+#define LLVM_LIBC_SRC_SEARCH_TFIND_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+__llvm_libc_tnode *tfind(const void *key, __llvm_libc_tnode *const *rootp,
+                         int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TFIND_H
diff --git a/libc/src/search/tsearch.cpp b/libc/src/search/tsearch.cpp
new file mode 100644
index 0000000000000..9363f15a6f284
--- /dev/null
+++ b/libc/src/search/tsearch.cpp
@@ -0,0 +1,27 @@
+//===-- Implementation of tsearch -------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/search/tsearch.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(__llvm_libc_tnode *, tsearch,
+                   (const void *key, __llvm_libc_tnode **rootp,
+                    int (*compar)(const void *, const void *))) {
+  if (!rootp)
+    return nullptr;
+  using Node = WeakAVLNode<const void *>;
+  Node *&root = *reinterpret_cast<Node **>(rootp);
+  Node::OptionalNodePtr node = Node::find_or_insert(root, key, compar);
+  return node ? reinterpret_cast<__llvm_libc_tnode *>(*node) : nullptr;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/tsearch.h b/libc/src/search/tsearch.h
new file mode 100644
index 0000000000000..859c39832c1ec
--- /dev/null
+++ b/libc/src/search/tsearch.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for tsearch -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SEARCH_TSEARCH_H
+#define LLVM_LIBC_SRC_SEARCH_TSEARCH_H
+
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+__llvm_libc_tnode *tsearch(const void *key, __llvm_libc_tnode **rootp,
+                           int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TSEARCH_H
diff --git a/libc/src/search/twalk.cpp b/libc/src/search/twalk.cpp
new file mode 100644
index 0000000000000..4b7ad98e3bc5a
--- /dev/null
+++ b/libc/src/search/twalk.cpp
@@ -0,0 +1,34 @@
+//===-- Implementation of twalk --------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/search/twalk.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+using Node = WeakAVLNode<const void *>;
+
+LLVM_LIBC_FUNCTION(void, twalk,
+                   (const __llvm_libc_tnode *root,
+                    void (*action)(const __llvm_libc_tnode *, VISIT, int))) {
+  if (!root || !action)
+    return;
+  const Node *node = reinterpret_cast<const Node *>(root);
+  Node::walk(node, [action](const Node *n, Node::WalkType type, int depth) {
+    VISIT v = (type == Node::WalkType::PreOrder)    ? preorder
+              : (type == Node::WalkType::InOrder)   ? postorder
+              : (type == Node::WalkType::PostOrder) ? endorder
+                                                    : leaf;
+    action(reinterpret_cast<const __llvm_libc_tnode *>(n), v, depth);
+  });
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/twalk.h b/libc/src/search/twalk.h
new file mode 100644
index 0000000000000..2353b6bf36191
--- /dev/null
+++ b/libc/src/search/twalk.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for twalk -------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SEARCH_TWALK_H
+#define LLVM_LIBC_SRC_SEARCH_TWALK_H
+
+#include "hdr/types/VISIT.h"
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void twalk(const __llvm_libc_tnode *root,
+           void (*action)(const __llvm_libc_tnode *, VISIT, int));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TWALK_H
diff --git a/libc/src/search/twalk_r.cpp b/libc/src/search/twalk_r.cpp
new file mode 100644
index 0000000000000..0f88becc2617f
--- /dev/null
+++ b/libc/src/search/twalk_r.cpp
@@ -0,0 +1,34 @@
+//===-- Implementation of twalk_r ------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/search/twalk_r.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/weak_avl.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+using Node = WeakAVLNode<const void *>;
+
+LLVM_LIBC_FUNCTION(void, twalk_r,
+                   (const __llvm_libc_tnode *root,
+                    void (*action)(const __llvm_libc_tnode *, VISIT, void *),
+                    void *closure)) {
+  if (!root || !action)
+    return;
+  const Node *node = reinterpret_cast<const Node *>(root);
+  Node::walk(node, [action, closure](const Node *n, Node::WalkType type, int) {
+    VISIT v = (type == Node::WalkType::PreOrder)    ? preorder
+              : (type == Node::WalkType::InOrder)   ? postorder
+              : (type == Node::WalkType::PostOrder) ? endorder
+                                                    : leaf;
+    action(reinterpret_cast<const __llvm_libc_tnode *>(n), v, closure);
+  });
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/twalk_r.h b/libc/src/search/twalk_r.h
new file mode 100644
index 0000000000000..95a29256d8256
--- /dev/null
+++ b/libc/src/search/twalk_r.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for twalk_r -----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SEARCH_TWALK_R_H
+#define LLVM_LIBC_SRC_SEARCH_TWALK_R_H
+
+#include "hdr/types/VISIT.h"
+#include "hdr/types/posix_tnode.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+void twalk_r(const __llvm_libc_tnode *root,
+             void (*action)(const __llvm_libc_tnode *, VISIT, void *),
+             void *closure);
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_TWALK_R_H
diff --git a/libc/test/src/search/CMakeLists.txt b/libc/test/src/search/CMakeLists.txt
index 9f34d4d3fd259..273ce9722148b 100644
--- a/libc/test/src/search/CMakeLists.txt
+++ b/libc/test/src/search/CMakeLists.txt
@@ -45,3 +45,18 @@ add_libc_unittest(
   DEPENDS
     libc.src.search.lsearch
 )
+
+add_libc_unittest(
+  tsearch_test
+  SUITE
+    libc_search_unittests
+  SRCS
+    tsearch_test.cpp
+  DEPENDS
+    libc.src.search.tsearch
+    libc.src.search.tfind
+    libc.src.search.tdelete
+    libc.src.search.twalk
+    libc.src.search.twalk_r
+    libc.src.search.tdestroy
+)
diff --git a/libc/test/src/search/tsearch_test.cpp b/libc/test/src/search/tsearch_test.cpp
new file mode 100644
index 0000000000000..609b6207f842b
--- /dev/null
+++ b/libc/test/src/search/tsearch_test.cpp
@@ -0,0 +1,137 @@
+//===-- Unittests for tsearch ---------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "hdr/types/posix_tnode.h"
+#include "src/search/tdelete.h"
+#include "src/search/tdestroy.h"
+#include "src/search/tfind.h"
+#include "src/search/tsearch.h"
+#include "src/search/twalk.h"
+#include "src/search/twalk_r.h"
+#include "test/UnitTest/Test.h"
+#include <search.h>
+
+static void *encode(int val) {
+  return reinterpret_cast<void *>(static_cast<intptr_t>(val));
+}
+
+static int decode(const void *val) {
+  return static_cast<int>(reinterpret_cast<intptr_t>(val));
+}
+
+static int read_and_decode(const __llvm_libc_tnode *val) {
+  return static_cast<int>(*static_cast<const intptr_t *>(val));
+}
+
+static int compare(const void *a, const void *b) {
+  int x = decode(a);
+  int y = decode(b);
+  return (x > y) - (x < y);
+}
+
+TEST(LlvmLibcTSearchTest, TSearch) {
+  void *root = nullptr;
+  void *result;
+  int key = 10;
+
+  // Insert 10
+  result = LIBC_NAMESPACE::tsearch(encode(key), &root, compare);
+  ASSERT_NE(result, nullptr);
+  ASSERT_EQ(read_and_decode(result), key);
+
+  // Find 10
+  result = LIBC_NAMESPACE::tfind(encode(key), &root, compare);
+  ASSERT_NE(result, nullptr);
+  ASSERT_EQ(read_and_decode(result), key);
+
+  // Insert 20
+  int key2 = 20;
+  result = LIBC_NAMESPACE::tsearch(encode(key2), &root, compare);
+  ASSERT_NE(result, nullptr);
+  ASSERT_EQ(read_and_decode(result), key2);
+
+  // Find 20
+  result = LIBC_NAMESPACE::tfind(encode(key2), &root, compare);
+  ASSERT_NE(result, nullptr);
+  ASSERT_EQ(read_and_decode(result), key2);
+
+  // Delete 10
+  result = LIBC_NAMESPACE::tdelete(encode(key), &root, compare);
+  ASSERT_NE(result, nullptr);
+
+  // Find 10 should fail
+  result = LIBC_NAMESPACE::tfind(encode(key), &root, compare);
+  ASSERT_EQ(result, nullptr);
+
+  // Delete 20
+  result = LIBC_NAMESPACE::tdelete(encode(key2), &root, compare);
+  ASSERT_NE(result, nullptr);
+  // Tree should be empty
+  ASSERT_EQ(root, nullptr);
+}
+
+static void free_node(void *) {
+  // Do nothing for integer keys
+}
+
+TEST(LlvmLibcTSearchTest, TDestroy) {
+  void *root = nullptr;
+  for (int i = 0; i < 10; ++i)
+    LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
+
+  LIBC_NAMESPACE::tdestroy(root, free_node);
+}
+
+static int walk_sum = 0;
+static void action(const __llvm_libc_tnode *node, VISIT visit, int) {
+  if (visit == leaf || visit == postorder)
+    walk_sum += read_and_decode(node);
+}
+
+TEST(LlvmLibcTSearchTest, TWalk) {
+  void *root = nullptr;
+  int sum = 0;
+  for (int i = 1; i <= 5; ++i) {
+    LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
+    sum += i;
+  }
+
+  walk_sum = 0;
+  LIBC_NAMESPACE::twalk(root, action);
+  ASSERT_EQ(walk_sum, sum);
+
+  LIBC_NAMESPACE::tdestroy(root, free_node);
+}
+
+static void action_closure(const __llvm_libc_tnode *node, VISIT visit,
+                           void *closure) {
+  if (visit == leaf || visit == postorder) {
+    int **cursor = static_cast<int **>(closure);
+    **cursor = read_and_decode(node);
+    *cursor += 1;
+  }
+}
+
+TEST(LlvmLibcTSearchTest, TWalkR) {
+  void *root = nullptr;
+  constexpr int LIMIT = 64;
+  int sorted[LIMIT];
+  for (int i = 0; i < LIMIT; ++i) {
+    int current = (i * 37) % LIMIT; // pseudo-random insertion order
+    LIBC_NAMESPACE::tsearch(encode(current), &root, compare);
+  }
+
+  walk_sum = 0;
+  int *cursor = &sorted[0];
+  LIBC_NAMESPACE::twalk_r(root, action_closure, &cursor);
+
+  for (int i = 0; i < LIMIT; ++i)
+    ASSERT_EQ(sorted[i], i);
+
+  LIBC_NAMESPACE::tdestroy(root, free_node);
+}

>From 79208f6210b9055fdc0b751ec00404135b71a0a3 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Thu, 22 Jan 2026 09:16:24 -0500
Subject: [PATCH 2/7] fix missed part from rebasing

---
 libc/test/src/__support/weak_avl_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/test/src/__support/weak_avl_test.cpp b/libc/test/src/__support/weak_avl_test.cpp
index 49ff2e8d97746..eb97172517757 100644
--- a/libc/test/src/__support/weak_avl_test.cpp
+++ b/libc/test/src/__support/weak_avl_test.cpp
@@ -265,7 +265,7 @@ TEST(LlvmLibcTreeWalk, InOrderTraversal) {
   Tree tree = Tree::build([](int x) { return stride(x, 1007); }, TEST_SIZE);
   int data[TEST_SIZE];
   int counter = 0;
-  Node::walk(tree.root, [&](Node *node, Node::WalkType type) {
+  Node::walk(tree.root, [&](const Node *node, Node::WalkType type, int) {
     if (type == Node::WalkType::InOrder || type == Node::WalkType::Leaf)
       data[counter++] = node->get_data();
   });

>From ecc9a67c967dc2086900f4e00045ad0827db1d99 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Thu, 29 Jan 2026 10:32:06 -0500
Subject: [PATCH 3/7] address CRs

---
 libc/docs/dev/undefined_behavior.rst | 11 +++++++++++
 libc/src/__support/weak_avl.h        |  2 +-
 libc/src/search/tdelete.cpp          |  5 +++--
 libc/src/search/tdestroy.cpp         |  3 +--
 libc/src/search/twalk.cpp            |  4 ++--
 libc/src/search/twalk_r.cpp          |  3 ++-
 6 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/libc/docs/dev/undefined_behavior.rst b/libc/docs/dev/undefined_behavior.rst
index 4f8ac22919b0a..3a7f2d2c9e57e 100644
--- a/libc/docs/dev/undefined_behavior.rst
+++ b/libc/docs/dev/undefined_behavior.rst
@@ -163,3 +163,14 @@ The current implementation of the `inet_aton` function utilizes the same code
 as `strtol` to parse IPv4 numbers-and-dots notations. This approach may permit
 the use of binary integers (prefixed with 0b), which is not supported by the
 standard.
+
+`tdelete` on Non-existent Key
+------------------------------
+The return value of `tdelete` is unspecified if the key is not found in the tree.
+For LLVM-libc, `tdelete` returns bit-casted `uintptr_t`'s maximum value.
+
+`twalk/twalk_r/tdestroy` with Null Function Pointer
+------------------------------------------------------
+The standard requires that ``twalk``, ``twalk_r``, and ``tdestroy``
+to be used with a valid function pointer. LLVM-libc does not apply any
+checks for null function pointers and will likely to crash on null values.
diff --git a/libc/src/__support/weak_avl.h b/libc/src/__support/weak_avl.h
index 40222f44a38e2..545501a88163f 100644
--- a/libc/src/__support/weak_avl.h
+++ b/libc/src/__support/weak_avl.h
@@ -237,7 +237,7 @@ template <typename T> class WeakAVLNode {
     destroy(node->children[0], finalizer);
     destroy(node->children[1], finalizer);
     finalizer(node->data);
-    ::delete node;
+    delete node;
   }
   // Rotate the subtree rooted at node in the given direction.
   //
diff --git a/libc/src/search/tdelete.cpp b/libc/src/search/tdelete.cpp
index 3fd5fc76bead4..b3c339a185ccd 100644
--- a/libc/src/search/tdelete.cpp
+++ b/libc/src/search/tdelete.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/search/tdelete.h"
+#include "hdr/types/posix_tnode.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/weak_avl.h"
@@ -17,7 +18,7 @@ namespace LIBC_NAMESPACE_DECL {
 // node, or an unspecified non-null pointer if the deleted node was the root
 // node, or a null pointer if the node is not found.
 LLVM_LIBC_FUNCTION(void *, tdelete,
-                   (const void *key, posix_tnode **rootp,
+                   (const void *key, __llvm_libc_tnode **rootp,
                     int (*compar)(const void *, const void *))) {
   if (!rootp)
     return nullptr;
@@ -28,7 +29,7 @@ LLVM_LIBC_FUNCTION(void *, tdelete,
     return nullptr;
   void *result = const_cast<Node *>(node.value()->get_parent());
   if (!result)
-    result = cpp::bit_cast<void *>(uintptr_t(-1));
+    result = cpp::bit_cast<void *>(cpp::numeric_limits<uintptr_t>::max());
   Node::erase(root, *node);
   return result;
 }
diff --git a/libc/src/search/tdestroy.cpp b/libc/src/search/tdestroy.cpp
index 3b93313879ae0..398e93bcb7e15 100644
--- a/libc/src/search/tdestroy.cpp
+++ b/libc/src/search/tdestroy.cpp
@@ -20,8 +20,7 @@ LLVM_LIBC_FUNCTION(void, tdestroy,
   using Node = WeakAVLNode<const void *>;
   Node *node = reinterpret_cast<Node *>(root);
   Node::destroy(node, [free_node](const void *&data) {
-    if (free_node)
-      free_node(const_cast<void *>(data));
+    free_node(const_cast<void *>(data));
   });
 }
 
diff --git a/libc/src/search/twalk.cpp b/libc/src/search/twalk.cpp
index 4b7ad98e3bc5a..1dbea20fafc46 100644
--- a/libc/src/search/twalk.cpp
+++ b/libc/src/search/twalk.cpp
@@ -7,7 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/search/twalk.h"
-#include "src/__support/CPP/bit.h"
+#include "hdr/types/posix_tnode.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/weak_avl.h"
@@ -19,7 +19,7 @@ using Node = WeakAVLNode<const void *>;
 LLVM_LIBC_FUNCTION(void, twalk,
                    (const __llvm_libc_tnode *root,
                     void (*action)(const __llvm_libc_tnode *, VISIT, int))) {
-  if (!root || !action)
+  if (!root)
     return;
   const Node *node = reinterpret_cast<const Node *>(root);
   Node::walk(node, [action](const Node *n, Node::WalkType type, int depth) {
diff --git a/libc/src/search/twalk_r.cpp b/libc/src/search/twalk_r.cpp
index 0f88becc2617f..8c03bdf76d92a 100644
--- a/libc/src/search/twalk_r.cpp
+++ b/libc/src/search/twalk_r.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/search/twalk_r.h"
+#include "hdr/types/posix_tnode.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/weak_avl.h"
@@ -19,7 +20,7 @@ LLVM_LIBC_FUNCTION(void, twalk_r,
                    (const __llvm_libc_tnode *root,
                     void (*action)(const __llvm_libc_tnode *, VISIT, void *),
                     void *closure)) {
-  if (!root || !action)
+  if (!root)
     return;
   const Node *node = reinterpret_cast<const Node *>(root);
   Node::walk(node, [action, closure](const Node *n, Node::WalkType type, int) {

>From 2749ec411249351617f5f89945e87f9ce540582c Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 2 Feb 2026 23:40:06 -0500
Subject: [PATCH 4/7] partially address the comments

---
 libc/test/src/search/tsearch_test.cpp | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/libc/test/src/search/tsearch_test.cpp b/libc/test/src/search/tsearch_test.cpp
index 609b6207f842b..aa70497a4d328 100644
--- a/libc/test/src/search/tsearch_test.cpp
+++ b/libc/test/src/search/tsearch_test.cpp
@@ -14,7 +14,6 @@
 #include "src/search/twalk.h"
 #include "src/search/twalk_r.h"
 #include "test/UnitTest/Test.h"
-#include <search.h>
 
 static void *encode(int val) {
   return reinterpret_cast<void *>(static_cast<intptr_t>(val));
@@ -75,16 +74,26 @@ TEST(LlvmLibcTSearchTest, TSearch) {
   ASSERT_EQ(root, nullptr);
 }
 
-static void free_node(void *) {
-  // Do nothing for integer keys
+constexpr size_t MAX_VALUE = 64;
+static bool free_flags[MAX_VALUE + 1];
+static void clear_free_flags() {
+  for (bool &flag : free_flags)
+    flag = false;
+}
+static void free_node(void *node) {
+  int key = decode(node);
+  free_flags[key] = true;
 }
 
 TEST(LlvmLibcTSearchTest, TDestroy) {
   void *root = nullptr;
+  clear_free_flags();
   for (int i = 0; i < 10; ++i)
     LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
 
   LIBC_NAMESPACE::tdestroy(root, free_node);
+  for (int i = 0; i < 10; ++i)
+    ASSERT_TRUE(free_flags[i]);
 }
 
 static int walk_sum = 0;
@@ -119,6 +128,7 @@ static void action_closure(const __llvm_libc_tnode *node, VISIT visit,
 
 TEST(LlvmLibcTSearchTest, TWalkR) {
   void *root = nullptr;
+  clear_free_flags();
   constexpr int LIMIT = 64;
   int sorted[LIMIT];
   for (int i = 0; i < LIMIT; ++i) {
@@ -134,4 +144,6 @@ TEST(LlvmLibcTSearchTest, TWalkR) {
     ASSERT_EQ(sorted[i], i);
 
   LIBC_NAMESPACE::tdestroy(root, free_node);
+  for (int i = 0; i < LIMIT; ++i)
+    ASSERT_TRUE(free_flags[i]);
 }

>From 0f54fe94b3d032e909cec042cc9e5ded8d5b7535 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Mon, 9 Feb 2026 11:16:35 -0500
Subject: [PATCH 5/7] [libc] tsearch: rewrite tests

---
 libc/test/src/search/CMakeLists.txt   |   3 +
 libc/test/src/search/tsearch_test.cpp | 532 ++++++++++++++++++++++----
 2 files changed, 453 insertions(+), 82 deletions(-)

diff --git a/libc/test/src/search/CMakeLists.txt b/libc/test/src/search/CMakeLists.txt
index 273ce9722148b..b0eac56ff560a 100644
--- a/libc/test/src/search/CMakeLists.txt
+++ b/libc/test/src/search/CMakeLists.txt
@@ -59,4 +59,7 @@ add_libc_unittest(
     libc.src.search.twalk
     libc.src.search.twalk_r
     libc.src.search.tdestroy
+    libc.src.string.strlen
+    libc.src.string.strcpy
+    libc.src.string.strcmp
 )
diff --git a/libc/test/src/search/tsearch_test.cpp b/libc/test/src/search/tsearch_test.cpp
index aa70497a4d328..4e0197a040113 100644
--- a/libc/test/src/search/tsearch_test.cpp
+++ b/libc/test/src/search/tsearch_test.cpp
@@ -13,8 +13,14 @@
 #include "src/search/tsearch.h"
 #include "src/search/twalk.h"
 #include "src/search/twalk_r.h"
+#include "src/string/strcmp.h"
+#include "src/string/strcpy.h"
+#include "src/string/strlen.h"
 #include "test/UnitTest/Test.h"
 
+// ---------------------------------------------------------------------------
+// Helpers: encode integers as void* keys (no heap allocation needed).
+// ---------------------------------------------------------------------------
 static void *encode(int val) {
   return reinterpret_cast<void *>(static_cast<intptr_t>(val));
 }
@@ -23,127 +29,489 @@ static int decode(const void *val) {
   return static_cast<int>(reinterpret_cast<intptr_t>(val));
 }
 
-static int read_and_decode(const __llvm_libc_tnode *val) {
-  return static_cast<int>(*static_cast<const intptr_t *>(val));
+static int read_node(const __llvm_libc_tnode *node) {
+  return static_cast<int>(*static_cast<const intptr_t *>(node));
 }
 
-static int compare(const void *a, const void *b) {
+static int int_compare(const void *a, const void *b) {
   int x = decode(a);
   int y = decode(b);
   return (x > y) - (x < y);
 }
 
-TEST(LlvmLibcTSearchTest, TSearch) {
+// ---------------------------------------------------------------------------
+// OOP-style RAII int_box around the POSIX tsearch family.
+//
+// The int_box owns the tree root and calls tdestroy in its destructor.
+// It is parameterised on a free-function so callers can track or customise
+// destruction.
+// ---------------------------------------------------------------------------
+using FreeFn = void (*)(void *);
+
+static void noop_free(void *) {}
+
+class TTree {
+  void *root;
+  FreeFn free_fn;
+
+  // Non-copyable.
+  TTree(const TTree &) = delete;
+  TTree &operator=(const TTree &) = delete;
+
+public:
+  explicit TTree(FreeFn f = noop_free) : root(nullptr), free_fn(f) {}
+
+  ~TTree() {
+    if (root)
+      LIBC_NAMESPACE::tdestroy(root, free_fn);
+  }
+
+  // Insert key.  Returns the node pointer on success, nullptr on failure.
+  void *insert(void *key) {
+    return LIBC_NAMESPACE::tsearch(key, &root, int_compare);
+  }
+
+  // Find key.  Returns nullptr when not found.
+  void *find(void *key) const {
+    return LIBC_NAMESPACE::tfind(key, &root, int_compare);
+  }
+
+  // Delete key.  Returns the parent node (or the new root) on success,
+  // nullptr when the key is not in the tree.
+  void *remove(void *key) {
+    return LIBC_NAMESPACE::tdelete(key, &root, int_compare);
+  }
+
+  void walk(void (*action)(const __llvm_libc_tnode *, VISIT, int)) const {
+    LIBC_NAMESPACE::twalk(root, action);
+  }
+
+  void walk_r(void (*action)(const __llvm_libc_tnode *, VISIT, void *),
+              void *closure) const {
+    LIBC_NAMESPACE::twalk_r(root, action, closure);
+  }
+
+  // Release ownership so the destructor will not call tdestroy.
+  void *release() {
+    void *r = root;
+    root = nullptr;
+    return r;
+  }
+
+  bool empty() const { return root == nullptr; }
+  void *get_root() const { return root; }
+};
+
+// ===== tsearch tests =======================================================
+
+TEST(LlvmLibcTSearchTest, InsertIntoEmptyTree) {
+  TTree tree;
+  void *r = tree.insert(encode(42));
+  ASSERT_NE(r, nullptr);
+  ASSERT_EQ(read_node(r), 42);
+}
+
+TEST(LlvmLibcTSearchTest, InsertMultiple) {
+  TTree tree;
+  for (int i = 0; i < 8; ++i) {
+    void *r = tree.insert(encode(i));
+    ASSERT_NE(r, nullptr);
+    ASSERT_EQ(read_node(r), i);
+  }
+}
+
+TEST(LlvmLibcTSearchTest, InsertDuplicateReturnsSameNode) {
+  TTree tree;
+  void *first = tree.insert(encode(7));
+  ASSERT_NE(first, nullptr);
+
+  // Inserting the same key again must return the existing node.
+  void *second = tree.insert(encode(7));
+  ASSERT_EQ(first, second);
+  ASSERT_EQ(read_node(second), 7);
+}
+
+// ===== tfind tests =========================================================
+
+TEST(LlvmLibcTSearchTest, FindInEmptyTree) {
+  TTree tree;
+  void *r = tree.find(encode(1));
+  ASSERT_EQ(r, nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, FindExistingKey) {
+  TTree tree;
+  tree.insert(encode(5));
+  tree.insert(encode(10));
+  tree.insert(encode(15));
+
+  void *r = tree.find(encode(10));
+  ASSERT_NE(r, nullptr);
+  ASSERT_EQ(read_node(r), 10);
+}
+
+TEST(LlvmLibcTSearchTest, FindNonExistentKey) {
+  TTree tree;
+  tree.insert(encode(1));
+  tree.insert(encode(3));
+
+  void *r = tree.find(encode(2));
+  ASSERT_EQ(r, nullptr);
+}
+
+// ===== tdelete tests =======================================================
+
+TEST(LlvmLibcTSearchTest, DeleteFromEmptyTree) {
   void *root = nullptr;
-  void *result;
-  int key = 10;
-
-  // Insert 10
-  result = LIBC_NAMESPACE::tsearch(encode(key), &root, compare);
-  ASSERT_NE(result, nullptr);
-  ASSERT_EQ(read_and_decode(result), key);
-
-  // Find 10
-  result = LIBC_NAMESPACE::tfind(encode(key), &root, compare);
-  ASSERT_NE(result, nullptr);
-  ASSERT_EQ(read_and_decode(result), key);
-
-  // Insert 20
-  int key2 = 20;
-  result = LIBC_NAMESPACE::tsearch(encode(key2), &root, compare);
-  ASSERT_NE(result, nullptr);
-  ASSERT_EQ(read_and_decode(result), key2);
-
-  // Find 20
-  result = LIBC_NAMESPACE::tfind(encode(key2), &root, compare);
-  ASSERT_NE(result, nullptr);
-  ASSERT_EQ(read_and_decode(result), key2);
-
-  // Delete 10
-  result = LIBC_NAMESPACE::tdelete(encode(key), &root, compare);
-  ASSERT_NE(result, nullptr);
-
-  // Find 10 should fail
-  result = LIBC_NAMESPACE::tfind(encode(key), &root, compare);
-  ASSERT_EQ(result, nullptr);
-
-  // Delete 20
-  result = LIBC_NAMESPACE::tdelete(encode(key2), &root, compare);
-  ASSERT_NE(result, nullptr);
-  // Tree should be empty
+  void *r = LIBC_NAMESPACE::tdelete(encode(1), &root, int_compare);
+  ASSERT_EQ(r, nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteNonExistentKey) {
+  TTree tree;
+  tree.insert(encode(10));
+  void *r = tree.remove(encode(99));
+  ASSERT_EQ(r, nullptr);
+  // Original key must still be findable.
+  ASSERT_NE(tree.find(encode(10)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteOnlyElement) {
+  void *root = nullptr;
+  LIBC_NAMESPACE::tsearch(encode(42), &root, int_compare);
+  void *r = LIBC_NAMESPACE::tdelete(encode(42), &root, int_compare);
+  // POSIX: on success tdelete returns a non-null value.
+  ASSERT_NE(r, nullptr);
+  // The tree is now empty.
   ASSERT_EQ(root, nullptr);
 }
 
+TEST(LlvmLibcTSearchTest, DeleteLeaf) {
+  TTree tree;
+  tree.insert(encode(10));
+  tree.insert(encode(5));
+  tree.insert(encode(15));
+
+  void *r = tree.remove(encode(5));
+  ASSERT_NE(r, nullptr);
+  ASSERT_EQ(tree.find(encode(5)), nullptr);
+  // Siblings remain.
+  ASSERT_NE(tree.find(encode(10)), nullptr);
+  ASSERT_NE(tree.find(encode(15)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteNodeWithOneChild) {
+  TTree tree;
+  // Build a small chain: 10 -> 5 -> 3
+  tree.insert(encode(10));
+  tree.insert(encode(5));
+  tree.insert(encode(3));
+
+  void *r = tree.remove(encode(5));
+  ASSERT_NE(r, nullptr);
+  ASSERT_EQ(tree.find(encode(5)), nullptr);
+  ASSERT_NE(tree.find(encode(3)), nullptr);
+  ASSERT_NE(tree.find(encode(10)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, DeleteNodeWithTwoChildren) {
+  TTree tree;
+  tree.insert(encode(10));
+  tree.insert(encode(5));
+  tree.insert(encode(15));
+  tree.insert(encode(3));
+  tree.insert(encode(7));
+
+  void *r = tree.remove(encode(5));
+  ASSERT_NE(r, nullptr);
+  ASSERT_EQ(tree.find(encode(5)), nullptr);
+  // All other keys survive.
+  ASSERT_NE(tree.find(encode(3)), nullptr);
+  ASSERT_NE(tree.find(encode(7)), nullptr);
+  ASSERT_NE(tree.find(encode(10)), nullptr);
+  ASSERT_NE(tree.find(encode(15)), nullptr);
+}
+
+TEST(LlvmLibcTSearchTest, InsertAndDeleteMany) {
+  TTree tree;
+  constexpr int N = 64;
+  // Insert in pseudo-random order.
+  for (int i = 0; i < N; ++i)
+    ASSERT_NE(tree.insert(encode((i * 37) % N)), nullptr);
+
+  // Delete every other element.
+  for (int i = 0; i < N; i += 2) {
+    void *r = tree.remove(encode(i));
+    ASSERT_NE(r, nullptr);
+  }
+
+  // Verify survivors and absences.
+  for (int i = 0; i < N; ++i) {
+    void *r = tree.find(encode(i));
+    if (i % 2 == 0)
+      ASSERT_EQ(r, nullptr);
+    else
+      ASSERT_NE(r, nullptr);
+  }
+}
+
+// ===== twalk tests =========================================================
+
+static int walk_sum;
+
+static void sum_action(const __llvm_libc_tnode *node, VISIT visit, int) {
+  if (visit == leaf || visit == postorder)
+    walk_sum += read_node(node);
+}
+
+TEST(LlvmLibcTSearchTest, WalkEmptyTree) {
+  walk_sum = 0;
+  LIBC_NAMESPACE::twalk(nullptr, sum_action);
+  ASSERT_EQ(walk_sum, 0);
+}
+
+TEST(LlvmLibcTSearchTest, WalkSingleNode) {
+  TTree tree;
+  tree.insert(encode(99));
+
+  walk_sum = 0;
+  tree.walk(sum_action);
+  // A single node is a leaf, visited once.
+  ASSERT_EQ(walk_sum, 99);
+}
+
+TEST(LlvmLibcTSearchTest, WalkSumsCorrectly) {
+  TTree tree;
+  int expected = 0;
+  for (int i = 1; i <= 10; ++i) {
+    tree.insert(encode(i));
+    expected += i;
+  }
+
+  walk_sum = 0;
+  tree.walk(sum_action);
+  ASSERT_EQ(walk_sum, expected);
+}
+
+// Verify that twalk visits nodes in sorted (in-order) sequence.
+static int walk_prev;
+static bool walk_sorted;
+
+static void sorted_action(const __llvm_libc_tnode *node, VISIT visit, int) {
+  if (visit == leaf || visit == postorder) {
+    int val = read_node(node);
+    if (val <= walk_prev)
+      walk_sorted = false;
+    walk_prev = val;
+  }
+}
+
+TEST(LlvmLibcTSearchTest, WalkInOrder) {
+  TTree tree;
+  // Insert in pseudo-random order.
+  constexpr int N = 32;
+  for (int i = 0; i < N; ++i)
+    tree.insert(encode((i * 13 + 7) % N));
+
+  walk_prev = -1;
+  walk_sorted = true;
+  tree.walk(sorted_action);
+  ASSERT_TRUE(walk_sorted);
+}
+
+// Verify twalk depth parameter.
+static int walk_max_depth;
+static int walk_node_count;
+
+static void depth_action(const __llvm_libc_tnode *, VISIT visit, int depth) {
+  if (visit == leaf || visit == postorder) {
+    ++walk_node_count;
+    if (depth > walk_max_depth)
+      walk_max_depth = depth;
+  }
+}
+
+TEST(LlvmLibcTSearchTest, WalkReportsDepth) {
+  TTree tree;
+  for (int i = 1; i <= 15; ++i)
+    tree.insert(encode(i));
+
+  walk_max_depth = -1;
+  walk_node_count = 0;
+  tree.walk(depth_action);
+  ASSERT_EQ(walk_node_count, 15);
+  // The maximum depth must be positive for a multi-node tree.
+  ASSERT_GT(walk_max_depth, 0);
+}
+
+// ===== twalk_r tests =======================================================
+
+static void collect_action(const __llvm_libc_tnode *node, VISIT visit,
+                           void *closure) {
+  if (visit == leaf || visit == postorder) {
+    int **cursor = static_cast<int **>(closure);
+    **cursor = read_node(node);
+    *cursor += 1;
+  }
+}
+
+TEST(LlvmLibcTSearchTest, WalkRProducesSortedOutput) {
+  TTree tree;
+  constexpr int N = 64;
+  for (int i = 0; i < N; ++i)
+    tree.insert(encode((i * 37) % N));
+
+  int sorted[N];
+  int *cursor = sorted;
+  tree.walk_r(collect_action, &cursor);
+
+  for (int i = 0; i < N; ++i)
+    ASSERT_EQ(sorted[i], i);
+}
+
+TEST(LlvmLibcTSearchTest, WalkREmptyTree) {
+  int count = 0;
+  auto counter = [](const __llvm_libc_tnode *, VISIT, void *c) {
+    ++*static_cast<int *>(c);
+  };
+  LIBC_NAMESPACE::twalk_r(nullptr, counter, &count);
+  ASSERT_EQ(count, 0);
+}
+
+// ===== tdestroy tests ======================================================
+
 constexpr size_t MAX_VALUE = 64;
 static bool free_flags[MAX_VALUE + 1];
+
 static void clear_free_flags() {
   for (bool &flag : free_flags)
     flag = false;
 }
-static void free_node(void *node) {
+
+static void flag_free(void *node) {
   int key = decode(node);
   free_flags[key] = true;
 }
 
-TEST(LlvmLibcTSearchTest, TDestroy) {
+TEST(LlvmLibcTSearchTest, DestroyNullTree) {
+  // tdestroy on a null root must not crash.
+  LIBC_NAMESPACE::tdestroy(nullptr, flag_free);
+}
+
+TEST(LlvmLibcTSearchTest, DestroyVisitsAllNodes) {
   void *root = nullptr;
   clear_free_flags();
-  for (int i = 0; i < 10; ++i)
-    LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
+  constexpr int N = 10;
+  for (int i = 0; i < N; ++i)
+    LIBC_NAMESPACE::tsearch(encode(i), &root, int_compare);
 
-  LIBC_NAMESPACE::tdestroy(root, free_node);
-  for (int i = 0; i < 10; ++i)
+  LIBC_NAMESPACE::tdestroy(root, flag_free);
+  for (int i = 0; i < N; ++i)
     ASSERT_TRUE(free_flags[i]);
 }
 
-static int walk_sum = 0;
-static void action(const __llvm_libc_tnode *node, VISIT visit, int) {
-  if (visit == leaf || visit == postorder)
-    walk_sum += read_and_decode(node);
+// ---------------------------------------------------------------------------
+// tdestroy with a free-function that has real semantics:
+// nodes hold heap-allocated C strings; the free-function calls free() on them.
+// ---------------------------------------------------------------------------
+static int string_compare(const void *a, const void *b) {
+  return LIBC_NAMESPACE::strcmp(static_cast<const char *>(a),
+                                static_cast<const char *>(b));
+}
+
+static int strings_freed;
+
+static void string_free(void *ptr) {
+  ++strings_freed;
+  delete[] (static_cast<char *>(ptr));
 }
 
-TEST(LlvmLibcTSearchTest, TWalk) {
+TEST(LlvmLibcTSearchTest, DestroyHeapStrings) {
   void *root = nullptr;
-  int sum = 0;
-  for (int i = 1; i <= 5; ++i) {
-    LIBC_NAMESPACE::tsearch(encode(i), &root, compare);
-    sum += i;
+
+  // Include deliberate duplicates to exercise the tsearch "already exists" path.
+  const char *words[] = {"cherry", "apple",  "banana", "date",
+                         "elderberry", "apple", "cherry"};
+  constexpr int NWORDS = sizeof(words) / sizeof(words[0]);
+  constexpr int UNIQUE_WORDS = 5; // number of distinct strings
+
+  int duplicates_detected = 0;
+  for (int i = 0; i < NWORDS; ++i) {
+    char *dup = new char[LIBC_NAMESPACE::strlen(words[i]) + 1];
+    ASSERT_NE(dup, nullptr);
+    LIBC_NAMESPACE::strcpy(dup, words[i]);
+    void *r = LIBC_NAMESPACE::tsearch(dup, &root, string_compare);
+    ASSERT_NE(r, nullptr);
+    // tsearch returns a pointer to the node's datum. If the stored key
+    // is not our newly allocated string, a pre-existing node was returned
+    // and we must free the unused duplicate.
+    if (*static_cast<void **>(r) != dup) {
+      ++duplicates_detected;
+      delete[] dup;
+    }
   }
 
-  walk_sum = 0;
-  LIBC_NAMESPACE::twalk(root, action);
-  ASSERT_EQ(walk_sum, sum);
+  ASSERT_EQ(duplicates_detected, NWORDS - UNIQUE_WORDS);
+
+  // Every unique word should be findable.
+  const char *unique[] = {"cherry", "apple", "banana", "date", "elderberry"};
+  for (int i = 0; i < UNIQUE_WORDS; ++i) {
+    void *r = LIBC_NAMESPACE::tfind(unique[i], &root, string_compare);
+    ASSERT_NE(r, nullptr);
+  }
 
-  LIBC_NAMESPACE::tdestroy(root, free_node);
+  strings_freed = 0;
+  LIBC_NAMESPACE::tdestroy(root, string_free);
+  ASSERT_EQ(strings_freed, UNIQUE_WORDS);
 }
 
-static void action_closure(const __llvm_libc_tnode *node, VISIT visit,
-                           void *closure) {
-  if (visit == leaf || visit == postorder) {
-    int **cursor = static_cast<int **>(closure);
-    **cursor = read_and_decode(node);
-    *cursor += 1;
-  }
+// ---------------------------------------------------------------------------
+// tdestroy via heap-allocated int boxes – demonstrates real resource cleanup
+// with proper duplicate handling.
+// ---------------------------------------------------------------------------
+static int boxed_int_compare(const void *a, const void *b) {
+  int x = *static_cast<const int *>(a);
+  int y = *static_cast<const int *>(b);
+  return (x > y) - (x < y);
 }
 
-TEST(LlvmLibcTSearchTest, TWalkR) {
+static int int_box_freed;
+
+static void int_box_free(void *ptr) {
+  ++int_box_freed;
+  delete static_cast<int *>(ptr);
+}
+
+TEST(LlvmLibcTSearchTest, DestroyIntBoxes) {
+  int_box_freed = 0;
   void *root = nullptr;
-  clear_free_flags();
-  constexpr int LIMIT = 64;
-  int sorted[LIMIT];
-  for (int i = 0; i < LIMIT; ++i) {
-    int current = (i * 37) % LIMIT; // pseudo-random insertion order
-    LIBC_NAMESPACE::tsearch(encode(current), &root, compare);
-  }
+  constexpr int N = 8;
 
-  walk_sum = 0;
-  int *cursor = &sorted[0];
-  LIBC_NAMESPACE::twalk_r(root, action_closure, &cursor);
+  // Insert 0..N-1, each as a heap-allocated int.
+  for (int i = 0; i < N; ++i) {
+    int *p = new int(i);
+    ASSERT_NE(p, nullptr);
+    void *r = LIBC_NAMESPACE::tsearch(p, &root, boxed_int_compare);
+    ASSERT_NE(r, nullptr);
+    // All keys are unique here, so the stored key must be ours.
+    ASSERT_EQ(*static_cast<void **>(r), static_cast<void *>(p));
+  }
 
-  for (int i = 0; i < LIMIT; ++i)
-    ASSERT_EQ(sorted[i], i);
+  // Now re-insert some duplicates; the caller must free the rejected key.
+  int duplicates_detected = 0;
+  for (int i = 0; i < N; i += 2) {
+    int *p = new int(i);
+    void *r = LIBC_NAMESPACE::tsearch(p, &root, boxed_int_compare);
+    ASSERT_NE(r, nullptr);
+    if (*static_cast<void **>(r) != static_cast<void *>(p)) {
+      ++duplicates_detected;
+      delete p;
+    }
+  }
+  ASSERT_EQ(duplicates_detected, N / 2);
 
-  LIBC_NAMESPACE::tdestroy(root, free_node);
-  for (int i = 0; i < LIMIT; ++i)
-    ASSERT_TRUE(free_flags[i]);
+  // Destroy the tree; int_box_free must be called exactly N times
+  // (once per unique key that was actually stored).
+  LIBC_NAMESPACE::tdestroy(root, int_box_free);
+  ASSERT_EQ(int_box_freed, N);
 }

>From 9a70eb1414128c3bcdd0758cc07b2e51dcc84882 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 23 Feb 2026 23:08:06 -0500
Subject: [PATCH 6/7] inline tests and add comments

the initial inlining is done by Claude Sonnet 4.6 based on existing code. Auditted by Schrodinger ZHU Yifan <i at zhuyi.fan>
---
 libc/test/src/search/tsearch_test.cpp | 254 ++++++++++----------------
 1 file changed, 97 insertions(+), 157 deletions(-)

diff --git a/libc/test/src/search/tsearch_test.cpp b/libc/test/src/search/tsearch_test.cpp
index 4e0197a040113..2ed54d17d3a12 100644
--- a/libc/test/src/search/tsearch_test.cpp
+++ b/libc/test/src/search/tsearch_test.cpp
@@ -39,124 +39,68 @@ static int int_compare(const void *a, const void *b) {
   return (x > y) - (x < y);
 }
 
-// ---------------------------------------------------------------------------
-// OOP-style RAII int_box around the POSIX tsearch family.
-//
-// The int_box owns the tree root and calls tdestroy in its destructor.
-// It is parameterised on a free-function so callers can track or customise
-// destruction.
-// ---------------------------------------------------------------------------
-using FreeFn = void (*)(void *);
-
 static void noop_free(void *) {}
 
-class TTree {
-  void *root;
-  FreeFn free_fn;
-
-  // Non-copyable.
-  TTree(const TTree &) = delete;
-  TTree &operator=(const TTree &) = delete;
-
-public:
-  explicit TTree(FreeFn f = noop_free) : root(nullptr), free_fn(f) {}
-
-  ~TTree() {
-    if (root)
-      LIBC_NAMESPACE::tdestroy(root, free_fn);
-  }
-
-  // Insert key.  Returns the node pointer on success, nullptr on failure.
-  void *insert(void *key) {
-    return LIBC_NAMESPACE::tsearch(key, &root, int_compare);
-  }
-
-  // Find key.  Returns nullptr when not found.
-  void *find(void *key) const {
-    return LIBC_NAMESPACE::tfind(key, &root, int_compare);
-  }
-
-  // Delete key.  Returns the parent node (or the new root) on success,
-  // nullptr when the key is not in the tree.
-  void *remove(void *key) {
-    return LIBC_NAMESPACE::tdelete(key, &root, int_compare);
-  }
-
-  void walk(void (*action)(const __llvm_libc_tnode *, VISIT, int)) const {
-    LIBC_NAMESPACE::twalk(root, action);
-  }
-
-  void walk_r(void (*action)(const __llvm_libc_tnode *, VISIT, void *),
-              void *closure) const {
-    LIBC_NAMESPACE::twalk_r(root, action, closure);
-  }
-
-  // Release ownership so the destructor will not call tdestroy.
-  void *release() {
-    void *r = root;
-    root = nullptr;
-    return r;
-  }
-
-  bool empty() const { return root == nullptr; }
-  void *get_root() const { return root; }
-};
-
 // ===== tsearch tests =======================================================
 
 TEST(LlvmLibcTSearchTest, InsertIntoEmptyTree) {
-  TTree tree;
-  void *r = tree.insert(encode(42));
+  void *root = nullptr;
+  void *r = LIBC_NAMESPACE::tsearch(encode(42), &root, int_compare);
   ASSERT_NE(r, nullptr);
   ASSERT_EQ(read_node(r), 42);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, InsertMultiple) {
-  TTree tree;
+  void *root = nullptr;
   for (int i = 0; i < 8; ++i) {
-    void *r = tree.insert(encode(i));
+    void *r = LIBC_NAMESPACE::tsearch(encode(i), &root, int_compare);
     ASSERT_NE(r, nullptr);
     ASSERT_EQ(read_node(r), i);
   }
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, InsertDuplicateReturnsSameNode) {
-  TTree tree;
-  void *first = tree.insert(encode(7));
+  void *root = nullptr;
+  void *first = LIBC_NAMESPACE::tsearch(encode(7), &root, int_compare);
   ASSERT_NE(first, nullptr);
 
   // Inserting the same key again must return the existing node.
-  void *second = tree.insert(encode(7));
+  void *second = LIBC_NAMESPACE::tsearch(encode(7), &root, int_compare);
   ASSERT_EQ(first, second);
   ASSERT_EQ(read_node(second), 7);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 // ===== tfind tests =========================================================
 
 TEST(LlvmLibcTSearchTest, FindInEmptyTree) {
-  TTree tree;
-  void *r = tree.find(encode(1));
+  void *root = nullptr;
+  void *r = LIBC_NAMESPACE::tfind(encode(1), &root, int_compare);
   ASSERT_EQ(r, nullptr);
 }
 
 TEST(LlvmLibcTSearchTest, FindExistingKey) {
-  TTree tree;
-  tree.insert(encode(5));
-  tree.insert(encode(10));
-  tree.insert(encode(15));
+  void *root = nullptr;
+  LIBC_NAMESPACE::tsearch(encode(5), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(10), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(15), &root, int_compare);
 
-  void *r = tree.find(encode(10));
+  void *r = LIBC_NAMESPACE::tfind(encode(10), &root, int_compare);
   ASSERT_NE(r, nullptr);
   ASSERT_EQ(read_node(r), 10);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, FindNonExistentKey) {
-  TTree tree;
-  tree.insert(encode(1));
-  tree.insert(encode(3));
+  void *root = nullptr;
+  LIBC_NAMESPACE::tsearch(encode(1), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(3), &root, int_compare);
 
-  void *r = tree.find(encode(2));
-  ASSERT_EQ(r, nullptr);
+  void *r = LIBC_NAMESPACE::tfind(encode(2), &root, int_compare);
+  ASSERT_EQ(r, nullptr); // not exist
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 // ===== tdelete tests =======================================================
@@ -168,12 +112,13 @@ TEST(LlvmLibcTSearchTest, DeleteFromEmptyTree) {
 }
 
 TEST(LlvmLibcTSearchTest, DeleteNonExistentKey) {
-  TTree tree;
-  tree.insert(encode(10));
-  void *r = tree.remove(encode(99));
+  void *root = nullptr;
+  LIBC_NAMESPACE::tsearch(encode(10), &root, int_compare);
+  void *r = LIBC_NAMESPACE::tdelete(encode(99), &root, int_compare);
   ASSERT_EQ(r, nullptr);
   // Original key must still be findable.
-  ASSERT_NE(tree.find(encode(10)), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(10), &root, int_compare), nullptr);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, DeleteOnlyElement) {
@@ -187,72 +132,58 @@ TEST(LlvmLibcTSearchTest, DeleteOnlyElement) {
 }
 
 TEST(LlvmLibcTSearchTest, DeleteLeaf) {
-  TTree tree;
-  tree.insert(encode(10));
-  tree.insert(encode(5));
-  tree.insert(encode(15));
+  void *root = nullptr;
+  LIBC_NAMESPACE::tsearch(encode(10), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(5), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(15), &root, int_compare);
 
-  void *r = tree.remove(encode(5));
+  void *r = LIBC_NAMESPACE::tdelete(encode(5), &root, int_compare);
   ASSERT_NE(r, nullptr);
-  ASSERT_EQ(tree.find(encode(5)), nullptr);
+  ASSERT_EQ(LIBC_NAMESPACE::tfind(encode(5), &root, int_compare), nullptr);
   // Siblings remain.
-  ASSERT_NE(tree.find(encode(10)), nullptr);
-  ASSERT_NE(tree.find(encode(15)), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(10), &root, int_compare), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(15), &root, int_compare), nullptr);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
-TEST(LlvmLibcTSearchTest, DeleteNodeWithOneChild) {
-  TTree tree;
+TEST(LlvmLibcTSearchTest, DeleteInternalNode) {
+  void *root = nullptr;
   // Build a small chain: 10 -> 5 -> 3
-  tree.insert(encode(10));
-  tree.insert(encode(5));
-  tree.insert(encode(3));
+  LIBC_NAMESPACE::tsearch(encode(10), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(5), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(3), &root, int_compare);
 
-  void *r = tree.remove(encode(5));
+  void *r = LIBC_NAMESPACE::tdelete(encode(5), &root, int_compare);
   ASSERT_NE(r, nullptr);
-  ASSERT_EQ(tree.find(encode(5)), nullptr);
-  ASSERT_NE(tree.find(encode(3)), nullptr);
-  ASSERT_NE(tree.find(encode(10)), nullptr);
+  ASSERT_EQ(LIBC_NAMESPACE::tfind(encode(5), &root, int_compare), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(3), &root, int_compare), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(10), &root, int_compare), nullptr);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, DeleteNodeWithTwoChildren) {
-  TTree tree;
-  tree.insert(encode(10));
-  tree.insert(encode(5));
-  tree.insert(encode(15));
-  tree.insert(encode(3));
-  tree.insert(encode(7));
-
-  void *r = tree.remove(encode(5));
+  void *root = nullptr;
+  //     10
+  //    / \
+  //   5   \
+  //  /\    \
+  // 3  7    15
+  // For insertion process, see libc/test/src/__support/weak_avl_test.cpp:208
+  LIBC_NAMESPACE::tsearch(encode(10), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(5), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(15), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(3), &root, int_compare);
+  LIBC_NAMESPACE::tsearch(encode(7), &root, int_compare);
+
+  void *r = LIBC_NAMESPACE::tdelete(encode(5), &root, int_compare);
   ASSERT_NE(r, nullptr);
-  ASSERT_EQ(tree.find(encode(5)), nullptr);
+  ASSERT_EQ(LIBC_NAMESPACE::tfind(encode(5), &root, int_compare), nullptr);
   // All other keys survive.
-  ASSERT_NE(tree.find(encode(3)), nullptr);
-  ASSERT_NE(tree.find(encode(7)), nullptr);
-  ASSERT_NE(tree.find(encode(10)), nullptr);
-  ASSERT_NE(tree.find(encode(15)), nullptr);
-}
-
-TEST(LlvmLibcTSearchTest, InsertAndDeleteMany) {
-  TTree tree;
-  constexpr int N = 64;
-  // Insert in pseudo-random order.
-  for (int i = 0; i < N; ++i)
-    ASSERT_NE(tree.insert(encode((i * 37) % N)), nullptr);
-
-  // Delete every other element.
-  for (int i = 0; i < N; i += 2) {
-    void *r = tree.remove(encode(i));
-    ASSERT_NE(r, nullptr);
-  }
-
-  // Verify survivors and absences.
-  for (int i = 0; i < N; ++i) {
-    void *r = tree.find(encode(i));
-    if (i % 2 == 0)
-      ASSERT_EQ(r, nullptr);
-    else
-      ASSERT_NE(r, nullptr);
-  }
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(3), &root, int_compare), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(7), &root, int_compare), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(10), &root, int_compare), nullptr);
+  ASSERT_NE(LIBC_NAMESPACE::tfind(encode(15), &root, int_compare), nullptr);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 // ===== twalk tests =========================================================
@@ -271,26 +202,28 @@ TEST(LlvmLibcTSearchTest, WalkEmptyTree) {
 }
 
 TEST(LlvmLibcTSearchTest, WalkSingleNode) {
-  TTree tree;
-  tree.insert(encode(99));
+  void *root = nullptr;
+  LIBC_NAMESPACE::tsearch(encode(99), &root, int_compare);
 
   walk_sum = 0;
-  tree.walk(sum_action);
+  LIBC_NAMESPACE::twalk(root, sum_action);
   // A single node is a leaf, visited once.
   ASSERT_EQ(walk_sum, 99);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, WalkSumsCorrectly) {
-  TTree tree;
+  void *root = nullptr;
   int expected = 0;
   for (int i = 1; i <= 10; ++i) {
-    tree.insert(encode(i));
+    ASSERT_NE(LIBC_NAMESPACE::tsearch(encode(i), &root, int_compare), nullptr);
     expected += i;
   }
 
   walk_sum = 0;
-  tree.walk(sum_action);
+  LIBC_NAMESPACE::twalk(root, sum_action);
   ASSERT_EQ(walk_sum, expected);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 // Verify that twalk visits nodes in sorted (in-order) sequence.
@@ -307,16 +240,19 @@ static void sorted_action(const __llvm_libc_tnode *node, VISIT visit, int) {
 }
 
 TEST(LlvmLibcTSearchTest, WalkInOrder) {
-  TTree tree;
+  void *root = nullptr;
   // Insert in pseudo-random order.
   constexpr int N = 32;
   for (int i = 0; i < N; ++i)
-    tree.insert(encode((i * 13 + 7) % N));
+    ASSERT_NE(
+        LIBC_NAMESPACE::tsearch(encode((i * 13 + 7) % N), &root, int_compare),
+        nullptr);
 
   walk_prev = -1;
   walk_sorted = true;
-  tree.walk(sorted_action);
+  LIBC_NAMESPACE::twalk(root, sorted_action);
   ASSERT_TRUE(walk_sorted);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 // Verify twalk depth parameter.
@@ -332,16 +268,17 @@ static void depth_action(const __llvm_libc_tnode *, VISIT visit, int depth) {
 }
 
 TEST(LlvmLibcTSearchTest, WalkReportsDepth) {
-  TTree tree;
+  void *root = nullptr;
   for (int i = 1; i <= 15; ++i)
-    tree.insert(encode(i));
+    ASSERT_NE(LIBC_NAMESPACE::tsearch(encode(i), &root, int_compare), nullptr);
 
   walk_max_depth = -1;
   walk_node_count = 0;
-  tree.walk(depth_action);
+  LIBC_NAMESPACE::twalk(root, depth_action);
   ASSERT_EQ(walk_node_count, 15);
   // The maximum depth must be positive for a multi-node tree.
   ASSERT_GT(walk_max_depth, 0);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 // ===== twalk_r tests =======================================================
@@ -356,17 +293,19 @@ static void collect_action(const __llvm_libc_tnode *node, VISIT visit,
 }
 
 TEST(LlvmLibcTSearchTest, WalkRProducesSortedOutput) {
-  TTree tree;
+  void *root = nullptr;
   constexpr int N = 64;
   for (int i = 0; i < N; ++i)
-    tree.insert(encode((i * 37) % N));
+    ASSERT_NE(LIBC_NAMESPACE::tsearch(encode((i * 37) % N), &root, int_compare),
+              nullptr);
 
   int sorted[N];
   int *cursor = sorted;
-  tree.walk_r(collect_action, &cursor);
+  LIBC_NAMESPACE::twalk_r(root, collect_action, &cursor);
 
   for (int i = 0; i < N; ++i)
     ASSERT_EQ(sorted[i], i);
+  LIBC_NAMESPACE::tdestroy(root, noop_free);
 }
 
 TEST(LlvmLibcTSearchTest, WalkREmptyTree) {
@@ -403,7 +342,7 @@ TEST(LlvmLibcTSearchTest, DestroyVisitsAllNodes) {
   clear_free_flags();
   constexpr int N = 10;
   for (int i = 0; i < N; ++i)
-    LIBC_NAMESPACE::tsearch(encode(i), &root, int_compare);
+    ASSERT_NE(LIBC_NAMESPACE::tsearch(encode(i), &root, int_compare), nullptr);
 
   LIBC_NAMESPACE::tdestroy(root, flag_free);
   for (int i = 0; i < N; ++i)
@@ -429,8 +368,9 @@ static void string_free(void *ptr) {
 TEST(LlvmLibcTSearchTest, DestroyHeapStrings) {
   void *root = nullptr;
 
-  // Include deliberate duplicates to exercise the tsearch "already exists" path.
-  const char *words[] = {"cherry", "apple",  "banana", "date",
+  // Include deliberate duplicates to exercise the tsearch "already exists"
+  // path.
+  const char *words[] = {"cherry",     "apple", "banana", "date",
                          "elderberry", "apple", "cherry"};
   constexpr int NWORDS = sizeof(words) / sizeof(words[0]);
   constexpr int UNIQUE_WORDS = 5; // number of distinct strings
@@ -447,7 +387,7 @@ TEST(LlvmLibcTSearchTest, DestroyHeapStrings) {
     // and we must free the unused duplicate.
     if (*static_cast<void **>(r) != dup) {
       ++duplicates_detected;
-      delete[] dup;
+      delete[] dup; // match new[] with delete[]
     }
   }
 
@@ -505,7 +445,7 @@ TEST(LlvmLibcTSearchTest, DestroyIntBoxes) {
     ASSERT_NE(r, nullptr);
     if (*static_cast<void **>(r) != static_cast<void *>(p)) {
       ++duplicates_detected;
-      delete p;
+      delete p; // match new with delete
     }
   }
   ASSERT_EQ(duplicates_detected, N / 2);

>From 15a263beb1bd927f124128f7ec7d4ab0ba5f4427 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Mon, 23 Feb 2026 23:14:58 -0500
Subject: [PATCH 7/7] Adjust UB for nullfunction ptr to align with config

---
 libc/docs/dev/undefined_behavior.rst | 4 ++--
 libc/src/search/CMakeLists.txt       | 3 +++
 libc/src/search/tdestroy.cpp         | 2 ++
 libc/src/search/twalk.cpp            | 2 ++
 libc/src/search/twalk_r.cpp          | 2 ++
 5 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/libc/docs/dev/undefined_behavior.rst b/libc/docs/dev/undefined_behavior.rst
index 3a7f2d2c9e57e..1b8870d963b07 100644
--- a/libc/docs/dev/undefined_behavior.rst
+++ b/libc/docs/dev/undefined_behavior.rst
@@ -172,5 +172,5 @@ For LLVM-libc, `tdelete` returns bit-casted `uintptr_t`'s maximum value.
 `twalk/twalk_r/tdestroy` with Null Function Pointer
 ------------------------------------------------------
 The standard requires that ``twalk``, ``twalk_r``, and ``tdestroy``
-to be used with a valid function pointer. LLVM-libc does not apply any
-checks for null function pointers and will likely to crash on null values.
+to be used with a valid function pointer. LLVM-libc follows the behavior of
+configured via the `LIBC_ADD_NULL_CHECKS` option.
diff --git a/libc/src/search/CMakeLists.txt b/libc/src/search/CMakeLists.txt
index 2cdab57396bd1..c59769cba2de7 100644
--- a/libc/src/search/CMakeLists.txt
+++ b/libc/src/search/CMakeLists.txt
@@ -169,6 +169,7 @@ add_entrypoint_object(
     libc.hdr.types.posix_tnode
     libc.hdr.types.VISIT
     libc.src.__support.weak_avl
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -181,6 +182,7 @@ add_entrypoint_object(
     libc.hdr.types.posix_tnode
     libc.hdr.types.VISIT
     libc.src.__support.weak_avl
+    libc.src.__support.macros.null_check
 )
 
 add_entrypoint_object(
@@ -192,4 +194,5 @@ add_entrypoint_object(
   DEPENDS
     libc.hdr.types.posix_tnode
     libc.src.__support.weak_avl
+    libc.src.__support.macros.null_check
 )
diff --git a/libc/src/search/tdestroy.cpp b/libc/src/search/tdestroy.cpp
index 398e93bcb7e15..f88f3b8d6011d 100644
--- a/libc/src/search/tdestroy.cpp
+++ b/libc/src/search/tdestroy.cpp
@@ -9,6 +9,7 @@
 #include "src/search/tdestroy.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/__support/weak_avl.h"
 
 namespace LIBC_NAMESPACE_DECL {
@@ -19,6 +20,7 @@ LLVM_LIBC_FUNCTION(void, tdestroy,
     return;
   using Node = WeakAVLNode<const void *>;
   Node *node = reinterpret_cast<Node *>(root);
+  LIBC_CRASH_ON_NULLPTR(free_node);
   Node::destroy(node, [free_node](const void *&data) {
     free_node(const_cast<void *>(data));
   });
diff --git a/libc/src/search/twalk.cpp b/libc/src/search/twalk.cpp
index 1dbea20fafc46..5fbe84da55e07 100644
--- a/libc/src/search/twalk.cpp
+++ b/libc/src/search/twalk.cpp
@@ -10,6 +10,7 @@
 #include "hdr/types/posix_tnode.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/__support/weak_avl.h"
 
 namespace LIBC_NAMESPACE_DECL {
@@ -22,6 +23,7 @@ LLVM_LIBC_FUNCTION(void, twalk,
   if (!root)
     return;
   const Node *node = reinterpret_cast<const Node *>(root);
+  LIBC_CRASH_ON_NULLPTR(action);
   Node::walk(node, [action](const Node *n, Node::WalkType type, int depth) {
     VISIT v = (type == Node::WalkType::PreOrder)    ? preorder
               : (type == Node::WalkType::InOrder)   ? postorder
diff --git a/libc/src/search/twalk_r.cpp b/libc/src/search/twalk_r.cpp
index 8c03bdf76d92a..17365bf28fd28 100644
--- a/libc/src/search/twalk_r.cpp
+++ b/libc/src/search/twalk_r.cpp
@@ -10,6 +10,7 @@
 #include "hdr/types/posix_tnode.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
 #include "src/__support/weak_avl.h"
 
 namespace LIBC_NAMESPACE_DECL {
@@ -23,6 +24,7 @@ LLVM_LIBC_FUNCTION(void, twalk_r,
   if (!root)
     return;
   const Node *node = reinterpret_cast<const Node *>(root);
+  LIBC_CRASH_ON_NULLPTR(action);
   Node::walk(node, [action, closure](const Node *n, Node::WalkType type, int) {
     VISIT v = (type == Node::WalkType::PreOrder)    ? preorder
               : (type == Node::WalkType::InOrder)   ? postorder



More information about the libc-commits mailing list