Make every Builder ::builder(), so BobTheBuilder::new() becomes BobThe::builder() (#2242)

* Make every builder ::builder()

* Fix no_std

* More

* Fix clippy, stuff

* More fun

* Make NopShMem do something

* Alloc

* more fmt

* Remove UB in tinyinst executor builder

* Make builder order not matter for tinyinst

* More better

* fix

* docs

* fmt

* more fmt

* clippy

* fix fixes

* tiny thing

* more betterg

* more more

* more builder

* more builder

* more nyx

* undo breaking clippy

* clip
This commit is contained in:
Dominik Maier 2024-05-23 18:56:39 +02:00 committed by GitHub
parent b97a9a1398
commit 1fafaf6454
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 253 additions and 109 deletions

View File

@ -9,7 +9,7 @@ use std::{env, net::SocketAddr, path::PathBuf, time::Duration};
use clap::Parser; use clap::Parser;
use libafl::{ use libafl::{
corpus::{InMemoryCorpus, OnDiskCorpus}, corpus::{InMemoryCorpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig, LlmpEventConverterBuilder}, events::{launcher::Launcher, llmp::LlmpEventConverter, EventConfig},
executors::{inprocess::InProcessExecutor, ExitKind}, executors::{inprocess::InProcessExecutor, ExitKind},
feedback_or, feedback_or,
feedbacks::{CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, NautilusChunksMetadata, NautilusFeedback},
@ -120,7 +120,7 @@ pub extern "C" fn libafl_main() {
let context = NautilusContext::from_file(15, "grammar.json"); let context = NautilusContext::from_file(15, "grammar.json");
let mut event_converter = opt.bytes_broker_port.map(|port| { let mut event_converter = opt.bytes_broker_port.map(|port| {
LlmpEventConverterBuilder::new() LlmpEventConverter::builder()
.build_on_port( .build_on_port(
shmem_provider.clone(), shmem_provider.clone(),
port, port,

View File

@ -19,7 +19,7 @@ use libafl_bolts::{
shmem::{ShMemProvider, StdShMemProvider}, shmem::{ShMemProvider, StdShMemProvider},
tuples::tuple_list, tuples::tuple_list,
}; };
use libafl_nyx::{executor::NyxExecutorBuilder, helper::NyxHelper, settings::NyxSettings}; use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
fn main() { fn main() {
let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory"); let shmem_provider = StdShMemProvider::new().expect("Failed to init shared memory");
@ -54,7 +54,7 @@ fn main() {
let mut feedback = MaxMapFeedback::new(&observer); let mut feedback = MaxMapFeedback::new(&observer);
let mut objective = CrashFeedback::new(); let mut objective = CrashFeedback::new();
let scheduler = RandScheduler::new(); let scheduler = RandScheduler::new();
let mut executor = NyxExecutorBuilder::new().build(helper, tuple_list!(observer)); let mut executor = NyxExecutor::builder().build(helper, tuple_list!(observer));
// If not restarting, create a State from scratch // If not restarting, create a State from scratch
let mut state = state.unwrap_or_else(|| { let mut state = state.unwrap_or_else(|| {

View File

@ -14,7 +14,7 @@ use libafl::{
Fuzzer, StdFuzzer, Fuzzer, StdFuzzer,
}; };
use libafl_bolts::{rands::StdRand, tuples::tuple_list}; use libafl_bolts::{rands::StdRand, tuples::tuple_list};
use libafl_nyx::{executor::NyxExecutorBuilder, helper::NyxHelper, settings::NyxSettings}; use libafl_nyx::{executor::NyxExecutor, helper::NyxHelper, settings::NyxSettings};
fn main() { fn main() {
// nyx stuff // nyx stuff
@ -44,7 +44,7 @@ fn main() {
let monitor = TuiMonitor::new(ui); let monitor = TuiMonitor::new(ui);
let mut mgr = SimpleEventManager::new(monitor); let mut mgr = SimpleEventManager::new(monitor);
let mut executor = NyxExecutorBuilder::new().build(helper, tuple_list!(observer)); let mut executor = NyxExecutor::builder().build(helper, tuple_list!(observer));
let mutator = StdScheduledMutator::new(havoc_mutations()); let mutator = StdScheduledMutator::new(havoc_mutations());
let mut stages = tuple_list!(StdMutationalStage::new(mutator)); let mut stages = tuple_list!(StdMutationalStage::new(mutator));

View File

@ -1,4 +1,4 @@
use std::path::PathBuf; use std::{path::PathBuf, ptr::addr_of_mut, time::Duration};
use libafl::{ use libafl::{
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus, Testcase}, corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus, Testcase},
@ -20,7 +20,7 @@ use libafl_bolts::shmem::Win32ShMemProvider;
use libafl_bolts::{ use libafl_bolts::{
ownedref::OwnedMutPtr, rands::StdRand, shmem::ShMemProvider, tuples::tuple_list, ownedref::OwnedMutPtr, rands::StdRand, shmem::ShMemProvider, tuples::tuple_list,
}; };
use libafl_tinyinst::executor::TinyInstExecutorBuilder; use libafl_tinyinst::executor::TinyInstExecutor;
static mut COVERAGE: Vec<u64> = vec![]; static mut COVERAGE: Vec<u64> = vec![];
#[cfg(not(any(target_vendor = "apple", windows, target_os = "linux")))] #[cfg(not(any(target_vendor = "apple", windows, target_os = "linux")))]
@ -37,7 +37,7 @@ fn main() {
// use file to pass testcases // use file to pass testcases
// let args = vec!["test.exe".to_string(), "-f".to_string(), "@@".to_string()]; // let args = vec!["test.exe".to_string(), "-f".to_string(), "@@".to_string()];
let coverage = unsafe { OwnedMutPtr::Ptr(core::ptr::addr_of_mut!(COVERAGE)) }; let coverage = unsafe { OwnedMutPtr::Ptr(addr_of_mut!(COVERAGE)) };
let observer = ListObserver::new("cov", coverage); let observer = ListObserver::new("cov", coverage);
let mut feedback = ListFeedback::new(&observer); let mut feedback = ListFeedback::new(&observer);
#[cfg(windows)] #[cfg(windows)]
@ -59,18 +59,19 @@ fn main() {
let scheduler = RandScheduler::new(); let scheduler = RandScheduler::new();
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let monitor = SimpleMonitor::new(|x| println!("{}", x)); let monitor = SimpleMonitor::new(|x| println!("{x}"));
let mut mgr = SimpleEventManager::new(monitor); let mut mgr = SimpleEventManager::new(monitor);
let mut executor = unsafe { let mut executor = unsafe {
TinyInstExecutorBuilder::new() TinyInstExecutor::builder()
.tinyinst_args(tinyinst_args) .tinyinst_args(tinyinst_args)
.program_args(args) .program_args(args)
.use_shmem() .use_shmem()
.persistent("test.exe".to_string(), "fuzz".to_string(), 1, 10000) .persistent("test.exe".to_string(), "fuzz".to_string(), 1, 10000)
.timeout(std::time::Duration::new(5, 0)) .timeout(Duration::new(5, 0))
.shmem_provider(&mut shmem_provider) .shmem_provider(&mut shmem_provider)
.build(&mut COVERAGE, tuple_list!(observer)) .coverage_ptr(addr_of_mut!(COVERAGE))
.build(tuple_list!(observer))
.unwrap() .unwrap()
}; };
let mutator = StdScheduledMutator::new(havoc_mutations()); let mutator = StdScheduledMutator::new(havoc_mutations());

View File

@ -19,11 +19,12 @@ use libafl_bolts::{
}; };
use libafl_bolts::{ use libafl_bolts::{
llmp::{self, LlmpBroker, LlmpClient, LlmpClientDescription, Tag}, llmp::{self, LlmpBroker, LlmpClient, LlmpClientDescription, Tag},
shmem::ShMemProvider, shmem::{NopShMemProvider, ShMemProvider},
ClientId, ClientId,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::NopEventManager;
#[cfg(feature = "llmp_compression")] #[cfg(feature = "llmp_compression")]
use crate::events::llmp::COMPRESS_THRESHOLD; use crate::events::llmp::COMPRESS_THRESHOLD;
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
@ -38,9 +39,9 @@ use crate::{
}, },
executors::{Executor, HasObservers}, executors::{Executor, HasObservers},
fuzzer::{EvaluatorObservers, ExecutionProcessor}, fuzzer::{EvaluatorObservers, ExecutionProcessor},
inputs::{Input, UsesInput}, inputs::{Input, NopInput, UsesInput},
observers::ObserversTuple, observers::ObserversTuple,
state::{HasExecutions, HasLastReportTime, UsesState}, state::{HasExecutions, HasLastReportTime, NopState, UsesState},
Error, HasMetadata, Error, HasMetadata,
}; };
@ -230,6 +231,14 @@ where
is_main: bool, is_main: bool,
} }
impl CentralizedEventManager<NopEventManager<NopState<NopInput>>, NopShMemProvider> {
/// Creates a builder for [`CentralizedEventManager`]
#[must_use]
pub fn builder() -> CentralizedEventManagerBuilder {
CentralizedEventManagerBuilder::new()
}
}
/// The builder or `CentralizedEventManager` /// The builder or `CentralizedEventManager`
#[derive(Debug)] #[derive(Debug)]
pub struct CentralizedEventManagerBuilder { pub struct CentralizedEventManagerBuilder {

View File

@ -48,10 +48,8 @@ use libafl_bolts::{
use typed_builder::TypedBuilder; use typed_builder::TypedBuilder;
use super::hooks::EventManagerHooksTuple; use super::hooks::EventManagerHooksTuple;
#[cfg(all(unix, feature = "std"))]
use crate::events::centralized::CentralizedEventManagerBuilder;
#[cfg(all(unix, feature = "std", feature = "fork"))] #[cfg(all(unix, feature = "std", feature = "fork"))]
use crate::events::{CentralizedEventManager, CentralizedLlmpEventBroker}; use crate::events::centralized::{CentralizedEventManager, CentralizedLlmpEventBroker};
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
use crate::observers::TimeObserver; use crate::observers::TimeObserver;
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -698,7 +696,7 @@ where
let builder = builder.time_ref(self.time_obs.handle()); let builder = builder.time_ref(self.time_obs.handle());
let (state, mgr) = builder.build().launch()?; let (state, mgr) = builder.build().launch()?;
let mut centralized_builder = CentralizedEventManagerBuilder::new(); let mut centralized_builder = CentralizedEventManager::builder();
if index == 1 { if index == 1 {
centralized_builder = centralized_builder.is_main(true); centralized_builder = centralized_builder.is_main(true);

View File

@ -18,7 +18,7 @@ use libafl_bolts::{
use libafl_bolts::{ use libafl_bolts::{
current_time, current_time,
llmp::{LlmpClient, LlmpClientDescription}, llmp::{LlmpClient, LlmpClientDescription},
shmem::ShMemProvider, shmem::{NopShMemProvider, ShMemProvider},
ClientId, ClientId,
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -44,9 +44,9 @@ use crate::{
}, },
executors::{Executor, HasObservers}, executors::{Executor, HasObservers},
fuzzer::{EvaluatorObservers, ExecutionProcessor}, fuzzer::{EvaluatorObservers, ExecutionProcessor},
inputs::UsesInput, inputs::{NopInput, UsesInput},
observers::ObserversTuple, observers::ObserversTuple,
state::{HasExecutions, HasLastReportTime, State, UsesState}, state::{HasExecutions, HasLastReportTime, NopState, State, UsesState},
Error, HasMetadata, Error, HasMetadata,
}; };
@ -85,6 +85,14 @@ where
phantom: PhantomData<S>, phantom: PhantomData<S>,
} }
impl LlmpEventManager<(), NopState<NopInput>, NopShMemProvider> {
/// Creates a builder for [`LlmpEventManager`]
#[must_use]
pub fn builder() -> LlmpEventManagerBuilder<()> {
LlmpEventManagerBuilder::new()
}
}
/// Builder for `LlmpEventManager` /// Builder for `LlmpEventManager`
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct LlmpEventManagerBuilder<EMH> { pub struct LlmpEventManagerBuilder<EMH> {

View File

@ -10,7 +10,7 @@ use libafl_bolts::{
}; };
use libafl_bolts::{ use libafl_bolts::{
llmp::{LlmpClient, LlmpClientDescription, Tag}, llmp::{LlmpClient, LlmpClientDescription, Tag},
shmem::ShMemProvider, shmem::{NopShMemProvider, ShMemProvider},
ClientId, ClientId,
}; };
use serde::Deserialize; use serde::Deserialize;
@ -19,8 +19,8 @@ use crate::{
events::{CustomBufEventResult, CustomBufHandlerFn, Event, EventFirer}, events::{CustomBufEventResult, CustomBufHandlerFn, Event, EventFirer},
executors::{Executor, HasObservers}, executors::{Executor, HasObservers},
fuzzer::{EvaluatorObservers, ExecutionProcessor}, fuzzer::{EvaluatorObservers, ExecutionProcessor},
inputs::{Input, InputConverter, UsesInput}, inputs::{Input, InputConverter, NopInput, NopInputConverter, UsesInput},
state::{HasExecutions, State, UsesState}, state::{HasExecutions, NopState, State, UsesState},
Error, HasMetadata, Error, HasMetadata,
}; };
@ -108,6 +108,22 @@ where
phantom: PhantomData<S>, phantom: PhantomData<S>,
} }
impl
LlmpEventConverter<
NopInput,
NopInputConverter<NopInput>,
NopInputConverter<NopInput>,
NopState<NopInput>,
NopShMemProvider,
>
{
/// Create a builder for [`LlmpEventConverter`]
#[must_use]
pub fn builder() -> LlmpEventConverterBuilder {
LlmpEventConverterBuilder::new()
}
}
/// Build `LlmpEventConverter` /// Build `LlmpEventConverter`
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct LlmpEventConverterBuilder { pub struct LlmpEventConverterBuilder {

View File

@ -1,4 +1,7 @@
//! Llmp restarting manager //! The `LLMP` restarting manager will
//! forward messages over lockless shared maps.
//! When the target crashes, a watch process (the parent) will
//! restart/refork it.
use alloc::vec::Vec; use alloc::vec::Vec;
#[cfg(all(unix, not(miri), feature = "std"))] #[cfg(all(unix, not(miri), feature = "std"))]
@ -42,7 +45,7 @@ use crate::{
events::{ events::{
hooks::EventManagerHooksTuple, Event, EventConfig, EventFirer, EventManager, hooks::EventManagerHooksTuple, Event, EventConfig, EventFirer, EventManager,
EventManagerId, EventProcessor, EventRestarter, HasEventManagerId, LlmpEventBroker, EventManagerId, EventProcessor, EventRestarter, HasEventManagerId, LlmpEventBroker,
LlmpEventManager, LlmpEventManagerBuilder, LlmpShouldSaveState, ProgressReporter, LlmpEventManager, LlmpShouldSaveState, ProgressReporter,
}, },
executors::{Executor, HasObservers}, executors::{Executor, HasObservers},
fuzzer::{EvaluatorObservers, ExecutionProcessor}, fuzzer::{EvaluatorObservers, ExecutionProcessor},
@ -483,11 +486,11 @@ where
} }
LlmpConnection::IsClient { client } => { LlmpConnection::IsClient { client } => {
#[cfg(not(feature = "adaptive_serialization"))] #[cfg(not(feature = "adaptive_serialization"))]
let mgr: LlmpEventManager<EMH, S, SP> = LlmpEventManagerBuilder::new() let mgr: LlmpEventManager<EMH, S, SP> = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_from_client(client, self.configuration)?; .build_from_client(client, self.configuration)?;
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
let mgr: LlmpEventManager<EMH, S, SP> = LlmpEventManagerBuilder::new() let mgr: LlmpEventManager<EMH, S, SP> = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_from_client( .build_from_client(
client, client,
@ -511,7 +514,7 @@ where
ManagerKind::Client { cpu_core } => { ManagerKind::Client { cpu_core } => {
// We are a client // We are a client
#[cfg(not(feature = "adaptive_serialization"))] #[cfg(not(feature = "adaptive_serialization"))]
let mgr = LlmpEventManagerBuilder::new() let mgr = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_on_port( .build_on_port(
self.shmem_provider.clone(), self.shmem_provider.clone(),
@ -519,7 +522,7 @@ where
self.configuration, self.configuration,
)?; )?;
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
let mgr = LlmpEventManagerBuilder::new() let mgr = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_on_port( .build_on_port(
self.shmem_provider.clone(), self.shmem_provider.clone(),
@ -648,7 +651,7 @@ where
let (state, mut mgr) = let (state, mut mgr) =
if let Some((state_opt, mgr_description)) = staterestorer.restore()? { if let Some((state_opt, mgr_description)) = staterestorer.restore()? {
#[cfg(not(feature = "adaptive_serialization"))] #[cfg(not(feature = "adaptive_serialization"))]
let llmp_mgr = LlmpEventManagerBuilder::new() let llmp_mgr = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_existing_client_from_description( .build_existing_client_from_description(
new_shmem_provider, new_shmem_provider,
@ -656,7 +659,7 @@ where
self.configuration, self.configuration,
)?; )?;
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
let llmp_mgr = LlmpEventManagerBuilder::new() let llmp_mgr = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_existing_client_from_description( .build_existing_client_from_description(
new_shmem_provider, new_shmem_provider,
@ -676,7 +679,7 @@ where
log::info!("First run. Let's set it all up"); log::info!("First run. Let's set it all up");
// Mgr to send and receive msgs from/to all other fuzzer instances // Mgr to send and receive msgs from/to all other fuzzer instances
#[cfg(not(feature = "adaptive_serialization"))] #[cfg(not(feature = "adaptive_serialization"))]
let mgr = LlmpEventManagerBuilder::new() let mgr = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_existing_client_from_env( .build_existing_client_from_env(
new_shmem_provider, new_shmem_provider,
@ -684,7 +687,7 @@ where
self.configuration, self.configuration,
)?; )?;
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
let mgr = LlmpEventManagerBuilder::new() let mgr = LlmpEventManager::builder()
.hooks(self.hooks) .hooks(self.hooks)
.build_existing_client_from_env( .build_existing_client_from_env(
new_shmem_provider, new_shmem_provider,
@ -738,7 +741,7 @@ mod tests {
use crate::{ use crate::{
corpus::{Corpus, InMemoryCorpus, Testcase}, corpus::{Corpus, InMemoryCorpus, Testcase},
events::llmp::{restarting::_ENV_FUZZER_SENDER, LlmpEventManagerBuilder}, events::llmp::{restarting::_ENV_FUZZER_SENDER, LlmpEventManager},
executors::{ExitKind, InProcessExecutor}, executors::{ExitKind, InProcessExecutor},
feedbacks::ConstFeedback, feedbacks::ConstFeedback,
fuzzer::Fuzzer, fuzzer::Fuzzer,
@ -788,11 +791,11 @@ mod tests {
} }
#[cfg(not(feature = "adaptive_serialization"))] #[cfg(not(feature = "adaptive_serialization"))]
let mut llmp_mgr = LlmpEventManagerBuilder::new() let mut llmp_mgr = LlmpEventManager::builder()
.build_from_client(llmp_client, "fuzzer".into()) .build_from_client(llmp_client, "fuzzer".into())
.unwrap(); .unwrap();
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
let mut llmp_mgr = LlmpEventManagerBuilder::new() let mut llmp_mgr = LlmpEventManager::builder()
.build_from_client(llmp_client, "fuzzer".into(), time_ref.clone()) .build_from_client(llmp_client, "fuzzer".into(), time_ref.clone())
.unwrap(); .unwrap();
@ -837,7 +840,7 @@ mod tests {
let (mut state_clone, mgr_description) = staterestorer.restore().unwrap().unwrap(); let (mut state_clone, mgr_description) = staterestorer.restore().unwrap().unwrap();
#[cfg(not(feature = "adaptive_serialization"))] #[cfg(not(feature = "adaptive_serialization"))]
let mut llmp_clone = LlmpEventManagerBuilder::new() let mut llmp_clone = LlmpEventManager::builder()
.build_existing_client_from_description( .build_existing_client_from_description(
shmem_provider, shmem_provider,
&mgr_description, &mgr_description,
@ -845,7 +848,7 @@ mod tests {
) )
.unwrap(); .unwrap();
#[cfg(feature = "adaptive_serialization")] #[cfg(feature = "adaptive_serialization")]
let mut llmp_clone = LlmpEventManagerBuilder::new() let mut llmp_clone = LlmpEventManager::builder()
.build_existing_client_from_description( .build_existing_client_from_description(
shmem_provider, shmem_provider,
&mgr_description, &mgr_description,

View File

@ -440,6 +440,17 @@ where
phantom: PhantomData<S>, phantom: PhantomData<S>,
} }
impl<S> TcpEventManager<(), S>
where
S: State,
{
/// Create a builder for [`TcpEventManager`]
#[must_use]
pub fn builder() -> TcpEventManagerBuilder<(), S> {
TcpEventManagerBuilder::new()
}
}
/// Builder for `TcpEventManager` /// Builder for `TcpEventManager`
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct TcpEventManagerBuilder<EMH, S> { pub struct TcpEventManagerBuilder<EMH, S> {

View File

@ -182,8 +182,7 @@ impl CommandExecutor<(), (), ()> {
/// By default, input is read from stdin, unless you specify a different location using /// By default, input is read from stdin, unless you specify a different location using
/// * `arg_input_arg` for input delivered _as_ an command line argument /// * `arg_input_arg` for input delivered _as_ an command line argument
/// * `arg_input_file` for input via a file of a specific name /// * `arg_input_file` for input via a file of a specific name
/// * `arg_input_file_std` for a file with default name /// * `arg_input_file_std` for a file with default name (at the right location in the arguments)
/// (at the right location in the arguments)
#[must_use] #[must_use]
pub fn builder() -> CommandExecutorBuilder { pub fn builder() -> CommandExecutorBuilder {
CommandExecutorBuilder::new() CommandExecutorBuilder::new()

View File

@ -1324,7 +1324,7 @@ mod tests {
use serial_test::serial; use serial_test::serial;
use crate::{ use crate::{
executors::forkserver::ForkserverExecutorBuilder, executors::forkserver::ForkserverExecutor,
observers::{ConstMapObserver, HitcountsMapObserver}, observers::{ConstMapObserver, HitcountsMapObserver},
Error, Error,
}; };
@ -1348,7 +1348,7 @@ mod tests {
shmem_buf, shmem_buf,
)); ));
let executor = ForkserverExecutorBuilder::new() let executor = ForkserverExecutor::builder()
.program(bin) .program(bin)
.args(args) .args(args)
.debug_child(false) .debug_child(false)

View File

@ -219,6 +219,7 @@ where
/// * `user_hooks` - the hooks run before and after the harness's execution /// * `user_hooks` - the hooks run before and after the harness's execution
/// * `harness_fn` - the harness, executing the function /// * `harness_fn` - the harness, executing the function
/// * `observers` - the observers observing the target during execution /// * `observers` - the observers observing the target during execution
///
/// This may return an error on unix, if signal handler setup fails /// This may return an error on unix, if signal handler setup fails
pub fn with_timeout_generic<E, EM, OF, Z>( pub fn with_timeout_generic<E, EM, OF, Z>(
user_hooks: HT, user_hooks: HT,

View File

@ -232,6 +232,7 @@ where
/// * `user_hooks` - the hooks run before and after the harness's execution /// * `user_hooks` - the hooks run before and after the harness's execution
/// * `harness_fn` - the harness, executing the function /// * `harness_fn` - the harness, executing the function
/// * `observers` - the observers observing the target during execution /// * `observers` - the observers observing the target during execution
///
/// This may return an error on unix, if signal handler setup fails /// This may return an error on unix, if signal handler setup fails
pub fn with_timeout<EM, OF, Z>( pub fn with_timeout<EM, OF, Z>(
harness_fn: &'a mut H, harness_fn: &'a mut H,
@ -329,12 +330,13 @@ where
}) })
} }
/// Create a new in mem executor. /// Create a new [`InProcessExecutor`].
/// Caution: crash and restart in one of them will lead to odd behavior if multiple are used, /// Caution: crash and restart in one of them will lead to odd behavior if multiple are used,
/// depending on different corpus or state. /// depending on different corpus or state.
/// * `user_hooks` - the hooks run before and after the harness's execution /// * `user_hooks` - the hooks run before and after the harness's execution
/// * `harness_fn` - the harness, executing the function /// * `harness_fn` - the harness, executing the function
/// * `observers` - the observers observing the target during execution /// * `observers` - the observers observing the target during execution
///
/// This may return an error on unix, if signal handler setup fails /// This may return an error on unix, if signal handler setup fails
pub fn with_timeout_generic<EM, OF, Z>( pub fn with_timeout_generic<EM, OF, Z>(
user_hooks: HT, user_hooks: HT,

View File

@ -227,6 +227,7 @@ where
/// * `user_hooks` - the hooks run before and after the harness's execution /// * `user_hooks` - the hooks run before and after the harness's execution
/// * `harness_fn` - the harness, executing the function /// * `harness_fn` - the harness, executing the function
/// * `observers` - the observers observing the target during execution /// * `observers` - the observers observing the target during execution
///
/// This may return an error on unix, if signal handler setup fails /// This may return an error on unix, if signal handler setup fails
pub fn with_timeout<EM, OF, Z>( pub fn with_timeout<EM, OF, Z>(
harness_fn: &'a mut H, harness_fn: &'a mut H,
@ -354,6 +355,7 @@ where
/// * `user_hooks` - the hooks run before and after the harness's execution /// * `user_hooks` - the hooks run before and after the harness's execution
/// * `harness_fn` - the harness, executing the function /// * `harness_fn` - the harness, executing the function
/// * `observers` - the observers observing the target during execution /// * `observers` - the observers observing the target during execution
///
/// This may return an error on unix, if signal handler setup fails /// This may return an error on unix, if signal handler setup fails
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn with_timeout_generic<EM, OF, Z>( pub fn with_timeout_generic<EM, OF, Z>(

View File

@ -1,35 +1,36 @@
//! # Concolic Tracing Serialization Format //! # Concolic Tracing Serialization Format
//!
//! ## Design Goals //! ## Design Goals
//! * The serialization format for concolic tracing was developed with the goal of being space and time efficient. //! * The serialization format for concolic tracing was developed with the goal of being space and time efficient.
//! * Additionally, it should be easy to maintain and extend. //! * Additionally, it should be easy to maintain and extend.
//! * It does not have to be compatible with other programming languages. //! * It does not have to be compatible with other programming languages.
//! * It should be resilient to crashes. Since we are fuzzing, we are expecting the traced program to crash at some //! * It should be resilient to crashes. Since we are fuzzing, we are expecting the traced program to crash at some
//! point. //! point.
//! //!
//! The format as implemented fulfils these design goals. //! The format as implemented fulfils these design goals.
//! Specifically: //! Specifically:
//! * it requires only constant memory space for serialization, which allows for tracing complex and/or //! * it requires only constant memory space for serialization, which allows for tracing complex and/or
//! long-running programs. //! long-running programs.
//! * the trace itself requires little space. A typical binary operation (such as an add) typically takes just 3 bytes. //! * the trace itself requires little space. A typical binary operation (such as an add) typically takes just 3 bytes.
//! * it easy to encode. There is no translation between the interface of the runtime itself and the trace it generates. //! * it easy to encode. There is no translation between the interface of the runtime itself and the trace it generates.
//! * it is similarly easy to decode and can be easily translated into an in-memory AST without overhead, because //! * it is similarly easy to decode and can be easily translated into an in-memory AST without overhead, because
//! expressions are decoded from leaf to root instead of root to leaf. //! expressions are decoded from leaf to root instead of root to leaf.
//! * At its core, it is just [`SymExpr`]s, which can be added to, modified and removed from with ease. The //! * At its core, it is just [`SymExpr`]s, which can be added to, modified and removed from with ease. The
//! definitions are automatically shared between the runtime and the consuming program, since both depend on the same //! definitions are automatically shared between the runtime and the consuming program, since both depend on the same
//! `LibAFL`. //! `LibAFL`.
//! //!
//! ## Techniques //! ## Techniques
//! The serialization format applies multiple techniques to achieve its goals. //! The serialization format applies multiple techniques to achieve its goals.
//! * It uses bincode for efficient binary serialization. Crucially, bincode uses variable length integer encoding, //! * It uses bincode for efficient binary serialization. Crucially, bincode uses variable length integer encoding,
//! allowing it encode small integers use fewer bytes. //! allowing it encode small integers use fewer bytes.
//! * References to previous expressions are stored relative to the current expressions id. The vast majority of //! * References to previous expressions are stored relative to the current expressions id. The vast majority of
//! expressions refer to other expressions that were defined close to their use. Therefore, encoding relative references //! expressions refer to other expressions that were defined close to their use. Therefore, encoding relative references
//! keeps references small. Therefore, they make optimal use of bincodes variable length integer encoding. //! keeps references small. Therefore, they make optimal use of bincodes variable length integer encoding.
//! * Ids of expressions ([`SymExprRef`]s) are implicitly derived by their position in the message stream. Effectively, //! * Ids of expressions ([`SymExprRef`]s) are implicitly derived by their position in the message stream. Effectively,
//! a counter is used to identify expressions. //! a counter is used to identify expressions.
//! * The current length of the trace in bytes in serialized in a fixed format at the beginning of the trace. //! * The current length of the trace in bytes in serialized in a fixed format at the beginning of the trace.
//! This length is updated regularly when the trace is in a consistent state. This allows the reader to avoid reading //! This length is updated regularly when the trace is in a consistent state. This allows the reader to avoid reading
//! malformed data if the traced process crashed. //! malformed data if the traced process crashed.
//! //!
//! ## Example //! ## Example
//! The expression `SymExpr::BoolAnd { a: SymExpr::True, b: SymExpr::False }` would be encoded as: //! The expression `SymExpr::BoolAnd { a: SymExpr::True, b: SymExpr::False }` would be encoded as:

View File

@ -22,13 +22,12 @@ static BUILD_ID: OnceLock<Uuid> = OnceLock::new();
/// invocations of identically laid out binaries. /// invocations of identically laid out binaries.
/// ///
/// As such: /// As such:
/// * It is guaranteed to be identical within multiple invocations of the same /// * It is guaranteed to be identical within multiple invocations of the same binary.
/// binary.
/// * It is guaranteed to be different across binaries with different code or /// * It is guaranteed to be different across binaries with different code or
/// data segments or layout. /// data segments or layout.
/// * Equality is unspecified if the binaries have identical code and data /// * Equality is unspecified if the binaries have identical code and data
/// segments and layout but differ immaterially (e.g. if a timestamp is included /// segments and layout but differ immaterially (e.g. if a timestamp is included
/// in the binary at compile time). /// in the binary at compile time).
/// ///
/// # Examples /// # Examples
/// ///

View File

@ -2,11 +2,9 @@
//! too.) //! too.)
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use alloc::{rc::Rc, string::ToString}; use alloc::{rc::Rc, string::ToString, vec::Vec};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use core::fmt::Display; use core::{cell::RefCell, fmt, fmt::Display, mem::ManuallyDrop};
#[cfg(feature = "alloc")]
use core::{cell::RefCell, fmt, mem::ManuallyDrop};
use core::{ use core::{
fmt::Debug, fmt::Debug,
mem, mem,
@ -318,6 +316,72 @@ pub trait ShMemProvider: Clone + Default + Debug {
} }
} }
/// An [`ShMemProvider`] that does not provide any [`ShMem`].
/// This is mainly for testing and type magic.
/// The resulting [`NopShMem`] is backed by a simple byte buffer to do some simple non-shared things with.
/// Calling [`NopShMemProvider::shmem_from_id_and_size`] will return new maps for the same id every time.
///
/// # Note
/// If you just want a simple shared memory implementation, use [`StdShMemProvider`] instead.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, Default)]
pub struct NopShMemProvider;
#[cfg(feature = "alloc")]
impl ShMemProvider for NopShMemProvider {
type ShMem = NopShMem;
fn new() -> Result<Self, Error> {
Ok(Self)
}
fn new_shmem(&mut self, map_size: usize) -> Result<Self::ShMem, Error> {
self.shmem_from_id_and_size(ShMemId::default(), map_size)
}
fn shmem_from_id_and_size(
&mut self,
id: ShMemId,
map_size: usize,
) -> Result<Self::ShMem, Error> {
Ok(NopShMem {
id,
buf: vec![0; map_size],
})
}
}
/// An [`ShMem]`] that does not have any mem nor share anything.
#[cfg(feature = "alloc")]
#[derive(Debug, Clone, Default)]
pub struct NopShMem {
id: ShMemId,
buf: Vec<u8>,
}
#[cfg(feature = "alloc")]
impl ShMem for NopShMem {
fn id(&self) -> ShMemId {
self.id
}
}
#[cfg(feature = "alloc")]
impl DerefMut for NopShMem {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buf
}
}
#[cfg(feature = "alloc")]
impl Deref for NopShMem {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.buf
}
}
/// A Handle Counted shared map, /// A Handle Counted shared map,
/// that can use internal mutability. /// that can use internal mutability.
/// Useful if the `ShMemProvider` needs to keep local state. /// Useful if the `ShMemProvider` needs to keep local state.

View File

@ -189,7 +189,7 @@ impl FridaInstrumentationHelperBuilder {
/// Instrument all modules in `/usr/lib` as well as `libfoo.so`: /// Instrument all modules in `/usr/lib` as well as `libfoo.so`:
/// ``` /// ```
///# use libafl_frida::helper::FridaInstrumentationHelperBuilder; ///# use libafl_frida::helper::FridaInstrumentationHelperBuilder;
/// let builder = FridaInstrumentationHelperBuilder::new() /// let builder = FridaInstrumentationHelper::builder()
/// .instrument_module_if(|module| module.name() == "libfoo.so") /// .instrument_module_if(|module| module.name() == "libfoo.so")
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib")); /// .instrument_module_if(|module| module.path().starts_with("/usr/lib"));
/// ``` /// ```
@ -218,7 +218,7 @@ impl FridaInstrumentationHelperBuilder {
/// ///
/// ``` /// ```
///# use libafl_frida::helper::FridaInstrumentationHelperBuilder; ///# use libafl_frida::helper::FridaInstrumentationHelperBuilder;
/// let builder = FridaInstrumentationHelperBuilder::new() /// let builder = FridaInstrumentationHelper::builder()
/// .instrument_module_if(|module| module.path().starts_with("/usr/lib")) /// .instrument_module_if(|module| module.path().starts_with("/usr/lib"))
/// .skip_module_if(|module| module.name() == "libfoo.so"); /// .skip_module_if(|module| module.name() == "libfoo.so");
/// ``` /// ```

View File

@ -236,6 +236,7 @@ pub fn merge(
.scheduler_mut() .scheduler_mut()
.on_remove(&mut state, idx, &Some(testcase))?; .on_remove(&mut state, idx, &Some(testcase))?;
} else { } else {
#[allow(clippy::needless_borrows_for_generic_args)] // False-positive: file_path is used just below
rename(&file_path, &new_file_path)?; rename(&file_path, &new_file_path)?;
*file_path = new_file_path; *file_path = new_file_path;
} }

View File

@ -30,6 +30,13 @@ pub struct NyxExecutor<S, OT> {
phantom: PhantomData<S>, phantom: PhantomData<S>,
} }
impl NyxExecutor<(), ()> {
/// Create a builder for [`NyxExeuctor`]
pub fn builder() -> NyxExecutorBuilder {
NyxExecutorBuilder::new()
}
}
impl<S, OT> UsesState for NyxExecutor<S, OT> impl<S, OT> UsesState for NyxExecutor<S, OT>
where where
S: State, S: State,

View File

@ -5,7 +5,7 @@ use std::{fs, net::SocketAddr, path::PathBuf, time::Duration};
use libafl::{ use libafl::{
corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus}, corpus::{CachedOnDiskCorpus, Corpus, OnDiskCorpus},
events::{launcher::Launcher, EventConfig, EventRestarter, LlmpRestartingEventManager}, events::{launcher::Launcher, EventConfig, EventRestarter, LlmpRestartingEventManager},
executors::forkserver::ForkserverExecutorBuilder, executors::forkserver::ForkserverExecutor,
feedback_or, feedback_or_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Fuzzer, StdFuzzer}, fuzzer::{Fuzzer, StdFuzzer},
@ -175,7 +175,7 @@ impl<'a> ForkserverBytesCoverageSugar<'a> {
let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective); let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);
let forkserver = if self.shmem_testcase { let forkserver = if self.shmem_testcase {
ForkserverExecutorBuilder::new() ForkserverExecutor::builder()
.program(self.program.clone()) .program(self.program.clone())
.parse_afl_cmdline(self.arguments) .parse_afl_cmdline(self.arguments)
.is_persistent(true) .is_persistent(true)
@ -186,7 +186,7 @@ impl<'a> ForkserverBytesCoverageSugar<'a> {
.shmem_provider(&mut shmem_provider_client) .shmem_provider(&mut shmem_provider_client)
.build_dynamic_map(edges_observer, tuple_list!(time_observer)) .build_dynamic_map(edges_observer, tuple_list!(time_observer))
} else { } else {
ForkserverExecutorBuilder::new() ForkserverExecutor::builder()
.program(self.program.clone()) .program(self.program.clone())
.parse_afl_cmdline(self.arguments) .parse_afl_cmdline(self.arguments)
.is_persistent(true) .is_persistent(true)

View File

@ -200,6 +200,7 @@ fn main() {
} }
} }
#[cfg(any(feature = "forkserver", feature = "windows_asan"))]
let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap(); let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
#[cfg(feature = "forkserver")] #[cfg(feature = "forkserver")]

View File

@ -1,5 +1,4 @@
use core::marker::PhantomData; use core::{marker::PhantomData, ptr, time::Duration};
use std::time::Duration;
use libafl::{ use libafl::{
executors::{Executor, ExitKind, HasObservers}, executors::{Executor, ExitKind, HasObservers},
@ -10,19 +9,19 @@ use libafl::{
}; };
use libafl_bolts::{ use libafl_bolts::{
fs::{InputFile, INPUTFILE_STD}, fs::{InputFile, INPUTFILE_STD},
shmem::{ShMem, ShMemProvider, StdShMemProvider}, shmem::{NopShMemProvider, ShMem, ShMemProvider},
tuples::RefIndexable, tuples::RefIndexable,
AsSlice, AsSliceMut, AsSlice, AsSliceMut,
}; };
use tinyinst::tinyinst::{litecov::RunResult, TinyInst}; use tinyinst::tinyinst::{litecov::RunResult, TinyInst};
/// Tinyinst executor /// [`TinyInst`](https://github.com/googleprojectzero/TinyInst) executor
pub struct TinyInstExecutor<'a, S, SP, OT> pub struct TinyInstExecutor<S, SP, OT>
where where
SP: ShMemProvider, SP: ShMemProvider,
{ {
tinyinst: TinyInst, tinyinst: TinyInst,
coverage: &'a mut Vec<u64>, coverage_ptr: *mut Vec<u64>,
timeout: Duration, timeout: Duration,
observers: OT, observers: OT,
phantom: PhantomData<S>, phantom: PhantomData<S>,
@ -30,7 +29,15 @@ where
map: Option<<SP as ShMemProvider>::ShMem>, map: Option<<SP as ShMemProvider>::ShMem>,
} }
impl<'a, S, SP, OT> std::fmt::Debug for TinyInstExecutor<'a, S, SP, OT> impl<'a> TinyInstExecutor<(), NopShMemProvider, ()> {
/// Create a builder for [`TinyInstExecutor`]
#[must_use]
pub fn builder() -> TinyInstExecutorBuilder<'a, NopShMemProvider> {
TinyInstExecutorBuilder::new()
}
}
impl<S, SP, OT> std::fmt::Debug for TinyInstExecutor<S, SP, OT>
where where
SP: ShMemProvider, SP: ShMemProvider,
{ {
@ -41,7 +48,7 @@ where
} }
} }
impl<'a, EM, S, SP, OT, Z> Executor<EM, Z> for TinyInstExecutor<'a, S, SP, OT> impl<EM, S, SP, OT, Z> Executor<EM, Z> for TinyInstExecutor<S, SP, OT>
where where
EM: UsesState<State = S>, EM: UsesState<State = S>,
S: State + HasExecutions, S: State + HasExecutions,
@ -80,7 +87,8 @@ where
let mut status = RunResult::OK; let mut status = RunResult::OK;
unsafe { unsafe {
status = self.tinyinst.run(); status = self.tinyinst.run();
self.tinyinst.vec_coverage(self.coverage, false); self.tinyinst
.vec_coverage(self.coverage_ptr.as_mut().unwrap(), false);
} }
match status { match status {
@ -100,30 +108,52 @@ pub struct TinyInstExecutorBuilder<'a, SP> {
tinyinst_args: Vec<String>, tinyinst_args: Vec<String>,
program_args: Vec<String>, program_args: Vec<String>,
timeout: Duration, timeout: Duration,
coverage_ptr: *mut Vec<u64>,
shmem_provider: Option<&'a mut SP>, shmem_provider: Option<&'a mut SP>,
} }
const MAX_FILE: usize = 1024 * 1024; const MAX_FILE: usize = 1024 * 1024;
const SHMEM_FUZZ_HDR_SIZE: usize = 4; const SHMEM_FUZZ_HDR_SIZE: usize = 4;
impl<'a> Default for TinyInstExecutorBuilder<'a, StdShMemProvider> { impl<'a> Default for TinyInstExecutorBuilder<'a, NopShMemProvider> {
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl<'a> TinyInstExecutorBuilder<'a, StdShMemProvider> { impl<'a> TinyInstExecutorBuilder<'a, NopShMemProvider> {
/// Constructor /// Constructor
#[must_use] #[must_use]
pub fn new() -> TinyInstExecutorBuilder<'a, StdShMemProvider> { pub fn new() -> TinyInstExecutorBuilder<'a, NopShMemProvider> {
Self { Self {
tinyinst_args: vec![], tinyinst_args: vec![],
program_args: vec![], program_args: vec![],
timeout: Duration::new(3, 0), timeout: Duration::new(3, 0),
shmem_provider: None, shmem_provider: None,
coverage_ptr: ptr::null_mut(),
} }
} }
/// Use this to enable shmem testcase passing.
#[must_use]
pub fn shmem_provider<SP: ShMemProvider>(
self,
shmem_provider: &'a mut SP,
) -> TinyInstExecutorBuilder<'a, SP> {
TinyInstExecutorBuilder {
tinyinst_args: self.tinyinst_args,
program_args: self.program_args,
timeout: self.timeout,
shmem_provider: Some(shmem_provider),
coverage_ptr: ptr::null_mut(),
}
}
}
impl<'a, SP> TinyInstExecutorBuilder<'a, SP>
where
SP: ShMemProvider,
{
/// Argument for tinyinst instrumentation /// Argument for tinyinst instrumentation
#[must_use] #[must_use]
pub fn tinyinst_arg(mut self, arg: String) -> Self { pub fn tinyinst_arg(mut self, arg: String) -> Self {
@ -207,31 +237,22 @@ impl<'a> TinyInstExecutorBuilder<'a, StdShMemProvider> {
self self
} }
/// Use this to enable shmem testcase passing. /// Set the pointer to the coverage vec used to observer the execution.
///
/// # Safety
/// The coverage vec pointer must point to a valid vec and outlive the time the [`TinyInstExecutor`] is alive.
/// The map will be dereferenced and borrowed mutably during execution. This may not happen concurrently.
#[must_use] #[must_use]
pub fn shmem_provider<SP: ShMemProvider>( pub fn coverage_ptr(mut self, coverage_ptr: *mut Vec<u64>) -> Self {
self, self.coverage_ptr = coverage_ptr;
shmem_provider: &'a mut SP, self
) -> TinyInstExecutorBuilder<'a, SP> {
TinyInstExecutorBuilder {
tinyinst_args: self.tinyinst_args,
program_args: self.program_args,
timeout: self.timeout,
shmem_provider: Some(shmem_provider),
}
} }
}
impl<'a, SP> TinyInstExecutorBuilder<'a, SP> /// Build [`TinyInst`](https://github.com/googleprojectzero/TinyInst) executor
where pub fn build<OT, S>(&mut self, observers: OT) -> Result<TinyInstExecutor<S, SP, OT>, Error> {
SP: ShMemProvider, if self.coverage_ptr.is_null() {
{ return Err(Error::illegal_argument("Coverage pointer may not be null."));
/// Build tinyinst executor }
pub fn build<OT, S>(
&mut self,
coverage: &'a mut Vec<u64>,
observers: OT,
) -> Result<TinyInstExecutor<'a, S, SP, OT>, Error> {
let (map, shmem_id) = match &mut self.shmem_provider { let (map, shmem_id) = match &mut self.shmem_provider {
Some(provider) => { Some(provider) => {
// setup shared memory // setup shared memory
@ -285,7 +306,7 @@ where
Ok(TinyInstExecutor { Ok(TinyInstExecutor {
tinyinst, tinyinst,
coverage, coverage_ptr: self.coverage_ptr,
timeout: self.timeout, timeout: self.timeout,
observers, observers,
phantom: PhantomData, phantom: PhantomData,
@ -295,7 +316,7 @@ where
} }
} }
impl<'a, S, SP, OT> HasObservers for TinyInstExecutor<'a, S, SP, OT> impl<S, SP, OT> HasObservers for TinyInstExecutor<S, SP, OT>
where where
S: State, S: State,
SP: ShMemProvider, SP: ShMemProvider,
@ -309,14 +330,14 @@ where
RefIndexable::from(&mut self.observers) RefIndexable::from(&mut self.observers)
} }
} }
impl<'a, S, SP, OT> UsesState for TinyInstExecutor<'a, S, SP, OT> impl<S, SP, OT> UsesState for TinyInstExecutor<S, SP, OT>
where where
S: State, S: State,
SP: ShMemProvider, SP: ShMemProvider,
{ {
type State = S; type State = S;
} }
impl<'a, S, SP, OT> UsesObservers for TinyInstExecutor<'a, S, SP, OT> impl<S, SP, OT> UsesObservers for TinyInstExecutor<S, SP, OT>
where where
OT: ObserversTuple<S>, OT: ObserversTuple<S>,
S: State, S: State,