diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml b/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml index f7905ec4c2..969bbf0bb1 100644 --- a/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml +++ b/fuzzers/baby/baby_fuzzer_custom_executor/Cargo.toml @@ -8,8 +8,9 @@ authors = [ edition = "2021" [features] -default = ["std"] +default = ["std", "bloom_input_filter"] tui = ["libafl/tui_monitor"] +bloom_input_filter = ["std"] std = [] [profile.dev] diff --git a/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs b/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs index 059aa6ef14..adeae32832 100644 --- a/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs +++ b/fuzzers/baby/baby_fuzzer_custom_executor/src/main.rs @@ -134,7 +134,11 @@ pub fn main() { let scheduler = QueueScheduler::new(); // A fuzzer with feedbacks and a corpus scheduler + #[cfg(not(feature = "bloom_input_filter"))] let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); + #[cfg(feature = "bloom_input_filter")] + let mut fuzzer = + StdFuzzer::with_bloom_input_filter(scheduler, feedback, objective, 10_000_000, 0.001); // Create the executor for an in-process function with just one observer let executor = CustomExecutor::new(&state); diff --git a/libafl/Cargo.toml b/libafl/Cargo.toml index 2592a2c7c1..7781fce16d 100644 --- a/libafl/Cargo.toml +++ b/libafl/Cargo.toml @@ -58,6 +58,7 @@ std = [ "serial_test", "libafl_bolts/std", "typed-builder", + "fastbloom", ] ## Tracks the Feedbacks and the Objectives that were interesting for a Testcase @@ -291,6 +292,7 @@ document-features = { workspace = true, optional = true } clap = { workspace = true, optional = true } num_enum = { workspace = true, optional = true } libipt = { workspace = true, optional = true } +fastbloom = { version = "0.8.0", optional = true } [lints] workspace = true diff --git a/libafl/src/executors/inprocess/mod.rs b/libafl/src/executors/inprocess/mod.rs index 431ab3ed0a..0efb911448 100644 --- a/libafl/src/executors/inprocess/mod.rs +++ b/libafl/src/executors/inprocess/mod.rs @@ -557,7 +557,7 @@ mod tests { let mut mgr = NopEventManager::new(); let mut state = StdState::new(rand, corpus, solutions, &mut feedback, &mut objective).unwrap(); - let mut fuzzer = StdFuzzer::<_, _, _>::new(sche, feedback, objective); + let mut fuzzer = StdFuzzer::new(sche, feedback, objective); let mut in_process_executor = InProcessExecutor::new( &mut harness, diff --git a/libafl/src/fuzzer/mod.rs b/libafl/src/fuzzer/mod.rs index 5b5728e3b0..1b8e41daa6 100644 --- a/libafl/src/fuzzer/mod.rs +++ b/libafl/src/fuzzer/mod.rs @@ -2,7 +2,11 @@ use alloc::{string::ToString, vec::Vec}; use core::{fmt::Debug, time::Duration}; +#[cfg(feature = "std")] +use std::hash::Hash; +#[cfg(feature = "std")] +use fastbloom::BloomFilter; use libafl_bolts::{current_time, tuples::MatchName}; use serde::Serialize; @@ -138,6 +142,16 @@ pub trait EvaluatorObservers { /// Evaluate an input modifying the state of the fuzzer pub trait Evaluator { + /// Runs the input if it was (likely) not previously run and triggers observers and feedback and adds the input to the previously executed list + /// returns if is interesting an (option) the index of the new [`crate::corpus::Testcase`] in the corpus + fn evaluate_filtered( + &mut self, + state: &mut S, + executor: &mut E, + manager: &mut EM, + input: I, + ) -> Result<(ExecuteInputResult, Option), Error>; + /// Runs the input and triggers observers and feedback, /// returns if is interesting an (option) the index of the new [`crate::corpus::Testcase`] in the corpus fn evaluate_input( @@ -242,13 +256,14 @@ pub enum ExecuteInputResult { /// Your default fuzzer instance, for everyday use. #[derive(Debug)] -pub struct StdFuzzer { +pub struct StdFuzzer { scheduler: CS, feedback: F, objective: OF, + input_filter: IF, } -impl HasScheduler<::Input, S> for StdFuzzer +impl HasScheduler<::Input, S> for StdFuzzer where S: HasCorpus, CS: Scheduler<::Input, S>, @@ -264,7 +279,7 @@ where } } -impl HasFeedback for StdFuzzer { +impl HasFeedback for StdFuzzer { type Feedback = F; fn feedback(&self) -> &Self::Feedback { @@ -276,7 +291,7 @@ impl HasFeedback for StdFuzzer { } } -impl HasObjective for StdFuzzer { +impl HasObjective for StdFuzzer { type Objective = OF; fn objective(&self) -> &OF { @@ -288,8 +303,8 @@ impl HasObjective for StdFuzzer { } } -impl ExecutionProcessor::Input, OT, S> - for StdFuzzer +impl ExecutionProcessor::Input, OT, S> + for StdFuzzer where CS: Scheduler<::Input, S>, EM: EventFirer, @@ -494,8 +509,8 @@ where } } -impl EvaluatorObservers::Input, S> - for StdFuzzer +impl EvaluatorObservers::Input, S> + for StdFuzzer where CS: Scheduler<::Input, S>, E: HasObservers + Executor, @@ -532,7 +547,48 @@ where } } -impl Evaluator::Input, S> for StdFuzzer +trait InputFilter { + fn should_execute(&mut self, input: &I) -> bool; +} + +/// A pseudo-filter that will execute each input. +#[derive(Debug)] +pub struct NopInputFilter; +impl InputFilter for NopInputFilter { + #[inline] + #[must_use] + fn should_execute(&mut self, _input: &I) -> bool { + true + } +} + +/// A filter that probabilistically prevents duplicate execution of the same input based on a bloom filter. +#[cfg(feature = "std")] +#[derive(Debug)] +pub struct BloomInputFilter { + bloom: BloomFilter, +} + +#[cfg(feature = "std")] +impl BloomInputFilter { + #[must_use] + fn new(items_count: usize, fp_p: f64) -> Self { + let bloom = BloomFilter::with_false_pos(fp_p).expected_items(items_count); + Self { bloom } + } +} + +#[cfg(feature = "std")] +impl InputFilter for BloomInputFilter { + #[inline] + #[must_use] + fn should_execute(&mut self, input: &I) -> bool { + !self.bloom.insert(input) + } +} + +impl Evaluator::Input, S> + for StdFuzzer where CS: Scheduler<::Input, S>, E: HasObservers + Executor, @@ -549,7 +605,22 @@ where + UsesInput::Input>, ::Input: Input, S::Solutions: Corpus::Input>, + IF: InputFilter<::Input>, { + fn evaluate_filtered( + &mut self, + state: &mut S, + executor: &mut E, + manager: &mut EM, + input: ::Input, + ) -> Result<(ExecuteInputResult, Option), Error> { + if self.input_filter.should_execute(&input) { + self.evaluate_input(state, executor, manager, input) + } else { + Ok((ExecuteInputResult::None, None)) + } + } + /// Process one input, adding to the respective corpora if needed and firing the right events #[inline] fn evaluate_input_events( @@ -562,6 +633,7 @@ where ) -> Result<(ExecuteInputResult, Option), Error> { self.evaluate_input_with_observers(state, executor, manager, input, send_events) } + fn add_disabled_input( &mut self, state: &mut S, @@ -573,6 +645,7 @@ where let id = state.corpus_mut().add_disabled(testcase)?; Ok(id) } + /// Adds an input, even if it's not considered `interesting` by any of the executors fn add_input( &mut self, @@ -672,7 +745,7 @@ where } } -impl Fuzzer for StdFuzzer +impl Fuzzer for StdFuzzer where CS: Scheduler, E: UsesState, @@ -796,17 +869,44 @@ where } } -impl StdFuzzer { - /// Create a new `StdFuzzer` with standard behavior. - pub fn new(scheduler: CS, feedback: F, objective: OF) -> Self { +impl StdFuzzer { + /// Create a new [`StdFuzzer`] with standard behavior and the provided duplicate input execution filter. + pub fn with_input_filter(scheduler: CS, feedback: F, objective: OF, input_filter: IF) -> Self { Self { scheduler, feedback, objective, + input_filter, } } } +impl StdFuzzer { + /// Create a new [`StdFuzzer`] with standard behavior and no duplicate input execution filtering. + pub fn new(scheduler: CS, feedback: F, objective: OF) -> Self { + Self::with_input_filter(scheduler, feedback, objective, NopInputFilter) + } +} + +#[cfg(feature = "std")] // hashing requires std +impl StdFuzzer { + /// Create a new [`StdFuzzer`], which, with a certain certainty, executes each input only once. + /// + /// This is achieved by hashing each input and using a bloom filter to differentiate inputs. + /// + /// Use this implementation if hashing each input is very fast compared to executing potential duplicate inputs. + pub fn with_bloom_input_filter( + scheduler: CS, + feedback: F, + objective: OF, + items_count: usize, + fp_p: f64, + ) -> Self { + let input_filter = BloomInputFilter::new(items_count, fp_p); + Self::with_input_filter(scheduler, feedback, objective, input_filter) + } +} + /// Structs with this trait will execute an input pub trait ExecutesInput { /// Runs the input and triggers observers and feedback @@ -819,8 +919,8 @@ pub trait ExecutesInput { ) -> Result; } -impl ExecutesInput::Input, S> - for StdFuzzer +impl ExecutesInput::Input, S> + for StdFuzzer where CS: Scheduler<::Input, S>, E: Executor + HasObservers, @@ -913,3 +1013,63 @@ where unimplemented!("NopFuzzer cannot fuzz"); } } + +#[cfg(all(test, feature = "std"))] +mod tests { + use core::cell::RefCell; + + use libafl_bolts::rands::StdRand; + + use super::{Evaluator, StdFuzzer}; + use crate::{ + corpus::InMemoryCorpus, + events::NopEventManager, + executors::{ExitKind, InProcessExecutor}, + inputs::BytesInput, + schedulers::StdScheduler, + state::StdState, + }; + + #[test] + fn filtered_execution() { + let execution_count = RefCell::new(0); + let scheduler = StdScheduler::new(); + let mut fuzzer = StdFuzzer::with_bloom_input_filter(scheduler, (), (), 100, 1e-4); + let mut state = StdState::new( + StdRand::new(), + InMemoryCorpus::new(), + InMemoryCorpus::new(), + &mut (), + &mut (), + ) + .unwrap(); + let mut manager = NopEventManager::new(); + let mut harness = |_input: &BytesInput| { + *execution_count.borrow_mut() += 1; + ExitKind::Ok + }; + let mut executor = + InProcessExecutor::new(&mut harness, (), &mut fuzzer, &mut state, &mut manager) + .unwrap(); + let input = BytesInput::new(vec![1, 2, 3]); + assert!(fuzzer + .evaluate_input(&mut state, &mut executor, &mut manager, input.clone()) + .is_ok()); + assert_eq!(1, *execution_count.borrow()); // evaluate_input does not add it to the filter + + assert!(fuzzer + .evaluate_filtered(&mut state, &mut executor, &mut manager, input.clone()) + .is_ok()); + assert_eq!(2, *execution_count.borrow()); // at to the filter + + assert!(fuzzer + .evaluate_filtered(&mut state, &mut executor, &mut manager, input.clone()) + .is_ok()); + assert_eq!(2, *execution_count.borrow()); // the harness is not called + + assert!(fuzzer + .evaluate_input(&mut state, &mut executor, &mut manager, input.clone()) + .is_ok()); + assert_eq!(3, *execution_count.borrow()); // evaluate_input ignores filters + } +} diff --git a/libafl/src/mutators/hash.rs b/libafl/src/mutators/hash.rs new file mode 100644 index 0000000000..8b231b7d75 --- /dev/null +++ b/libafl/src/mutators/hash.rs @@ -0,0 +1,80 @@ +//! A wrapper around a [`Mutator`] that ensures an input really changed [`MutationResult::Mutated`] +//! by hashing pre- and post-mutation +use std::{borrow::Cow, hash::Hash}; + +use libafl_bolts::{generic_hash_std, Error, Named}; + +use super::{MutationResult, Mutator}; + +/// A wrapper around a [`Mutator`] that ensures an input really changed [`MutationResult::Mutated`] +/// by hashing pre- and post-mutation and comparing the values +#[derive(Debug)] +pub struct MutationChecker { + inner: M, + name: Cow<'static, str>, +} + +impl MutationChecker +where + M: Named, +{ + /// Create a new [`MutationChecker`] + pub fn new(inner: M) -> Self { + let name = Cow::Owned(format!("MutationChecker<{}>", inner.name().clone())); + Self { inner, name } + } +} + +impl Mutator for MutationChecker +where + I: Hash, + M: Mutator, +{ + fn mutate(&mut self, state: &mut S, input: &mut I) -> Result { + let before = generic_hash_std(input); + self.inner.mutate(state, input)?; + if before == generic_hash_std(input) { + Ok(MutationResult::Skipped) + } else { + Ok(MutationResult::Mutated) + } + } +} + +impl Named for MutationChecker { + fn name(&self) -> &Cow<'static, str> { + &self.name + } +} + +#[cfg(test)] +mod tests { + use crate::{ + inputs::BytesInput, + mutators::{BytesSetMutator, MutationChecker, MutationResult, Mutator}, + state::NopState, + }; + + #[test] + fn not_mutated() { + let mut state: NopState = NopState::new(); + let mut inner = BytesSetMutator::new(); + + let mut input = BytesInput::new(vec![0; 5]); + + // nothing changed, yet `MutationResult::Mutated` was reported + assert_eq!( + MutationResult::Mutated, + inner.mutate(&mut state, &mut input).unwrap() + ); + assert_eq!(BytesInput::new(vec![0; 5]), input); + + // now it is correctly reported as `MutationResult::Skipped` + let mut hash_mutator = MutationChecker::new(inner); + assert_eq!( + MutationResult::Skipped, + hash_mutator.mutate(&mut state, &mut input).unwrap() + ); + assert_eq!(BytesInput::new(vec![0; 5]), input); + } +} diff --git a/libafl/src/mutators/mod.rs b/libafl/src/mutators/mod.rs index 22dec0d64f..5b2de83bd4 100644 --- a/libafl/src/mutators/mod.rs +++ b/libafl/src/mutators/mod.rs @@ -28,6 +28,11 @@ pub use mapping::*; pub mod tuneable; pub use tuneable::*; +#[cfg(feature = "std")] +pub mod hash; +#[cfg(feature = "std")] +pub use hash::*; + #[cfg(feature = "unicode")] pub mod unicode; #[cfg(feature = "unicode")] @@ -84,12 +89,15 @@ impl From for MutationId { } } -/// The result of a mutation. -/// If the mutation got skipped, the target -/// will not be executed with the returned input. +/// Result of the mutation. +/// +/// [`MutationResult::Skipped`] does not necessarily mean that the input changed, +/// just that the mutator did something. For slow targets, consider using +/// a filtered fuzzer (see [`crate::fuzzer::StdFuzzer::with_input_filter`]) +/// or wrapping your mutator in a [`hash::MutationChecker`]. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MutationResult { - /// The [`Mutator`] mutated this `Input`. + /// The [`Mutator`] executed on this `Input`. It may not guarantee that the input has actually been changed. Mutated, /// The [`Mutator`] did not mutate this `Input`. It was `Skipped`. Skipped, diff --git a/libafl/src/stages/concolic.rs b/libafl/src/stages/concolic.rs index 2f2ebd0876..be5765f3c6 100644 --- a/libafl/src/stages/concolic.rs +++ b/libafl/src/stages/concolic.rs @@ -417,8 +417,7 @@ where for (index, new_byte) in mutation { input_copy.bytes_mut()[index] = new_byte; } - // Time is measured directly the `evaluate_input` function - fuzzer.evaluate_input(state, executor, manager, input_copy)?; + fuzzer.evaluate_filtered(state, executor, manager, input_copy)?; } } Ok(()) diff --git a/libafl/src/stages/generation.rs b/libafl/src/stages/generation.rs index 8fe6ff28fa..ccf1b94f9d 100644 --- a/libafl/src/stages/generation.rs +++ b/libafl/src/stages/generation.rs @@ -44,7 +44,7 @@ where manager: &mut EM, ) -> Result<(), Error> { let input = self.0.generate(state)?; - fuzzer.evaluate_input(state, executor, manager, input)?; + fuzzer.evaluate_filtered(state, executor, manager, input)?; Ok(()) } diff --git a/libafl/src/stages/mutational.rs b/libafl/src/stages/mutational.rs index 61ac616eee..9b3ffdbb9f 100644 --- a/libafl/src/stages/mutational.rs +++ b/libafl/src/stages/mutational.rs @@ -277,9 +277,9 @@ where continue; } - // Time is measured directly the `evaluate_input` function let (untransformed, post) = input.try_transform_into(state)?; - let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?; + let (_, corpus_id) = + fuzzer.evaluate_filtered(state, executor, manager, untransformed)?; start_timer!(state); self.mutator_mut().post_exec(state, corpus_id)?; @@ -345,9 +345,9 @@ where let generated = self.mutator.multi_mutate(state, &input, None)?; for new_input in generated { - // Time is measured directly the `evaluate_input` function let (untransformed, post) = new_input.try_transform_into(state)?; - let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?; + let (_, corpus_id) = + fuzzer.evaluate_filtered(state, executor, manager, untransformed)?; self.mutator.multi_post_exec(state, corpus_id)?; post.post_exec(state, corpus_id)?; } diff --git a/libafl/src/stages/power.rs b/libafl/src/stages/power.rs index e3d64def59..695d2c6038 100644 --- a/libafl/src/stages/power.rs +++ b/libafl/src/stages/power.rs @@ -189,9 +189,9 @@ where continue; } - // Time is measured directly the `evaluate_input` function let (untransformed, post) = input.try_transform_into(state)?; - let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?; + let (_, corpus_id) = + fuzzer.evaluate_filtered(state, executor, manager, untransformed)?; start_timer!(state); self.mutator_mut().post_exec(state, corpus_id)?; diff --git a/libafl/src/stages/tuneable.rs b/libafl/src/stages/tuneable.rs index 9c3aeab6af..2764291281 100644 --- a/libafl/src/stages/tuneable.rs +++ b/libafl/src/stages/tuneable.rs @@ -453,9 +453,8 @@ where return Ok(()); } - // Time is measured directly the `evaluate_input` function let (untransformed, post) = input.try_transform_into(state)?; - let (_, corpus_id) = fuzzer.evaluate_input(state, executor, manager, untransformed)?; + let (_, corpus_id) = fuzzer.evaluate_filtered(state, executor, manager, untransformed)?; start_timer!(state); self.mutator_mut().post_exec(state, corpus_id)?; diff --git a/libafl_bolts/src/llmp.rs b/libafl_bolts/src/llmp.rs index 614d502557..d191e02b6c 100644 --- a/libafl_bolts/src/llmp.rs +++ b/libafl_bolts/src/llmp.rs @@ -2669,7 +2669,11 @@ where self.inner .llmp_clients .binary_search_by_key(&client_id, |x| x.id) - .expect("Fatal error, client ID {client_id} not found in llmp_clients.") + .unwrap_or_else(|_| { + panic!( + "Fatal error, client ID {client_id:?} not found in llmp_clients." + ) + }) }; let client = &mut self.inner.llmp_clients[pos]; match client.recv()? { @@ -2768,7 +2772,7 @@ where self.inner .llmp_clients .binary_search_by_key(&client_id, |x| x.id) - .expect("Fatal error, client ID {client_id} not found in llmp_clients.") + .unwrap_or_else(|_| panic!("Fatal error, client ID {client_id:?} not found in llmp_clients.")) }; let map = &mut self.inner.llmp_clients[pos].current_recv_shmem; diff --git a/libafl_bolts/src/rands/mod.rs b/libafl_bolts/src/rands/mod.rs index a6a8730c95..c55f105b2b 100644 --- a/libafl_bolts/src/rands/mod.rs +++ b/libafl_bolts/src/rands/mod.rs @@ -18,7 +18,7 @@ static SEED_COUNTER: AtomicUsize = AtomicUsize::new(0); /// Return a pseudo-random seed. For `no_std` environments, a single deterministic sequence is used. #[must_use] -#[expect(unreachable_code)] +#[allow(unreachable_code)] // cfg dependent pub fn random_seed() -> u64 { #[cfg(feature = "std")] return random_seed_from_random_state(); @@ -365,7 +365,7 @@ impl Rand for Lehmer64Rand { fn set_seed(&mut self, mut seed: u64) { let hi = splitmix64(&mut seed); let lo = splitmix64(&mut seed) | 1; - self.s = u128::from(hi) << 64 | u128::from(lo); + self.s = (u128::from(hi) << 64) | u128::from(lo); } #[inline] diff --git a/libafl_bolts/src/shmem.rs b/libafl_bolts/src/shmem.rs index 7df2bbe154..2144af1ab5 100644 --- a/libafl_bolts/src/shmem.rs +++ b/libafl_bolts/src/shmem.rs @@ -624,12 +624,9 @@ where /// Is needed on top. #[cfg(all(unix, feature = "std", not(target_os = "haiku")))] pub mod unix_shmem { - /// Mmap [`ShMemProvider`] for Unix - #[cfg(not(target_os = "android"))] - pub use default::MmapShMemProvider; /// Mmap [`ShMem`] for Unix #[cfg(not(target_os = "android"))] - pub use default::{MmapShMem, MAX_MMAP_FILENAME_LEN}; + pub use default::{MmapShMem, MmapShMemProvider, MAX_MMAP_FILENAME_LEN}; #[cfg(doc)] use crate::shmem::{ShMem, ShMemProvider}; @@ -669,7 +666,7 @@ pub mod unix_shmem { Error, }; - /// The size of the buffer of the filename of mmap mapped memory regions + /// The max number of bytes used when generating names for [`MmapShMem`]s. pub const MAX_MMAP_FILENAME_LEN: usize = 20; /// Mmap-based The sharedmap impl for unix using [`shm_open`] and [`mmap`]. diff --git a/libafl_qemu/src/modules/usermode/asan.rs b/libafl_qemu/src/modules/usermode/asan.rs index 96674a899d..5ff4cd5b31 100644 --- a/libafl_qemu/src/modules/usermode/asan.rs +++ b/libafl_qemu/src/modules/usermode/asan.rs @@ -1532,8 +1532,7 @@ mod addr2line_legacy { /// # Safety /// Will access the global [`FullBacktraceCollector`]. /// Calling this function concurrently might be racey. -#[expect(clippy::unnecessary_cast)] -#[expect(clippy::too_many_lines)] +#[expect(clippy::too_many_lines, clippy::unnecessary_cast)] pub unsafe fn asan_report(rt: &AsanGiovese, qemu: Qemu, pc: GuestAddr, err: &AsanError) { let mut regions = HashMap::new(); for region in qemu.mappings() { diff --git a/libafl_qemu/src/modules/usermode/snapshot.rs b/libafl_qemu/src/modules/usermode/snapshot.rs index 776fc749da..e3579ed0e9 100644 --- a/libafl_qemu/src/modules/usermode/snapshot.rs +++ b/libafl_qemu/src/modules/usermode/snapshot.rs @@ -780,8 +780,7 @@ where SyscallHookResult::new(None) } -#[expect(clippy::too_many_arguments)] -#[expect(non_upper_case_globals)] +#[expect(non_upper_case_globals, clippy::too_many_arguments)] pub fn trace_mmap_snapshot( emulator_modules: &mut EmulatorModules, _state: Option<&mut S>, diff --git a/libafl_targets/src/libfuzzer/mod.rs b/libafl_targets/src/libfuzzer/mod.rs index f5d9d41bdb..4cd967808b 100644 --- a/libafl_targets/src/libfuzzer/mod.rs +++ b/libafl_targets/src/libfuzzer/mod.rs @@ -26,8 +26,7 @@ extern "C" { /// /// # Safety /// Calls the libfuzzer-style init function which is native code. -#[expect(clippy::similar_names)] -#[expect(clippy::must_use_candidate)] // nobody uses that return code... +#[expect(clippy::must_use_candidate, clippy::similar_names)] // nobody uses that return code... pub unsafe fn libfuzzer_initialize(args: &[String]) -> i32 { let args: Vec = args.iter().map(|x| x.clone() + "\0").collect(); let argv: Vec<*const u8> = args.iter().map(|x| x.as_bytes().as_ptr()).collect();