[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