#![deny(missing_docs, missing_debug_implementations)]
#![recursion_limit = "512"]
mod component;
mod config;
mod core;
pub use crate::core::{
ConfiguredModule, InstructionKind, InstructionKinds, MaybeInvalidModule, Module,
};
use arbitrary::{Result, Unstructured};
pub use component::{Component, ConfiguredComponent};
pub use config::{Config, DefaultConfig, SwarmConfig};
use std::{collections::HashSet, fmt::Write, str};
use wasmparser::types::{KebabStr, KebabString};
pub(crate) fn arbitrary_loop<'a>(
u: &mut Unstructured<'a>,
min: usize,
max: usize,
mut f: impl FnMut(&mut Unstructured<'a>) -> Result<bool>,
) -> Result<()> {
assert!(max >= min);
for _ in 0..min {
if !f(u)? {
return Err(arbitrary::Error::IncorrectFormat);
}
}
for _ in 0..(max - min) {
let keep_going = u.arbitrary().unwrap_or(false);
if !keep_going {
break;
}
if !f(u)? {
break;
}
}
Ok(())
}
pub(crate) fn limited_str<'a>(max_size: usize, u: &mut Unstructured<'a>) -> Result<&'a str> {
let size = u.arbitrary_len::<u8>()?;
let size = std::cmp::min(size, max_size);
match str::from_utf8(u.peek_bytes(size).unwrap()) {
Ok(s) => {
u.bytes(size).unwrap();
Ok(s)
}
Err(e) => {
let i = e.valid_up_to();
let valid = u.bytes(i).unwrap();
let s = unsafe {
debug_assert!(str::from_utf8(valid).is_ok());
str::from_utf8_unchecked(valid)
};
Ok(s)
}
}
}
pub(crate) fn limited_string(max_size: usize, u: &mut Unstructured) -> Result<String> {
Ok(limited_str(max_size, u)?.into())
}
pub(crate) fn unique_string(
max_size: usize,
names: &mut HashSet<String>,
u: &mut Unstructured,
) -> Result<String> {
let mut name = limited_string(max_size, u)?;
while names.contains(&name) {
write!(&mut name, "{}", names.len()).unwrap();
}
names.insert(name.clone());
Ok(name)
}
pub(crate) fn unique_kebab_string(
max_size: usize,
names: &mut HashSet<KebabString>,
u: &mut Unstructured,
) -> Result<KebabString> {
let size = std::cmp::min(u.arbitrary_len::<u8>()?, max_size);
let mut name = String::with_capacity(size);
let mut require_alpha = true;
for _ in 0..size {
name.push(match u.int_in_range::<u8>(0..=36)? {
x if (0..26).contains(&x) => {
require_alpha = false;
(b'a' + x) as char
}
x if (26..36).contains(&x) => {
if require_alpha {
require_alpha = false;
(b'a' + (x - 26)) as char
} else {
(b'0' + (x - 26)) as char
}
}
x if x == 36 => {
if require_alpha {
require_alpha = false;
'a'
} else {
require_alpha = true;
'-'
}
}
_ => unreachable!(),
});
}
if name.is_empty() || name.ends_with('-') {
name.push('a');
}
while names.contains(KebabStr::new(&name).unwrap()) {
write!(&mut name, "{}", names.len()).unwrap();
}
let name = KebabString::new(name).unwrap();
names.insert(name.clone());
Ok(name)
}
pub(crate) fn unique_url(
max_size: usize,
names: &mut HashSet<KebabString>,
u: &mut Unstructured,
) -> Result<String> {
let path = unique_kebab_string(max_size, names, u)?;
Ok(format!("https://example.com/{path}"))
}