Coverage Report

Created: 2026-08-31 06:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ada-url/fuzz/parse.cc
Line
Count
Source
1
#include <fuzzer/FuzzedDataProvider.h>
2
3
#include <cassert>
4
#include <cstdio>
5
#include <memory>
6
#include <string>
7
8
#include "ada.cpp"
9
#include "ada.h"
10
11
56.5k
bool is_valid_utf8_string(const char* buf, size_t len) {
12
56.5k
  const uint8_t* data = reinterpret_cast<const uint8_t*>(buf);
13
56.5k
  uint64_t pos = 0;
14
56.5k
  uint32_t code_point = 0;
15
112k
  while (pos < len) {
16
89.1k
    uint64_t next_pos = pos + 16;
17
89.1k
    if (next_pos <= len) {  // if it is safe to read 16 more bytes, check that
18
                            // they are ascii
19
47.3k
      uint64_t v1;
20
47.3k
      std::memcpy(&v1, data + pos, sizeof(uint64_t));
21
47.3k
      uint64_t v2;
22
47.3k
      std::memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
23
47.3k
      uint64_t v{v1 | v2};
24
47.3k
      if ((v & 0x8080808080808080) == 0) {
25
19.1k
        pos = next_pos;
26
19.1k
        continue;
27
19.1k
      }
28
47.3k
    }
29
70.0k
    unsigned char byte = data[pos];
30
265k
    while (byte < 0b10000000) {
31
221k
      if (++pos == len) {
32
26.2k
        return true;
33
26.2k
      }
34
195k
      byte = data[pos];
35
195k
    }
36
37
43.7k
    if ((byte & 0b11100000) == 0b11000000) {
38
8.25k
      next_pos = pos + 2;
39
8.25k
      if (next_pos > len) {
40
484
        return false;
41
484
      }
42
7.77k
      if ((data[pos + 1] & 0b11000000) != 0b10000000) {
43
920
        return false;
44
920
      }
45
6.85k
      code_point = (byte & 0b00011111) << 6 | (data[pos + 1] & 0b00111111);
46
6.85k
      if ((code_point < 0x80) || (0x7ff < code_point)) {
47
51
        return false;
48
51
      }
49
35.4k
    } else if ((byte & 0b11110000) == 0b11100000) {
50
29.3k
      next_pos = pos + 3;
51
29.3k
      if (next_pos > len) {
52
211
        return false;
53
211
      }
54
29.1k
      if ((data[pos + 1] & 0b11000000) != 0b10000000) {
55
417
        return false;
56
417
      }
57
28.7k
      if ((data[pos + 2] & 0b11000000) != 0b10000000) {
58
87
        return false;
59
87
      }
60
28.6k
      code_point = (byte & 0b00001111) << 12 |
61
28.6k
                   (data[pos + 1] & 0b00111111) << 6 |
62
28.6k
                   (data[pos + 2] & 0b00111111);
63
28.6k
      if ((code_point < 0x800) || (0xffff < code_point) ||
64
28.6k
          (0xd7ff < code_point && code_point < 0xe000)) {
65
40
        return false;
66
40
      }
67
28.6k
    } else if ((byte & 0b11111000) == 0b11110000) {  // 0b11110000
68
1.69k
      next_pos = pos + 4;
69
1.69k
      if (next_pos > len) {
70
181
        return false;
71
181
      }
72
1.51k
      if ((data[pos + 1] & 0b11000000) != 0b10000000) {
73
225
        return false;
74
225
      }
75
1.29k
      if ((data[pos + 2] & 0b11000000) != 0b10000000) {
76
46
        return false;
77
46
      }
78
1.24k
      if ((data[pos + 3] & 0b11000000) != 0b10000000) {
79
77
        return false;
80
77
      }
81
1.16k
      code_point =
82
1.16k
          (byte & 0b00000111) << 18 | (data[pos + 1] & 0b00111111) << 12 |
83
1.16k
          (data[pos + 2] & 0b00111111) << 6 | (data[pos + 3] & 0b00111111);
84
1.16k
      if (code_point <= 0xffff || 0x10ffff < code_point) {
85
52
        return false;
86
52
      }
87
4.41k
    } else {
88
4.41k
      return false;
89
4.41k
    }
90
36.5k
    pos = next_pos;
91
36.5k
  }
92
23.1k
  return true;
93
56.5k
}
94
95
// Exercise all getters and boolean predicates on ada::url
96
11.4k
static void exercise_url_predicates(const ada::url& u) {
97
11.4k
  volatile size_t length = 0;
98
11.4k
  length += u.get_href().size();
99
11.4k
  length += u.get_origin().size();
100
11.4k
  length += u.get_protocol().size();
101
11.4k
  length += u.get_username().size();
102
11.4k
  length += u.get_password().size();
103
11.4k
  length += u.get_host().size();
104
11.4k
  length += u.get_hostname().size();
105
11.4k
  length += u.get_pathname().size();
106
11.4k
  length += u.get_search().size();
107
11.4k
  length += u.get_hash().size();
108
11.4k
  length += u.get_port().size();
109
11.4k
  length += u.to_string().size();
110
11.4k
  length += u.get_pathname_length();
111
11.4k
  (void)u.has_valid_domain();
112
11.4k
  (void)u.has_credentials();
113
11.4k
  (void)u.has_empty_hostname();
114
11.4k
  (void)u.has_hostname();
115
11.4k
  (void)u.has_port();
116
11.4k
  (void)u.has_hash();
117
11.4k
  (void)u.has_search();
118
11.4k
  (void)u.get_components();
119
11.4k
}
120
121
// Exercise all getters and boolean predicates on ada::url_aggregator
122
29.7k
static void exercise_aggregator_predicates(const ada::url_aggregator& u) {
123
29.7k
  volatile size_t length = 0;
124
29.7k
  length += u.get_href().size();
125
29.7k
  length += u.get_origin().size();
126
29.7k
  length += u.get_protocol().size();
127
29.7k
  length += u.get_username().size();
128
29.7k
  length += u.get_password().size();
129
29.7k
  length += u.get_host().size();
130
29.7k
  length += u.get_hostname().size();
131
29.7k
  length += u.get_pathname().size();
132
29.7k
  length += u.get_search().size();
133
29.7k
  length += u.get_hash().size();
134
29.7k
  length += u.get_port().size();
135
29.7k
  length += u.to_string().size();
136
29.7k
  length += u.get_pathname_length();
137
29.7k
  (void)u.has_valid_domain();
138
29.7k
  (void)u.has_credentials();
139
29.7k
  (void)u.has_empty_hostname();
140
29.7k
  (void)u.has_hostname();
141
29.7k
  (void)u.has_non_empty_username();
142
29.7k
  (void)u.has_non_empty_password();
143
29.7k
  (void)u.has_password();
144
29.7k
  (void)u.has_port();
145
29.7k
  (void)u.has_hash();
146
29.7k
  (void)u.has_search();
147
29.7k
  (void)u.get_components();
148
29.7k
  volatile bool is_valid = u.validate();
149
29.7k
  (void)is_valid;
150
29.7k
  (void)u.to_diagram();
151
29.7k
}
152
153
19.9k
static std::string make_simple_absolute_candidate(FuzzedDataProvider& fdp) {
154
19.9k
  static constexpr const char* kSchemes[] = {"http://",  "https://", "HTTP://",
155
19.9k
                                             "Https://", "http:",    "https:"};
156
19.9k
  static constexpr const char* kHosts[] = {
157
19.9k
      "example.com",
158
19.9k
      "www.example.com",
159
19.9k
      "WWW.Example.COM",
160
19.9k
      "a",
161
19.9k
      "a.b.c",
162
19.9k
      "192.168.0.1",
163
19.9k
      "0x7f.1",
164
19.9k
      "127.1",
165
19.9k
      "xn--nxasmq6b.com",
166
19.9k
      "xn--a",
167
19.9k
      "user@example.com",
168
19.9k
      "user:pass@example.com",
169
19.9k
      "example.com:8080",
170
19.9k
      "example.com:0",
171
19.9k
      "",
172
19.9k
  };
173
19.9k
  static constexpr const char* kPaths[] = {
174
19.9k
      "",
175
19.9k
      "/",
176
19.9k
      "/path",
177
19.9k
      "/path/file.js",
178
19.9k
      "/a/./b/../c",
179
19.9k
      "/foo/%2e",
180
19.9k
      "/foo/%2e%2e",
181
19.9k
      "/path with space",
182
19.9k
      "/path\twith\ttab",
183
19.9k
      "\\path",
184
19.9k
      "/continue=https%3A%2F%2Fexample.com%2F",
185
19.9k
  };
186
19.9k
  static constexpr const char* kQueries[] = {
187
19.9k
      "",
188
19.9k
      "?",
189
19.9k
      "?q=1",
190
19.9k
      "?hl=en&tab=wi",
191
19.9k
      "?continue=https%3A%2F%2Fexample.com%2F",
192
19.9k
  };
193
19.9k
  static constexpr const char* kFrags[] = {"", "#", "#frag", "#x%20y"};
194
195
19.9k
  std::string out;
196
19.9k
  out += kSchemes[fdp.ConsumeIntegralInRange<size_t>(
197
19.9k
      0, sizeof(kSchemes) / sizeof(kSchemes[0]) - 1)];
198
19.9k
  out += kHosts[fdp.ConsumeIntegralInRange<size_t>(
199
19.9k
      0, sizeof(kHosts) / sizeof(kHosts[0]) - 1)];
200
19.9k
  out += kPaths[fdp.ConsumeIntegralInRange<size_t>(
201
19.9k
      0, sizeof(kPaths) / sizeof(kPaths[0]) - 1)];
202
19.9k
  out += kQueries[fdp.ConsumeIntegralInRange<size_t>(
203
19.9k
      0, sizeof(kQueries) / sizeof(kQueries[0]) - 1)];
204
19.9k
  out += kFrags[fdp.ConsumeIntegralInRange<size_t>(
205
19.9k
      0, sizeof(kFrags) / sizeof(kFrags[0]) - 1)];
206
207
19.9k
  if (fdp.ConsumeBool() && !out.empty()) {
208
838
    std::string mid = fdp.ConsumeRandomLengthString(16);
209
838
    size_t pos = fdp.ConsumeIntegralInRange<size_t>(0, out.size());
210
838
    out.insert(pos, mid);
211
838
  }
212
19.9k
  return out;
213
19.9k
}
214
215
26.7k
static void check_href_size_invariant(const ada::url& u) {
216
26.7k
  if (u.get_href_size() != u.get_href().size()) {
217
0
    printf("get_href_size mismatch (url): size=%zu href.size=%zu href=%s\n",
218
0
           u.get_href_size(), u.get_href().size(), u.get_href().c_str());
219
0
    abort();
220
0
  }
221
26.7k
}
222
223
26.7k
static void check_href_size_invariant(const ada::url_aggregator& u) {
224
26.7k
  if (u.get_href_size() != u.get_href().size()) {
225
0
    printf(
226
0
        "get_href_size mismatch (aggregator): size=%zu href.size=%zu href=%s\n",
227
0
        u.get_href_size(), u.get_href().size(),
228
0
        std::string(u.get_href()).c_str());
229
0
    abort();
230
0
  }
231
26.7k
}
232
233
19.9k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
234
19.9k
  FuzzedDataProvider fdp(data, size);
235
19.9k
  std::string source = fdp.ConsumeRandomLengthString(256);
236
19.9k
  std::string base = fdp.ConsumeRandomLengthString(256);
237
238
  // volatile forces the compiler to store the results without undue
239
  // optimizations
240
19.9k
  volatile size_t length = 0;
241
242
19.9k
  std::string simple_abs = make_simple_absolute_candidate(fdp);
243
19.9k
  {
244
19.9k
    auto su = ada::parse<ada::url>(simple_abs);
245
19.9k
    auto sa = ada::parse<ada::url_aggregator>(simple_abs);
246
19.9k
    if (su.has_value() ^ sa.has_value()) {
247
0
      printf("simple_abs parse agreement fail: %s\n", simple_abs.c_str());
248
0
      abort();
249
0
    }
250
19.9k
    if (su) {
251
19.7k
      if (su->get_href() != std::string(sa->get_href())) {
252
0
        printf("simple_abs href mismatch:\n  in=%s\n  url=%s\n  agg=%s\n",
253
0
               simple_abs.c_str(), su->get_href().c_str(),
254
0
               std::string(sa->get_href()).c_str());
255
0
        abort();
256
0
      }
257
19.7k
      check_href_size_invariant(*su);
258
19.7k
      check_href_size_invariant(*sa);
259
19.7k
      length += su->get_href().size();
260
19.7k
    }
261
19.9k
  }
262
263
19.9k
  auto parse_url = ada::parse<ada::url>(source);
264
19.9k
  auto parse_url_aggregator = ada::parse<ada::url_aggregator>(source);
265
266
19.9k
  if (is_valid_utf8_string(source.data(), source.length())) {
267
16.6k
    if (parse_url.has_value() ^ parse_url_aggregator.has_value()) {
268
0
      printf("Source used to parse: %s", source.c_str());
269
0
      abort();
270
0
    }
271
16.6k
  }
272
273
19.9k
  if (parse_url) {
274
6.95k
    length += parse_url->get_href().size();
275
6.95k
    length += parse_url->get_origin().size();
276
6.95k
    check_href_size_invariant(*parse_url);
277
6.95k
  }
278
279
19.9k
  if (parse_url_aggregator) {
280
6.95k
    length += parse_url_aggregator->get_href().size();
281
6.95k
    length += parse_url_aggregator->get_origin().size();
282
6.95k
    check_href_size_invariant(*parse_url_aggregator);
283
284
6.95k
    volatile bool is_parse_url_aggregator_output_valid = false;
285
6.95k
    is_parse_url_aggregator_output_valid = parse_url_aggregator->validate();
286
287
6.95k
    assert(parse_url->get_protocol() == parse_url_aggregator->get_protocol());
288
6.95k
    assert(parse_url->get_href() == parse_url_aggregator->get_href());
289
6.95k
    assert(std::string(parse_url->get_hostname()) ==
290
6.95k
           std::string(parse_url_aggregator->get_hostname()));
291
6.95k
    assert(std::string(parse_url->get_pathname()) ==
292
6.95k
           std::string(parse_url_aggregator->get_pathname()));
293
6.95k
    assert(std::string(parse_url->get_search()) ==
294
6.95k
           std::string(parse_url_aggregator->get_search()));
295
6.95k
    assert(std::string(parse_url->get_hash()) ==
296
6.95k
           std::string(parse_url_aggregator->get_hash()));
297
6.95k
    assert(std::string(parse_url->get_port()) ==
298
6.95k
           std::string(parse_url_aggregator->get_port()));
299
6.95k
    assert(parse_url->get_username() ==
300
6.95k
           std::string(parse_url_aggregator->get_username()));
301
6.95k
    assert(parse_url->get_password() ==
302
6.95k
           std::string(parse_url_aggregator->get_password()));
303
6.95k
    assert(std::string(parse_url->get_host()) ==
304
6.95k
           std::string(parse_url_aggregator->get_host()));
305
306
    // Exercise all predicates on both types
307
6.95k
    exercise_url_predicates(*parse_url);
308
6.95k
    exercise_aggregator_predicates(*parse_url_aggregator);
309
310
    // Test set_href consistency
311
6.95k
    parse_url->set_href(source);
312
6.95k
    parse_url_aggregator->set_href(source);
313
6.95k
    assert(parse_url->get_href() == parse_url_aggregator->get_href());
314
6.95k
  }
315
316
  /**
317
   * Test copy and move semantics
318
   */
319
19.9k
  if (parse_url) {
320
    // Copy constructor
321
6.95k
    ada::url copied_url = *parse_url;
322
6.95k
    assert(copied_url.get_href() == parse_url->get_href());
323
324
    // Copy assignment
325
6.95k
    ada::url assigned_url;
326
6.95k
    assigned_url = *parse_url;
327
6.95k
    assert(assigned_url.get_href() == parse_url->get_href());
328
329
    // Move constructor
330
6.95k
    ada::url moved_url = std::move(copied_url);
331
6.95k
    assert(moved_url.get_href() == parse_url->get_href());
332
6.95k
  }
333
334
19.9k
  if (parse_url_aggregator) {
335
    // Copy constructor
336
6.95k
    ada::url_aggregator copied_agg = *parse_url_aggregator;
337
6.95k
    assert(std::string(copied_agg.get_href()) ==
338
6.95k
           std::string(parse_url_aggregator->get_href()));
339
340
    // Copy assignment
341
6.95k
    ada::url_aggregator assigned_agg;
342
6.95k
    assigned_agg = *parse_url_aggregator;
343
6.95k
    assert(std::string(assigned_agg.get_href()) ==
344
6.95k
           std::string(parse_url_aggregator->get_href()));
345
346
    // Move constructor
347
6.95k
    ada::url_aggregator moved_agg = std::move(copied_agg);
348
6.95k
    assert(std::string(moved_agg.get_href()) ==
349
6.95k
           std::string(parse_url_aggregator->get_href()));
350
351
    // Move assignment
352
6.95k
    ada::url_aggregator move_assigned_agg;
353
6.95k
    move_assigned_agg = std::move(assigned_agg);
354
6.95k
    assert(std::string(move_assigned_agg.get_href()) ==
355
6.95k
           std::string(parse_url_aggregator->get_href()));
356
6.95k
  }
357
358
  /**
359
   * ada::parse<ada::url>
360
   */
361
19.9k
  auto out_url = ada::parse<ada::url>("https://www.ada-url.com");
362
363
19.9k
  if (out_url) {
364
19.9k
    out_url->set_protocol(source);
365
19.9k
    out_url->set_username(source);
366
19.9k
    out_url->set_password(source);
367
19.9k
    out_url->set_hostname(source);
368
19.9k
    out_url->set_host(source);
369
19.9k
    out_url->set_pathname(source);
370
19.9k
    out_url->set_search(source);
371
19.9k
    out_url->set_hash(source);
372
19.9k
    out_url->set_port(source);
373
374
    // getters
375
19.9k
    length += out_url->get_protocol().size();
376
19.9k
    length += out_url->get_username().size();
377
19.9k
    length += out_url->get_password().size();
378
19.9k
    length += out_url->get_hostname().size();
379
19.9k
    length += out_url->get_host().size();
380
19.9k
    length += out_url->get_pathname().size();
381
19.9k
    length += out_url->get_search().size();
382
19.9k
    length += out_url->get_hash().size();
383
19.9k
    length += out_url->get_origin().size();
384
19.9k
    length += out_url->get_port().size();
385
19.9k
    length += out_url->get_pathname_length();
386
387
19.9k
    length += out_url->to_string().size();
388
389
    // boolean predicates after setters
390
19.9k
    (void)out_url->has_valid_domain();
391
19.9k
    (void)out_url->has_credentials();
392
19.9k
    (void)out_url->has_empty_hostname();
393
19.9k
    (void)out_url->has_hostname();
394
19.9k
    (void)out_url->has_port();
395
19.9k
    (void)out_url->has_hash();
396
19.9k
    (void)out_url->has_search();
397
19.9k
    (void)out_url->get_components();
398
19.9k
  }
399
400
  /**
401
   * ada::parse<ada::url_aggregator>
402
   */
403
19.9k
  auto out_aggregator =
404
19.9k
      ada::parse<ada::url_aggregator>("https://www.ada-url.com");
405
406
19.9k
  if (out_aggregator) {
407
19.9k
    out_aggregator->set_protocol(source);
408
19.9k
    out_aggregator->set_username(source);
409
19.9k
    out_aggregator->set_password(source);
410
19.9k
    out_aggregator->set_hostname(source);
411
19.9k
    out_aggregator->set_host(source);
412
19.9k
    out_aggregator->set_pathname(source);
413
19.9k
    out_aggregator->set_search(source);
414
19.9k
    out_aggregator->set_hash(source);
415
19.9k
    out_aggregator->set_port(source);
416
417
    // getters
418
19.9k
    length += out_aggregator->get_protocol().size();
419
19.9k
    length += out_aggregator->get_username().size();
420
19.9k
    length += out_aggregator->get_password().size();
421
19.9k
    length += out_aggregator->get_hostname().size();
422
19.9k
    length += out_aggregator->get_host().size();
423
19.9k
    length += out_aggregator->get_pathname().size();
424
19.9k
    length += out_aggregator->get_search().size();
425
19.9k
    length += out_aggregator->get_hash().size();
426
19.9k
    length += out_aggregator->get_origin().size();
427
19.9k
    length += out_aggregator->get_port().size();
428
19.9k
    length += out_aggregator->get_pathname_length();
429
430
19.9k
    length += out_aggregator->to_string().size();
431
432
19.9k
    volatile bool is_output_valid = false;
433
19.9k
    is_output_valid = out_aggregator->validate();
434
435
19.9k
    (void)out_aggregator->to_diagram();
436
437
    // boolean predicates after setters
438
19.9k
    (void)out_aggregator->has_valid_domain();
439
19.9k
    (void)out_aggregator->has_credentials();
440
19.9k
    (void)out_aggregator->has_empty_hostname();
441
19.9k
    (void)out_aggregator->has_hostname();
442
19.9k
    (void)out_aggregator->has_non_empty_username();
443
19.9k
    (void)out_aggregator->has_non_empty_password();
444
19.9k
    (void)out_aggregator->has_password();
445
19.9k
    (void)out_aggregator->has_port();
446
19.9k
    (void)out_aggregator->has_hash();
447
19.9k
    (void)out_aggregator->has_search();
448
19.9k
    (void)out_aggregator->get_components();
449
450
    // clear methods + postcondition assertions
451
19.9k
    out_aggregator->clear_port();
452
19.9k
    if (out_aggregator->has_port()) {
453
0
      printf("clear_port() did not clear has_port()\n");
454
0
      abort();
455
0
    }
456
19.9k
    if (!out_aggregator->get_port().empty()) {
457
0
      printf("clear_port() left non-empty get_port()\n");
458
0
      abort();
459
0
    }
460
461
19.9k
    out_aggregator->clear_search();
462
19.9k
    if (out_aggregator->has_search()) {
463
0
      printf("clear_search() did not clear has_search()\n");
464
0
      abort();
465
0
    }
466
19.9k
    if (!out_aggregator->get_search().empty()) {
467
0
      printf("clear_search() left non-empty get_search()\n");
468
0
      abort();
469
0
    }
470
471
19.9k
    out_aggregator->clear_hash();
472
19.9k
    if (out_aggregator->has_hash()) {
473
0
      printf("clear_hash() did not clear has_hash()\n");
474
0
      abort();
475
0
    }
476
19.9k
    if (!out_aggregator->get_hash().empty()) {
477
0
      printf("clear_hash() left non-empty get_hash()\n");
478
0
      abort();
479
0
    }
480
19.9k
  }
481
482
  /**
483
   * Relative URL parsing with base (tests the base URL resolution code path)
484
   */
485
19.9k
  auto base_url = ada::parse<ada::url>(base);
486
19.9k
  auto base_agg = ada::parse<ada::url_aggregator>(base);
487
488
19.9k
  if (base_url) {
489
5.03k
    auto result = ada::parse<ada::url>(source, &*base_url);
490
5.03k
    if (result) {
491
4.47k
      length += result->get_href().size();
492
4.47k
      length += result->get_origin().size();
493
4.47k
      exercise_url_predicates(*result);
494
4.47k
    }
495
5.03k
  }
496
497
19.9k
  if (base_agg) {
498
5.03k
    auto result = ada::parse<ada::url_aggregator>(source, &*base_agg);
499
5.03k
    if (result) {
500
4.47k
      length += result->get_href().size();
501
4.47k
      length += result->get_origin().size();
502
4.47k
      exercise_aggregator_predicates(*result);
503
4.47k
    }
504
5.03k
  }
505
506
  // Cross-type consistency: relative URL parsing with a base should agree
507
  // between url and url_aggregator representations for valid UTF-8 inputs.
508
19.9k
  if (is_valid_utf8_string(source.data(), source.length()) &&
509
16.6k
      is_valid_utf8_string(base.data(), base.length()) && base_url &&
510
3.81k
      base_agg) {
511
3.81k
    auto res_url = ada::parse<ada::url>(source, &*base_url);
512
3.81k
    auto res_agg = ada::parse<ada::url_aggregator>(source, &*base_agg);
513
3.81k
    if (res_url.has_value() ^ res_agg.has_value()) {
514
0
      printf("Relative parse inconsistency for source=%s base=%s\n",
515
0
             source.c_str(), base.c_str());
516
0
      abort();
517
0
    }
518
3.81k
    if (res_url && res_agg) {
519
3.29k
      if (res_url->get_href() != std::string(res_agg->get_href())) {
520
0
        printf("Relative parse href mismatch for source=%s base=%s\n",
521
0
               source.c_str(), base.c_str());
522
0
        abort();
523
0
      }
524
3.29k
    }
525
3.81k
  }
526
527
  /**
528
   * Chained relative URL resolution: parse source against base, then use the
529
   * result as the base for a second parse. Exercises multi-level inheritance.
530
   */
531
19.9k
  if (base_agg) {
532
5.03k
    auto level1 = ada::parse<ada::url_aggregator>(source, &*base_agg);
533
5.03k
    if (level1) {
534
4.47k
      std::string input2 = fdp.ConsumeRandomLengthString(128);
535
4.47k
      auto level2 = ada::parse<ada::url_aggregator>(input2, &*level1);
536
4.47k
      if (level2) {
537
3.45k
        length += level2->get_href().size();
538
3.45k
        volatile bool v = level2->validate();
539
3.45k
        (void)v;
540
3.45k
      }
541
4.47k
    }
542
5.03k
  }
543
544
  /**
545
   * Known-good base URL with fuzzed relative input. Using a fixed valid base
546
   * lets the fuzzer focus entropy entirely on the relative-input code paths
547
   * (path resolution, query/fragment inheritance, scheme-relative URLs, etc.)
548
   */
549
19.9k
  {
550
19.9k
    auto known_base =
551
19.9k
        ada::parse<ada::url_aggregator>("https://example.com/a/b/c?query#hash");
552
19.9k
    if (known_base) {
553
19.9k
      auto result = ada::parse<ada::url_aggregator>(source, &*known_base);
554
19.9k
      if (result) {
555
18.2k
        length += result->get_href().size();
556
18.2k
        exercise_aggregator_predicates(*result);
557
18.2k
      }
558
19.9k
    }
559
19.9k
  }
560
561
  /**
562
   * Node.js specific
563
   */
564
19.9k
  length += ada::href_from_file(source).size();
565
566
  /**
567
   * Others
568
   */
569
19.9k
  bool is_valid = ada::checkers::verify_dns_length(source);
570
571
19.9k
  (void)is_valid;
572
573
  /**
574
   * Sequential setter interactions with FDP-controlled ordering.
575
   *
576
   * The existing code calls every setter with the same `source` value in a
577
   * fixed order. Here we let the fuzzer choose an arbitrary sequence of
578
   * setter/value pairs, checking that url and url_aggregator stay in sync
579
   * after every step. This exercises setter-interaction state bugs that
580
   * fixed-order testing would miss.
581
   */
582
19.9k
  {
583
19.9k
    auto url_seq = ada::parse<ada::url>(
584
19.9k
        "https://user:pass@example.com:8080/path?query=1#hash");
585
19.9k
    auto agg_seq = ada::parse<ada::url_aggregator>(
586
19.9k
        "https://user:pass@example.com:8080/path?query=1#hash");
587
19.9k
    if (url_seq && agg_seq) {
588
19.9k
      int steps = fdp.ConsumeIntegralInRange(1, 8);
589
63.0k
      for (int i = 0; i < steps; ++i) {
590
43.1k
        std::string val = fdp.ConsumeRandomLengthString(64);
591
43.1k
        int which = fdp.ConsumeIntegralInRange(0, 8);
592
43.1k
        switch (which) {
593
27.2k
          case 0:
594
27.2k
            url_seq->set_protocol(val);
595
27.2k
            agg_seq->set_protocol(val);
596
27.2k
            break;
597
1.84k
          case 1:
598
1.84k
            url_seq->set_username(val);
599
1.84k
            agg_seq->set_username(val);
600
1.84k
            break;
601
2.00k
          case 2:
602
2.00k
            url_seq->set_password(val);
603
2.00k
            agg_seq->set_password(val);
604
2.00k
            break;
605
3.23k
          case 3:
606
3.23k
            url_seq->set_hostname(val);
607
3.23k
            agg_seq->set_hostname(val);
608
3.23k
            break;
609
4.28k
          case 4:
610
4.28k
            url_seq->set_host(val);
611
4.28k
            agg_seq->set_host(val);
612
4.28k
            break;
613
1.69k
          case 5:
614
1.69k
            url_seq->set_pathname(val);
615
1.69k
            agg_seq->set_pathname(val);
616
1.69k
            break;
617
659
          case 6:
618
659
            url_seq->set_search(val);
619
659
            agg_seq->set_search(val);
620
659
            break;
621
414
          case 7:
622
414
            url_seq->set_hash(val);
623
414
            agg_seq->set_hash(val);
624
414
            break;
625
1.72k
          case 8:
626
1.72k
            url_seq->set_port(val);
627
1.72k
            agg_seq->set_port(val);
628
1.72k
            break;
629
43.1k
        }
630
        // After every setter both representations must agree on href.
631
43.1k
        if (url_seq->get_href() != std::string(agg_seq->get_href())) {
632
0
          printf(
633
0
              "Sequential setter href mismatch after setter=%d val='%s'\n"
634
0
              "  url:  %s\n  agg:  %s\n",
635
0
              which, val.c_str(), url_seq->get_href().c_str(),
636
0
              std::string(agg_seq->get_href()).c_str());
637
0
          abort();
638
0
        }
639
        // url_aggregator internal invariant must still hold.
640
43.1k
        volatile bool v = agg_seq->validate();
641
43.1k
        (void)v;
642
43.1k
      }
643
19.9k
    }
644
19.9k
  }
645
646
  /**
647
   * Re-parse idempotency.
648
   *
649
   * If parse(source) succeeds, then parse(href) must also succeed and
650
   * produce the same href. Serialization and parsing must be consistent:
651
   * a normalized URL is always its own fixed point.
652
   */
653
19.9k
  if (parse_url_aggregator) {
654
6.95k
    std::string href1 = std::string(parse_url_aggregator->get_href());
655
6.95k
    auto reparsed = ada::parse<ada::url_aggregator>(href1);
656
6.95k
    if (!reparsed) {
657
0
      printf("Re-parse of href failed unexpectedly: '%s'\n", href1.c_str());
658
0
      abort();
659
0
    }
660
6.95k
    std::string href2 = std::string(reparsed->get_href());
661
6.95k
    if (href1 != href2) {
662
0
      printf(
663
0
          "Re-parse idempotency failure!\n"
664
0
          "  href1: %s\n  href2: %s\n",
665
0
          href1.c_str(), href2.c_str());
666
0
      abort();
667
0
    }
668
6.95k
  }
669
670
  /**
671
   * URL search params round-trip via URL integration.
672
   *
673
   * Construct a URL whose query is the fuzz source, extract the search
674
   * component as a url_search_params, mutate it, serialise it back, and
675
   * set it on the URL. Exercises the interaction between URL objects and
676
   * url_search_params and verifies that the combined pipeline doesn't crash.
677
   *
678
   * Also verifies the url_search_params serialisation idempotency property:
679
   *   url_search_params(sp.to_string()).to_string() == sp.to_string()
680
   */
681
19.9k
  {
682
19.9k
    std::string search_url = "https://example.com/?" + source;
683
19.9k
    auto url_with_search = ada::parse<ada::url_aggregator>(search_url);
684
19.9k
    if (url_with_search) {
685
      // Extract the search string (may include leading '?').
686
19.9k
      std::string search_raw = std::string(url_with_search->get_search());
687
19.9k
      std::string_view search_view = search_raw;
688
19.9k
      if (!search_view.empty() && search_view[0] == '?') {
689
16.3k
        search_view = search_view.substr(1);
690
16.3k
      }
691
692
19.9k
      ada::url_search_params sp(search_view);
693
694
      // Mutate with additional entries from the fuzz corpus.
695
19.9k
      sp.append(source, base);
696
697
19.9k
      std::string serialized = sp.to_string();
698
699
      // Idempotency: re-parsing the serialised form must yield the same string.
700
19.9k
      ada::url_search_params sp2(serialized);
701
19.9k
      std::string serialized2 = sp2.to_string();
702
19.9k
      if (serialized2 != serialized) {
703
0
        printf(
704
0
            "url_search_params serialisation not idempotent!\n"
705
0
            "  first:  %s\n  second: %s\n",
706
0
            serialized.c_str(), serialized2.c_str());
707
0
        abort();
708
0
      }
709
710
      // Set the serialised params back on the URL.
711
19.9k
      url_with_search->set_search(serialized);
712
19.9k
      volatile bool v = url_with_search->validate();
713
19.9k
      (void)v;
714
19.9k
    }
715
19.9k
  }
716
717
19.9k
  return 0;
718
19.9k
}  // extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {