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 @@ -20,6 +20,7 @@ libc.src.string.strcpy libc.src.string.strlen libc.src.string.strnlen + libc.src.string.strrchr libc.src.string.strstr ) 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 @@ -38,6 +38,7 @@ libc.src.string.strcpy libc.src.string.strlen libc.src.string.strnlen + libc.src.string.strrchr libc.src.string.strstr # sys/mman.h entrypoints 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 @@ -86,6 +86,14 @@ memrchr.h ) +add_entrypoint_object( + strrchr + SRCS + strrchr.cpp + HDRS + strrchr.h +) + # 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/strrchr.h b/libc/src/string/strrchr.h new file mode 100644 --- /dev/null +++ b/libc/src/string/strrchr.h @@ -0,0 +1,18 @@ +//===-- Implementation header for strrchr -----------------------*- 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_STRRCHR_H +#define LLVM_LIBC_SRC_STRING_STRRCHR_H + +namespace __llvm_libc { + +char *strrchr(const char *src, int c); + +} // namespace __llvm_libc + +#endif // LLVM_LIBC_SRC_STRING_STRRCHR_H diff --git a/libc/src/string/strrchr.cpp b/libc/src/string/strrchr.cpp new file mode 100644 --- /dev/null +++ b/libc/src/string/strrchr.cpp @@ -0,0 +1,28 @@ +//===-- Implementation of strrchr------------------------------------------===// +// +// 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/strrchr.h" + +#include "src/__support/common.h" + +namespace __llvm_libc { + +char *LLVM_LIBC_ENTRYPOINT(strrchr)(const char *src, int c) { + unsigned char *str = + const_cast(reinterpret_cast(src)); + const unsigned char ch = c; + + unsigned char *last_occurrence = nullptr; + do { + if (*str == ch) + last_occurrence = str; + } while (*str++); + return reinterpret_cast(last_occurrence); +} + +} // 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 @@ -92,6 +92,16 @@ libc.src.string.memrchr ) +add_libc_unittest( + strrchr_test + SUITE + libc_string_unittests + SRCS + strrchr_test.cpp + DEPENDS + libc.src.string.strrchr +) + # 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/strrchr_test.cpp b/libc/test/src/string/strrchr_test.cpp new file mode 100644 --- /dev/null +++ b/libc/test/src/string/strrchr_test.cpp @@ -0,0 +1,81 @@ +//===-- Unittests for strrchr ---------------------------------------------===// +// +// 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/strrchr.h" +#include "utils/UnitTest/Test.h" + +TEST(StrRChrTest, FindsFirstCharacter) { + const char *src = "abcde"; + const char *src_copy = src; + + // Should return original string since 'a' is the first character. + ASSERT_STREQ(__llvm_libc::strrchr(src, 'a'), "abcde"); + // Source string should not change. + ASSERT_STREQ(src, src_copy); +} + +TEST(StrRChrTest, FindsMiddleCharacter) { + const char *src = "abcde"; + const char *src_copy = src; + + // Should return characters after (and including) 'c'. + ASSERT_STREQ(__llvm_libc::strrchr(src, 'c'), "cde"); + // Source string should not change. + ASSERT_STREQ(src, src_copy); +} + +TEST(StrRChrTest, FindsLastCharacterThatIsNotNullTerminator) { + const char *src = "abcde"; + const char *src_copy = src; + + // Should return 'e' and null-terminator. + ASSERT_STREQ(__llvm_libc::strrchr(src, 'e'), "e"); + // Source string should not change. + ASSERT_STREQ(src, src_copy); +} + +TEST(StrRChrTest, FindsNullTerminator) { + const char *src = "abcde"; + const char *src_copy = src; + + // Should return null terminator. + ASSERT_STREQ(__llvm_libc::strrchr(src, '\0'), ""); + // Source string should not change. + ASSERT_STREQ(src, src_copy); +} + +TEST(StrRChrTest, FindsLastNullTerminator) { + const char src[5] = {'a', '\0', 'b', '\0', 'c'}; + // 'b' is behind a null terminator, so should not be found. + ASSERT_STREQ(__llvm_libc::strrchr(src, 'b'), nullptr); + // Same goes for 'c'. + ASSERT_STREQ(__llvm_libc::strrchr(src, 'c'), nullptr); +} + +TEST(StrRChrTest, CharacterNotWithinStringShouldReturnNullptr) { + // Since 'z' is not within the string, should return nullptr. + ASSERT_STREQ(__llvm_libc::strrchr("123?", 'z'), nullptr); +} + +TEST(StrRChrTest, ShouldFindLastOfDuplicates) { + // '1' is duplicated in the string, but it should find the last copy. + ASSERT_STREQ(__llvm_libc::strrchr("abc1def1ghi", '1'), "1ghi"); + + const char *dups = "XXXXX"; + // Should return the last occurrence of 'X'. + ASSERT_STREQ(__llvm_libc::strrchr(dups, 'X'), "X"); +} + +TEST(StrRChrTest, EmptyStringShouldOnlyMatchNullTerminator) { + // Null terminator should match. + ASSERT_STREQ(__llvm_libc::strrchr("", '\0'), ""); + // All other characters should not match. + ASSERT_STREQ(__llvm_libc::strrchr("", 'A'), nullptr); + ASSERT_STREQ(__llvm_libc::strrchr("", '2'), nullptr); + ASSERT_STREQ(__llvm_libc::strrchr("", '*'), nullptr); +}