[libc-commits] [libc] [libc] Don't match the byte after the range in vector find_first_character (PR #235288)

Fabian Meumertzheim via libc-commits libc-commits at lists.llvm.org
Sat Oct 24 05:26:04 PDT 2026


https://github.com/fmeum created https://github.com/llvm/llvm-project/pull/235288

The vectorized `find_first_character` implementations (`clang_vector` and the x86_64 `arch_vector`) returned the position of a match that was the first byte after the searched range. For example, `memchr("ab", 'b', 1)` returned a pointer to the `'b'` instead of `nullptr`.

The new test covers all implementations, since no configuration selects the vectorized ones by default. On aarch64 Linux, it fails without the fix and passes with it. The x86_64 changes were only compiled, not run.

Assisted-by: Claude Code (Claude Opus 5.5)


>From f5062d84c38e8a7eaba9fd96f24fd092c076196a Mon Sep 17 00:00:00 2001
From: Fabian Meumertzheim <fabian at meumertzhe.im>
Date: Sat, 24 Oct 2026 14:25:08 +0200
Subject: [PATCH] [libc] Don't match the byte after the range in vector
 find_first_character

The vectorized find_first_character implementations (clang_vector and
the x86_64 arch_vector) returned the position of a match that was the
first byte after the searched range. For example, memchr("ab", 'b', 1)
returned a pointer to the 'b' instead of nullptr.

The new test covers all implementations, since no configuration selects
the vectorized ones by default.

Assisted-by: Claude Code (Claude Opus 5.5)
---
 .../memory_utils/generic/inline_strlen.h      |  2 +-
 .../memory_utils/x86_64/inline_strlen.h       |  2 +-
 .../src/strings/wide_read_memory_test.cpp     | 33 +++++++++++++++++++
 3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/libc/src/string/memory_utils/generic/inline_strlen.h b/libc/src/string/memory_utils/generic/inline_strlen.h
index 833ff9fbcbf80a..9f84a293478972 100644
--- a/libc/src/string/memory_utils/generic/inline_strlen.h
+++ b/libc/src/string/memory_utils/generic/inline_strlen.h
@@ -50,7 +50,7 @@ LIBC_NO_SANITIZE_OOB_ACCESS LIBC_INLINE size_t string_length(const char *src) {
 LIBC_INLINE static void *calculate_find_first_character_return(
     const char *src, cpp::simd_mask<char> c_mask, size_t n_left) {
   size_t c_offset = cpp::find_first_set(c_mask);
-  if (n_left < c_offset)
+  if (n_left <= c_offset)
     return nullptr;
   return const_cast<char *>(src) + c_offset;
 }
diff --git a/libc/src/string/memory_utils/x86_64/inline_strlen.h b/libc/src/string/memory_utils/x86_64/inline_strlen.h
index 0343e22f3e58e3..cc85d9c8fda848 100644
--- a/libc/src/string/memory_utils/x86_64/inline_strlen.h
+++ b/libc/src/string/memory_utils/x86_64/inline_strlen.h
@@ -50,7 +50,7 @@ LIBC_INLINE static void *
 calculate_find_first_character_return(const unsigned char *src, Mask c_mask,
                                       size_t n_left) {
   size_t c_offset = cpp::countr_zero(c_mask);
-  if (n_left < c_offset)
+  if (n_left <= c_offset)
     return nullptr;
   return const_cast<unsigned char *>(src) + c_offset;
 }
diff --git a/libc/test/src/strings/wide_read_memory_test.cpp b/libc/test/src/strings/wide_read_memory_test.cpp
index 7b173d479dc9db..ffe57942b33e39 100644
--- a/libc/test/src/strings/wide_read_memory_test.cpp
+++ b/libc/test/src/strings/wide_read_memory_test.cpp
@@ -120,4 +120,37 @@ TEST_F(LlvmLibcWideAccessMemoryTest, FindFirstChar) {
   });
 }
 
+TEST_F(LlvmLibcWideAccessMemoryTest, FindFirstCharAfterRange) {
+  // All implementations, since the configured one is usually not vectorized.
+  static constexpr void *(*kImpls[])(const unsigned char *, unsigned char,
+                                     size_t) = {
+      internal::element::find_first_character,
+      internal::word::find_first_character,
+#if LIBC_HAS_VECTOR_TYPE
+      clang_vector::find_first_character,
+#endif
+#ifdef LIBC_TARGET_ARCH_IS_X86
+      internal::arch_vector::find_first_character,
+#endif
+  };
+  // 1.5 k long vector of a's.
+  TwoKilobyteBuffer buf;
+  inline_memset(buf.data(), 'a', buf.size());
+  buf[buf.size() - 1] = 'b';
+  this->TestMemoryAccess(buf, [buf](const char *test_data) {
+    const unsigned char *b =
+        reinterpret_cast<const unsigned char *>(test_data) + buf.size() - 1;
+    for (auto find_first_character : kImpls) {
+      // The 'b' right after the searched range must not be found.
+      for (size_t n = 0; n < 128; ++n)
+        ASSERT_EQ(
+            reinterpret_cast<const void *>(find_first_character(b - n, 'b', n)),
+            nullptr);
+      ASSERT_EQ(reinterpret_cast<const void *>(find_first_character(
+                    b - (buf.size() - 1), 'b', buf.size() - 1)),
+                nullptr);
+    }
+  });
+}
+
 } // namespace LIBC_NAMESPACE_DECL



More information about the libc-commits mailing list