Skip to content

[libc][search] implement POSIX lsearch function #116870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

duncpro
Copy link
Contributor

@duncpro duncpro commented Nov 19, 2024

@llvmbot llvmbot added the libc label Nov 19, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 19, 2024

@llvm/pr-subscribers-libc

Author: Duncan (duncpro)

Changes

Full diff: https://github.com/llvm/llvm-project/pull/116870.diff

13 Files Affected:

  • (modified) libc/config/darwin/x86_64/entrypoints.txt (+2-1)
  • (modified) libc/config/linux/aarch64/entrypoints.txt (+1)
  • (modified) libc/config/linux/arm/entrypoints.txt (+1)
  • (modified) libc/config/linux/riscv/entrypoints.txt (+1)
  • (modified) libc/config/linux/x86_64/entrypoints.txt (+1)
  • (modified) libc/docs/libc_search.rst (+1-1)
  • (modified) libc/newhdrgen/yaml/search.yaml (+10)
  • (modified) libc/spec/posix.td (+11)
  • (modified) libc/src/search/CMakeLists.txt (+13)
  • (added) libc/src/search/lsearch.cpp (+43)
  • (added) libc/src/search/lsearch.h (+20)
  • (modified) libc/test/src/search/CMakeLists.txt (+10)
  • (added) libc/test/src/search/lsearch_test.cpp (+79)
diff --git a/libc/config/darwin/x86_64/entrypoints.txt b/libc/config/darwin/x86_64/entrypoints.txt
index 64eeed18f3819e..5efdf7ee086d7d 100644
--- a/libc/config/darwin/x86_64/entrypoints.txt
+++ b/libc/config/darwin/x86_64/entrypoints.txt
@@ -18,7 +18,8 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.ctype.toupper
 
     # search.h entrypoints
-    libc.src.search.lfind 
+    libc.src.search.lfind
+    libc.src.search.lsearch 
 
     # string.h entrypoints
     libc.src.string.bcmp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 74ca3742977a5f..a64fe17f4cacbd 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -961,6 +961,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.search.hsearch_r
     libc.src.search.insque
     libc.src.search.lfind
+    libc.src.search.lsearch
     libc.src.search.remque
 
     # threads.h entrypoints
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index 31d81de06fb6b0..259fa1bd9d984d 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -185,6 +185,7 @@ if(LLVM_LIBC_FULL_BUILD)
   list(APPEND TARGET_LIBC_ENTRYPOINTS
     # search.h entrypoints
     libc.src.search.lfind
+    libc.src.search.lsearch
 
     # setjmp.h entrypoints
     libc.src.setjmp.longjmp
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 5419462d4f5b3b..e1c7ecc89bc3ee 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -899,6 +899,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.search.hsearch_r
     libc.src.search.insque
     libc.src.search.lfind
+    libc.src.search.lsearch
     libc.src.search.remque
 
     # threads.h entrypoints
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 957e28bd66cc4c..7d3a4237be613c 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1044,6 +1044,7 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.search.hsearch_r
     libc.src.search.insque
     libc.src.search.lfind
+    libc.src.search.lsearch
     libc.src.search.remque
 
     # threads.h entrypoints
diff --git a/libc/docs/libc_search.rst b/libc/docs/libc_search.rst
index 774622d1e66c3f..7d31181427507f 100644
--- a/libc/docs/libc_search.rst
+++ b/libc/docs/libc_search.rst
@@ -43,7 +43,7 @@ hdestroy                     |check|
 hsearch                      |check|
 insque                       |check|
 lfind                        |check|
-lsearch
+lsearch                      |check|
 remque                       |check|
 tdelete
 tfind
diff --git a/libc/newhdrgen/yaml/search.yaml b/libc/newhdrgen/yaml/search.yaml
index a0c73bc679d819..38b950fc8a2314 100644
--- a/libc/newhdrgen/yaml/search.yaml
+++ b/libc/newhdrgen/yaml/search.yaml
@@ -68,3 +68,13 @@ functions:
       - type: size_t *
       - type: size_t
       - type: __lsearchcompare_t
+  - name: lsearch
+    standards:
+      - POSIX
+    return_type: void *
+    arguments:
+      - type: const void *
+      - type: void *
+      - type: size_t *
+      - type: size_t
+      - type: __lsearchcompare_t
diff --git a/libc/spec/posix.td b/libc/spec/posix.td
index e354deef340f1b..5ba334888bd5f4 100644
--- a/libc/spec/posix.td
+++ b/libc/spec/posix.td
@@ -1632,6 +1632,17 @@ def POSIX : StandardSpec<"POSIX"> {
                 ArgSpec<SizeTType>,
                 ArgSpec<LSearchCompareT>
             ]
+        >,  
+        FunctionSpec<
+            "lsearch",
+            RetValSpec<VoidPtr>,
+            [
+                ArgSpec<ConstVoidPtr>,
+                ArgSpec<VoidPtr>,
+                ArgSpec<SizeTPtr>,
+                ArgSpec<SizeTType>,
+                ArgSpec<LSearchCompareT>
+            ]
         >
     ]
   >;
diff --git a/libc/src/search/CMakeLists.txt b/libc/src/search/CMakeLists.txt
index 497657f40f2f02..d78ea062342a1d 100644
--- a/libc/src/search/CMakeLists.txt
+++ b/libc/src/search/CMakeLists.txt
@@ -110,3 +110,16 @@ add_entrypoint_object(
     libc.src.__support.CPP.cstddef
     libc.src.__support.memory_size
 )
+
+add_entrypoint_object(
+  lsearch
+  SRCS
+    lsearch.cpp
+  HDRS
+    lsearch.h
+  DEPENDS
+    libc.include.search
+    libc.src.__support.CPP.cstddef
+    libc.src.__support.memory_size
+    libc.src.string.memory_utils.inline_memcpy
+)
diff --git a/libc/src/search/lsearch.cpp b/libc/src/search/lsearch.cpp
new file mode 100644
index 00000000000000..a9f0aa3e7530fc
--- /dev/null
+++ b/libc/src/search/lsearch.cpp
@@ -0,0 +1,43 @@
+//===-- Implementation of lsearch -------------------------------*- 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/lsearch.h"
+#include "src/__support/CPP/cstddef.h" // cpp::byte
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/memory_size.h"
+#include "src/string/memory_utils/inline_memcpy.h"
+
+namespace LIBC_NAMESPACE_DECL {
+LLVM_LIBC_FUNCTION(void *, lsearch,
+                   (const void *key, void *base, size_t *nmemb, size_t size,
+                    int (*compar)(const void *, const void *))) {
+  if (key == nullptr || base == nullptr || nmemb == nullptr ||
+      compar == nullptr)
+    return nullptr;
+
+  size_t byte_len = 0;
+  if (internal::mul_overflow(*nmemb, size, &byte_len))
+    return nullptr;
+
+  cpp::byte *next = reinterpret_cast<cpp::byte *>(const_cast<void *>(base));
+  const cpp::byte *end = next + byte_len;
+
+  for (; next < end; next += size)
+    if (compar(key, next) == 0)
+      break;
+
+  if (next == end) {
+    LIBC_NAMESPACE::inline_memcpy(next, key, size);
+    *nmemb += 1;
+  }
+
+  return next;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/search/lsearch.h b/libc/src/search/lsearch.h
new file mode 100644
index 00000000000000..2278bcdd458830
--- /dev/null
+++ b/libc/src/search/lsearch.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for lfind -------------------------*- 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_LSEARCH_H
+#define LLVM_LIBC_SRC_SEARCH_LSEARCH_H
+
+#include "src/__support/macros/config.h"
+#include <stddef.h> // size_t
+
+namespace LIBC_NAMESPACE_DECL {
+void *lsearch(const void *key, void *base, size_t *nmemb, size_t size,
+              int (*compar)(const void *, const void *));
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SEARCH_LSEARCH_H
diff --git a/libc/test/src/search/CMakeLists.txt b/libc/test/src/search/CMakeLists.txt
index a1f9aac2094c9b..9f34d4d3fd2597 100644
--- a/libc/test/src/search/CMakeLists.txt
+++ b/libc/test/src/search/CMakeLists.txt
@@ -35,3 +35,13 @@ add_libc_unittest(
   DEPENDS
     libc.src.search.lfind
 )
+
+add_libc_unittest(
+  lsearch_test
+  SUITE
+    libc_search_unittests
+  SRCS
+    lsearch_test.cpp
+  DEPENDS
+    libc.src.search.lsearch
+)
diff --git a/libc/test/src/search/lsearch_test.cpp b/libc/test/src/search/lsearch_test.cpp
new file mode 100644
index 00000000000000..ab6ee3dcf8ac57
--- /dev/null
+++ b/libc/test/src/search/lsearch_test.cpp
@@ -0,0 +1,79 @@
+//===-- Unittests for lfind -----------------------------------------------===//
+//
+// 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/lsearch.h"
+#include "test/UnitTest/Test.h"
+
+int compar(const void *a, const void *b) {
+  return *reinterpret_cast<const int *>(a) != *reinterpret_cast<const int *>(b);
+}
+
+TEST(LlvmLibcLsearchTest, SearchHead) {
+  int list[4] = {1, 2, 3, 4};
+  size_t len = 3;
+  int key = 1;
+  void *ret = LIBC_NAMESPACE::lsearch(&key, list, &len, sizeof(int), compar);
+
+  ASSERT_EQ(static_cast<int *>(ret), &list[0]);
+  ASSERT_EQ(len, static_cast<size_t>(3));
+  ASSERT_EQ(list[1], 2);
+  ASSERT_EQ(list[2], 3);
+  ASSERT_EQ(list[3], 4);
+  ASSERT_EQ(list[3], 4);
+}
+
+TEST(LlvmLibcLsearchTest, SearchMiddle) {
+  int list[4] = {1, 2, 3, 4};
+  size_t len = 3;
+  int key = 2;
+  void *ret = LIBC_NAMESPACE::lsearch(&key, list, &len, sizeof(int), compar);
+  ASSERT_EQ(static_cast<int *>(ret), &list[1]);
+  ASSERT_EQ(len, static_cast<size_t>(3));
+  ASSERT_EQ(list[0], 1);
+  ASSERT_EQ(list[1], 2);
+  ASSERT_EQ(list[2], 3);
+  ASSERT_EQ(list[3], 4);
+}
+
+TEST(LlvmLibcLsearchTest, SearchTail) {
+  int list[4] = {1, 2, 3, 4};
+  size_t len = 3;
+  int key = 3;
+  void *ret = LIBC_NAMESPACE::lsearch(&key, list, &len, sizeof(int), compar);
+  ASSERT_EQ(static_cast<int *>(ret), &list[2]);
+  ASSERT_EQ(len, static_cast<size_t>(3));
+  ASSERT_EQ(list[0], 1);
+  ASSERT_EQ(list[1], 2);
+  ASSERT_EQ(list[2], 3);
+  ASSERT_EQ(list[3], 4);
+}
+
+TEST(LlvmLibcLsearchTest, SearchNonExistent) {
+  int list[4] = {1, 2, 3, 4};
+  size_t len = 3;
+  int key = 5;
+  void *ret = LIBC_NAMESPACE::lsearch(&key, list, &len, sizeof(int), compar);
+
+  ASSERT_EQ(static_cast<int *>(ret), &list[3]);
+  ASSERT_EQ(len, static_cast<size_t>(4));
+  ASSERT_EQ(list[0], 1);
+  ASSERT_EQ(list[1], 2);
+  ASSERT_EQ(list[2], 3);
+  ASSERT_EQ(list[3], 5);
+}
+
+TEST(LlvmLibcLsearchTest, SearchNonExistentEmpty) {
+  int list[1] = {1};
+  size_t len = 0;
+  int key = 0;
+  void *ret = LIBC_NAMESPACE::lsearch(&key, list, &len, sizeof(int), compar);
+
+  ASSERT_EQ(static_cast<int *>(ret), &list[0]);
+  ASSERT_EQ(len, static_cast<size_t>(1));
+  ASSERT_EQ(list[0], 0);
+}

Comment on lines 26 to 27
ASSERT_EQ(list[3], 4);
ASSERT_EQ(list[3], 4);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated assertion? Did you mean to look at list[0] initially?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I meant to say ASSERT_EQ(list[0], 1). I will fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved by 47f7800

Comment on lines +57 to +58
int list[4] = {1, 2, 3, 4};
size_t len = 3;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a test where len is equal to the number of elements in the list, but the key is not found?

Copy link
Contributor Author

@duncpro duncpro Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a test where len is equal to the number of elements in the list, but the key is not found?

@nickdesaulniers If len is equal to the size of the array, and key is not found, lsearch will write out of bounds.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ cat foo.c
#include <search.h>

int compar(const void *a, const void *b) {
  return *(int*)a != *(int*)b;
}

int main () {
  int list [4] = {1, 2, 3, 4};
  size_t len = 4;
  int key = 5;
  void *ret = lsearch(&key, list, &len, sizeof(int), compar);
  return list[3] == 4;
}
$ gcc -O2 foo.c -fsanitize=address
$ ./a.out
$ echo $?
1

Did asan miss an OOB write? Or is glibc's lsearch doing something more sophisticated?

Copy link
Contributor Author

@duncpro duncpro Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The glibc implementation of lsearch will write out of bounds as well, unless their memcpy somehow knows the address is OOB and ignores the call.

ArgSpec<SizeTPtr>,
ArgSpec<SizeTType>,
ArgSpec<LSearchCompareT>
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: old hdrgen is about to be deleted in #117220, so landing this may conflict with that.

@nickdesaulniers
Copy link
Member

need me to merge this for you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants