diff --git a/libc/config/baremetal/aarch64/entrypoints.txt b/libc/config/baremetal/aarch64/entrypoints.txt index 82910c320b48..452abd985b3a 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 4cf34764916f..9c0e71b2beec 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/baremetal/riscv/entrypoints.txt b/libc/config/baremetal/riscv/entrypoints.txt index c4a11c6f8733..5c88bc4ae1fb 100644 --- a/libc/config/baremetal/riscv/entrypoints.txt +++ b/libc/config/baremetal/riscv/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 0888f4b0d922..c93ba47a6f2d 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/darwin/x86_64/entrypoints.txt b/libc/config/darwin/x86_64/entrypoints.txt index 046d1b409742..b941c968255e 100644 --- a/libc/config/darwin/x86_64/entrypoints.txt +++ b/libc/config/darwin/x86_64/entrypoints.txt @@ -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 diff --git a/libc/config/gpu/amdgpu/entrypoints.txt b/libc/config/gpu/amdgpu/entrypoints.txt index 40566029c8a4..c76900a8371f 100644 --- a/libc/config/gpu/amdgpu/entrypoints.txt +++ b/libc/config/gpu/amdgpu/entrypoints.txt @@ -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 diff --git a/libc/config/gpu/nvptx/entrypoints.txt b/libc/config/gpu/nvptx/entrypoints.txt index 22f2bc849523..6538732f785f 100644 --- a/libc/config/gpu/nvptx/entrypoints.txt +++ b/libc/config/gpu/nvptx/entrypoints.txt @@ -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 diff --git a/libc/config/gpu/spirv/entrypoints.txt b/libc/config/gpu/spirv/entrypoints.txt index 5c3c2c7e4303..11edc358339b 100644 --- a/libc/config/gpu/spirv/entrypoints.txt +++ b/libc/config/gpu/spirv/entrypoints.txt @@ -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 diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index 72836d031c0e..c0d2f6c39f9f 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 31dcb31c67c7..167d762f36cb 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/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt index 2038b776e539..fe393241ce8d 100644 --- a/libc/config/linux/riscv/entrypoints.txt +++ b/libc/config/linux/riscv/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/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index b43ec51d1ece..184aa57016ce 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -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 diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt index e8080bc97c59..c6db359933f0 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 0918e92552ea..31dd1f29a1f5 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_differential_fuzz + SRCS + strnlen_s_differential_fuzz.cpp + DEPENDS + libc.src.string.strnlen_s +) diff --git a/libc/fuzzing/string/strnlen_s_differential_fuzz.cpp b/libc/fuzzing/string/strnlen_s_differential_fuzz.cpp new file mode 100644 index 000000000000..218349a9a42f --- /dev/null +++ b/libc/fuzzing/string/strnlen_s_differential_fuzz.cpp @@ -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 +#include + +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(data), max_len); +#else + size_t ref = ::strnlen(reinterpret_cast(data), max_len); +#endif + size_t impl = + LIBC_NAMESPACE::strnlen_s(reinterpret_cast(data), max_len); + + if (ref != impl) + __builtin_trap(); + + return 0; +} diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt index 41d541f47529..07a39c045178 100644 --- a/libc/include/CMakeLists.txt +++ b/libc/include/CMakeLists.txt @@ -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 diff --git a/libc/include/string.yaml b/libc/include/string.yaml index ec9678f8ab47..7711b0d148a9 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: 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 diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt index a640e1d2cc77..e28fe7af8cf2 100644 --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -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 diff --git a/libc/src/string/string_utils.h b/libc/src/string/string_utils.h index b0144e01a900..8c8879b4886e 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(s), '\0', max_len); + return temp ? reinterpret_cast(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 26fcd5a04c0d..6bfbb86a5014 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(src), '\0', n); - return temp ? reinterpret_cast(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 000000000000..dab0dec77efd --- /dev/null +++ b/libc/src/string/strnlen_s.cpp @@ -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 diff --git a/libc/src/string/strnlen_s.h b/libc/src/string/strnlen_s.h new file mode 100644 index 000000000000..2e42d37f4941 --- /dev/null +++ b/libc/src/string/strnlen_s.h @@ -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 diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt index ced60750a45c..17927ea93ed1 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 000000000000..89662f1a8738 --- /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 + +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(0), LIBC_NAMESPACE::strnlen_s(empty, 0)); + // If N is greater than string length, this should still return 0. + ASSERT_EQ(static_cast(0), LIBC_NAMESPACE::strnlen_s(empty, 1)); +} + +TEST(LlvmLibcStrNLenSTest, OneCharacterString) { + const char *single = "X"; + ASSERT_EQ(static_cast(1), LIBC_NAMESPACE::strnlen_s(single, 1)); + // If N is zero, this should return 0. + ASSERT_EQ(static_cast(0), LIBC_NAMESPACE::strnlen_s(single, 0)); + // If N is greater than string length, this should still return 1. + ASSERT_EQ(static_cast(1), LIBC_NAMESPACE::strnlen_s(single, 2)); +} + +TEST(LlvmLibcStrNLenSTest, ManyCharacterString) { + const char *many = "123456789"; + ASSERT_EQ(static_cast(9), LIBC_NAMESPACE::strnlen_s(many, 9)); + // If N is smaller than the string length, it should return N. + ASSERT_EQ(static_cast(3), LIBC_NAMESPACE::strnlen_s(many, 3)); + // If N is zero, this should return 0. + ASSERT_EQ(static_cast(0), LIBC_NAMESPACE::strnlen_s(many, 0)); + // If N is greater than the string length, this should still return 9. + ASSERT_EQ(static_cast(9), LIBC_NAMESPACE::strnlen_s(many, 42)); +} + +TEST(LlvmLibcStrNLenSTest, CharactersAfterNullTerminatorShouldNotBeIncluded) { + const char str[5] = {'a', 'b', 'c', '\0', 'd'}; + ASSERT_EQ(static_cast(3), LIBC_NAMESPACE::strnlen_s(str, 3)); + // This should only read up to the null terminator. + ASSERT_EQ(static_cast(3), LIBC_NAMESPACE::strnlen_s(str, 4)); + ASSERT_EQ(static_cast(3), LIBC_NAMESPACE::strnlen_s(str, 5)); +} diff --git a/libc/utils/docgen/string.yaml b/libc/utils/docgen/string.yaml index d703a8e3593e..94a6756bed8c 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: ''