Skip to content

Commit 50c4447

Browse files
authored
[libc] fix behavior of strrchr(x, '\0') (#112620)
`strrchr("foo", '\0')` is defined to point to the end of `foo`, rather than returning NULL. This wasn't caught by tests, since llvm-libc's `ASSERT_STREQ(nullptr, "");` is not an assertion error. While I'm here, refactor the test slightly to check for NULL more specifically. I considered adding fancier `ASSERT`s (and changing the semantics of `ASSERT_STREQ`), but opted for a more local fix by fair dice roll.
1 parent 5d35747 commit 50c4447

File tree

3 files changed

+34
-16
lines changed

3 files changed

+34
-16
lines changed

libc/src/string/string_utils.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,13 @@ LIBC_INLINE constexpr static char *strrchr_implementation(const char *src,
239239
int c) {
240240
char ch = static_cast<char>(c);
241241
char *last_occurrence = nullptr;
242-
for (; *src; ++src) {
242+
while (true) {
243243
if (*src == ch)
244244
last_occurrence = const_cast<char *>(src);
245+
if (!*src)
246+
return last_occurrence;
247+
++src;
245248
}
246-
return last_occurrence;
247249
}
248250

249251
} // namespace internal

libc/test/UnitTest/LibcTest.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,14 @@ class Test {
162162
(unsigned long long)RHS, LHSStr, RHSStr, Loc);
163163
}
164164

165+
// Helper to allow macro invocations like `ASSERT_EQ(foo, nullptr)`.
166+
template <typename ValType,
167+
cpp::enable_if_t<cpp::is_pointer_v<ValType>, ValType> = nullptr>
168+
bool test(TestCond Cond, ValType LHS, std::nullptr_t, const char *LHSStr,
169+
const char *RHSStr, internal::Location Loc) {
170+
return test(Cond, LHS, static_cast<ValType>(nullptr), LHSStr, RHSStr, Loc);
171+
}
172+
165173
template <
166174
typename ValType,
167175
cpp::enable_if_t<

libc/test/src/string/StrchrTest.h

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,16 @@ template <auto Func> struct StrchrTest : public LIBC_NAMESPACE::testing::Test {
4040
const char *src = "abcde";
4141

4242
// Should return null terminator.
43-
ASSERT_STREQ(Func(src, '\0'), "");
43+
const char *nul_terminator = Func(src, '\0');
44+
ASSERT_NE(nul_terminator, nullptr);
45+
ASSERT_STREQ(nul_terminator, "");
4446
// Source string should not change.
4547
ASSERT_STREQ(src, "abcde");
4648
}
4749

4850
void characterNotWithinStringShouldReturnNullptr() {
4951
// Since 'z' is not within the string, should return nullptr.
50-
ASSERT_STREQ(Func("123?", 'z'), nullptr);
52+
ASSERT_EQ(Func("123?", 'z'), nullptr);
5153
}
5254

5355
void theSourceShouldNotChange() {
@@ -74,11 +76,13 @@ template <auto Func> struct StrchrTest : public LIBC_NAMESPACE::testing::Test {
7476

7577
void emptyStringShouldOnlyMatchNullTerminator() {
7678
// Null terminator should match.
77-
ASSERT_STREQ(Func("", '\0'), "");
79+
const char empty_string[] = "";
80+
ASSERT_EQ(static_cast<const char *>(Func(empty_string, '\0')),
81+
empty_string);
7882
// All other characters should not match.
79-
ASSERT_STREQ(Func("", 'Z'), nullptr);
80-
ASSERT_STREQ(Func("", '3'), nullptr);
81-
ASSERT_STREQ(Func("", '*'), nullptr);
83+
ASSERT_EQ(Func("", 'Z'), nullptr);
84+
ASSERT_EQ(Func("", '3'), nullptr);
85+
ASSERT_EQ(Func("", '*'), nullptr);
8286
}
8387
};
8488

@@ -114,25 +118,27 @@ template <auto Func> struct StrrchrTest : public LIBC_NAMESPACE::testing::Test {
114118
const char *src = "abcde";
115119

116120
// Should return null terminator.
117-
ASSERT_STREQ(Func(src, '\0'), "");
121+
const char *nul_terminator = Func(src, '\0');
122+
ASSERT_NE(nul_terminator, nullptr);
123+
ASSERT_STREQ(nul_terminator, "");
118124
// Source string should not change.
119125
ASSERT_STREQ(src, "abcde");
120126
}
121127

122128
void findsLastBehindFirstNullTerminator() {
123129
static const char src[6] = {'a', 'a', '\0', 'b', '\0', 'c'};
124130
// 'b' is behind a null terminator, so should not be found.
125-
ASSERT_STREQ(Func(src, 'b'), nullptr);
131+
ASSERT_EQ(Func(src, 'b'), nullptr);
126132
// Same goes for 'c'.
127-
ASSERT_STREQ(Func(src, 'c'), nullptr);
133+
ASSERT_EQ(Func(src, 'c'), nullptr);
128134

129135
// Should find the second of the two a's.
130136
ASSERT_STREQ(Func(src, 'a'), "a");
131137
}
132138

133139
void characterNotWithinStringShouldReturnNullptr() {
134140
// Since 'z' is not within the string, should return nullptr.
135-
ASSERT_STREQ(Func("123?", 'z'), nullptr);
141+
ASSERT_EQ(Func("123?", 'z'), nullptr);
136142
}
137143

138144
void shouldFindLastOfDuplicates() {
@@ -146,11 +152,13 @@ template <auto Func> struct StrrchrTest : public LIBC_NAMESPACE::testing::Test {
146152

147153
void emptyStringShouldOnlyMatchNullTerminator() {
148154
// Null terminator should match.
149-
ASSERT_STREQ(Func("", '\0'), "");
155+
const char empty_string[] = "";
156+
ASSERT_EQ(static_cast<const char *>(Func(empty_string, '\0')),
157+
empty_string);
150158
// All other characters should not match.
151-
ASSERT_STREQ(Func("", 'A'), nullptr);
152-
ASSERT_STREQ(Func("", '2'), nullptr);
153-
ASSERT_STREQ(Func("", '*'), nullptr);
159+
ASSERT_EQ(Func("", 'A'), nullptr);
160+
ASSERT_EQ(Func("", '2'), nullptr);
161+
ASSERT_EQ(Func("", '*'), nullptr);
154162
}
155163
};
156164

0 commit comments

Comments
 (0)