/src/behaviortreecpp/fuzzing/bb_fuzzer.cpp
Line | Count | Source |
1 | | #include "behaviortree_cpp/blackboard.h" |
2 | | #include <fuzzer/FuzzedDataProvider.h> |
3 | | #include <iostream> |
4 | | #include <vector> |
5 | | #include <memory> |
6 | | #include <thread> |
7 | | |
8 | | #include <iomanip> |
9 | | |
10 | | class ExceptionFilter |
11 | | { |
12 | | public: |
13 | | static bool isExpectedException(const std::exception& e) |
14 | 341k | { |
15 | 341k | const std::string what = e.what(); |
16 | 341k | const std::vector<std::string> expected_patterns = { "Blackboard::set", |
17 | 341k | "once declared, the type of a " |
18 | 341k | "port shall not change", |
19 | 341k | "Missing key", |
20 | 341k | "hasn't been initialized", |
21 | 341k | "Missing parent blackboard", |
22 | 341k | "Floating point truncated", |
23 | 341k | "Value outside the max " |
24 | 341k | "numerical limit", |
25 | 341k | "Value outside the lovest " |
26 | 341k | "numerical limit", |
27 | 341k | "Value is negative and can't be " |
28 | 341k | "converted to unsigned", |
29 | 341k | "Implicit casting to bool is " |
30 | 341k | "not allowed" }; |
31 | | |
32 | 341k | for(const auto& pattern : expected_patterns) |
33 | 1.29M | { |
34 | 1.29M | if(what.find(pattern) != std::string::npos) |
35 | 338k | { |
36 | 338k | return true; |
37 | 338k | } |
38 | 1.29M | } |
39 | 2.62k | return false; |
40 | 341k | } |
41 | | }; |
42 | | |
43 | | class BlackboardFuzzer |
44 | | { |
45 | | private: |
46 | | std::vector<BT::Blackboard::Ptr> blackboards_; |
47 | | std::vector<std::string> generated_keys_; |
48 | | FuzzedDataProvider& fuzz_data_; |
49 | | |
50 | | std::string generateKey() |
51 | 845k | { |
52 | 845k | const std::string key_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01" |
53 | 845k | "23456789_@"; |
54 | 845k | size_t length = fuzz_data_.ConsumeIntegralInRange<size_t>(1, 32); |
55 | 845k | std::string key; |
56 | 1.88M | for(size_t i = 0; i < length; ++i) |
57 | 1.03M | { |
58 | 1.03M | key += |
59 | 1.03M | key_chars[fuzz_data_.ConsumeIntegralInRange<size_t>(0, key_chars.length() - 1)]; |
60 | 1.03M | } |
61 | 845k | generated_keys_.push_back(key); |
62 | 845k | return key; |
63 | 845k | } |
64 | | |
65 | | void fuzzSingleBB(BT::Blackboard::Ptr bb) |
66 | 840k | { |
67 | 840k | if(!bb) |
68 | 0 | return; |
69 | | |
70 | 840k | try |
71 | 840k | { |
72 | | // Create random entry |
73 | 840k | std::string key = generateKey(); |
74 | 840k | switch(fuzz_data_.ConsumeIntegralInRange<size_t>(0, 6)) |
75 | 840k | { |
76 | 786k | case 0: |
77 | 786k | bb->set(key, fuzz_data_.ConsumeIntegral<int>()); |
78 | 786k | break; |
79 | 6.45k | case 1: |
80 | 6.45k | bb->set(key, fuzz_data_.ConsumeFloatingPoint<double>()); |
81 | 6.45k | break; |
82 | 13.3k | case 2: |
83 | 13.3k | bb->set(key, fuzz_data_.ConsumeRandomLengthString()); |
84 | 13.3k | break; |
85 | 16.8k | case 3: |
86 | 16.8k | bb->set(key, fuzz_data_.ConsumeBool()); |
87 | 16.8k | break; |
88 | 7.95k | case 4: |
89 | 7.95k | bb->set(key, fuzz_data_.ConsumeIntegral<uint64_t>()); |
90 | 7.95k | break; |
91 | 5.62k | case 5: |
92 | 5.62k | bb->set(key, fuzz_data_.ConsumeFloatingPoint<float>()); |
93 | 5.62k | break; |
94 | 4.27k | case 6: { |
95 | | // Try to get non-existent key |
96 | 4.27k | bb->get<int>(generateKey()); |
97 | 4.27k | break; |
98 | 0 | } |
99 | 840k | } |
100 | | |
101 | | // Random operations on existing keys |
102 | 528k | if(!generated_keys_.empty()) |
103 | 528k | { |
104 | 528k | const auto& existing_key = |
105 | 528k | generated_keys_[fuzz_data_.ConsumeIntegralInRange<size_t>( |
106 | 528k | 0, generated_keys_.size() - 1)]; |
107 | | |
108 | 528k | switch(fuzz_data_.ConsumeIntegralInRange<size_t>(0, 4)) |
109 | 528k | { |
110 | 497k | case 0: |
111 | 497k | bb->unset(existing_key); |
112 | 497k | break; |
113 | 9.18k | case 1: |
114 | 9.18k | bb->getEntry(existing_key); |
115 | 9.18k | break; |
116 | 6.49k | case 2: |
117 | 6.49k | bb->get<int>(existing_key); |
118 | 6.49k | break; |
119 | 8.13k | case 3: |
120 | 8.13k | bb->get<double>(existing_key); |
121 | 8.13k | break; |
122 | 7.55k | case 4: |
123 | 7.55k | bb->get<std::string>(existing_key); |
124 | 7.55k | break; |
125 | 528k | } |
126 | 528k | } |
127 | | |
128 | | // Random remapping operations |
129 | 521k | if(generated_keys_.size() >= 2) |
130 | 513k | { |
131 | 513k | size_t idx1 = |
132 | 513k | fuzz_data_.ConsumeIntegralInRange<size_t>(0, generated_keys_.size() - 1); |
133 | 513k | size_t idx2 = |
134 | 513k | fuzz_data_.ConsumeIntegralInRange<size_t>(0, generated_keys_.size() - 1); |
135 | 513k | bb->addSubtreeRemapping(generated_keys_[idx1], generated_keys_[idx2]); |
136 | 513k | } |
137 | 521k | } |
138 | 840k | catch(const std::exception& e) |
139 | 840k | { |
140 | 319k | if(!ExceptionFilter::isExpectedException(e)) |
141 | 262 | { |
142 | 262 | throw; |
143 | 262 | } |
144 | 319k | } |
145 | 840k | } |
146 | | |
147 | | void createBlackboardHierarchy() |
148 | 7.39k | { |
149 | 7.39k | if(blackboards_.empty()) |
150 | 0 | return; |
151 | | |
152 | 7.39k | auto parent = blackboards_[fuzz_data_.ConsumeIntegralInRange<size_t>( |
153 | 7.39k | 0, blackboards_.size() - 1)]; |
154 | | |
155 | 7.39k | auto child = BT::Blackboard::create(parent); |
156 | 7.39k | if(fuzz_data_.ConsumeBool()) |
157 | 5.65k | { |
158 | 5.65k | child->enableAutoRemapping(true); |
159 | 5.65k | } |
160 | | |
161 | 7.39k | blackboards_.push_back(child); |
162 | 7.39k | } |
163 | | |
164 | | void fuzzJsonOperations(BT::Blackboard::Ptr bb) |
165 | 63.2k | { |
166 | 63.2k | try |
167 | 63.2k | { |
168 | 63.2k | auto json = BT::ExportBlackboardToJSON(*bb); |
169 | 63.2k | if(fuzz_data_.ConsumeBool()) |
170 | 48.7k | { |
171 | 48.7k | std::string json_str = json.dump(); |
172 | 48.7k | size_t pos = fuzz_data_.ConsumeIntegralInRange<size_t>(0, json_str.length()); |
173 | 48.7k | json_str.insert(pos, fuzz_data_.ConsumeRandomLengthString()); |
174 | 48.7k | json = nlohmann::json::parse(json_str); |
175 | 48.7k | } |
176 | 63.2k | BT::ImportBlackboardFromJSON(json, *bb); |
177 | 63.2k | } |
178 | 63.2k | catch(const std::exception& e) |
179 | 63.2k | { |
180 | 19.6k | if(!ExceptionFilter::isExpectedException(e)) |
181 | 612 | { |
182 | 612 | throw; |
183 | 612 | } |
184 | 19.6k | } |
185 | 63.2k | } |
186 | | |
187 | | public: |
188 | 8.64k | explicit BlackboardFuzzer(FuzzedDataProvider& provider) : fuzz_data_(provider) |
189 | 8.64k | { |
190 | 8.64k | blackboards_.push_back(BT::Blackboard::create()); |
191 | 8.64k | } |
192 | | |
193 | | void fuzz() |
194 | 8.64k | { |
195 | 8.64k | size_t num_operations = fuzz_data_.ConsumeIntegralInRange<size_t>(50, 200); |
196 | | |
197 | 944k | for(size_t i = 0; i < num_operations && !blackboards_.empty(); ++i) |
198 | 936k | { |
199 | 936k | try |
200 | 936k | { |
201 | | // Randomly select a blackboard to operate on |
202 | 936k | size_t bb_idx = |
203 | 936k | fuzz_data_.ConsumeIntegralInRange<size_t>(0, blackboards_.size() - 1); |
204 | 936k | auto bb = blackboards_[bb_idx]; |
205 | | |
206 | 936k | switch(fuzz_data_.ConsumeIntegralInRange<size_t>(0, 3)) |
207 | 936k | { |
208 | 840k | case 0: |
209 | | // Fuzz single blackboard operations |
210 | 840k | fuzzSingleBB(bb); |
211 | 840k | break; |
212 | | |
213 | 12.6k | case 1: |
214 | | // Create new blackboards in hierarchy |
215 | 12.6k | if(fuzz_data_.ConsumeBool()) |
216 | 7.39k | { |
217 | 7.39k | createBlackboardHierarchy(); |
218 | 7.39k | } |
219 | 12.6k | break; |
220 | | |
221 | 63.2k | case 2: |
222 | | // JSON operations |
223 | 63.2k | fuzzJsonOperations(bb); |
224 | 63.2k | break; |
225 | | |
226 | 19.9k | case 3: |
227 | | // Cleanup operations |
228 | 19.9k | if(fuzz_data_.ConsumeBool() && blackboards_.size() > 1) |
229 | 1.97k | { |
230 | 1.97k | size_t remove_idx = |
231 | 1.97k | fuzz_data_.ConsumeIntegralInRange<size_t>(0, blackboards_.size() - 1); |
232 | 1.97k | blackboards_.erase(blackboards_.begin() + remove_idx); |
233 | 1.97k | } |
234 | 19.9k | break; |
235 | 936k | } |
236 | 936k | } |
237 | 936k | catch(const std::exception& e) |
238 | 936k | { |
239 | 874 | if(!ExceptionFilter::isExpectedException(e)) |
240 | 874 | { |
241 | 874 | std::cerr << "Unexpected exception: " << e.what() << std::endl; |
242 | 874 | throw; |
243 | 874 | } |
244 | 874 | } |
245 | 936k | } |
246 | 8.64k | } |
247 | | }; |
248 | | |
249 | | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) |
250 | 8.65k | { |
251 | 8.65k | if(size < 64) |
252 | 10 | return 0; |
253 | | |
254 | 8.64k | try |
255 | 8.64k | { |
256 | 8.64k | FuzzedDataProvider fuzz_data(data, size); |
257 | 8.64k | BlackboardFuzzer fuzzer(fuzz_data); |
258 | 8.64k | fuzzer.fuzz(); |
259 | 8.64k | } |
260 | 8.64k | catch(const std::exception& e) |
261 | 8.64k | { |
262 | 874 | if(!ExceptionFilter::isExpectedException(e)) |
263 | 874 | { |
264 | 874 | std::cerr << "Unexpected top-level exception: " << e.what() << std::endl; |
265 | 874 | return 1; |
266 | 874 | } |
267 | 874 | } |
268 | | |
269 | 7.77k | return 0; |
270 | 8.64k | } |