Skip to content

Commit b4d7cbc

Browse files
[libc] add hashtable fuzzing
1 parent 54c24ec commit b4d7cbc

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

libc/fuzzing/__support/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ add_libc_fuzzer(
55
DEPENDS
66
libc.src.__support.big_int
77
)
8+
9+
add_libc_fuzzer(
10+
hashtable_fuzz
11+
SRCS
12+
hashtable_fuzz.cpp
13+
DEPENDS
14+
libc.src.__support.HashTable.table
15+
libc.src.string.memcpy
16+
)
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
#include "src/__support/CPP/new.h"
2+
#include "src/__support/CPP/optional.h"
3+
#include "src/__support/HashTable/table.h"
4+
#include "src/string/memcpy.h"
5+
#include <search.h>
6+
#include <stdint.h>
7+
namespace LIBC_NAMESPACE {
8+
9+
enum class Action { Find, Insert, CrossCheck };
10+
static uint8_t *global_buffer = nullptr;
11+
static size_t remaining = 0;
12+
13+
static cpp::optional<uint8_t> next_u8() {
14+
if (remaining == 0)
15+
return cpp::nullopt;
16+
uint8_t result = *global_buffer;
17+
global_buffer++;
18+
remaining--;
19+
return result;
20+
}
21+
22+
static cpp::optional<uint64_t> next_uint64() {
23+
uint64_t result;
24+
if (remaining < sizeof(result))
25+
return cpp::nullopt;
26+
memcpy(&result, global_buffer, sizeof(result));
27+
global_buffer += sizeof(result);
28+
remaining -= sizeof(result);
29+
return result;
30+
}
31+
32+
static cpp::optional<Action> next_action() {
33+
if (cpp::optional<uint8_t> action = next_u8()) {
34+
switch (*action % 3) {
35+
case 0:
36+
return Action::Find;
37+
case 1:
38+
return Action::Insert;
39+
case 2:
40+
return Action::CrossCheck;
41+
}
42+
}
43+
return cpp::nullopt;
44+
}
45+
46+
static cpp::optional<char *> next_cstr() {
47+
char *result = reinterpret_cast<char *>(global_buffer);
48+
if (cpp::optional<uint64_t> len = next_uint64()) {
49+
uint64_t length;
50+
for (length = 0; length < *len % 128; length++) {
51+
if (length >= remaining)
52+
return cpp::nullopt;
53+
if (*global_buffer == '\0')
54+
break;
55+
}
56+
if (length >= remaining)
57+
return cpp::nullopt;
58+
global_buffer[length] = '\0';
59+
global_buffer += length + 1;
60+
remaining -= length + 1;
61+
return result;
62+
}
63+
return cpp::nullopt;
64+
}
65+
66+
#define GET_VAL(op) \
67+
__extension__({ \
68+
auto val = op(); \
69+
if (!val) \
70+
return 0; \
71+
*val; \
72+
})
73+
74+
template <typename Fn> struct CleanUpHook {
75+
cpp::optional<Fn> fn;
76+
~CleanUpHook() {
77+
if (fn)
78+
(*fn)();
79+
}
80+
CleanUpHook(Fn fn) : fn(cpp::move(fn)) {}
81+
CleanUpHook(const CleanUpHook &) = delete;
82+
CleanUpHook(CleanUpHook &&other) : fn(cpp::move(other.fn)) {
83+
other.fn = cpp::nullopt;
84+
}
85+
};
86+
87+
#define register_cleanup(ID, ...) \
88+
auto cleanup_hook##ID = __extension__({ \
89+
auto a = __VA_ARGS__; \
90+
CleanUpHook<decltype(a)>{a}; \
91+
});
92+
93+
static void trap_with_message(const char *msg) { __builtin_trap(); }
94+
95+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
96+
AllocChecker ac;
97+
global_buffer = static_cast<uint8_t *>(::operator new(size, ac));
98+
register_cleanup(0, [global_buffer = global_buffer, size] {
99+
::operator delete(global_buffer, size);
100+
});
101+
if (!ac)
102+
return 0;
103+
memcpy(global_buffer, data, size);
104+
105+
remaining = size;
106+
uint64_t size_a = GET_VAL(next_uint64) % 256;
107+
uint64_t size_b = GET_VAL(next_uint64) % 256;
108+
uint64_t rand_a = GET_VAL(next_uint64);
109+
uint64_t rand_b = GET_VAL(next_uint64);
110+
internal::HashTable *table_a = internal::HashTable::allocate(size_a, rand_a);
111+
register_cleanup(1, [&table_a] { internal::HashTable::deallocate(table_a); });
112+
internal::HashTable *table_b = internal::HashTable::allocate(size_b, rand_b);
113+
register_cleanup(2, [&table_b] { internal::HashTable::deallocate(table_b); });
114+
if (!table_a || !table_b)
115+
return 0;
116+
for (;;) {
117+
Action action = GET_VAL(next_action);
118+
switch (action) {
119+
case Action::Find: {
120+
const char *key = GET_VAL(next_cstr);
121+
if (!key)
122+
return 0;
123+
if (static_cast<bool>(table_a->find(key)) !=
124+
static_cast<bool>(table_b->find(key)))
125+
trap_with_message(key);
126+
break;
127+
}
128+
case Action::Insert: {
129+
char *key = GET_VAL(next_cstr);
130+
if (!key)
131+
return 0;
132+
ENTRY *a = internal::HashTable::insert(table_a, ENTRY{key, key});
133+
ENTRY *b = internal::HashTable::insert(table_b, ENTRY{key, key});
134+
if (a->data != b->data)
135+
__builtin_trap();
136+
break;
137+
}
138+
case Action::CrossCheck: {
139+
for (ENTRY a : *table_a) {
140+
if (const ENTRY *b = table_b->find(a.key)) {
141+
if (a.data != b->data)
142+
__builtin_trap();
143+
}
144+
}
145+
for (ENTRY b : *table_b) {
146+
if (const ENTRY *a = table_a->find(b.key)) {
147+
if (a->data != b.data)
148+
__builtin_trap();
149+
}
150+
}
151+
break;
152+
}
153+
}
154+
}
155+
}
156+
157+
} // namespace LIBC_NAMESPACE

0 commit comments

Comments
 (0)