1 //===-- hashtable_fuzz.cpp ------------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 /// Fuzzing test for llvm-libc hashtable implementations.
11 //===----------------------------------------------------------------------===//
12 #include "include/llvm-libc-types/ENTRY.h"
13 #include "src/__support/CPP/bit.h"
14 #include "src/__support/CPP/string_view.h"
15 #include "src/__support/HashTable/table.h"
16 #include "src/__support/macros/config.h"
18 namespace LIBC_NAMESPACE_DECL
{
20 // A fuzzing payload starts with
21 // - uint16_t: initial capacity for table A
22 // - uint64_t: seed for table A
23 // - uint16_t: initial capacity for table B
24 // - uint64_t: seed for table B
25 // Followed by a sequence of actions:
26 // - CrossCheck: only a single byte valued (4 mod 5)
27 // - Find: a single byte valued (3 mod 5) followed by a null-terminated string
28 // - Insert: a single byte valued (0,1,2 mod 5) followed by a null-terminated
30 static constexpr size_t INITIAL_HEADER_SIZE
=
31 2 * (sizeof(uint16_t) + sizeof(uint64_t));
32 extern "C" size_t LLVMFuzzerMutate(uint8_t *data
, size_t size
, size_t max_size
);
33 extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *data
, size_t size
,
34 size_t max_size
, unsigned int seed
) {
35 size
= LLVMFuzzerMutate(data
, size
, max_size
);
36 // not enough to read the initial capacities and seeds
37 if (size
< INITIAL_HEADER_SIZE
)
40 // skip the initial capacities and seeds
41 size_t i
= INITIAL_HEADER_SIZE
;
44 if (static_cast<uint8_t>(data
[i
]) % 5 == 4) {
45 // skip the cross check byte
51 // check if there is enough space for the action byte and the
53 if (i
+ 2 >= max_size
)
55 // skip the action byte
57 // skip the null-terminated string
58 while (i
< max_size
&& data
[i
] != 0)
60 // in the case the string is not null-terminated, null-terminate it
61 if (i
== max_size
&& data
[i
- 1] != 0) {
66 // move to the next action
69 // return the new size
75 enum class Tag
{ Find
, Insert
, CrossCheck
} tag
;
83 template <typename T
> T
next() {
84 static_assert(cpp::is_integral
<T
>::value
, "T must be an integral type");
88 for (size_t i
= 0; i
< sizeof(T
); i
++)
91 remaining
-= sizeof(T
);
92 return cpp::bit_cast
<T
>(data
);
95 cpp::string_view
next_string() {
96 cpp::string_view
result(buffer
);
97 buffer
= result
.end() + 1;
98 remaining
-= result
.size() + 1;
102 Action
next_action() {
103 uint8_t byte
= next
<uint8_t>();
106 return {Action::Tag::CrossCheck
, {}};
108 return {Action::Tag::Find
, next_string()};
110 return {Action::Tag::Insert
, next_string()};
116 internal::HashTable
*table
;
119 HashTable(uint64_t size
, uint64_t seed
)
120 : table(internal::HashTable::allocate(size
, seed
)) {}
121 HashTable(internal::HashTable
*table
) : table(table
) {}
122 ~HashTable() { internal::HashTable::deallocate(table
); }
123 HashTable(HashTable
&&other
) : table(other
.table
) { other
.table
= nullptr; }
124 bool is_valid() const { return table
!= nullptr; }
125 ENTRY
*find(const char *key
) { return table
->find(key
); }
126 ENTRY
*insert(const ENTRY
&entry
) {
127 return internal::HashTable::insert(this->table
, entry
);
129 using iterator
= internal::HashTable::iterator
;
130 iterator
begin() const { return table
->begin(); }
131 iterator
end() const { return table
->end(); }
134 HashTable
next_hashtable() {
135 size_t size
= global_status
.next
<uint16_t>();
136 uint64_t seed
= global_status
.next
<uint64_t>();
137 return HashTable(size
, seed
);
140 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data
, size_t size
) {
141 global_status
.buffer
= reinterpret_cast<const char *>(data
);
142 global_status
.remaining
= size
;
143 if (global_status
.remaining
< INITIAL_HEADER_SIZE
)
146 HashTable table_a
= next_hashtable();
147 HashTable table_b
= next_hashtable();
149 if (global_status
.remaining
== 0)
151 Action action
= global_status
.next_action();
152 switch (action
.tag
) {
153 case Action::Tag::Find
: {
154 if (static_cast<bool>(table_a
.find(action
.key
.data())) !=
155 static_cast<bool>(table_b
.find(action
.key
.data())))
159 case Action::Tag::Insert
: {
160 char *ptr
= const_cast<char *>(action
.key
.data());
161 ENTRY
*a
= table_a
.insert(ENTRY
{ptr
, ptr
});
162 ENTRY
*b
= table_b
.insert(ENTRY
{ptr
, ptr
});
163 if (a
->data
!= b
->data
)
167 case Action::Tag::CrossCheck
: {
168 for (ENTRY a
: table_a
)
169 if (const ENTRY
*b
= table_b
.find(a
.key
); a
.data
!= b
->data
)
172 for (ENTRY b
: table_b
)
173 if (const ENTRY
*a
= table_a
.find(b
.key
); a
->data
!= b
.data
)
183 } // namespace LIBC_NAMESPACE_DECL