Coverage Report

Created: 2023-04-25 07:07

/src/wasm-tools/crates/wasm-smith/src/lib.rs
Line
Count
Source (jump to first uncovered line)
1
//! A WebAssembly test case generator.
2
//!
3
//! ## Usage
4
//!
5
//! First, use [`cargo fuzz`](https://github.com/rust-fuzz/cargo-fuzz) to define
6
//! a new fuzz target:
7
//!
8
//! ```shell
9
//! $ cargo fuzz add my_wasm_smith_fuzz_target
10
//! ```
11
//!
12
//! Next, add `wasm-smith` to your dependencies:
13
//!
14
//! ```shell
15
//! $ cargo add wasm-smith
16
//! ```
17
//!
18
//! Then, define your fuzz target so that it takes arbitrary
19
//! `wasm_smith::Module`s as an argument, convert the module into serialized
20
//! Wasm bytes via the `to_bytes` method, and then feed it into your system:
21
//!
22
//! ```no_run
23
//! // fuzz/fuzz_targets/my_wasm_smith_fuzz_target.rs
24
//!
25
//! #![no_main]
26
//!
27
//! use libfuzzer_sys::fuzz_target;
28
//! use wasm_smith::Module;
29
//!
30
//! fuzz_target!(|module: Module| {
31
//!     let wasm_bytes = module.to_bytes();
32
//!
33
//!     // Your code here...
34
//! });
35
//! ```
36
//!
37
//! Finally, start fuzzing:
38
//!
39
//! ```shell
40
//! $ cargo fuzz run my_wasm_smith_fuzz_target
41
//! ```
42
//!
43
//! > **Note:** For a real world example, also check out [the `validate` fuzz
44
//! > target](https://github.com/fitzgen/wasm-smith/blob/main/fuzz/fuzz_targets/validate.rs)
45
//! > defined in this repository. Using the `wasmparser` crate, it checks that
46
//! > every module generated by `wasm-smith` validates successfully.
47
//!
48
//! ## Design
49
//!
50
//! The design and implementation strategy of wasm-smith is outlined in
51
//! [this article](https://fitzgeraldnick.com/2020/08/24/writing-a-test-case-generator.html).
52
53
#![deny(missing_docs, missing_debug_implementations)]
54
// Needed for the `instructions!` macro in `src/code_builder.rs`.
55
#![recursion_limit = "512"]
56
57
mod component;
58
mod config;
59
mod core;
60
61
pub use crate::core::{
62
    ConfiguredModule, InstructionKind, InstructionKinds, MaybeInvalidModule, Module,
63
};
64
use arbitrary::{Result, Unstructured};
65
pub use component::{Component, ConfiguredComponent};
66
pub use config::{Config, DefaultConfig, SwarmConfig};
67
use std::{collections::HashSet, fmt::Write, str};
68
use wasmparser::types::{KebabStr, KebabString};
69
70
/// Do something an arbitrary number of times.
71
///
72
/// The callback can return `false` to exit the loop early.
73
2.01M
pub(crate) fn arbitrary_loop<'a>(
74
2.01M
    u: &mut Unstructured<'a>,
75
2.01M
    min: usize,
76
2.01M
    max: usize,
77
2.01M
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
2.01M
) -> Result<()> {
79
2.01M
    assert!(max >= min);
80
2.01M
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
2.01M
    for _ in 0..(max - min) {
86
30.4M
        let keep_going = u.arbitrary().unwrap_or(false);
87
30.4M
        if !keep_going {
88
1.52M
            break;
89
28.9M
        }
90
28.9M
91
28.9M
        if !f(u)? {
92
5.63k
            break;
93
28.9M
        }
94
    }
95
96
2.01M
    Ok(())
97
2.01M
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_tags::{closure#0}>
Line
Count
Source
73
4.85k
pub(crate) fn arbitrary_loop<'a>(
74
4.85k
    u: &mut Unstructured<'a>,
75
4.85k
    min: usize,
76
4.85k
    max: usize,
77
4.85k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
4.85k
) -> Result<()> {
79
4.85k
    assert!(max >= min);
80
4.85k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
4.85k
    for _ in 0..(max - min) {
86
65.1k
        let keep_going = u.arbitrary().unwrap_or(false);
87
65.1k
        if !keep_going {
88
4.38k
            break;
89
60.8k
        }
90
60.8k
91
60.8k
        if !f(u)? {
92
220
            break;
93
60.5k
        }
94
    }
95
96
4.85k
    Ok(())
97
4.85k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_variant_type::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_component_type::{closure#0}::{closure#0}>
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_types::{closure#0}>
Line
Count
Source
73
38.2k
pub(crate) fn arbitrary_loop<'a>(
74
38.2k
    u: &mut Unstructured<'a>,
75
38.2k
    min: usize,
76
38.2k
    max: usize,
77
38.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
38.2k
) -> Result<()> {
79
38.2k
    assert!(max >= min);
80
38.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
38.2k
    for _ in 0..(max - min) {
86
353k
        let keep_going = u.arbitrary().unwrap_or(false);
87
353k
        if !keep_going {
88
36.6k
            break;
89
317k
        }
90
317k
91
317k
        if !f(u)? {
92
0
            break;
93
317k
        }
94
    }
95
96
38.2k
    Ok(())
97
38.2k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_tables::{closure#0}>
Line
Count
Source
73
38.2k
pub(crate) fn arbitrary_loop<'a>(
74
38.2k
    u: &mut Unstructured<'a>,
75
38.2k
    min: usize,
76
38.2k
    max: usize,
77
38.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
38.2k
) -> Result<()> {
79
38.2k
    assert!(max >= min);
80
38.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
38.2k
    for _ in 0..(max - min) {
86
118k
        let keep_going = u.arbitrary().unwrap_or(false);
87
118k
        if !keep_going {
88
27.0k
            break;
89
91.1k
        }
90
91.1k
91
91.1k
        if !f(u)? {
92
2.42k
            break;
93
88.7k
        }
94
    }
95
96
38.2k
    Ok(())
97
38.2k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_imports::{closure#0}>
Line
Count
Source
73
38.2k
pub(crate) fn arbitrary_loop<'a>(
74
38.2k
    u: &mut Unstructured<'a>,
75
38.2k
    min: usize,
76
38.2k
    max: usize,
77
38.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
38.2k
) -> Result<()> {
79
38.2k
    assert!(max >= min);
80
38.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
38.2k
    for _ in 0..(max - min) {
86
303k
        let keep_going = u.arbitrary().unwrap_or(false);
87
303k
        if !keep_going {
88
33.6k
            break;
89
269k
        }
90
269k
91
269k
        if !f(u)? {
92
774
            break;
93
268k
        }
94
    }
95
96
38.2k
    Ok(())
97
38.2k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_flags_type::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_import_section::{closure#0}>
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_globals::{closure#0}>
Line
Count
Source
73
38.2k
pub(crate) fn arbitrary_loop<'a>(
74
38.2k
    u: &mut Unstructured<'a>,
75
38.2k
    min: usize,
76
38.2k
    max: usize,
77
38.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
38.2k
) -> Result<()> {
79
38.2k
    assert!(max >= min);
80
38.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
38.2k
    for _ in 0..(max - min) {
86
348k
        let keep_going = u.arbitrary().unwrap_or(false);
87
348k
        if !keep_going {
88
35.4k
            break;
89
313k
        }
90
313k
91
313k
        if !f(u)? {
92
345
            break;
93
313k
        }
94
    }
95
96
38.2k
    Ok(())
97
38.2k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_elems::{closure#7}::{closure#2}>
Line
Count
Source
73
94.3k
pub(crate) fn arbitrary_loop<'a>(
74
94.3k
    u: &mut Unstructured<'a>,
75
94.3k
    min: usize,
76
94.3k
    max: usize,
77
94.3k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
94.3k
) -> Result<()> {
79
94.3k
    assert!(max >= min);
80
94.3k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
94.3k
    for _ in 0..(max - min) {
86
9.69M
        let keep_going = u.arbitrary().unwrap_or(false);
87
9.69M
        if !keep_going {
88
43.6k
            break;
89
9.64M
        }
90
9.64M
91
9.64M
        if !f(u)? {
92
0
            break;
93
9.64M
        }
94
    }
95
96
94.3k
    Ok(())
97
94.3k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_elems::{closure#7}>
Line
Count
Source
73
25.2k
pub(crate) fn arbitrary_loop<'a>(
74
25.2k
    u: &mut Unstructured<'a>,
75
25.2k
    min: usize,
76
25.2k
    max: usize,
77
25.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
25.2k
) -> Result<()> {
79
25.2k
    assert!(max >= min);
80
25.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
25.2k
    for _ in 0..(max - min) {
86
191k
        let keep_going = u.arbitrary().unwrap_or(false);
87
191k
        if !keep_going {
88
22.6k
            break;
89
168k
        }
90
168k
91
168k
        if !f(u)? {
92
0
            break;
93
168k
        }
94
    }
95
96
25.2k
    Ok(())
97
25.2k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_memories::{closure#0}>
Line
Count
Source
73
38.2k
pub(crate) fn arbitrary_loop<'a>(
74
38.2k
    u: &mut Unstructured<'a>,
75
38.2k
    min: usize,
76
38.2k
    max: usize,
77
38.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
38.2k
) -> Result<()> {
79
38.2k
    assert!(max >= min);
80
38.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
38.2k
    for _ in 0..(max - min) {
86
131k
        let keep_going = u.arbitrary().unwrap_or(false);
87
131k
        if !keep_going {
88
28.5k
            break;
89
102k
        }
90
102k
91
102k
        if !f(u)? {
92
564
            break;
93
102k
        }
94
    }
95
96
38.2k
    Ok(())
97
38.2k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_record_type::{closure#0}>
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_locals::{closure#0}>
Line
Count
Source
73
902k
pub(crate) fn arbitrary_loop<'a>(
74
902k
    u: &mut Unstructured<'a>,
75
902k
    min: usize,
76
902k
    max: usize,
77
902k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
902k
) -> Result<()> {
79
902k
    assert!(max >= min);
80
902k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
902k
    for _ in 0..(max - min) {
86
6.95M
        let keep_going = u.arbitrary().unwrap_or(false);
87
6.95M
        if !keep_going {
88
859k
            break;
89
6.09M
        }
90
6.09M
91
6.09M
        if !f(u)? {
92
0
            break;
93
6.09M
        }
94
    }
95
96
902k
    Ok(())
97
902k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_enum_type::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_tuple_type::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_module_type::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_canonical_section::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_core_type_section::{closure#0}>
wasm_smith::arbitrary_loop::<wasm_smith::core::arbitrary_func_type::{closure#1}>
Line
Count
Source
73
317k
pub(crate) fn arbitrary_loop<'a>(
74
317k
    u: &mut Unstructured<'a>,
75
317k
    min: usize,
76
317k
    max: usize,
77
317k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
317k
) -> Result<()> {
79
317k
    assert!(max >= min);
80
317k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
317k
    for _ in 0..(max - min) {
86
3.17M
        let keep_going = u.arbitrary().unwrap_or(false);
87
3.17M
        if !keep_going {
88
132k
            break;
89
3.04M
        }
90
3.04M
91
3.04M
        if !f(u)? {
92
0
            break;
93
3.04M
        }
94
    }
95
96
317k
    Ok(())
97
317k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_data::{closure#4}>
Line
Count
Source
73
30.6k
pub(crate) fn arbitrary_loop<'a>(
74
30.6k
    u: &mut Unstructured<'a>,
75
30.6k
    min: usize,
76
30.6k
    max: usize,
77
30.6k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
30.6k
) -> Result<()> {
79
30.6k
    assert!(max >= min);
80
30.6k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
30.6k
    for _ in 0..(max - min) {
86
131k
        let keep_going = u.arbitrary().unwrap_or(false);
87
131k
        if !keep_going {
88
28.8k
            break;
89
102k
        }
90
102k
91
102k
        if !f(u)? {
92
0
            break;
93
102k
        }
94
    }
95
96
30.6k
    Ok(())
97
30.6k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_type_section::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_instance_type::{closure#0}::{closure#0}>
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_funcs::{closure#0}>
Line
Count
Source
73
34.5k
pub(crate) fn arbitrary_loop<'a>(
74
34.5k
    u: &mut Unstructured<'a>,
75
34.5k
    min: usize,
76
34.5k
    max: usize,
77
34.5k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
34.5k
) -> Result<()> {
79
34.5k
    assert!(max >= min);
80
34.5k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
34.5k
    for _ in 0..(max - min) {
86
935k
        let keep_going = u.arbitrary().unwrap_or(false);
87
935k
        if !keep_going {
88
32.7k
            break;
89
902k
        }
90
902k
91
902k
        if !f(u)? {
92
568
            break;
93
902k
        }
94
    }
95
96
34.5k
    Ok(())
97
34.5k
}
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_exports::{closure#4}>
Line
Count
Source
73
38.2k
pub(crate) fn arbitrary_loop<'a>(
74
38.2k
    u: &mut Unstructured<'a>,
75
38.2k
    min: usize,
76
38.2k
    max: usize,
77
38.2k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
38.2k
) -> Result<()> {
79
38.2k
    assert!(max >= min);
80
38.2k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
38.2k
    for _ in 0..(max - min) {
86
215k
        let keep_going = u.arbitrary().unwrap_or(false);
87
215k
        if !keep_going {
88
34.0k
            break;
89
181k
        }
90
181k
91
181k
        if !f(u)? {
92
743
            break;
93
180k
        }
94
    }
95
96
38.2k
    Ok(())
97
38.2k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_func_type::{closure#0}>
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_union_type::{closure#0}>
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_elems::{closure#7}::{closure#3}>
Line
Count
Source
73
59.3k
pub(crate) fn arbitrary_loop<'a>(
74
59.3k
    u: &mut Unstructured<'a>,
75
59.3k
    min: usize,
76
59.3k
    max: usize,
77
59.3k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
59.3k
) -> Result<()> {
79
59.3k
    assert!(max >= min);
80
59.3k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
59.3k
    for _ in 0..(max - min) {
86
4.33M
        let keep_going = u.arbitrary().unwrap_or(false);
87
4.33M
        if !keep_going {
88
31.4k
            break;
89
4.30M
        }
90
4.30M
91
4.30M
        if !f(u)? {
92
0
            break;
93
4.30M
        }
94
    }
95
96
59.3k
    Ok(())
97
59.3k
}
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_func_type::{closure#1}>
wasm_smith::arbitrary_loop::<wasm_smith::core::arbitrary_func_type::{closure#0}>
Line
Count
Source
73
317k
pub(crate) fn arbitrary_loop<'a>(
74
317k
    u: &mut Unstructured<'a>,
75
317k
    min: usize,
76
317k
    max: usize,
77
317k
    mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
78
317k
) -> Result<()> {
79
317k
    assert!(max >= min);
80
317k
    for _ in 0..min {
81
0
        if !f(u)? {
82
0
            return Err(arbitrary::Error::IncorrectFormat);
83
0
        }
84
    }
85
317k
    for _ in 0..(max - min) {
86
3.50M
        let keep_going = u.arbitrary().unwrap_or(false);
87
3.50M
        if !keep_going {
88
174k
            break;
89
3.32M
        }
90
3.32M
91
3.32M
        if !f(u)? {
92
0
            break;
93
3.32M
        }
94
    }
95
96
317k
    Ok(())
97
317k
}
98
99
// Mirror what happens in `Arbitrary for String`, but do so with a clamped size.
100
718k
pub(crate) fn limited_str<'a>(max_size: usize, u: &mut Unstructured<'a>) -> Result<&'a str> {
101
718k
    let size = u.arbitrary_len::<u8>()?;
102
718k
    let size = std::cmp::min(size, max_size);
103
718k
    match str::from_utf8(u.peek_bytes(size).unwrap()) {
104
91.3k
        Ok(s) => {
105
91.3k
            u.bytes(size).unwrap();
106
91.3k
            Ok(s)
107
        }
108
626k
        Err(e) => {
109
626k
            let i = e.valid_up_to();
110
626k
            let valid = u.bytes(i).unwrap();
111
626k
            let s = unsafe {
112
0
                debug_assert!(str::from_utf8(valid).is_ok());
113
626k
                str::from_utf8_unchecked(valid)
114
626k
            };
115
626k
            Ok(s)
116
        }
117
    }
118
718k
}
119
120
718k
pub(crate) fn limited_string(max_size: usize, u: &mut Unstructured) -> Result<String> {
121
718k
    Ok(limited_str(max_size, u)?.into())
122
718k
}
123
124
180k
pub(crate) fn unique_string(
125
180k
    max_size: usize,
126
180k
    names: &mut HashSet<String>,
127
180k
    u: &mut Unstructured,
128
180k
) -> Result<String> {
129
180k
    let mut name = limited_string(max_size, u)?;
130
330k
    while names.contains(&name) {
131
149k
        write!(&mut name, "{}", names.len()).unwrap();
132
149k
    }
133
180k
    names.insert(name.clone());
134
180k
    Ok(name)
135
180k
}
136
137
0
pub(crate) fn unique_kebab_string(
138
0
    max_size: usize,
139
0
    names: &mut HashSet<KebabString>,
140
0
    u: &mut Unstructured,
141
0
) -> Result<KebabString> {
142
0
    let size = std::cmp::min(u.arbitrary_len::<u8>()?, max_size);
143
0
    let mut name = String::with_capacity(size);
144
0
    let mut require_alpha = true;
145
0
    for _ in 0..size {
146
0
        name.push(match u.int_in_range::<u8>(0..=36)? {
147
0
            x if (0..26).contains(&x) => {
148
0
                require_alpha = false;
149
0
                (b'a' + x) as char
150
            }
151
0
            x if (26..36).contains(&x) => {
152
0
                if require_alpha {
153
0
                    require_alpha = false;
154
0
                    (b'a' + (x - 26)) as char
155
                } else {
156
0
                    (b'0' + (x - 26)) as char
157
                }
158
            }
159
0
            x if x == 36 => {
160
0
                if require_alpha {
161
0
                    require_alpha = false;
162
0
                    'a'
163
                } else {
164
0
                    require_alpha = true;
165
0
                    '-'
166
                }
167
            }
168
0
            _ => unreachable!(),
169
        });
170
    }
171
172
0
    if name.is_empty() || name.ends_with('-') {
173
0
        name.push('a');
174
0
    }
175
176
0
    while names.contains(KebabStr::new(&name).unwrap()) {
177
0
        write!(&mut name, "{}", names.len()).unwrap();
178
0
    }
179
180
0
    let name = KebabString::new(name).unwrap();
181
0
    names.insert(name.clone());
182
0
183
0
    Ok(name)
184
0
}
185
186
0
pub(crate) fn unique_url(
187
0
    max_size: usize,
188
0
    names: &mut HashSet<KebabString>,
189
0
    u: &mut Unstructured,
190
0
) -> Result<String> {
191
0
    let path = unique_kebab_string(max_size, names, u)?;
192
0
    Ok(format!("https://example.com/{path}"))
193
0
}