diff --git a/libc/config/baremetal/arm/entrypoints.txt b/libc/config/baremetal/arm/entrypoints.txt --- a/libc/config/baremetal/arm/entrypoints.txt +++ b/libc/config/baremetal/arm/entrypoints.txt @@ -38,6 +38,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/config/darwin/arm/entrypoints.txt b/libc/config/darwin/arm/entrypoints.txt --- a/libc/config/darwin/arm/entrypoints.txt +++ b/libc/config/darwin/arm/entrypoints.txt @@ -38,6 +38,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/config/darwin/x86_64/entrypoints.txt b/libc/config/darwin/x86_64/entrypoints.txt --- a/libc/config/darwin/x86_64/entrypoints.txt +++ b/libc/config/darwin/x86_64/entrypoints.txt @@ -32,6 +32,7 @@ libc.src.string.stpncpy libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/config/gpu/entrypoints.txt b/libc/config/gpu/entrypoints.txt --- a/libc/config/gpu/entrypoints.txt +++ b/libc/config/gpu/entrypoints.txt @@ -34,6 +34,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt --- a/libc/config/linux/aarch64/entrypoints.txt +++ b/libc/config/linux/aarch64/entrypoints.txt @@ -47,6 +47,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/config/linux/arm/entrypoints.txt b/libc/config/linux/arm/entrypoints.txt --- a/libc/config/linux/arm/entrypoints.txt +++ b/libc/config/linux/arm/entrypoints.txt @@ -38,6 +38,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/config/linux/riscv64/entrypoints.txt b/libc/config/linux/riscv64/entrypoints.txt --- a/libc/config/linux/riscv64/entrypoints.txt +++ b/libc/config/linux/riscv64/entrypoints.txt @@ -47,6 +47,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcoll libc.src.string.strcpy diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -47,6 +47,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcoll libc.src.string.strcpy diff --git a/libc/config/windows/entrypoints.txt b/libc/config/windows/entrypoints.txt --- a/libc/config/windows/entrypoints.txt +++ b/libc/config/windows/entrypoints.txt @@ -35,6 +35,7 @@ libc.src.string.strcasestr libc.src.string.strcat libc.src.string.strchr + libc.src.string.strchrnul libc.src.string.strcmp libc.src.string.strcpy libc.src.string.strcspn diff --git a/libc/spec/gnu_ext.td b/libc/spec/gnu_ext.td --- a/libc/spec/gnu_ext.td +++ b/libc/spec/gnu_ext.td @@ -73,6 +73,11 @@ RetValSpec, [ArgSpec, ArgSpec] >, + FunctionSpec< + "strchrnul", + RetValSpec, + [ArgSpec, ArgSpec] + >, ] >; diff --git a/libc/src/string/CMakeLists.txt b/libc/src/string/CMakeLists.txt --- a/libc/src/string/CMakeLists.txt +++ b/libc/src/string/CMakeLists.txt @@ -107,6 +107,16 @@ strchr.h ) +add_entrypoint_object( + strchrnul + SRCS + strchrnul.cpp + HDRS + strchrnul.h + DEPENDS + .string_utils +) + add_entrypoint_object( strcmp SRCS diff --git a/libc/src/string/strchrnul.h b/libc/src/string/strchrnul.h new file mode 100644 --- /dev/null +++ b/libc/src/string/strchrnul.h @@ -0,0 +1,18 @@ +//===-- Implementation header for strchrnul --------------------*- 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_STRCHRNUL_H +#define LLVM_LIBC_SRC_STRING_STRCHRNUL_H + +namespace __llvm_libc { + +char *strchrnul(const char *src, int c); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_STRCHRNUL_H diff --git a/libc/src/string/strchrnul.cpp b/libc/src/string/strchrnul.cpp new file mode 100644 --- /dev/null +++ b/libc/src/string/strchrnul.cpp @@ -0,0 +1,23 @@ +//===-- Implementation of strchrnul --------------------------------------===// +// +// 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/strchrnul.h" +#include "src/string/string_utils.h" + +#include "src/__support/common.h" + +namespace __llvm_libc { + +LLVM_LIBC_FUNCTION(char *, strchrnul, (const char *src, int c)) { + const char ch = static_cast(c); + for (; *src && *src != ch; ++src) + ; + return const_cast(src); +} + +} // namespace __llvm_libc diff --git a/libc/test/src/string/CMakeLists.txt b/libc/test/src/string/CMakeLists.txt --- a/libc/test/src/string/CMakeLists.txt +++ b/libc/test/src/string/CMakeLists.txt @@ -94,6 +94,16 @@ libc.src.string.strchr ) +add_libc_unittest( + strchrnul_test + SUITE + libc_string_unittests + SRCS + strchrnul_test.cpp + DEPENDS + libc.src.string.strchrnul +) + add_libc_unittest( strcmp_test SUITE diff --git a/libc/test/src/string/strchr_test.cpp b/libc/test/src/string/strchr_test.cpp --- a/libc/test/src/string/strchr_test.cpp +++ b/libc/test/src/string/strchr_test.cpp @@ -58,7 +58,7 @@ // Same case for when the character is not found. __llvm_libc::strchr(src, 'z'); ASSERT_STREQ(src, "abcde"); - // Same case for when looking for nullptr. + // Same case for when looking for null terminator. __llvm_libc::strchr(src, '\0'); ASSERT_STREQ(src, "abcde"); } diff --git a/libc/test/src/string/strchrnul_test.cpp b/libc/test/src/string/strchrnul_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/string/strchrnul_test.cpp @@ -0,0 +1,96 @@ +//===-- Unittests for strchrnul -------------------------------------------===// +// +// 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/strchrnul.h" +#include "test/UnitTest/Test.h" + +TEST(LlvmLibcStrChrNulTest, FindsFirstCharacter) { + const char *src = "abcde"; + + // Should return original string since 'a' is the first character. + ASSERT_STREQ(__llvm_libc::strchrnul(src, 'a'), "abcde"); + // Source string should not change. + ASSERT_STREQ(src, "abcde"); +} + +TEST(LlvmLibcStrChrNulTest, FindsMiddleCharacter) { + const char *src = "abcde"; + + // Should return characters after (and including) 'c'. + ASSERT_STREQ(__llvm_libc::strchrnul(src, 'c'), "cde"); + // Source string should not change. + ASSERT_STREQ(src, "abcde"); +} + +TEST(LlvmLibcStrChrNulTest, FindsLastCharacterThatIsNotNullTerminator) { + const char *src = "abcde"; + + // Should return 'e' and null-terminator. + ASSERT_STREQ(__llvm_libc::strchrnul(src, 'e'), "e"); + // Source string should not change. + ASSERT_STREQ(src, "abcde"); +} + +TEST(LlvmLibcStrChrNulTest, FindsNullTerminator) { + const char *src = "abcde"; + + // Should return null terminator. + ASSERT_STREQ(__llvm_libc::strchrnul(src, '\0'), ""); + // Source string should not change. + ASSERT_STREQ(src, "abcde"); +} + +TEST(LlvmLibcStrChrNulTest, + CharacterNotWithinStringShouldReturnNullTerminator) { + const char *src = "123?"; + + // Since 'z' is not within the string, should return a pointer to the source + // string's null terminator. + char *result = __llvm_libc::strchrnul(src, 'z'); + ASSERT_EQ(*result, '\0'); + + char *term = const_cast(src) + 4; + ASSERT_EQ(result, term); +} + +TEST(LlvmLibcStrChrNulTest, TheSourceShouldNotChange) { + const char *src = "abcde"; + // When the character is found, the source string should not change. + __llvm_libc::strchrnul(src, 'd'); + ASSERT_STREQ(src, "abcde"); + // Same case for when the character is not found. + __llvm_libc::strchrnul(src, 'z'); + ASSERT_STREQ(src, "abcde"); + // Same case for when looking for null terminator. + __llvm_libc::strchrnul(src, '\0'); + ASSERT_STREQ(src, "abcde"); +} + +TEST(LlvmLibcStrChrNulTest, ShouldFindFirstOfDuplicates) { + // '1' is duplicated in the string, but it should find the first copy. + ASSERT_STREQ(__llvm_libc::strchrnul("abc1def1ghi", '1'), "1def1ghi"); + + const char *dups = "XXXXX"; + // Should return original string since 'X' is the first character. + ASSERT_STREQ(__llvm_libc::strchrnul(dups, 'X'), dups); +} + +TEST(LlvmLibcStrChrNulTest, EmptyStringShouldOnlyMatchNullTerminator) { + // Null terminator should match. + ASSERT_STREQ(__llvm_libc::strchrnul("", '\0'), ""); + + // All other characters should not match. + char *result = __llvm_libc::strchrnul("", 'Z'); + ASSERT_EQ(*result, '\0'); + + result = __llvm_libc::strchrnul("", '3'); + ASSERT_EQ(*result, '\0'); + + result = __llvm_libc::strchrnul("", '*'); + ASSERT_EQ(*result, '\0'); +}