diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt index cd805d7b8653..4a0aa28c9d2b 100644 --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -12,6 +12,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memchr libc.src.string.strchr libc.src.string.strstr + libc.src.string.strnlen ) set(TARGET_LIBM_ENTRYPOINTS diff --git a/libc/config/linux/api.td b/libc/config/linux/api.td index 22e97b891a75..7fc199eabc6b 100644 --- a/libc/config/linux/api.td +++ b/libc/config/linux/api.td @@ -213,6 +213,7 @@ def StringAPI : PublicAPI<"string.h"> { "strtok", "strerror", "strlen", + "strnlen" ]; let TypeDeclarations = [ diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index e8a1adbb278e..04acfb31da04 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -30,6 +30,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.string.memchr libc.src.string.strchr libc.src.string.strstr + libc.src.string.strnlen # sys/mman.h entrypoints libc.src.sys.mman.mmap diff --git a/libc/spec/posix.td b/libc/spec/posix.td index 7085e81a6b80..732b6a6be250 100644 --- a/libc/spec/posix.td +++ b/libc/spec/posix.td @@ -11,6 +11,8 @@ def RestrictStructSigactionPtr : RestrictedPtrType; def ConstRestrictStructSigactionPtr : ConstType; def POSIX : StandardSpec<"POSIX"> { + PtrType CharPtr = PtrType; + ConstType ConstCharPtr = ConstType; NamedType OffTType = NamedType<"off_t">; NamedType SSizeTType = NamedType<"ssize_t">; @@ -203,11 +205,30 @@ def POSIX : StandardSpec<"POSIX"> { >, ] >; + + HeaderSpec String = HeaderSpec< + "string.h", + [ + Macro<"NULL">, + ], + [ + SizeTType, + ], + [], // Enumerations + [ + FunctionSpec< + "strnlen", + RetValSpec, + [ArgSpec, ArgSpec] + >, + ] + >; let Headers = [ Errno, SysMMan, Signal, UniStd, + String ]; } diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt index 98693ccc0598..8bd7c1c045cf 100644 --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -68,6 +68,16 @@ add_entrypoint_object( strstr.h ) +add_entrypoint_object( + strnlen + SRCS + strnlen.cpp + HDRS + strnlen.h + DEPENDS + .memchr +) + # Helper to define a function with multiple implementations # - Computes flags to satisfy required/rejected features and arch, # - Declares an entry point, diff --git a/libc/src/string/strnlen.cpp b/libc/src/string/strnlen.cpp new file mode 100644 index 000000000000..17dd6e171504 --- /dev/null +++ b/libc/src/string/strnlen.cpp @@ -0,0 +1,23 @@ +//===-- Implementation of strnlen------------------------------------------===// +// +// 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.h" + +#include "src/__support/common.h" +#include "src/string/memchr.h" +#include + +namespace __llvm_libc { + +size_t LLVM_LIBC_ENTRYPOINT(strnlen)(const char *src, size_t n) { + const char *temp = + reinterpret_cast(__llvm_libc::memchr(src, '\0', n)); + return temp ? temp - src : n; +} + +} // namespace __llvm_libc diff --git a/libc/src/string/strnlen.h b/libc/src/string/strnlen.h new file mode 100644 index 000000000000..2d2ee9703d83 --- /dev/null +++ b/libc/src/string/strnlen.h @@ -0,0 +1,20 @@ +//===-- Implementation header for strnlen ------------------------*- 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_H +#define LLVM_LIBC_SRC_STRING_STRNLEN_H + +#include + +namespace __llvm_libc { + +size_t strnlen(const char *src, size_t n); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_STRNLEN_H diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt index 81ae08d7249a..be43cc912b5a 100644 --- a/libc/test/src/string/CMakeLists.txt +++ b/libc/test/src/string/CMakeLists.txt @@ -72,6 +72,16 @@ add_libc_unittest( libc.src.string.strstr ) +add_libc_unittest( + strnlen_test + SUITE + libc_string_unittests + SRCS + strnlen_test.cpp + DEPENDS + libc.src.string.strnlen +) + # Tests all implementations that can run on the host. function(add_libc_multi_impl_test name) get_property(fq_implementations GLOBAL PROPERTY ${name}_implementations) diff --git a/libc/test/src/string/strnlen_test.cpp b/libc/test/src/string/strnlen_test.cpp new file mode 100644 index 000000000000..9d8616bc8bd7 --- /dev/null +++ b/libc/test/src/string/strnlen_test.cpp @@ -0,0 +1,46 @@ +//===-- Unittests for strnlen----------------------------------------------===// +// +// 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.h" +#include "utils/UnitTest/Test.h" +#include + +TEST(StrNLenTest, EmptyString) { + const char *empty = ""; + ASSERT_EQ(static_cast(0), __llvm_libc::strnlen(empty, 0)); + // If N is greater than string length, this should still return 0. + ASSERT_EQ(static_cast(0), __llvm_libc::strnlen(empty, 1)); +} + +TEST(StrNLenTest, OneCharacterString) { + const char *single = "X"; + ASSERT_EQ(static_cast(1), __llvm_libc::strnlen(single, 1)); + // If N is zero, this should return 0. + ASSERT_EQ(static_cast(0), __llvm_libc::strnlen(single, 0)); + // If N is greater than string length, this should still return 1. + ASSERT_EQ(static_cast(1), __llvm_libc::strnlen(single, 2)); +} + +TEST(StrNLenTest, ManyCharacterString) { + const char *many = "123456789"; + ASSERT_EQ(static_cast(9), __llvm_libc::strnlen(many, 9)); + // If N is smaller than the string length, it should return N. + ASSERT_EQ(static_cast(3), __llvm_libc::strnlen(many, 3)); + // If N is zero, this should return 0. + ASSERT_EQ(static_cast(0), __llvm_libc::strnlen(many, 0)); + // If N is greater than the string length, this should still return 9. + ASSERT_EQ(static_cast(9), __llvm_libc::strnlen(many, 42)); +} + +TEST(StrNLenTest, CharactersAfterNullTerminatorShouldNotBeIncluded) { + const char str[5] = {'a', 'b', 'c', '\0', 'd'}; + ASSERT_EQ(static_cast(3), __llvm_libc::strnlen(str, 3)); + // This should only read up to the null terminator. + ASSERT_EQ(static_cast(3), __llvm_libc::strnlen(str, 4)); + ASSERT_EQ(static_cast(3), __llvm_libc::strnlen(str, 5)); +}