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