Coverage Report

Created: 2026-01-17 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/simdutf/fuzz/base64.cpp
Line
Count
Source
1
#include <cstddef>
2
#include <cstdint>
3
#include <array>
4
5
#include "helpers/common.h"
6
#include "simdutf.h"
7
8
constexpr std::array options = {
9
    simdutf::base64_default,
10
    simdutf::base64_url,
11
    simdutf::base64_default_no_padding,
12
    simdutf::base64_url_with_padding,
13
};
14
15
constexpr std::array last_chunk = {
16
    simdutf::last_chunk_handling_options::loose,
17
    simdutf::last_chunk_handling_options::strict,
18
    simdutf::last_chunk_handling_options::stop_before_partial};
19
20
struct decoderesult {
21
  std::size_t maxbinarylength{};
22
  simdutf::result convertresult{};
23
  auto operator<=>(const decoderesult&) const = default;
24
};
25
26
template <typename FromChar>
27
void decode(std::span<const FromChar> base64_, const auto selected_option,
28
2.04k
            const auto last_chunk_option) {
29
2.04k
  std::vector<FromChar> base64(begin(base64_), end(base64_));
30
2.04k
  const auto implementations = get_supported_implementations();
31
2.04k
  std::vector<decoderesult> results;
32
2.04k
  results.reserve(implementations.size());
33
6.14k
  for (auto impl : implementations) {
34
6.14k
    auto& r = results.emplace_back();
35
6.14k
    r.maxbinarylength =
36
6.14k
        impl->maximal_binary_length_from_base64(base64.data(), base64.size());
37
6.14k
    std::vector<char> output(r.maxbinarylength);
38
6.14k
    r.convertresult =
39
6.14k
        impl->base64_to_binary(base64.data(), base64.size(), output.data(),
40
6.14k
                               selected_option, last_chunk_option);
41
6.14k
  }
42
4.09k
  auto neq = [](const auto& a, const auto& b) { return a != b; };
auto decode<char, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options)::{lambda(auto:1 const&, auto:2 const&)#1}::operator()<decoderesult, {lambda(auto:1 const&, auto:2 const&)#1}::operator()>(decoderesult const&, {lambda(auto:1 const&, auto:2 const&)#1}::operator() const&) const
Line
Count
Source
42
1.83k
  auto neq = [](const auto& a, const auto& b) { return a != b; };
auto decode<char16_t, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char16_t const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options)::{lambda(auto:1 const&, auto:2 const&)#1}::operator()<decoderesult, {lambda(auto:1 const&, auto:2 const&)#1}::operator()>(decoderesult const&, {lambda(auto:1 const&, auto:2 const&)#1}::operator() const&) const
Line
Count
Source
42
2.26k
  auto neq = [](const auto& a, const auto& b) { return a != b; };
43
2.04k
  if (std::ranges::adjacent_find(results, neq) != results.end()) {
44
0
    std::cerr << "output differs between implementations for decode\n";
45
0
    const auto implementations = get_supported_implementations();
46
0
    std::size_t i = 0;
47
0
    for (const auto& r : results) {
48
0
      std::cerr << "impl " << implementations[i]->name()
49
0
                << " got maxbinarylength=" << r.maxbinarylength
50
0
                << " convertresult=" << r.convertresult << "\n";
51
0
      ++i;
52
0
    }
53
0
    std::cerr << "option: " << selected_option << '\n';
54
0
    std::cerr << "data: "
55
0
              << (std::is_same_v<FromChar, char> ? "char" : "char16_t") << "{";
56
0
    for (int v : base64) {
57
0
      std::cerr << v << ", ";
58
0
    }
59
0
    std::cerr << "}\n";
60
0
    std::abort();
61
0
  }
62
2.04k
}
void decode<char, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options)
Line
Count
Source
28
915
            const auto last_chunk_option) {
29
915
  std::vector<FromChar> base64(begin(base64_), end(base64_));
30
915
  const auto implementations = get_supported_implementations();
31
915
  std::vector<decoderesult> results;
32
915
  results.reserve(implementations.size());
33
2.74k
  for (auto impl : implementations) {
34
2.74k
    auto& r = results.emplace_back();
35
2.74k
    r.maxbinarylength =
36
2.74k
        impl->maximal_binary_length_from_base64(base64.data(), base64.size());
37
2.74k
    std::vector<char> output(r.maxbinarylength);
38
2.74k
    r.convertresult =
39
2.74k
        impl->base64_to_binary(base64.data(), base64.size(), output.data(),
40
2.74k
                               selected_option, last_chunk_option);
41
2.74k
  }
42
915
  auto neq = [](const auto& a, const auto& b) { return a != b; };
43
915
  if (std::ranges::adjacent_find(results, neq) != results.end()) {
44
0
    std::cerr << "output differs between implementations for decode\n";
45
0
    const auto implementations = get_supported_implementations();
46
0
    std::size_t i = 0;
47
0
    for (const auto& r : results) {
48
0
      std::cerr << "impl " << implementations[i]->name()
49
0
                << " got maxbinarylength=" << r.maxbinarylength
50
0
                << " convertresult=" << r.convertresult << "\n";
51
0
      ++i;
52
0
    }
53
0
    std::cerr << "option: " << selected_option << '\n';
54
0
    std::cerr << "data: "
55
0
              << (std::is_same_v<FromChar, char> ? "char" : "char16_t") << "{";
56
0
    for (int v : base64) {
57
0
      std::cerr << v << ", ";
58
0
    }
59
0
    std::cerr << "}\n";
60
0
    std::abort();
61
0
  }
62
915
}
void decode<char16_t, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char16_t const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options)
Line
Count
Source
28
1.13k
            const auto last_chunk_option) {
29
1.13k
  std::vector<FromChar> base64(begin(base64_), end(base64_));
30
1.13k
  const auto implementations = get_supported_implementations();
31
1.13k
  std::vector<decoderesult> results;
32
1.13k
  results.reserve(implementations.size());
33
3.39k
  for (auto impl : implementations) {
34
3.39k
    auto& r = results.emplace_back();
35
3.39k
    r.maxbinarylength =
36
3.39k
        impl->maximal_binary_length_from_base64(base64.data(), base64.size());
37
3.39k
    std::vector<char> output(r.maxbinarylength);
38
3.39k
    r.convertresult =
39
3.39k
        impl->base64_to_binary(base64.data(), base64.size(), output.data(),
40
3.39k
                               selected_option, last_chunk_option);
41
3.39k
  }
42
1.13k
  auto neq = [](const auto& a, const auto& b) { return a != b; };
43
1.13k
  if (std::ranges::adjacent_find(results, neq) != results.end()) {
44
0
    std::cerr << "output differs between implementations for decode\n";
45
0
    const auto implementations = get_supported_implementations();
46
0
    std::size_t i = 0;
47
0
    for (const auto& r : results) {
48
0
      std::cerr << "impl " << implementations[i]->name()
49
0
                << " got maxbinarylength=" << r.maxbinarylength
50
0
                << " convertresult=" << r.convertresult << "\n";
51
0
      ++i;
52
0
    }
53
0
    std::cerr << "option: " << selected_option << '\n';
54
0
    std::cerr << "data: "
55
0
              << (std::is_same_v<FromChar, char> ? "char" : "char16_t") << "{";
56
0
    for (int v : base64) {
57
0
      std::cerr << v << ", ";
58
0
    }
59
0
    std::cerr << "}\n";
60
0
    std::abort();
61
0
  }
62
1.13k
}
63
64
template <typename FromChar>
65
void decode_safe(std::span<const FromChar> base64_, const auto selected_option,
66
                 const std::size_t decode_buf_size,
67
875
                 const auto last_chunk_option) {
68
875
  std::vector<FromChar> base64(begin(base64_), end(base64_));
69
875
  std::vector<char> output(decode_buf_size);
70
875
  std::size_t outlen = decode_buf_size;
71
875
  const auto convertresult = simdutf::base64_to_binary_safe(
72
875
      base64.data(), base64.size(), output.data(), outlen, selected_option,
73
875
      last_chunk_option);
74
75
  // the number of written bytes must always be less than the supplied buffer
76
875
  assert(outlen <= decode_buf_size);
77
78
875
  switch (convertresult.error) {
79
119
  case simdutf::error_code::OUTPUT_BUFFER_TOO_SMALL: {
80
119
    if (!(convertresult.count <= base64.size())) {
81
0
      std::cerr << " decode_buf_size=" << decode_buf_size
82
0
                << " outlen=" << outlen << " and result=" << convertresult
83
0
                << '\n';
84
0
      std::abort();
85
0
    }
86
119
  } break;
87
255
  case simdutf::error_code::INVALID_BASE64_CHARACTER: {
88
255
    assert(convertresult.count < base64.size());
89
255
  } break;
90
255
  case simdutf::error_code::BASE64_INPUT_REMAINDER: {
91
57
    if (!(convertresult.count <= base64.size())) {
92
0
      std::cerr << "on input with size=" << base64.size()
93
0
                << ": got BASE64_INPUT_REMAINDER decode_buf_size="
94
0
                << decode_buf_size << " outlen=" << outlen
95
0
                << " and result=" << convertresult << '\n';
96
0
      std::abort();
97
0
    }
98
57
  } break;
99
431
  case simdutf::error_code::SUCCESS: {
100
    // possibility to compare with the normal function
101
431
  } break;
102
13
  default:;
103
875
  }
104
875
}
void decode_safe<char, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char const, 18446744073709551615ul>, simdutf::base64_options, unsigned long, simdutf::last_chunk_handling_options)
Line
Count
Source
67
370
                 const auto last_chunk_option) {
68
370
  std::vector<FromChar> base64(begin(base64_), end(base64_));
69
370
  std::vector<char> output(decode_buf_size);
70
370
  std::size_t outlen = decode_buf_size;
71
370
  const auto convertresult = simdutf::base64_to_binary_safe(
72
370
      base64.data(), base64.size(), output.data(), outlen, selected_option,
73
370
      last_chunk_option);
74
75
  // the number of written bytes must always be less than the supplied buffer
76
370
  assert(outlen <= decode_buf_size);
77
78
370
  switch (convertresult.error) {
79
56
  case simdutf::error_code::OUTPUT_BUFFER_TOO_SMALL: {
80
56
    if (!(convertresult.count <= base64.size())) {
81
0
      std::cerr << " decode_buf_size=" << decode_buf_size
82
0
                << " outlen=" << outlen << " and result=" << convertresult
83
0
                << '\n';
84
0
      std::abort();
85
0
    }
86
56
  } break;
87
65
  case simdutf::error_code::INVALID_BASE64_CHARACTER: {
88
65
    assert(convertresult.count < base64.size());
89
65
  } break;
90
65
  case simdutf::error_code::BASE64_INPUT_REMAINDER: {
91
23
    if (!(convertresult.count <= base64.size())) {
92
0
      std::cerr << "on input with size=" << base64.size()
93
0
                << ": got BASE64_INPUT_REMAINDER decode_buf_size="
94
0
                << decode_buf_size << " outlen=" << outlen
95
0
                << " and result=" << convertresult << '\n';
96
0
      std::abort();
97
0
    }
98
23
  } break;
99
219
  case simdutf::error_code::SUCCESS: {
100
    // possibility to compare with the normal function
101
219
  } break;
102
7
  default:;
103
370
  }
104
370
}
void decode_safe<char16_t, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char16_t const, 18446744073709551615ul>, simdutf::base64_options, unsigned long, simdutf::last_chunk_handling_options)
Line
Count
Source
67
505
                 const auto last_chunk_option) {
68
505
  std::vector<FromChar> base64(begin(base64_), end(base64_));
69
505
  std::vector<char> output(decode_buf_size);
70
505
  std::size_t outlen = decode_buf_size;
71
505
  const auto convertresult = simdutf::base64_to_binary_safe(
72
505
      base64.data(), base64.size(), output.data(), outlen, selected_option,
73
505
      last_chunk_option);
74
75
  // the number of written bytes must always be less than the supplied buffer
76
505
  assert(outlen <= decode_buf_size);
77
78
505
  switch (convertresult.error) {
79
63
  case simdutf::error_code::OUTPUT_BUFFER_TOO_SMALL: {
80
63
    if (!(convertresult.count <= base64.size())) {
81
0
      std::cerr << " decode_buf_size=" << decode_buf_size
82
0
                << " outlen=" << outlen << " and result=" << convertresult
83
0
                << '\n';
84
0
      std::abort();
85
0
    }
86
63
  } break;
87
190
  case simdutf::error_code::INVALID_BASE64_CHARACTER: {
88
190
    assert(convertresult.count < base64.size());
89
190
  } break;
90
190
  case simdutf::error_code::BASE64_INPUT_REMAINDER: {
91
34
    if (!(convertresult.count <= base64.size())) {
92
0
      std::cerr << "on input with size=" << base64.size()
93
0
                << ": got BASE64_INPUT_REMAINDER decode_buf_size="
94
0
                << decode_buf_size << " outlen=" << outlen
95
0
                << " and result=" << convertresult << '\n';
96
0
      std::abort();
97
0
    }
98
34
  } break;
99
212
  case simdutf::error_code::SUCCESS: {
100
    // possibility to compare with the normal function
101
212
  } break;
102
6
  default:;
103
505
  }
104
505
}
105
106
struct roundtripresult {
107
  std::size_t length{};
108
  std::size_t maxbinarylength{};
109
  std::string outputhash;
110
  std::size_t written{};
111
  simdutf::result convertbackresult{};
112
  auto operator<=>(const roundtripresult&) const = default;
113
};
114
115
/// verifies that base64 with lines is the same as without lines, but with
116
/// newlines every line_length:th byte
117
void verify_lines(std::span<const char> without_lines,
118
                  std::span<const char> with_lines,
119
3.34k
                  const std::size_t line_length) {
120
  // ensure we get the same as output, with a newline every line_length:th
121
  // byte
122
49.4M
  for (std::size_t i = 0, j = 0;;) {
123
    // check one line
124
315M
    for (int count = 0; count < line_length && j < with_lines.size(); ++count) {
125
266M
      if (without_lines[i++] != with_lines[j++]) {
126
        // unexpected - different content
127
0
        std::abort();
128
0
      }
129
266M
    }
130
49.4M
    if (j == with_lines.size()) {
131
      // we are at the end of with_lines
132
3.34k
      if (i != without_lines.size()) {
133
        // unexpected - we are not at the end of without_lines
134
0
        std::abort();
135
0
      }
136
3.34k
      break;
137
3.34k
    }
138
49.4M
    if (with_lines[j++] != '\n') {
139
      // unexpected - not a newline
140
0
      std::abort();
141
0
    }
142
49.4M
  }
143
3.34k
}
144
145
void roundtrip(std::span<const char> binary, const auto selected_option,
146
1.11k
               const auto last_chunk_option, const std::size_t line_length) {
147
1.11k
  if (last_chunk_option ==
148
1.11k
      simdutf::last_chunk_handling_options::stop_before_partial) {
149
1
    return; // this is not a valid option for roundtrip
150
1
  }
151
1.11k
  const auto inputhash = FNV1A_hash::as_str(binary);
152
1.11k
  const auto implementations = get_supported_implementations();
153
1.11k
  std::vector<roundtripresult> results;
154
1.11k
  results.reserve(implementations.size());
155
3.34k
  for (auto impl : implementations) {
156
3.34k
    auto& r = results.emplace_back();
157
3.34k
    r.length = impl->base64_length_from_binary(binary.size(), selected_option);
158
3.34k
    std::vector<char> output(r.length);
159
3.34k
    r.written = impl->binary_to_base64(binary.data(), binary.size(),
160
3.34k
                                       output.data(), selected_option);
161
3.34k
    if (r.length != r.written) {
162
0
      std::abort();
163
0
    }
164
165
    // make sure generating base64 with lines gives the expected result
166
3.34k
    const auto length_with_lines =
167
3.34k
        simdutf::base64_length_from_binary_with_lines(
168
3.34k
            binary.size(), selected_option, line_length);
169
3.34k
    assert(length_with_lines >= r.length);
170
3.34k
    std::string output_with_lines(length_with_lines, '\0');
171
3.34k
    const auto nwritten_with_lines = impl->binary_to_base64_with_lines(
172
3.34k
        binary.data(), binary.size(), output_with_lines.data(), line_length,
173
3.34k
        selected_option);
174
3.34k
    if (nwritten_with_lines != length_with_lines) {
175
0
      std::cerr << nwritten_with_lines << "!=" << length_with_lines << '\n';
176
0
      std::abort();
177
0
    }
178
3.34k
    verify_lines(output, output_with_lines, line_length);
179
180
3.34k
    r.outputhash = FNV1A_hash::as_str(output);
181
    // convert back to binary
182
3.34k
    r.maxbinarylength =
183
3.34k
        impl->maximal_binary_length_from_base64(output.data(), output.size());
184
3.34k
    std::vector<char> restored(r.maxbinarylength);
185
3.34k
    r.convertbackresult =
186
3.34k
        impl->base64_to_binary(output.data(), output.size(), restored.data(),
187
3.34k
                               selected_option, last_chunk_option);
188
3.34k
    if (const auto restoredhash = FNV1A_hash::as_str(restored);
189
3.34k
        inputhash != restoredhash) {
190
0
      std::abort();
191
0
    }
192
3.34k
    if (restored.size() != binary.size()) {
193
0
      std::abort();
194
0
    }
195
3.34k
  }
196
197
2.23k
  auto neq = [](const auto& a, const auto& b) { return a != b; };
198
1.11k
  if (std::ranges::adjacent_find(results, neq) != results.end()) {
199
0
    std::cerr << "output differs between implementations\n";
200
0
    for (const auto& r : results) {
201
0
      std::cout << "written=" << r.written << " maxlength=" << r.maxbinarylength
202
0
                << " length=" << r.length << '\n';
203
0
    }
204
0
    std::abort();
205
0
  }
206
1.11k
}
207
208
4.04k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
209
  // pick one of the function pointers, based on the fuzz data
210
  // the first byte is which action to take. step forward
211
  // several bytes so the input is aligned.
212
4.04k
  constexpr auto optionbytes = 6u;
213
4.04k
  static_assert(optionbytes % 2 == 0,
214
4.04k
                "optionbytes must be even to avoid misaligned char16 pointers");
215
216
4.04k
  if (size < optionbytes) {
217
4
    return 0;
218
4
  }
219
4.04k
  constexpr auto Ncases = 5u;
220
4.04k
  constexpr auto actionmask = std::bit_ceil(Ncases) - 1;
221
4.04k
  const auto action = data[0] & actionmask;
222
223
  // pick a random option
224
4.04k
  const auto selected_option = [](auto index) {
225
4.04k
    if (index >= options.size())
226
0
      return options[0];
227
4.04k
    else {
228
4.04k
      return options[index];
229
4.04k
    }
230
4.04k
  }(data[1] & (std::bit_ceil(options.size()) - 1));
231
4.04k
  const auto selected_last_chunk =
232
4.04k
      (selected_option == simdutf::base64_url ||
233
3.30k
       selected_option == simdutf::base64_default_no_padding)
234
4.04k
          ? simdutf::last_chunk_handling_options::loose
235
4.04k
          : [](auto index) {
236
2.90k
              if (index >= last_chunk.size())
237
360
                return last_chunk[0];
238
2.54k
              else {
239
2.54k
                return last_chunk[index];
240
2.54k
              }
241
2.90k
            }(data[2] & (std::bit_ceil(last_chunk.size()) - 1));
242
243
  // decode buffer size
244
4.04k
  const std::size_t decode_buffer_size = (data[4] << 8) + data[3];
245
246
  // line length must be at least 4
247
4.04k
  const std::size_t line_length = unsigned{data[5]} + 4u;
248
249
4.04k
  data += optionbytes;
250
4.04k
  size -= optionbytes;
251
252
4.04k
  switch (action) {
253
1.11k
  case 0: {
254
1.11k
    const std::span<const char> chardata{(const char*)data, size};
255
1.11k
    roundtrip(chardata, selected_option, selected_last_chunk, line_length);
256
1.11k
  } break;
257
915
  case 1: {
258
915
    const std::span<const char> chardata{(const char*)data, size};
259
915
    decode(chardata, selected_option, selected_last_chunk);
260
915
  } break;
261
1.13k
  case 2: {
262
1.13k
    const std::span<const char16_t> chardata{(const char16_t*)data, size / 2};
263
1.13k
    decode(chardata, selected_option, selected_last_chunk);
264
1.13k
  } break;
265
370
  case 3: {
266
370
    const std::span<const char> chardata{(const char*)data, size};
267
370
    decode_safe(chardata, selected_option, decode_buffer_size,
268
370
                selected_last_chunk);
269
370
  } break;
270
505
  case 4: {
271
505
    const std::span<const char16_t> chardata{(const char16_t*)data, size / 2};
272
505
    decode_safe(chardata, selected_option, decode_buffer_size,
273
505
                selected_last_chunk);
274
505
  } break;
275
4.04k
  }
276
277
4.04k
  return 0;
278
4.04k
}