Skip to content

Commit 5780d94

Browse files
authored
Merge pull request managarm#1836 from Dennisbonke/tests
options/ansi: strengthen qsort and string function coverage
2 parents e88aef2 + b733179 commit 5780d94

6 files changed

Lines changed: 384 additions & 41 deletions

File tree

options/ansi/generic/stdlib.cpp

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -344,24 +344,12 @@ void *bsearch(const void *key, const void *base, size_t count, size_t size,
344344
return nullptr;
345345
}
346346

347-
static int qsort_callback(const void *a, const void *b, void *arg) {
348-
auto compare = reinterpret_cast<int (*)(const void *, const void *)>(arg);
349-
350-
return compare(a, b);
351-
}
352-
353-
void qsort(void *base, size_t count, size_t size,
354-
int (*compare)(const void *, const void *)) {
355-
return qsort_r(base, count, size, qsort_callback, (void *) compare);
356-
}
357-
358-
void qsort_r(void *base, size_t count, size_t size,
359-
int (*compare)(const void *, const void *, void *),
360-
void *arg) {
347+
template<typename Compare>
348+
static void qsort_impl(void *base, size_t count, size_t size, Compare compare) {
361349
auto compare_idx = [&] (size_t i, size_t j) -> int {
362350
auto *pi = reinterpret_cast<uint8_t *>(base) + i * size;
363351
auto *pj = reinterpret_cast<uint8_t *>(base) + j * size;
364-
return compare(pi, pj, arg);
352+
return compare(pi, pj);
365353
};
366354

367355
auto swap_idx = [&] (size_t i, size_t j) {
@@ -403,6 +391,21 @@ void qsort_r(void *base, size_t count, size_t size,
403391
quick_sort(0, count);
404392
}
405393

394+
void qsort(void *base, size_t count, size_t size,
395+
int (*compare)(const void *, const void *)) {
396+
qsort_impl(base, count, size, [compare] (const void *a, const void *b) {
397+
return compare(a, b);
398+
});
399+
}
400+
401+
void qsort_r(void *base, size_t count, size_t size,
402+
int (*compare)(const void *, const void *, void *),
403+
void *arg) {
404+
qsort_impl(base, count, size, [compare, arg] (const void *a, const void *b) {
405+
return compare(a, b, arg);
406+
});
407+
}
408+
406409
int abs(int num) {
407410
return num < 0 ? -num : num;
408411
}

options/ansi/generic/string.cpp

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,17 +191,17 @@ size_t strspn(const char *s, const char *chrs) {
191191
}
192192
}
193193
char *strstr(const char *s, const char *pattern) {
194-
for(size_t i = 0; s[i]; i++) {
195-
bool found = true;
196-
for(size_t j = 0; pattern[j]; j++) {
197-
if(!pattern[j] || s[i + j] == pattern[j])
198-
continue;
194+
// The empty pattern matches at the beginning of every string, including
195+
// the empty string.
196+
if(!*pattern)
197+
return const_cast<char *>(s);
199198

200-
found = false;
201-
break;
202-
}
199+
for(size_t i = 0; s[i]; i++) {
200+
size_t j = 0;
201+
while(pattern[j] && s[i + j] && s[i + j] == pattern[j])
202+
j++;
203203

204-
if(found)
204+
if(!pattern[j])
205205
return const_cast<char *>(&s[i]);
206206
}
207207

@@ -248,9 +248,10 @@ char *strtok(char *__restrict s, const char *__restrict delimiter) {
248248

249249
// This is a GNU extension.
250250
char *strchrnul(const char *s, int c) {
251+
const unsigned char target = static_cast<unsigned char>(c);
251252
size_t i = 0;
252253
while(s[i]) {
253-
if(s[i] == c)
254+
if(static_cast<unsigned char>(s[i]) == target)
254255
return const_cast<char *>(s + i);
255256
i++;
256257
}

tests/ansi/qsort.c

Lines changed: 155 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,168 @@
33
#include <stdio.h>
44
#include <string.h>
55

6-
static const char *arr[] = { "xyz", "abc", "ghi", "def" };
6+
struct record {
7+
int key;
8+
char value[12];
9+
};
710

8-
static const size_t magic = 0xDEADBEEF;
11+
struct sort_context {
12+
int descending;
13+
unsigned int calls;
14+
unsigned int magic;
15+
};
916

10-
static int cmpstringp(const void *p1, const void *p2, void *ctx) {
11-
/* The actual arguments to this function are "pointers to
12-
* pointers to char", but strcmp(3) arguments are "pointers
13-
* to char", hence the following cast plus dereference. */
14-
assert(*(size_t *) ctx == magic);
15-
return strcmp(*(const char **) p1, *(const char **) p2);
17+
static int compare_ints(const void *p1, const void *p2) {
18+
int a = *(const int *) p1;
19+
int b = *(const int *) p2;
20+
if (a < b)
21+
return -100;
22+
if (a > b)
23+
return 100;
24+
return 0;
1625
}
1726

18-
int main() {
19-
qsort_r(&arr[0], sizeof(arr) / sizeof(*arr), sizeof(char *), cmpstringp, (void *) &magic);
27+
static int compare_bytes(const void *p1, const void *p2) {
28+
unsigned char a = *(const unsigned char *) p1;
29+
unsigned char b = *(const unsigned char *) p2;
30+
if (a < b)
31+
return -100;
32+
if (a > b)
33+
return 100;
34+
return 0;
35+
}
36+
37+
static int compare_records(const void *p1, const void *p2, void *ctx) {
38+
const struct record *a = p1;
39+
const struct record *b = p2;
40+
struct sort_context *context = ctx;
41+
assert(context->magic == 0xDEADBEEF);
42+
context->calls++;
43+
44+
if (a->key == b->key)
45+
return 0;
46+
if (context->descending)
47+
return a->key < b->key ? 100 : -100;
48+
return a->key < b->key ? -100 : 100;
49+
}
50+
51+
static int compare_strings(const void *p1, const void *p2, void *ctx) {
52+
struct sort_context *context = ctx;
53+
assert(context->magic == 0xDEADBEEF);
54+
context->calls++;
55+
return strcmp(*(const char *const *) p1, *(const char *const *) p2);
56+
}
57+
58+
static void assert_record_values(const struct record *records, size_t count) {
59+
static const char *expected[] = { "seven", "minus3", "four", "four-b", "zero", "nineteen" };
60+
for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); i++) {
61+
int found = 0;
62+
for (size_t j = 0; j < count; j++) {
63+
if (!strcmp(records[j].value, expected[i])) {
64+
found = 1;
65+
break;
66+
}
67+
}
68+
assert(found);
69+
}
70+
}
71+
72+
static void test_qsort(void) {
73+
// ISO C qsort(): ordinary integer ordering, duplicates, and non-trivial
74+
// comparator return values (the comparator need not return +/-1).
75+
int values[] = { 7, -3, 4, 4, 0, 19, -10, 2 };
76+
const int expected[] = { -10, -3, 0, 2, 4, 4, 7, 19 };
77+
qsort(values, sizeof(values) / sizeof(values[0]), sizeof(values[0]), compare_ints);
78+
assert(!memcmp(values, expected, sizeof(values)));
79+
80+
// Sorting an already reverse-ordered input exercises repeated partitioning.
81+
int reverse[] = { 5, 4, 3, 2, 1, 0 };
82+
qsort(reverse, sizeof(reverse) / sizeof(reverse[0]), sizeof(reverse[0]), compare_ints);
83+
for (size_t i = 0; i < sizeof(reverse) / sizeof(reverse[0]); i++)
84+
assert(reverse[i] == (int) i);
2085

21-
assert(!strcmp(arr[0], "abc"));
22-
assert(!strcmp(arr[1], "def"));
23-
assert(!strcmp(arr[2], "ghi"));
24-
assert(!strcmp(arr[3], "xyz"));
86+
// qsort() must honor arbitrary element sizes, including one-byte objects.
87+
unsigned char bytes[] = { 3, 1, 2, 0, 255 };
88+
qsort(bytes, sizeof(bytes), sizeof(bytes[0]), compare_bytes);
89+
assert(bytes[0] == 0 && bytes[1] == 1 && bytes[2] == 2 && bytes[3] == 3 && bytes[4] == 255);
90+
91+
// A one-element range requires no comparisons or swaps.
92+
int one[] = { 42 };
93+
qsort(one, 1, sizeof(one[0]), compare_ints);
94+
assert(one[0] == 42);
95+
96+
// A zero-count call must not access or modify the base object.
97+
int empty[1] = { 42 };
98+
qsort(empty, 0, sizeof(empty[0]), compare_ints);
99+
assert(empty[0] == 42);
100+
}
25101

26-
for(size_t i = 0; i < sizeof(arr) / sizeof(*arr); i++) {
27-
fprintf(stderr, "%s\n", arr[i]);
102+
static void test_qsort_r(void) {
103+
// POSIX qsort_r(): the callback receives a caller-provided
104+
// context, and sorting must move complete multi-field records.
105+
struct {
106+
unsigned char before[16];
107+
struct record records[6];
108+
unsigned char after[16];
109+
} guarded = {
110+
{ 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5,
111+
0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5, 0xA5 },
112+
{
113+
{ 7, "seven" }, { -3, "minus3" }, { 4, "four" },
114+
{ 4, "four-b" }, { 0, "zero" }, { 19, "nineteen" }
115+
},
116+
{ 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A,
117+
0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A, 0x5A }
118+
};
119+
struct sort_context context = { 0, 0, 0xDEADBEEF };
120+
qsort_r(guarded.records, sizeof(guarded.records) / sizeof(guarded.records[0]),
121+
sizeof(guarded.records[0]),
122+
compare_records, &context);
123+
assert(context.calls > 0);
124+
for (size_t i = 1; i < sizeof(guarded.records) / sizeof(guarded.records[0]); i++)
125+
assert(guarded.records[i - 1].key <= guarded.records[i].key);
126+
assert_record_values(guarded.records, sizeof(guarded.records) / sizeof(guarded.records[0]));
127+
128+
// Reuse the same context to verify that callback state can select a
129+
// different ordering direction.
130+
context.descending = 1;
131+
context.calls = 0;
132+
qsort_r(guarded.records, sizeof(guarded.records) / sizeof(guarded.records[0]),
133+
sizeof(guarded.records[0]),
134+
compare_records, &context);
135+
assert(context.calls > 0);
136+
for (size_t i = 1; i < sizeof(guarded.records) / sizeof(guarded.records[0]); i++)
137+
assert(guarded.records[i - 1].key >= guarded.records[i].key);
138+
assert_record_values(guarded.records, sizeof(guarded.records) / sizeof(guarded.records[0]));
139+
for (size_t i = 0; i < sizeof(guarded.before); i++) {
140+
assert(guarded.before[i] == 0xA5);
141+
assert(guarded.after[i] == 0x5A);
28142
}
29143

144+
// qsort_r() also handles pointer-sized elements and string comparators.
145+
char *strings[] = { "xyz", "abc", "ghi", "def" };
146+
context.descending = 0;
147+
context.calls = 0;
148+
qsort_r(strings, sizeof(strings) / sizeof(strings[0]), sizeof(strings[0]),
149+
compare_strings, &context);
150+
assert(context.calls > 0);
151+
assert(!strcmp(strings[0], "abc"));
152+
assert(!strcmp(strings[1], "def"));
153+
assert(!strcmp(strings[2], "ghi"));
154+
assert(!strcmp(strings[3], "xyz"));
155+
156+
// As with qsort(), a zero-count range must not invoke the comparator or
157+
// modify the element passed as the base address.
158+
struct record empty[] = { { 42, "unchanged" } };
159+
context.calls = 0;
160+
qsort_r(empty, 0, sizeof(empty[0]), compare_records, &context);
161+
assert(context.calls == 0);
162+
assert(empty[0].key == 42);
163+
assert(!strcmp(empty[0].value, "unchanged"));
164+
}
165+
166+
int main() {
167+
test_qsort();
168+
test_qsort_r();
30169
return 0;
31170
}

tests/ansi/string-search.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#include <assert.h>
2+
#include <stddef.h>
3+
#include <string.h>
4+
5+
// ISO C string-search functions:
6+
// https://en.cppreference.com/w/c/string/byte
7+
// GNU strchrnul() extension:
8+
// https://man7.org/linux/man-pages/man3/strchrnul.3.html
9+
10+
static void test_strstr(void) {
11+
// A nonempty needle matches at the first occurrence and returns a pointer
12+
// into the original haystack.
13+
char haystack[] = "abcabc";
14+
assert(strstr(haystack, "abc") == haystack);
15+
assert(strstr(haystack + 1, "abc") == haystack + 3);
16+
17+
// A missing needle and a needle longer than the remaining suffix return NULL.
18+
assert(strstr(haystack, "abd") == NULL);
19+
assert(strstr(haystack + 4, "abc") == NULL);
20+
21+
// The empty needle matches at the beginning, including for an empty string.
22+
char empty[] = "";
23+
assert(strstr(haystack, "") == haystack);
24+
assert(strstr(empty, "") == empty);
25+
26+
// String functions stop at the first embedded null byte.
27+
char embedded[] = { 'a', 'b', '\0', 'c', 'd', '\0' };
28+
assert(strstr(embedded, "ab") == embedded);
29+
assert(strstr(embedded, "cd") == NULL);
30+
}
31+
32+
static void test_strpbrk(void) {
33+
// strpbrk() returns the first character from the accept set.
34+
char text[] = "hello world";
35+
assert(strpbrk(text, "ow") == text + 4);
36+
assert(strpbrk(text, "d") == text + 10);
37+
38+
// No matching character and an empty accept set both produce NULL.
39+
assert(strpbrk(text, "xyz") == NULL);
40+
assert(strpbrk(text, "") == NULL);
41+
42+
// Search sets are byte-oriented, including bytes with the high bit set.
43+
char high_byte[] = { 'a', (char) 0xff, 'b', '\0' };
44+
char high_set[] = { (char) 0xff, '\0' };
45+
assert(strpbrk(high_byte, high_set) == high_byte + 1);
46+
}
47+
48+
static void test_strcspn(void) {
49+
// strcspn() counts the prefix containing no character from the reject set.
50+
assert(strcspn("abc:def", ":") == 3);
51+
assert(strcspn("abc", "a") == 0);
52+
assert(strcspn("abc", "xyz") == 3);
53+
54+
// An empty reject set accepts the whole string, and embedded nulls end it.
55+
assert(strcspn("abc", "") == 3);
56+
char embedded[] = { 'a', 'b', '\0', ':', '\0' };
57+
assert(strcspn(embedded, ":") == 2);
58+
59+
char high_byte[] = { 'a', (char) 0xff, 'b', '\0' };
60+
char high_set[] = { (char) 0xff, '\0' };
61+
assert(strcspn(high_byte, high_set) == 1);
62+
}
63+
64+
static void test_strspn(void) {
65+
// strspn() counts the initial prefix containing only accepted characters.
66+
assert(strspn("12345abc", "0123456789") == 5);
67+
assert(strspn("abc123", "abc") == 3);
68+
assert(strspn("x123", "0123456789") == 0);
69+
70+
// An empty accept set accepts nothing, while an embedded null ends the scan.
71+
assert(strspn("abc", "") == 0);
72+
char embedded[] = { '1', '2', '\0', '3', '\0' };
73+
assert(strspn(embedded, "0123456789") == 2);
74+
75+
char high_prefix[] = { (char) 0xff, (char) 0xff, 'a', '\0' };
76+
char high_set[] = { (char) 0xff, '\0' };
77+
assert(strspn(high_prefix, high_set) == 2);
78+
}
79+
80+
static void test_strchrnul(void) {
81+
// GNU strchrnul() returns the matching character, or the terminating null
82+
// byte when no match exists.
83+
char text[] = "abc";
84+
assert(strchrnul(text, 'b') == text + 1);
85+
assert(strchrnul(text, 'x') == text + 3);
86+
assert(strchrnul(text, '\0') == text + 3);
87+
88+
// The search value is converted to unsigned char, so high-bit bytes and
89+
// values outside the unsigned-char range are handled consistently.
90+
char high_byte[] = { 'a', (char) 0xff, 'b', '\0' };
91+
assert(strchrnul(high_byte, 0xff) == high_byte + 1);
92+
assert(strchrnul(high_byte, 0x1ff) == high_byte + 1);
93+
}
94+
95+
int main(void) {
96+
test_strstr();
97+
test_strpbrk();
98+
test_strcspn();
99+
test_strspn();
100+
test_strchrnul();
101+
return 0;
102+
}

tests/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ all_test_cases = [
6060
'ansi/threads-once',
6161
'ansi/threads-tss',
6262
'ansi/threads-thrd',
63+
'ansi/string-search',
6364
'bsd/ns_get_put',
6465
'bsd/reallocarray',
6566
'bsd/strl',
@@ -168,6 +169,7 @@ all_test_cases = [
168169
'posix/getwchar',
169170
'posix/strcoll_l',
170171
'posix/strxfrm_l',
172+
'posix/strtok',
171173
'posix/wcscoll_l',
172174
'posix/wcsxfrm_l',
173175
'posix/aio',

0 commit comments

Comments
 (0)