/src/wasm-tools/crates/wasm-smith/src/lib.rs
Line | Count | Source |
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 | | //! # #[cfg(not(target_family = "wasm"))] mod x { |
28 | | //! use libfuzzer_sys::fuzz_target; |
29 | | //! use wasm_smith::Module; |
30 | | //! |
31 | | //! fuzz_target!(|module: Module| { |
32 | | //! let wasm_bytes = module.to_bytes(); |
33 | | //! |
34 | | //! // Your code here... |
35 | | //! }); |
36 | | //! # } |
37 | | //! ``` |
38 | | //! |
39 | | //! Finally, start fuzzing: |
40 | | //! |
41 | | //! ```shell |
42 | | //! $ cargo fuzz run my_wasm_smith_fuzz_target |
43 | | //! ``` |
44 | | //! |
45 | | //! > **Note:** For a real world example, also check out [the `validate` fuzz |
46 | | //! > target](https://github.com/bytecodealliance/wasm-tools/blob/main/fuzz/src/validate.rs) |
47 | | //! > defined in this repository. Using the `wasmparser` crate, it checks that |
48 | | //! > every module generated by `wasm-smith` validates successfully. |
49 | | //! |
50 | | //! ## Design |
51 | | //! |
52 | | //! The design and implementation strategy of wasm-smith is outlined in |
53 | | //! [this article](https://fitzgeraldnick.com/2020/08/24/writing-a-test-case-generator.html). |
54 | | |
55 | | #![cfg_attr(docsrs, feature(doc_cfg))] |
56 | | #![deny(missing_docs, missing_debug_implementations)] |
57 | | // Needed for the `instructions!` macro in `src/code_builder.rs`. |
58 | | #![recursion_limit = "1024"] |
59 | | |
60 | | #[cfg(feature = "component-model")] |
61 | | mod component; |
62 | | mod config; |
63 | | mod core; |
64 | | |
65 | | pub use crate::core::{InstructionKind, InstructionKinds, Module}; |
66 | | use arbitrary::{Result, Unstructured}; |
67 | | #[cfg(feature = "component-model")] |
68 | | pub use component::Component; |
69 | | pub use config::{Config, MemoryOffsetChoices}; |
70 | | use std::{collections::HashSet, fmt::Write, str}; |
71 | | |
72 | | #[doc(hidden)] |
73 | | pub use config::InternalOptionalConfig; |
74 | | |
75 | | /// Do something an arbitrary number of times. |
76 | | /// |
77 | | /// The callback can return `false` to exit the loop early. |
78 | 381k | pub(crate) fn arbitrary_loop<'a>( |
79 | 381k | u: &mut Unstructured<'a>, |
80 | 381k | min: usize, |
81 | 381k | max: usize, |
82 | 381k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, |
83 | 381k | ) -> Result<()> { |
84 | 381k | assert!(max >= min); |
85 | 381k | for _ in 0..min { |
86 | 0 | if !f(u)? { |
87 | 0 | return Err(arbitrary::Error::IncorrectFormat); |
88 | 0 | } |
89 | | } |
90 | 381k | for _ in 0..(max - min) { |
91 | 5.50M | let keep_going = u.arbitrary().unwrap_or(false); |
92 | 5.50M | if !keep_going { |
93 | 232k | break; |
94 | 5.27M | } |
95 | | |
96 | 5.27M | if !f(u)? { |
97 | 1.14k | break; |
98 | 5.27M | } |
99 | | } |
100 | | |
101 | 381k | Ok(()) |
102 | 381k | } wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_elems::{closure#5}::{closure#2}>Line | Count | Source | 78 | 5.21k | pub(crate) fn arbitrary_loop<'a>( | 79 | 5.21k | u: &mut Unstructured<'a>, | 80 | 5.21k | min: usize, | 81 | 5.21k | max: usize, | 82 | 5.21k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 5.21k | ) -> Result<()> { | 84 | 5.21k | assert!(max >= min); | 85 | 5.21k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 5.21k | for _ in 0..(max - min) { | 91 | 287k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 287k | if !keep_going { | 93 | 2.47k | break; | 94 | 284k | } | 95 | | | 96 | 284k | if !f(u)? { | 97 | 0 | break; | 98 | 284k | } | 99 | | } | 100 | | | 101 | 5.21k | Ok(()) | 102 | 5.21k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_elems::{closure#5}::{closure#3}>Line | Count | Source | 78 | 18.3k | pub(crate) fn arbitrary_loop<'a>( | 79 | 18.3k | u: &mut Unstructured<'a>, | 80 | 18.3k | min: usize, | 81 | 18.3k | max: usize, | 82 | 18.3k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 18.3k | ) -> Result<()> { | 84 | 18.3k | assert!(max >= min); | 85 | 18.3k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 18.3k | for _ in 0..(max - min) { | 91 | 665k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 665k | if !keep_going { | 93 | 7.55k | break; | 94 | 658k | } | 95 | | | 96 | 658k | if !f(u)? { | 97 | 0 | break; | 98 | 658k | } | 99 | | } | 100 | | | 101 | 18.3k | Ok(()) | 102 | 18.3k | } |
Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_instance_type::{closure#0}::{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_data::{closure#4}>Line | Count | Source | 78 | 3.80k | pub(crate) fn arbitrary_loop<'a>( | 79 | 3.80k | u: &mut Unstructured<'a>, | 80 | 3.80k | min: usize, | 81 | 3.80k | max: usize, | 82 | 3.80k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 3.80k | ) -> Result<()> { | 84 | 3.80k | assert!(max >= min); | 85 | 3.80k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 3.80k | for _ in 0..(max - min) { | 91 | 25.4k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 25.4k | if !keep_going { | 93 | 3.63k | break; | 94 | 21.8k | } | 95 | | | 96 | 21.8k | if !f(u)? { | 97 | 0 | break; | 98 | 21.8k | } | 99 | | } | 100 | | | 101 | 3.80k | Ok(()) | 102 | 3.80k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_tags::{closure#0}>Line | Count | Source | 78 | 1.00k | pub(crate) fn arbitrary_loop<'a>( | 79 | 1.00k | u: &mut Unstructured<'a>, | 80 | 1.00k | min: usize, | 81 | 1.00k | max: usize, | 82 | 1.00k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 1.00k | ) -> Result<()> { | 84 | 1.00k | assert!(max >= min); | 85 | 1.00k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 1.00k | for _ in 0..(max - min) { | 91 | 26.6k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 26.6k | if !keep_going { | 93 | 817 | break; | 94 | 25.8k | } | 95 | | | 96 | 25.8k | if !f(u)? { | 97 | 76 | break; | 98 | 25.7k | } | 99 | | } | 100 | | | 101 | 1.00k | Ok(()) | 102 | 1.00k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_elems::{closure#5}>Line | Count | Source | 78 | 3.50k | pub(crate) fn arbitrary_loop<'a>( | 79 | 3.50k | u: &mut Unstructured<'a>, | 80 | 3.50k | min: usize, | 81 | 3.50k | max: usize, | 82 | 3.50k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 3.50k | ) -> Result<()> { | 84 | 3.50k | assert!(max >= min); | 85 | 3.50k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 3.50k | for _ in 0..(max - min) { | 91 | 32.5k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 32.5k | if !keep_going { | 93 | 3.20k | break; | 94 | 29.3k | } | 95 | | | 96 | 29.3k | if !f(u)? { | 97 | 0 | break; | 98 | 29.3k | } | 99 | | } | 100 | | | 101 | 3.50k | Ok(()) | 102 | 3.50k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_funcs::{closure#1}>Line | Count | Source | 78 | 3.28k | pub(crate) fn arbitrary_loop<'a>( | 79 | 3.28k | u: &mut Unstructured<'a>, | 80 | 3.28k | min: usize, | 81 | 3.28k | max: usize, | 82 | 3.28k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 3.28k | ) -> Result<()> { | 84 | 3.28k | assert!(max >= min); | 85 | 3.28k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 3.28k | for _ in 0..(max - min) { | 91 | 144k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 144k | if !keep_going { | 93 | 3.02k | break; | 94 | 141k | } | 95 | | | 96 | 141k | if !f(u)? { | 97 | 116 | break; | 98 | 141k | } | 99 | | } | 100 | | | 101 | 3.28k | Ok(()) | 102 | 3.28k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_locals::{closure#0}>Line | Count | Source | 78 | 141k | pub(crate) fn arbitrary_loop<'a>( | 79 | 141k | u: &mut Unstructured<'a>, | 80 | 141k | min: usize, | 81 | 141k | max: usize, | 82 | 141k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 141k | ) -> Result<()> { | 84 | 141k | assert!(max >= min); | 85 | 141k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 141k | for _ in 0..(max - min) { | 91 | 2.18M | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 2.18M | if !keep_going { | 93 | 123k | break; | 94 | 2.06M | } | 95 | | | 96 | 2.06M | if !f(u)? { | 97 | 0 | break; | 98 | 2.06M | } | 99 | | } | 100 | | | 101 | 141k | Ok(()) | 102 | 141k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_tables::{closure#0}>Line | Count | Source | 78 | 4.34k | pub(crate) fn arbitrary_loop<'a>( | 79 | 4.34k | u: &mut Unstructured<'a>, | 80 | 4.34k | min: usize, | 81 | 4.34k | max: usize, | 82 | 4.34k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 4.34k | ) -> Result<()> { | 84 | 4.34k | assert!(max >= min); | 85 | 4.34k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 4.34k | for _ in 0..(max - min) { | 91 | 18.7k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 18.7k | if !keep_going { | 93 | 2.53k | break; | 94 | 16.2k | } | 95 | | | 96 | 16.2k | if !f(u)? { | 97 | 522 | break; | 98 | 15.7k | } | 99 | | } | 100 | | | 101 | 4.34k | Ok(()) | 102 | 4.34k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_exports::{closure#4}>Line | Count | Source | 78 | 4.34k | pub(crate) fn arbitrary_loop<'a>( | 79 | 4.34k | u: &mut Unstructured<'a>, | 80 | 4.34k | min: usize, | 81 | 4.34k | max: usize, | 82 | 4.34k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 4.34k | ) -> Result<()> { | 84 | 4.34k | assert!(max >= min); | 85 | 4.34k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 4.34k | for _ in 0..(max - min) { | 91 | 51.2k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 51.2k | if !keep_going { | 93 | 3.90k | break; | 94 | 47.3k | } | 95 | | | 96 | 47.3k | if !f(u)? { | 97 | 194 | break; | 98 | 47.1k | } | 99 | | } | 100 | | | 101 | 4.34k | Ok(()) | 102 | 4.34k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_globals::{closure#0}>Line | Count | Source | 78 | 4.34k | pub(crate) fn arbitrary_loop<'a>( | 79 | 4.34k | u: &mut Unstructured<'a>, | 80 | 4.34k | min: usize, | 81 | 4.34k | max: usize, | 82 | 4.34k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 4.34k | ) -> Result<()> { | 84 | 4.34k | assert!(max >= min); | 85 | 4.34k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 4.34k | for _ in 0..(max - min) { | 91 | 86.2k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 86.2k | if !keep_going { | 93 | 4.08k | break; | 94 | 82.1k | } | 95 | | | 96 | 82.1k | if !f(u)? { | 97 | 41 | break; | 98 | 82.1k | } | 99 | | } | 100 | | | 101 | 4.34k | Ok(()) | 102 | 4.34k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_memories::{closure#0}>Line | Count | Source | 78 | 4.34k | pub(crate) fn arbitrary_loop<'a>( | 79 | 4.34k | u: &mut Unstructured<'a>, | 80 | 4.34k | min: usize, | 81 | 4.34k | max: usize, | 82 | 4.34k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 4.34k | ) -> Result<()> { | 84 | 4.34k | assert!(max >= min); | 85 | 4.34k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 4.34k | for _ in 0..(max - min) { | 91 | 26.2k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 26.2k | if !keep_going { | 93 | 3.58k | break; | 94 | 22.6k | } | 95 | | | 96 | 22.6k | if !f(u)? { | 97 | 199 | break; | 98 | 22.4k | } | 99 | | } | 100 | | | 101 | 4.34k | Ok(()) | 102 | 4.34k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_func_type::{closure#0}>Line | Count | Source | 78 | 93.6k | pub(crate) fn arbitrary_loop<'a>( | 79 | 93.6k | u: &mut Unstructured<'a>, | 80 | 93.6k | min: usize, | 81 | 93.6k | max: usize, | 82 | 93.6k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 93.6k | ) -> Result<()> { | 84 | 93.6k | assert!(max >= min); | 85 | 93.6k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 93.6k | for _ in 0..(max - min) { | 91 | 1.25M | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 1.25M | if !keep_going { | 93 | 41.7k | break; | 94 | 1.21M | } | 95 | | | 96 | 1.21M | if !f(u)? { | 97 | 0 | break; | 98 | 1.21M | } | 99 | | } | 100 | | | 101 | 93.6k | Ok(()) | 102 | 93.6k | } |
wasm_smith::arbitrary_loop::<<wasm_smith::core::Module>::arbitrary_func_type::{closure#1}>Line | Count | Source | 78 | 93.6k | pub(crate) fn arbitrary_loop<'a>( | 79 | 93.6k | u: &mut Unstructured<'a>, | 80 | 93.6k | min: usize, | 81 | 93.6k | max: usize, | 82 | 93.6k | mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>, | 83 | 93.6k | ) -> Result<()> { | 84 | 93.6k | assert!(max >= min); | 85 | 93.6k | for _ in 0..min { | 86 | 0 | if !f(u)? { | 87 | 0 | return Err(arbitrary::Error::IncorrectFormat); | 88 | 0 | } | 89 | | } | 90 | 93.6k | for _ in 0..(max - min) { | 91 | 704k | let keep_going = u.arbitrary().unwrap_or(false); | 92 | 704k | if !keep_going { | 93 | 32.5k | break; | 94 | 671k | } | 95 | | | 96 | 671k | if !f(u)? { | 97 | 0 | break; | 98 | 671k | } | 99 | | } | 100 | | | 101 | 93.6k | Ok(()) | 102 | 93.6k | } |
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_func_type::{closure#0}>Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_func_type::{closure#1}>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_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_record_type::{closure#0}>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_variant_type::{closure#0}>Unexecuted instantiation: wasm_smith::arbitrary_loop::<<wasm_smith::component::ComponentBuilder>::arbitrary_import_section::{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}>Unexecuted instantiation: wasm_smith::arbitrary_loop::<wasm_smith::component::arbitrary_func_type::{closure#0}>Unexecuted instantiation: wasm_smith::arbitrary_loop::<wasm_smith::component::arbitrary_func_type::{closure#1}> |
103 | | |
104 | | // Mirror what happens in `Arbitrary for String`, but do so with a clamped size. |
105 | 171k | pub(crate) fn limited_str<'a>(max_size: usize, u: &mut Unstructured<'a>) -> Result<&'a str> { |
106 | 171k | let size = u.arbitrary_len::<u8>()?; |
107 | 171k | let size = std::cmp::min(size, max_size); |
108 | 171k | match str::from_utf8(u.peek_bytes(size).unwrap()) { |
109 | 8.51k | Ok(s) => { |
110 | 8.51k | u.bytes(size).unwrap(); |
111 | 8.51k | Ok(s) |
112 | | } |
113 | 163k | Err(e) => { |
114 | 163k | let i = e.valid_up_to(); |
115 | 163k | let valid = u.bytes(i).unwrap(); |
116 | 163k | let s = str::from_utf8(valid).unwrap(); |
117 | 163k | Ok(s) |
118 | | } |
119 | | } |
120 | 171k | } |
121 | | |
122 | 171k | pub(crate) fn limited_string(max_size: usize, u: &mut Unstructured) -> Result<String> { |
123 | 171k | Ok(limited_str(max_size, u)?.into()) |
124 | 171k | } |
125 | | |
126 | 50.1k | pub(crate) fn unique_string( |
127 | 50.1k | max_size: usize, |
128 | 50.1k | names: &mut HashSet<String>, |
129 | 50.1k | u: &mut Unstructured, |
130 | 50.1k | ) -> Result<String> { |
131 | 50.1k | let mut name = limited_string(max_size, u)?; |
132 | 93.6k | while names.contains(&name) { |
133 | 43.4k | write!(&mut name, "{}", names.len()).unwrap(); |
134 | 43.4k | } |
135 | 50.1k | names.insert(name.clone()); |
136 | 50.1k | Ok(name) |
137 | 50.1k | } |
138 | | |
139 | | #[cfg(feature = "component-model")] |
140 | 0 | pub(crate) fn unique_kebab_string( |
141 | 0 | max_size: usize, |
142 | 0 | names: &mut HashSet<String>, |
143 | 0 | u: &mut Unstructured, |
144 | 0 | ) -> Result<String> { |
145 | 0 | let size = std::cmp::min(u.arbitrary_len::<u8>()?, max_size); |
146 | 0 | let mut name = String::with_capacity(size); |
147 | 0 | let mut empty_segment = true; |
148 | 0 | for i in 0..size { |
149 | 0 | name.push(match u.int_in_range::<u8>(0..=36)? { |
150 | 0 | x if (0..26).contains(&x) => { |
151 | 0 | empty_segment = false; |
152 | 0 | (b'a' + x) as char |
153 | | } |
154 | 0 | x if (26..36).contains(&x) => { |
155 | 0 | empty_segment = false; |
156 | 0 | if i == 0 { |
157 | 0 | (b'a' + (x - 26)) as char |
158 | | } else { |
159 | 0 | (b'0' + (x - 26)) as char |
160 | | } |
161 | | } |
162 | 0 | x if x == 36 => { |
163 | 0 | if empty_segment { |
164 | 0 | empty_segment = false; |
165 | 0 | 'a' |
166 | | } else { |
167 | 0 | empty_segment = true; |
168 | 0 | '-' |
169 | | } |
170 | | } |
171 | 0 | _ => unreachable!(), |
172 | | }); |
173 | | } |
174 | | |
175 | 0 | if name.is_empty() || name.ends_with('-') { |
176 | 0 | name.push('a'); |
177 | 0 | } |
178 | | |
179 | 0 | while names.contains(&name) { |
180 | 0 | write!(&mut name, "{}", names.len()).unwrap(); |
181 | 0 | } |
182 | | |
183 | 0 | names.insert(name.clone()); |
184 | | |
185 | 0 | Ok(name) |
186 | 0 | } |
187 | | |
188 | | #[cfg(feature = "component-model")] |
189 | 0 | pub(crate) fn unique_url( |
190 | 0 | max_size: usize, |
191 | 0 | names: &mut HashSet<String>, |
192 | 0 | u: &mut Unstructured, |
193 | 0 | ) -> Result<String> { |
194 | 0 | let path = unique_kebab_string(max_size, names, u)?; |
195 | 0 | Ok(format!("https://example.com/{path}")) |
196 | 0 | } |