Coverage Report

Created: 2025-12-12 06:15

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