1#![cfg_attr(docsrs, feature(doc_cfg))]
56#![deny(missing_docs, missing_debug_implementations)]
57#![recursion_limit = "1024"]
59
60#[cfg(feature = "component-model")]
61mod component;
62mod config;
63mod core;
64
65pub use crate::core::{InstructionKind, InstructionKinds, Module};
66use arbitrary::{Result, Unstructured};
67#[cfg(feature = "component-model")]
68pub use component::Component;
69pub use config::{Config, MemoryOffsetChoices};
70use std::{collections::HashSet, fmt::Write, str};
71
72#[doc(hidden)]
73pub use config::InternalOptionalConfig;
74
75pub(crate) fn arbitrary_loop<'a>(
79 u: &mut Unstructured<'a>,
80 min: usize,
81 max: usize,
82 mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
83) -> Result<()> {
84 assert!(max >= min);
85 for _ in 0..min {
86 if !f(u)? {
87 return Err(arbitrary::Error::IncorrectFormat);
88 }
89 }
90 for _ in 0..(max - min) {
91 let keep_going = u.arbitrary().unwrap_or(false);
92 if !keep_going {
93 break;
94 }
95
96 if !f(u)? {
97 break;
98 }
99 }
100
101 Ok(())
102}
103
104pub(crate) fn limited_str<'a>(max_size: usize, u: &mut Unstructured<'a>) -> Result<&'a str> {
106 let size = u.arbitrary_len::<u8>()?;
107 let size = std::cmp::min(size, max_size);
108 match str::from_utf8(u.peek_bytes(size).unwrap()) {
109 Ok(s) => {
110 u.bytes(size).unwrap();
111 Ok(s)
112 }
113 Err(e) => {
114 let i = e.valid_up_to();
115 let valid = u.bytes(i).unwrap();
116 let s = str::from_utf8(valid).unwrap();
117 Ok(s)
118 }
119 }
120}
121
122pub(crate) fn limited_string(max_size: usize, u: &mut Unstructured) -> Result<String> {
123 Ok(limited_str(max_size, u)?.into())
124}
125
126pub(crate) fn unique_string(
127 max_size: usize,
128 names: &mut HashSet<String>,
129 u: &mut Unstructured,
130) -> Result<String> {
131 let mut name = limited_string(max_size, u)?;
132 while names.contains(&name) {
133 write!(&mut name, "{}", names.len()).unwrap();
134 }
135 names.insert(name.clone());
136 Ok(name)
137}
138
139#[cfg(feature = "component-model")]
140pub(crate) fn unique_kebab_string(
141 max_size: usize,
142 names: &mut HashSet<String>,
143 u: &mut Unstructured,
144) -> Result<String> {
145 let size = std::cmp::min(u.arbitrary_len::<u8>()?, max_size);
146 let mut name = String::with_capacity(size);
147 let mut empty_segment = true;
148 for i in 0..size {
149 name.push(match u.int_in_range::<u8>(0..=36)? {
150 x if (0..26).contains(&x) => {
151 empty_segment = false;
152 (b'a' + x) as char
153 }
154 x if (26..36).contains(&x) => {
155 empty_segment = false;
156 if i == 0 {
157 (b'a' + (x - 26)) as char
158 } else {
159 (b'0' + (x - 26)) as char
160 }
161 }
162 x if x == 36 => {
163 if empty_segment {
164 empty_segment = false;
165 'a'
166 } else {
167 empty_segment = true;
168 '-'
169 }
170 }
171 _ => unreachable!(),
172 });
173 }
174
175 if name.is_empty() || name.ends_with('-') {
176 name.push('a');
177 }
178
179 while names.contains(&name) {
180 write!(&mut name, "{}", names.len()).unwrap();
181 }
182
183 names.insert(name.clone());
184
185 Ok(name)
186}
187
188#[cfg(feature = "component-model")]
189pub(crate) fn unique_url(
190 max_size: usize,
191 names: &mut HashSet<String>,
192 u: &mut Unstructured,
193) -> Result<String> {
194 let path = unique_kebab_string(max_size, names, u)?;
195 Ok(format!("https://example.com/{path}"))
196}