Coverage Report

Created: 2025-11-24 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}