diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt index 545b9227349fe..e6cbfe0257a3e 100644 --- a/libc/config/linux/x86_64/entrypoints.txt +++ b/libc/config/linux/x86_64/entrypoints.txt @@ -367,6 +367,7 @@ set(TARGET_LIBC_ENTRYPOINTS libc.src.wchar.wmemset libc.src.wchar.wcschr libc.src.wchar.wcspbrk + libc.src.wchar.wcsrchr libc.src.wchar.wcsspn libc.src.wchar.wmemcmp libc.src.wchar.wmemcpy diff --git a/libc/include/wchar.yaml b/libc/include/wchar.yaml index bfd9a10342019..39732feb62fcd 100644 --- a/libc/include/wchar.yaml +++ b/libc/include/wchar.yaml @@ -49,6 +49,13 @@ functions: arguments: - type: const wchar_t * - type: const wchar_t * + - name: wcsrchr + standards: + - stdc + return_type: const wchar_t * + arguments: + - type: const wchar_t * + - type: wchar_t - name: wcsspn standards: - stdc diff --git a/libc/src/wchar/CMakeLists.txt b/libc/src/wchar/CMakeLists.txt index 9db121762348b..ebce3a7b7f911 100644 --- a/libc/src/wchar/CMakeLists.txt +++ b/libc/src/wchar/CMakeLists.txt @@ -68,6 +68,16 @@ add_entrypoint_object( libc.src.__support.wctype_utils ) +add_entrypoint_object( + wcsrchr + SRCS + wcsrchr.cpp + HDRS + wcsrchr.h + DEPENDS + libc.hdr.wchar_macros +) + add_entrypoint_object( wcsspn SRCS diff --git a/libc/src/wchar/wcsrchr.cpp b/libc/src/wchar/wcsrchr.cpp new file mode 100644 index 0000000000000..bb4e373bb1ce1 --- /dev/null +++ b/libc/src/wchar/wcsrchr.cpp @@ -0,0 +1,31 @@ +//===-- Implementation of wcsrchr -----------------------------------------===// +// +// 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/wchar/wcsrchr.h" + +#include "hdr/types/wchar_t.h" +#include "src/__support/common.h" +#include "src/__support/macros/config.h" +#include "src/__support/macros/null_check.h" + +namespace LIBC_NAMESPACE_DECL { + +LLVM_LIBC_FUNCTION(const wchar_t *, wcsrchr, (const wchar_t *s, wchar_t c)) { + LIBC_CRASH_ON_NULLPTR(s); + + const wchar_t *last_occurrence = nullptr; + while (true) { + if (*s == c) + last_occurrence = s; + if (*s == L'\0') + return last_occurrence; + ++s; + } +} + +} // namespace LIBC_NAMESPACE_DECL diff --git a/libc/src/wchar/wcsrchr.h b/libc/src/wchar/wcsrchr.h new file mode 100644 index 0000000000000..5e9d8c3c2db92 --- /dev/null +++ b/libc/src/wchar/wcsrchr.h @@ -0,0 +1,21 @@ +//===-- Implementation header for wcsrchr ---------------------------------===// +// +// 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_WCHAR_WCSRCHR_H +#define LLVM_LIBC_SRC_WCHAR_WCSRCHR_H + +#include "hdr/types/wchar_t.h" +#include "src/__support/macros/config.h" + +namespace LIBC_NAMESPACE_DECL { + +const wchar_t *wcsrchr(const wchar_t *s, wchar_t c); + +} // namespace LIBC_NAMESPACE_DECL + +#endif // LLVM_LIBC_SRC_WCHAR_WCSRCHR_H diff --git a/libc/test/src/wchar/CMakeLists.txt b/libc/test/src/wchar/CMakeLists.txt index 9bc230e0bddf3..6a212a620f853 100644 --- a/libc/test/src/wchar/CMakeLists.txt +++ b/libc/test/src/wchar/CMakeLists.txt @@ -65,6 +65,16 @@ add_libc_test( libc.src.wchar.wcspbrk ) +add_libc_test( + wcsrchr_test + SUITE + libc_wchar_unittests + SRCS + wcsrchr_test.cpp + DEPENDS + libc.src.wchar.wcsrchr +) + add_libc_test( wcsspn_test SUITE diff --git a/libc/test/src/wchar/wcsrchr_test.cpp b/libc/test/src/wchar/wcsrchr_test.cpp new file mode 100644 index 0000000000000..707dfb6756ee9 --- /dev/null +++ b/libc/test/src/wchar/wcsrchr_test.cpp @@ -0,0 +1,68 @@ +//===-- Unittests for wcsrchr ---------------------------------------------===// +// +// 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 "hdr/types/wchar_t.h" +#include "src/wchar/wcsrchr.h" +#include "test/UnitTest/Test.h" + +TEST(LlvmLibcWCSRChrTest, FindsFirstCharacter) { + // Should return pointer to original string since 'a' is the first character. + const wchar_t *src = L"abcde"; + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'a'), src); +} + +TEST(LlvmLibcWCSRChrTest, FindsMiddleCharacter) { + // Should return pointer to 'c'. + const wchar_t *src = L"abcde"; + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'c'), (src + 2)); +} + +TEST(LlvmLibcWCSRChrTest, FindsLastCharacterThatIsNotNullTerminator) { + // Should return pointer to 'e'. + const wchar_t *src = L"abcde"; + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'e'), (src + 4)); +} + +TEST(LlvmLibcWCSRChrTest, FindsNullTerminator) { + // Should return pointer to null terminator. + const wchar_t *src = L"abcde"; + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'\0'), (src + 5)); +} + +TEST(LlvmLibcWCSRChrTest, CharacterNotWithinStringShouldReturnNullptr) { + // Since 'z' is not within the string, should return nullptr. + const wchar_t *src = L"abcde"; + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'z'), nullptr); +} + +TEST(LlvmLibcWCSRChrTest, ShouldFindLastOfDuplicates) { + // Should return pointer to the last '1'. + const wchar_t *src = L"abc1def1ghi"; + ASSERT_EQ((int)(LIBC_NAMESPACE::wcsrchr(src, L'1') - src), 7); + + // Should return pointer to the last 'X' + const wchar_t *dups = L"XXXXX"; + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(dups, L'X'), dups + 4); +} + +TEST(LlvmLibcWCSRChrTest, EmptyStringShouldOnlyMatchNullTerminator) { + // Null terminator should match + const wchar_t *src = L""; + ASSERT_EQ(src, LIBC_NAMESPACE::wcsrchr(src, L'\0')); + // All other characters should not match + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'Z'), nullptr); + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'3'), nullptr); + ASSERT_EQ(LIBC_NAMESPACE::wcsrchr(src, L'*'), nullptr); +} + +#if defined(LIBC_ADD_NULL_CHECKS) && !defined(LIBC_HAS_SANITIZER) +TEST(LlvmLibcWCSRChrTest, NullptrCrash) { + // Passing in a nullptr should crash the program. + EXPECT_DEATH([] { LIBC_NAMESPACE::wcsrchr(nullptr, L'a'); }, WITH_SIGNAL(-1)); +} +#endif // LIBC_HAS_ADDRESS_SANITIZER