[libc-commits] [libc] [libc] Add Annex K strnlen_s function (PR #186112)
via libc-commits
libc-commits at lists.llvm.org
Thu Mar 12 07:41:15 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: Victor Campos (vhscampos)
<details>
<summary>Changes</summary>
This patch adds the `strnlen_s` function from Annex K.
In order to reduce duplication between `strnlen` and `strnlen_s`, the common logic has been extracted to a new internal function which both now call.
In addition to the function definition, the patch adds a unit test and a fuzzing test.
---
Full diff: https://github.com/llvm/llvm-project/pull/186112.diff
18 Files Affected:
- (modified) libc/config/baremetal/aarch64/entrypoints.txt (+1)
- (modified) libc/config/baremetal/arm/entrypoints.txt (+1)
- (modified) libc/config/darwin/aarch64/entrypoints.txt (+1)
- (modified) libc/config/linux/aarch64/entrypoints.txt (+1)
- (modified) libc/config/linux/arm/entrypoints.txt (+1)
- (modified) libc/config/windows/entrypoints.txt (+1)
- (modified) libc/fuzzing/string/CMakeLists.txt (+8)
- (added) libc/fuzzing/string/strnlen_s_fuzz.cpp (+58)
- (modified) libc/include/CMakeLists.txt (+1)
- (modified) libc/include/string.yaml (+10)
- (modified) libc/src/string/CMakeLists.txt (+11)
- (modified) libc/src/string/string_utils.h (+6)
- (modified) libc/src/string/strnlen.cpp (+1-3)
- (added) libc/src/string/strnlen_s.cpp (+16)
- (added) libc/src/string/strnlen_s.h (+19)
- (modified) libc/test/src/string/CMakeLists.txt (+10)
- (added) libc/test/src/string/strnlen_s_test.cpp (+57)
- (modified) libc/utils/docgen/string.yaml (+3)
``````````diff
diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt
index 4e720a234d471..16be74a3b202d 100644
--- a/libc/config/baremetal/aarch64/entrypoints.txt
+++ b/libc/config/baremetal/aarch64/entrypoints.txt
@@ -87,6 +87,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.string.strncpy
libc.src.string.strndup
libc.src.string.strnlen
+ libc.src.string.strnlen_s
libc.src.string.strpbrk
libc.src.string.strrchr
libc.src.string.strsep
diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt
index f352e8df52590..1f4d119f285b8 100644
--- a/libc/config/baremetal/arm/entrypoints.txt
+++ b/libc/config/baremetal/arm/entrypoints.txt
@@ -87,6 +87,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.string.strncpy
libc.src.string.strndup
libc.src.string.strnlen
+ libc.src.string.strnlen_s
libc.src.string.strpbrk
libc.src.string.strrchr
libc.src.string.strsep
diff --git a/libc/config/darwin/aarch64/entrypoints.txt b/libc/config/darwin/aarch64/entrypoints.txt
index 7fe6d1b21dd81..04836d7cd45d9 100644
--- a/libc/config/darwin/aarch64/entrypoints.txt
+++ b/libc/config/darwin/aarch64/entrypoints.txt
@@ -46,6 +46,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.string.strncmp
libc.src.string.strncpy
libc.src.string.strnlen
+ libc.src.string.strnlen_s
libc.src.string.strpbrk
libc.src.string.strrchr
libc.src.string.strspn
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index 658015cbefae9..82ce1234ff18d 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -79,6 +79,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.string.strncpy
libc.src.string.strndup
libc.src.string.strnlen
+ libc.src.string.strnlen_s
libc.src.string.strpbrk
libc.src.string.strrchr
libc.src.string.strsep
diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt
index 92ffe7e8d0bd5..307c6df3fae75 100644
--- a/libc/config/linux/arm/entrypoints.txt
+++ b/libc/config/linux/arm/entrypoints.txt
@@ -49,6 +49,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.string.strncmp
libc.src.string.strncpy
libc.src.string.strnlen
+ libc.src.string.strnlen_s
libc.src.string.strpbrk
libc.src.string.strrchr
libc.src.string.strsep
diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt
index 28608e8c327d1..5fd8d27d50ab0 100644
--- a/libc/config/windows/entrypoints.txt
+++ b/libc/config/windows/entrypoints.txt
@@ -43,6 +43,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.string.strncmp
libc.src.string.strncpy
libc.src.string.strnlen
+ libc.src.string.strnlen_s
libc.src.string.strpbrk
libc.src.string.strrchr
libc.src.string.strspn
diff --git a/libc/fuzzing/string/CMakeLists.txt b/libc/fuzzing/string/CMakeLists.txt
index 0918e92552ea7..92880d7e37407 100644
--- a/libc/fuzzing/string/CMakeLists.txt
+++ b/libc/fuzzing/string/CMakeLists.txt
@@ -48,3 +48,11 @@ add_libc_fuzzer(
DEPENDS
libc.src.string.strlen
)
+
+add_libc_fuzzer(
+ strnlen_s_fuzz
+ SRCS
+ strnlen_s_fuzz.cpp
+ DEPENDS
+ libc.src.string.strnlen_s
+)
diff --git a/libc/fuzzing/string/strnlen_s_fuzz.cpp b/libc/fuzzing/string/strnlen_s_fuzz.cpp
new file mode 100644
index 0000000000000..93176ae871a65
--- /dev/null
+++ b/libc/fuzzing/string/strnlen_s_fuzz.cpp
@@ -0,0 +1,58 @@
+//===-- strnlen_s_fuzz.cpp ------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// Fuzzing test for llvm-libc strnlen_s implementation.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/string/strnlen_s.h"
+#define __STDC_WANT_LIB_EXT1__ 1
+#include <stdint.h>
+#include <string.h>
+
+extern "C" size_t LLVMFuzzerMutate(uint8_t *data, size_t size, size_t max_size);
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size,
+ size_t max_size,
+ unsigned int /*seed*/) {
+ // The buffer is constructed as follows:
+ // data = max_len (size_t) + null-terminated string
+ if (max_size < sizeof(size_t) + 1)
+ return size;
+
+ do {
+ size = LLVMFuzzerMutate(data, size, max_size);
+ } while (size < sizeof(size_t) + 1);
+
+ data[size - 1] = '\0';
+ return size;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < sizeof(size_t) + 1)
+ return 0;
+
+ size_t max_len;
+ ::memcpy(&max_len, data, sizeof(size_t));
+ data += sizeof(size_t);
+
+ // If Annex K is not available in the system's C library, we compare against
+ // strnlen instead. We can assume this is valid because in the case where the
+ // input string is not null, the two functions must have identical semantics.
+#ifdef __STDC_LIB_EXT1__
+ size_t ref = ::strnlen_s(reinterpret_cast<const char *>(data), max_len);
+#else
+ size_t ref = ::strnlen(reinterpret_cast<const char *>(data), max_len);
+#endif
+ size_t impl =
+ LIBC_NAMESPACE::strnlen_s(reinterpret_cast<const char *>(data), max_len);
+
+ if (ref != impl)
+ __builtin_trap();
+
+ return 0;
+}
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index b2ebd035872aa..b1c437059d6be 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -252,6 +252,7 @@ add_header_macro(
string.h
DEPENDS
.llvm_libc_common_h
+ .llvm-libc-macros.annex_k_macros
.llvm-libc-macros.null_macro
.llvm-libc-types.size_t
)
diff --git a/libc/include/string.yaml b/libc/include/string.yaml
index 22010f4afa812..b52f2bb1a5933 100644
--- a/libc/include/string.yaml
+++ b/libc/include/string.yaml
@@ -4,6 +4,8 @@ standards:
macros:
- macro_name: "NULL"
macro_header: null-macro.h
+ - macro_name: LIBC_HAS_ANNEX_K
+ macro_header: annex-k-macros.h
types:
- type_name: locale_t
- type_name: size_t
@@ -249,6 +251,14 @@ functions:
arguments:
- type: const char *
- type: size_t
+ - name: strnlen_s
+ standards:
+ - stdc
+ return_type: size_t
+ arguments:
+ - type: const char *
+ - type: size_t
+ guard: LIBC_HAS_ANNEX_K
- name: strpbrk
standards:
- stdc
diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt
index a640e1d2cc774..2a38c6509605e 100644
--- a/libc/src/string/CMakeLists.txt
+++ b/libc/src/string/CMakeLists.txt
@@ -302,6 +302,17 @@ add_entrypoint_object(
.string_utils
)
+add_entrypoint_object(
+ strnlen_s
+ SRCS
+ strnlen_s.cpp
+ HDRS
+ strnlen_s.h
+ DEPENDS
+ libc.hdr.types.size_t
+ .string_utils
+)
+
add_entrypoint_object(
strpbrk
SRCS
diff --git a/libc/src/string/string_utils.h b/libc/src/string/string_utils.h
index b0144e01a9006..8c8879b4886e8 100644
--- a/libc/src/string/string_utils.h
+++ b/libc/src/string/string_utils.h
@@ -124,6 +124,12 @@ LIBC_INLINE void *find_first_character(const unsigned char *src,
return find_first_character_impl(src, ch, max_strlen);
}
+LIBC_INLINE size_t strnlen(const char *s, size_t max_len) {
+ const void *temp = internal::find_first_character(
+ reinterpret_cast<const unsigned char *>(s), '\0', max_len);
+ return temp ? reinterpret_cast<const char *>(temp) - s : max_len;
+}
+
} // namespace internal
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/string/strnlen.cpp b/libc/src/string/strnlen.cpp
index 26fcd5a04c0d7..6bfbb86a5014e 100644
--- a/libc/src/string/strnlen.cpp
+++ b/libc/src/string/strnlen.cpp
@@ -16,9 +16,7 @@
namespace LIBC_NAMESPACE_DECL {
LLVM_LIBC_FUNCTION(size_t, strnlen, (const char *src, size_t n)) {
- const void *temp = internal::find_first_character(
- reinterpret_cast<const unsigned char *>(src), '\0', n);
- return temp ? reinterpret_cast<const char *>(temp) - src : n;
+ return internal::strnlen(src, n);
}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/string/strnlen_s.cpp b/libc/src/string/strnlen_s.cpp
new file mode 100644
index 0000000000000..c76591d30d70b
--- /dev/null
+++ b/libc/src/string/strnlen_s.cpp
@@ -0,0 +1,16 @@
+//===-- Implementation header for strnlen_s ----------------------*- 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/string/strnlen_s.h"
+#include "src/string/string_utils.h"
+
+namespace LIBC_NAMESPACE_DECL {
+LLVM_LIBC_FUNCTION(size_t, strnlen_s, (const char *s, size_t n)) {
+ return (s != 0) ? internal::strnlen(s, n) : 0;
+}
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/string/strnlen_s.h b/libc/src/string/strnlen_s.h
new file mode 100644
index 0000000000000..89bbbd53b0494
--- /dev/null
+++ b/libc/src/string/strnlen_s.h
@@ -0,0 +1,19 @@
+//===-- Implementation header for strnlen_s ----------------------*- 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_STRING_STRNLEN_S_H
+#define LLVM_LIBC_SRC_STRING_STRNLEN_S_H
+
+#include "hdr/types/size_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+size_t strnlen_s(const char *src, size_t n);
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STRING_STRNLEN_S_H
diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt
index ced60750a45c7..17927ea93ed1e 100644
--- a/libc/test/src/string/CMakeLists.txt
+++ b/libc/test/src/string/CMakeLists.txt
@@ -274,6 +274,16 @@ add_libc_test(
libc.src.string.strnlen
)
+add_libc_test(
+ strnlen_s_test
+ SUITE
+ libc-string-tests
+ SRCS
+ strnlen_s_test.cpp
+ DEPENDS
+ libc.src.string.strnlen_s
+)
+
add_libc_test(
strpbrk_test
SUITE
diff --git a/libc/test/src/string/strnlen_s_test.cpp b/libc/test/src/string/strnlen_s_test.cpp
new file mode 100644
index 0000000000000..4749a544c8fb5
--- /dev/null
+++ b/libc/test/src/string/strnlen_s_test.cpp
@@ -0,0 +1,57 @@
+//===-- Unittests for strnlen_s -------------------------------------------===//
+//
+// 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/string/strnlen_s.h"
+#include "test/UnitTest/Test.h"
+#include <stddef.h>
+
+TEST(LlvmLibcStrNLenSTest, NullPointerInput) {
+ const char *str = nullptr;
+ // If the string input is a null pointer, it should return 0 regardless of
+ // the max len arg value.
+ ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(str, 0));
+ ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(str, 1));
+}
+
+// The semantics when the string input is not null are the same as strnlen. The
+// following tests are copied from the latter's tests.
+
+TEST(LlvmLibcStrNLenSTest, EmptyString) {
+ const char *empty = "";
+ ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(empty, 0));
+ // If N is greater than string length, this should still return 0.
+ ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(empty, 1));
+}
+
+TEST(LlvmLibcStrNLenSTest, OneCharacterString) {
+ const char *single = "X";
+ ASSERT_EQ(static_cast<size_t>(1), LIBC_NAMESPACE::strnlen_s(single, 1));
+ // If N is zero, this should return 0.
+ ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(single, 0));
+ // If N is greater than string length, this should still return 1.
+ ASSERT_EQ(static_cast<size_t>(1), LIBC_NAMESPACE::strnlen_s(single, 2));
+}
+
+TEST(LlvmLibcStrNLenSTest, ManyCharacterString) {
+ const char *many = "123456789";
+ ASSERT_EQ(static_cast<size_t>(9), LIBC_NAMESPACE::strnlen_s(many, 9));
+ // If N is smaller than the string length, it should return N.
+ ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(many, 3));
+ // If N is zero, this should return 0.
+ ASSERT_EQ(static_cast<size_t>(0), LIBC_NAMESPACE::strnlen_s(many, 0));
+ // If N is greater than the string length, this should still return 9.
+ ASSERT_EQ(static_cast<size_t>(9), LIBC_NAMESPACE::strnlen_s(many, 42));
+}
+
+TEST(LlvmLibcStrNLenSTest, CharactersAfterNullTerminatorShouldNotBeIncluded) {
+ const char str[5] = {'a', 'b', 'c', '\0', 'd'};
+ ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(str, 3));
+ // This should only read up to the null terminator.
+ ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(str, 4));
+ ASSERT_EQ(static_cast<size_t>(3), LIBC_NAMESPACE::strnlen_s(str, 5));
+}
diff --git a/libc/utils/docgen/string.yaml b/libc/utils/docgen/string.yaml
index d703a8e3593e1..94a6756bed8c0 100644
--- a/libc/utils/docgen/string.yaml
+++ b/libc/utils/docgen/string.yaml
@@ -66,6 +66,9 @@ functions:
strndup:
c-definition: 7.26.2.7
in-latest-posix: ''
+ strnlen_s:
+ c-definition: K.3.7.4.4
+ in-latest-posix: ''
strpbrk:
c-definition: 7.26.5.5
in-latest-posix: ''
``````````
</details>
https://github.com/llvm/llvm-project/pull/186112
More information about the libc-commits
mailing list