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

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Mon Feb 23 20:15:20 PST 2026


================
@@ -0,0 +1,517 @@
+//===-- 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 "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));
+}
+
+static int decode(const void *val) {
+  return static_cast<int>(reinterpret_cast<intptr_t>(val));
+}
+
+static int read_node(const __llvm_libc_tnode *node) {
+  return static_cast<int>(*static_cast<const intptr_t *>(node));
+}
+
+static int int_compare(const void *a, const void *b) {
+  int x = decode(a);
+  int y = decode(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));
+  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 *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);
+  }
+}
----------------
SchrodingerZhu wrote:

Removed

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


More information about the libc-commits mailing list