fix qemu launcher bug (#3206)

* no more shellscript

* metadatas

* clp

* clippo

* fix bug

* taplo

* Merge branch 'qemu_launcher_insane' of github.com:AFLplusplus/LibAFL into qemu_launcher_insane

* fix wrong code
This commit is contained in:
Dongjia "toka" Zhang 2025-05-12 15:12:20 +02:00 committed by GitHub
parent 60c05396da
commit 390008e1d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 263 deletions

View File

@ -2,9 +2,10 @@ use std::env;
use libafl::{ use libafl::{
corpus::{InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{InMemoryOnDiskCorpus, OnDiskCorpus},
events::ClientDescription, events::{
ClientDescription, EventFirer, EventReceiver, EventRestarter, ProgressReporter, SendExiting,
},
inputs::BytesInput, inputs::BytesInput,
monitors::Monitor,
state::StdState, state::StdState,
Error, Error,
}; };
@ -14,11 +15,7 @@ use libafl_qemu::modules::{
utils::filters::StdAddressFilter, DrCovModule, InjectionModule, utils::filters::StdAddressFilter, DrCovModule, InjectionModule,
}; };
use crate::{ use crate::{harness::Harness, instance::Instance, options::FuzzerOptions};
harness::Harness,
instance::{ClientMgr, Instance},
options::FuzzerOptions,
};
#[expect(clippy::module_name_repetitions)] #[expect(clippy::module_name_repetitions)]
pub type ClientState = pub type ClientState =
@ -51,12 +48,19 @@ impl Client<'_> {
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]
pub fn run<M: Monitor>( pub fn run<EM>(
&self, &self,
state: Option<ClientState>, state: Option<ClientState>,
mgr: ClientMgr<M>, mgr: EM,
client_description: ClientDescription, client_description: ClientDescription,
) -> Result<(), Error> { ) -> Result<(), Error>
where
EM: EventFirer<BytesInput, ClientState>
+ EventRestarter<ClientState>
+ ProgressReporter<ClientState>
+ SendExiting
+ EventReceiver<BytesInput, ClientState>,
{
let core_id = client_description.core_id(); let core_id = client_description.core_id();
let mut args = self.args()?; let mut args = self.args()?;
Harness::edit_args(&mut args); Harness::edit_args(&mut args);

View File

@ -5,20 +5,16 @@ use std::{
}; };
use clap::Parser; use clap::Parser;
#[cfg(feature = "simplemgr")]
use libafl::events::SimpleEventManager;
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl::events::{EventConfig, Launcher, LlmpEventManagerBuilder, MonitorTypedEventManager}; use libafl::events::{EventConfig, Launcher};
use libafl::{ use libafl::{
events::ClientDescription, events::{ClientDescription, SimpleEventManager},
monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
Error, Error,
}; };
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider}; use libafl_bolts::shmem::{ShMemProvider, StdShMemProvider};
use libafl_bolts::{core_affinity::CoreId, current_time}; use libafl_bolts::{core_affinity::CoreId, current_time};
#[cfg(not(feature = "simplemgr"))]
use libafl_bolts::{llmp::LlmpBroker, staterestore::StateRestorer, tuples::tuple_list};
#[cfg(unix)] #[cfg(unix)]
use { use {
nix::unistd::dup, nix::unistd::dup,
@ -84,7 +80,8 @@ impl Fuzzer {
{ {
// The shared memory allocator // The shared memory allocator
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
let mut shmem_provider = StdShMemProvider::new()?; let shmem_provider = StdShMemProvider::new()?;
/* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */ /* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */
#[cfg(not(feature = "simplemgr"))] #[cfg(not(feature = "simplemgr"))]
let stdout = if self.options.verbose { let stdout = if self.options.verbose {
@ -95,45 +92,10 @@ impl Fuzzer {
let client = Client::new(&self.options); let client = Client::new(&self.options);
#[cfg(not(feature = "simplemgr"))]
if self.options.rerun_input.is_some() {
// If we want to rerun a single input but we use a restarting mgr, we'll have to create a fake restarting mgr that doesn't actually restart.
// It's not pretty but better than recompiling with simplemgr.
// Just a random number, let's hope it's free :)
let broker_port = 13120;
let _fake_broker = LlmpBroker::create_attach_to_tcp(
shmem_provider.clone(),
tuple_list!(),
broker_port,
)
.unwrap();
// To rerun an input, instead of using a launcher, we create dummy parameters and run the client directly.
// NOTE: This is a hack for debugging that that will only work for non-crashing inputs.
return client.run(
None,
MonitorTypedEventManager::<_, M>::new(
LlmpEventManagerBuilder::builder()
.build_on_port(
shmem_provider.clone(),
broker_port,
EventConfig::AlwaysUnique,
Some(StateRestorer::new(
shmem_provider.new_shmem(0x1000).unwrap(),
)),
)
.unwrap(),
),
ClientDescription::new(0, 0, CoreId(0)),
);
}
#[cfg(feature = "simplemgr")]
if self.options.rerun_input.is_some() { if self.options.rerun_input.is_some() {
return client.run( return client.run(
None, None,
SimpleEventManager::new(monitor), SimpleEventManager::new(monitor.clone()),
ClientDescription::new(0, 0, CoreId(0)), ClientDescription::new(0, 0, CoreId(0)),
); );
} }
@ -141,7 +103,7 @@ impl Fuzzer {
#[cfg(feature = "simplemgr")] #[cfg(feature = "simplemgr")]
return client.run( return client.run(
None, None,
SimpleEventManager::new(monitor), SimpleEventManager::new(monitor.clone()),
ClientDescription::new(0, 0, CoreId(0)), ClientDescription::new(0, 0, CoreId(0)),
); );
@ -152,7 +114,7 @@ impl Fuzzer {
.broker_port(self.options.port) .broker_port(self.options.port)
.configuration(EventConfig::from_build_id()) .configuration(EventConfig::from_build_id())
.monitor(monitor) .monitor(monitor)
.run_client(|s, m, c| client.run(s, MonitorTypedEventManager::<_, M>::new(m), c)) .run_client(|s, m, c| client.run(s, m, c))
.cores(&self.options.cores) .cores(&self.options.cores)
.stdout_file(stdout) .stdout_file(stdout)
.stderr_file(stdout) .stderr_file(stdout)

View File

@ -1,19 +1,16 @@
use core::fmt::Debug; use core::fmt::Debug;
use std::{fs, marker::PhantomData, ops::Range, process}; use std::{fs, ops::Range, process};
#[cfg(feature = "simplemgr")]
use libafl::events::SimpleEventManager;
#[cfg(not(feature = "simplemgr"))]
use libafl::events::{LlmpRestartingEventManager, MonitorTypedEventManager};
use libafl::{ use libafl::{
corpus::{Corpus, HasCurrentCorpusId, InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{Corpus, HasCurrentCorpusId, InMemoryOnDiskCorpus, OnDiskCorpus},
events::{ClientDescription, EventRestarter}, events::{
ClientDescription, EventFirer, EventReceiver, EventRestarter, ProgressReporter, SendExiting,
},
executors::{Executor, ExitKind, ShadowExecutor}, executors::{Executor, ExitKind, ShadowExecutor},
feedback_and_fast, feedback_or, feedback_or_fast, feedback_and_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Evaluator, Fuzzer, StdFuzzer}, fuzzer::{Evaluator, Fuzzer, StdFuzzer},
inputs::{BytesInput, Input}, inputs::{BytesInput, Input},
monitors::Monitor,
mutators::{ mutators::{
havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, HavocScheduledMutator, havoc_mutations, token_mutations::I2SRandReplace, tokens_mutations, HavocScheduledMutator,
StdMOptMutator, Tokens, StdMOptMutator, Tokens,
@ -31,8 +28,6 @@ use libafl::{
state::{HasCorpus, HasExecutions, HasSolutions, StdState}, state::{HasCorpus, HasExecutions, HasSolutions, StdState},
Error, HasMetadata, Error, HasMetadata,
}; };
#[cfg(not(feature = "simplemgr"))]
use libafl_bolts::shmem::{StdShMem, StdShMemProvider};
use libafl_bolts::{ use libafl_bolts::{
ownedref::OwnedMutSlice, ownedref::OwnedMutSlice,
rands::StdRand, rands::StdRand,
@ -57,14 +52,6 @@ use crate::{harness::Harness, options::FuzzerOptions};
pub type ClientState = pub type ClientState =
StdState<InMemoryOnDiskCorpus<BytesInput>, BytesInput, StdRand, OnDiskCorpus<BytesInput>>; StdState<InMemoryOnDiskCorpus<BytesInput>, BytesInput, StdRand, OnDiskCorpus<BytesInput>>;
#[cfg(feature = "simplemgr")]
pub type ClientMgr<M> = SimpleEventManager<BytesInput, M, ClientState>;
#[cfg(not(feature = "simplemgr"))]
pub type ClientMgr<M> = MonitorTypedEventManager<
LlmpRestartingEventManager<(), BytesInput, ClientState, StdShMem, StdShMemProvider>,
M,
>;
/* /*
* The snapshot and iterations options interact as follows: * The snapshot and iterations options interact as follows:
* *
@ -87,17 +74,22 @@ pub type ClientMgr<M> = MonitorTypedEventManager<
*/ */
#[derive(TypedBuilder)] #[derive(TypedBuilder)]
pub struct Instance<'a, M: Monitor> { pub struct Instance<'a, EM> {
options: &'a FuzzerOptions, options: &'a FuzzerOptions,
mgr: ClientMgr<M>, mgr: EM,
client_description: ClientDescription, client_description: ClientDescription,
#[builder(default)] #[builder(default)]
extra_tokens: Vec<String>, extra_tokens: Vec<String>,
#[builder(default=PhantomData)]
phantom: PhantomData<M>,
} }
impl<M: Monitor> Instance<'_, M> { impl<MTEM> Instance<'_, MTEM>
where
MTEM: EventFirer<BytesInput, ClientState>
+ EventRestarter<ClientState>
+ ProgressReporter<ClientState>
+ SendExiting
+ EventReceiver<BytesInput, ClientState>,
{
fn coverage_filter(&self, qemu: Qemu) -> Result<StdAddressFilter, Error> { fn coverage_filter(&self, qemu: Qemu) -> Result<StdAddressFilter, Error> {
/* Conversion is required on 32-bit targets, but not on 64-bit ones */ /* Conversion is required on 32-bit targets, but not on 64-bit ones */
if let Some(includes) = &self.options.include { if let Some(includes) = &self.options.include {
@ -438,10 +430,10 @@ impl<M: Monitor> Instance<'_, M> {
stages: &mut ST, stages: &mut ST,
) -> Result<(), Error> ) -> Result<(), Error>
where where
ST: StagesTuple<E, ClientMgr<M>, ClientState, Z>, ST: StagesTuple<E, MTEM, ClientState, Z>,
RSM: Fn(&mut E, Qemu), RSM: Fn(&mut E, Qemu),
Z: Fuzzer<E, ClientMgr<M>, BytesInput, ClientState, ST> Z: Fuzzer<E, MTEM, BytesInput, ClientState, ST>
+ Evaluator<E, ClientMgr<M>, BytesInput, ClientState>, + Evaluator<E, MTEM, BytesInput, ClientState>,
{ {
if state.must_load_initial_inputs() { if state.must_load_initial_inputs() {
let corpus_dirs = [self.options.input_dir()]; let corpus_dirs = [self.options.input_dir()];

View File

@ -1,6 +1,10 @@
use core::marker::PhantomData;
use libafl::{ use libafl::{
corpus::{InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{InMemoryOnDiskCorpus, OnDiskCorpus},
events::ClientDescription, events::{
ClientDescription, EventFirer, EventReceiver, EventRestarter, ProgressReporter, SendExiting,
},
inputs::BytesInput, inputs::BytesInput,
monitors::Monitor, monitors::Monitor,
state::StdState, state::StdState,
@ -8,11 +12,7 @@ use libafl::{
}; };
use libafl_bolts::rands::StdRand; use libafl_bolts::rands::StdRand;
use crate::{ use crate::{instance::Instance, options::FuzzerOptions};
instance::{ClientMgr, Instance},
options::FuzzerOptions,
};
#[allow(clippy::module_name_repetitions)] #[allow(clippy::module_name_repetitions)]
pub type ClientState = pub type ClientState =
StdState<InMemoryOnDiskCorpus<BytesInput>, BytesInput, StdRand, OnDiskCorpus<BytesInput>>; StdState<InMemoryOnDiskCorpus<BytesInput>, BytesInput, StdRand, OnDiskCorpus<BytesInput>>;
@ -26,12 +26,19 @@ impl Client<'_> {
Client { options } Client { options }
} }
pub fn run<M: Monitor>( pub fn run<EM>(
&self, &self,
state: Option<ClientState>, state: Option<ClientState>,
mgr: ClientMgr<M>, mgr: EM,
client_description: ClientDescription, client_description: ClientDescription,
) -> Result<(), Error> { ) -> Result<(), Error>
where
EM: EventFirer<BytesInput, ClientState>
+ EventRestarter<ClientState>
+ ProgressReporter<ClientState>
+ SendExiting
+ EventReceiver<BytesInput, ClientState>,
{
let instance = Instance::builder() let instance = Instance::builder()
.options(self.options) .options(self.options)
.mgr(mgr) .mgr(mgr)

View File

@ -7,7 +7,7 @@ use std::{
use clap::Parser; use clap::Parser;
use libafl::{ use libafl::{
events::{ events::{
ClientDescription, EventConfig, Launcher, LlmpEventManagerBuilder, MonitorTypedEventManager, ClientDescription, EventConfig, Launcher, LlmpEventManagerBuilder, SimpleEventManager,
}, },
monitors::{tui::TuiMonitor, Monitor, MultiMonitor}, monitors::{tui::TuiMonitor, Monitor, MultiMonitor},
Error, Error,
@ -83,7 +83,7 @@ impl Fuzzer {
M: Monitor + Clone, M: Monitor + Clone,
{ {
// The shared memory allocator // The shared memory allocator
let mut shmem_provider = StdShMemProvider::new()?; let shmem_provider = StdShMemProvider::new()?;
/* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */ /* If we are running in verbose, don't provide a replacement stdout, otherwise, use /dev/null */
let stdout = if self.options.verbose { let stdout = if self.options.verbose {
@ -95,45 +95,28 @@ impl Fuzzer {
let client = Client::new(&self.options); let client = Client::new(&self.options);
if self.options.rerun_input.is_some() { if self.options.rerun_input.is_some() {
// If we want to rerun a single input but we use a restarting mgr, we'll have to create a fake restarting mgr that doesn't actually restart.
// It's not pretty but better than recompiling with simplemgr.
// Just a random number, let's hope it's free :)
let broker_port = 13120;
let _fake_broker = LlmpBroker::create_attach_to_tcp(
shmem_provider.clone(),
tuple_list!(),
broker_port,
)
.unwrap();
// To rerun an input, instead of using a launcher, we create dummy parameters and run the client directly.
return client.run( return client.run(
None, None,
MonitorTypedEventManager::<_, M>::new( SimpleEventManager::new(monitor.clone()),
LlmpEventManagerBuilder::builder().build_on_port(
shmem_provider.clone(),
broker_port,
EventConfig::AlwaysUnique,
Some(StateRestorer::new(
shmem_provider.new_shmem(0x1000).unwrap(),
)),
)?,
),
ClientDescription::new(0, 0, CoreId(0)), ClientDescription::new(0, 0, CoreId(0)),
); );
} }
#[cfg(feature = "simplemgr")] #[cfg(feature = "simplemgr")]
return client.run(None, SimpleEventManager::new(monitor), CoreId(0)); return client.run(
None,
SimpleEventManager::new(monitor.clone()),
ClientDescription::new(0, 0, CoreId(0)),
);
// Build and run a Launcher // Build and run a Launcher
#[cfg(not(feature = "simplemgr"))]
match Launcher::builder() match Launcher::builder()
.shmem_provider(shmem_provider) .shmem_provider(shmem_provider)
.broker_port(self.options.port) .broker_port(self.options.port)
.configuration(EventConfig::from_build_id()) .configuration(EventConfig::from_build_id())
.monitor(monitor) .monitor(monitor)
.run_client(|s, m, c| client.run(s, MonitorTypedEventManager::<_, M>::new(m), c)) .run_client(|s, m, c| client.run(s, m, c))
.cores(&self.options.cores) .cores(&self.options.cores)
.stdout_file(stdout) .stdout_file(stdout)
.stderr_file(stdout) .stderr_file(stdout)

View File

@ -3,15 +3,14 @@ use std::{marker::PhantomData, process};
use libafl::{ use libafl::{
corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus}, corpus::{Corpus, InMemoryOnDiskCorpus, OnDiskCorpus},
events::{ events::{
ClientDescription, EventRestarter, LlmpRestartingEventManager, MonitorTypedEventManager, ClientDescription, EventFirer, EventReceiver, EventRestarter, LlmpRestartingEventManager,
NopEventManager, NopEventManager, ProgressReporter, SendExiting,
}, },
executors::{Executor, ShadowExecutor}, executors::{Executor, ShadowExecutor},
feedback_and_fast, feedback_or, feedback_or_fast, feedback_and_fast, feedback_or, feedback_or_fast,
feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback}, feedbacks::{CrashFeedback, MaxMapFeedback, TimeFeedback, TimeoutFeedback},
fuzzer::{Evaluator, Fuzzer, StdFuzzer}, fuzzer::{Evaluator, Fuzzer, StdFuzzer},
inputs::BytesInput, inputs::BytesInput,
monitors::Monitor,
mutators::{ mutators::{
havoc_mutations, tokens_mutations, HavocScheduledMutator, I2SRandReplace, StdMOptMutator, havoc_mutations, tokens_mutations, HavocScheduledMutator, I2SRandReplace, StdMOptMutator,
Tokens, Tokens,
@ -43,22 +42,22 @@ use crate::options::FuzzerOptions;
pub type ClientState = pub type ClientState =
StdState<InMemoryOnDiskCorpus<BytesInput>, BytesInput, StdRand, OnDiskCorpus<BytesInput>>; StdState<InMemoryOnDiskCorpus<BytesInput>, BytesInput, StdRand, OnDiskCorpus<BytesInput>>;
pub type ClientMgr<M> = MonitorTypedEventManager<
LlmpRestartingEventManager<(), BytesInput, ClientState, StdShMem, StdShMemProvider>,
M,
>;
#[derive(TypedBuilder)] #[derive(TypedBuilder)]
pub struct Instance<'a, M: Monitor> { pub struct Instance<'a, EM> {
options: &'a FuzzerOptions, options: &'a FuzzerOptions,
/// The harness. We create it before forking, then `take()` it inside the client. /// The harness. We create it before forking, then `take()` it inside the client.
mgr: ClientMgr<M>, mgr: EM,
client_description: ClientDescription, client_description: ClientDescription,
#[builder(default=PhantomData)]
phantom: PhantomData<M>,
} }
impl<M: Monitor> Instance<'_, M> { impl<EM> Instance<'_, EM>
where
EM: EventFirer<BytesInput, ClientState>
+ EventRestarter<ClientState>
+ ProgressReporter<ClientState>
+ SendExiting
+ EventReceiver<BytesInput, ClientState>,
{
pub fn run(mut self, state: Option<ClientState>) -> Result<(), Error> { pub fn run(mut self, state: Option<ClientState>) -> Result<(), Error> {
let parent_cpu_id = self let parent_cpu_id = self
.options .options
@ -229,9 +228,8 @@ impl<M: Monitor> Instance<'_, M> {
stages: &mut ST, stages: &mut ST,
) -> Result<(), Error> ) -> Result<(), Error>
where where
Z: Fuzzer<E, ClientMgr<M>, BytesInput, ClientState, ST> Z: Fuzzer<E, EM, BytesInput, ClientState, ST> + Evaluator<E, EM, BytesInput, ClientState>,
+ Evaluator<E, ClientMgr<M>, BytesInput, ClientState>, ST: StagesTuple<E, EM, ClientState, Z>,
ST: StagesTuple<E, ClientMgr<M>, ClientState, Z>,
{ {
let corpus_dirs = [self.options.input_dir()]; let corpus_dirs = [self.options.input_dir()];

View File

@ -697,137 +697,6 @@ impl HasEventManagerId for NopEventManager {
} }
} }
/// An `EventManager` type that wraps another manager, but captures a `monitor` type as well.
/// This is useful to keep the same API between managers with and without an internal `monitor`.
#[derive(Copy, Clone, Debug)]
pub struct MonitorTypedEventManager<EM, M> {
inner: EM,
phantom: PhantomData<M>,
}
impl<EM, M> MonitorTypedEventManager<EM, M> {
/// Creates a new `EventManager` that wraps another manager, but captures a `monitor` type as well.
#[must_use]
pub fn new(inner: EM) -> Self {
MonitorTypedEventManager {
inner,
phantom: PhantomData,
}
}
}
impl<EM, I, M, S> EventFirer<I, S> for MonitorTypedEventManager<EM, M>
where
EM: EventFirer<I, S>,
{
fn should_send(&self) -> bool {
true
}
#[inline]
fn fire(&mut self, state: &mut S, event: EventWithStats<I>) -> Result<(), Error> {
self.inner.fire(state, event)
}
#[inline]
fn log(
&mut self,
state: &mut S,
severity_level: LogSeverity,
message: String,
) -> Result<(), Error>
where
S: HasExecutions,
{
self.inner.log(state, severity_level, message)
}
#[inline]
fn configuration(&self) -> EventConfig {
self.inner.configuration()
}
}
impl<EM, M, S> EventRestarter<S> for MonitorTypedEventManager<EM, M>
where
EM: EventRestarter<S>,
{
#[inline]
fn on_restart(&mut self, state: &mut S) -> Result<(), Error> {
self.inner.on_restart(state)
}
}
impl<EM, M> SendExiting for MonitorTypedEventManager<EM, M>
where
EM: SendExiting,
{
#[inline]
fn send_exiting(&mut self) -> Result<(), Error> {
self.inner.send_exiting()
}
fn on_shutdown(&mut self) -> Result<(), Error> {
self.inner.on_shutdown()
}
}
impl<EM, M> AwaitRestartSafe for MonitorTypedEventManager<EM, M>
where
EM: AwaitRestartSafe,
{
#[inline]
fn await_restart_safe(&mut self) {
self.inner.await_restart_safe();
}
}
impl<EM, I, M, S> EventReceiver<I, S> for MonitorTypedEventManager<EM, M>
where
EM: EventReceiver<I, S>,
{
#[inline]
fn try_receive(&mut self, state: &mut S) -> Result<Option<(EventWithStats<I>, bool)>, Error> {
self.inner.try_receive(state)
}
fn on_interesting(
&mut self,
_state: &mut S,
_event_vec: EventWithStats<I>,
) -> Result<(), Error> {
Ok(())
}
}
impl<EM, M, S> ProgressReporter<S> for MonitorTypedEventManager<EM, M>
where
EM: ProgressReporter<S>,
{
#[inline]
fn maybe_report_progress(
&mut self,
state: &mut S,
monitor_timeout: Duration,
) -> Result<(), Error> {
self.inner.maybe_report_progress(state, monitor_timeout)
}
#[inline]
fn report_progress(&mut self, state: &mut S) -> Result<(), Error> {
self.inner.report_progress(state)
}
}
impl<EM, M> HasEventManagerId for MonitorTypedEventManager<EM, M>
where
EM: HasEventManagerId,
{
#[inline]
fn mgr_id(&self) -> EventManagerId {
self.inner.mgr_id()
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {