[libc] Add Annex K strnlen_s function (#186112)

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.
This commit is contained in:
Victor Campos
2026-04-13 13:10:27 +01:00
committed by GitHub
parent dd0c5ebe69
commit e455e6c9ec
25 changed files with 223 additions and 3 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -45,6 +45,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

View File

@@ -65,6 +65,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

View File

@@ -65,6 +65,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

View File

@@ -62,6 +62,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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -81,6 +81,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

View File

@@ -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

View File

@@ -48,3 +48,11 @@ add_libc_fuzzer(
DEPENDS
libc.src.string.strlen
)
add_libc_fuzzer(
strnlen_s_differential_fuzz
SRCS
strnlen_s_differential_fuzz.cpp
DEPENDS
libc.src.string.strnlen_s
)

View File

@@ -0,0 +1,59 @@
//===-- strnlen_s_differential_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
//
//===----------------------------------------------------------------------===//
///
/// Differential fuzz test for llvm-libc strnlen_s implementation.
///
//===----------------------------------------------------------------------===//
#define __STDC_WANT_LIB_EXT1__ 1
#include "src/string/strnlen_s.h"
#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;
}

View File

@@ -265,6 +265,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.errno_t
.llvm-libc-types.rsize_t

View File

@@ -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: errno_t
- type_name: locale_t
@@ -251,6 +253,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

View File

@@ -302,6 +302,19 @@ 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
libc.src.__support.common
libc.src.__support.macros.config
)
add_entrypoint_object(
strpbrk
SRCS

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,21 @@
//===-- Implementation of 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 "hdr/types/size_t.h"
#include "src/__support/common.h"
#include "src/__support/macros/config.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

View File

@@ -0,0 +1,21 @@
//===-- 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

View File

@@ -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

View File

@@ -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(LIBC_NAMESPACE::strnlen_s(str, 0), size_t(0));
ASSERT_EQ(LIBC_NAMESPACE::strnlen_s(str, 1), size_t(0));
}
// 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));
}

View File

@@ -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: ''