Coverage Report

Created: 2025-08-24 06:23

/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
}