crabgrind/
lib.rs

1//! `crabgrind` is a small library that enables `Rust` programs to tap into `Valgrind`'s tools and virtualized environment.
2//!
3//! `Valgrind` offers a ["client request interface"](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq) that is accessible through `C` macros in its header files.
4//! However, these macros can’t be used in languages fortunate enough to lack `C` preprocessor support, such as `Rust`. To address this,`crabgrind` wraps those macros in `C` functions and expose this API via FFI.
5//!
6//! Essentially, `crabgrind` acts as a thin wrapper. It adds some type conversions and structure, but all the real things are done by `Valgrind` itself.
7//!
8//! ### Valgrind 3 API coverage
9//! - Supported tool-specific client request interface:
10//! [valgrind](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq),
11//! [callgrind](https://valgrind.org/docs/manual/cl-manual.html),
12//! [memcheck](https://valgrind.org/docs/manual/mc-manual.html),
13//! [helgrind](https://valgrind.org/docs/manual/hg-manual.html),
14//! [massif](https://valgrind.org/docs/manual/ms-manual.html),
15//! [cachegrind](https://valgrind.org/docs/manual/cg-manual.html#cg-manual.clientrequests),
16//! [dhat](https://valgrind.org/docs/manual/dh-manual.html)
17//! - [Monitor commands](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.gdbserver-commandhandling) interface
18//!
19//! ### Compatibility
20//! `crabgrind` usually builds against the latest `Valgrind` releases, even if some new APIs aren't available—at least it compiles. However, some releases may introduce breaking changes. So, if you run into build errors or need a specific new feature, check out the compatibility table.
21//!
22//! | Valgrind | crabgrind |
23//! |----------|-----------|
24//! | 3.23     | 0.1.11    |
25//! | 3.22     | 0.1.10    |
26//! | 3.21     | 0.1.9     |
27//!
28//! ## Quickstart
29//! `crabgrind` does not link against `Valgrind` but instead reads its header files, which must be accessible during build.
30//! If you have installed `Valgrind` using OS-specific package manager, the paths to the headers are likely to be resolved automatically by [`cc`](https://docs.rs/cc/latest/cc/index.html).
31//! In case of manual installation, you can set the path to the `Valgrind` headers location through the `DEP_VALGRIND` environment variable. For example:
32//!
33//! ```bash
34//! DEP_VALGRIND=/usr/include cargo build
35//! ```
36//!
37//! Next, add dependency to `Cargo.toml`
38//! ```toml
39//! [dependencies]
40//! crabgrind = "0.1"
41//! ```
42//!
43//! Then, use some of [Valgrind's API](https://docs.rs/crabgrind/latest/crabgrind/#modules)
44//! ```no_run
45//! use crabgrind as cg;
46//!
47//! fn main() {
48//!     if matches!(cg::run_mode(), cg::RunMode::Native) {
49//!         println!("run me under Valgrind");
50//!     } else {
51//!         cg::println!("Hey, Valgrind!");
52//!     }
53//! }
54//! ```
55//! and run under `Valgrind`
56//!
57//! ``` bash
58//! cargo build
59//! valgrind ./target/debug/appname
60//! ```
61//!
62//! ### Examples
63//!
64//! ##### Print current function stack-trace to the Valgrind log
65//! Valgrind provides `VALGRIND_PRINTF_BACKTRACE` macro to print the message with the stack-trace attached,
66//! `crabgrind::print_stacktrace` is it's crabbed wrapper.
67//! ```no_run
68//! use crabgrind as cg;
69//!
70//! #[inline(never)]
71//! fn print_trace(){
72//!     let mode = cg::run_mode();
73//!     cg::print_stacktrace!("current mode: {mode:?}");
74//! }
75//!
76//! print_trace();
77//! ```
78//!
79//! ##### Exclude expensive initialization code from the measurements
80//! One way to do this would be to turn off stats collection at stratup with the
81//! [`--collect-atstart=no`](https://valgrind.org/docs/manual/cl-manual.html#opt.collect-atstart)
82//! callgrind command-line attribute, and enable/disable it from the code with `callgrind::toggle_collect`
83//!
84//! ```no_run
85//! use crabgrind as cg;
86//!
87//! // ... some expensive initialization
88//!
89//! cg::callgrind::toggle_collect();
90//! // code of interest
91//! cg::callgrind::toggle_collect();
92//!
93//! // ... some deinitialization
94//! ```
95//!
96//! ##### Run a closure on the real CPU while running under Valgrind
97//! We can run on the real CPU instead of the virtual one using `valgrind::non_simd_call`,
98//! refer to `valgrind.h` for details on limitations and various ways to crash.
99//!
100//! ```no_run
101//! use crabgrind as cg;
102//!
103//! let mut state = 0;
104//! cg::valgrind::non_simd_call(|tid| {
105//!     // uncomment following line to see "the 'impossible' happened"
106//!     // println!("tid: {tid}");
107//!     state = tid;
108//! });
109//!
110//! println!("tid: {state}");
111//! ```
112//! ##### Save current memory usage snapshot to a file
113//! We'll use `Massif` tool and the [monitor command](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.gdbserver-commandhandling)
114//! interface to run the corresponding Massif command.
115//! ```no_run
116//! use crabgrind as cg;
117//!
118//! let heap = String::from("alloca");
119//!
120//! if cg::monitor_command("snapshot mem.snapshot").is_ok(){
121//!     println!("snapshot is saved to \"mem.snapshot\"");
122//! }
123//! ```
124//!
125//! ##### Dump Callgrind counters on a per-function basis
126//! ```no_run
127//! use crabgrind as cg;
128//!
129//! fn factorial1(num: u128) -> u128 {
130//!     match num {
131//!         0 => 1,
132//!         1 => 1,
133//!         _ => factorial1(num - 1) * num,
134//!     }
135//! }
136//!
137//! fn factorial2(num: u128) -> u128 {
138//!     (1..=num).product()
139//! }
140//!
141//! cg::callgrind::zero_stats();
142//!
143//! let a = factorial1(20);
144//! cg::callgrind::dump_stats("factorial1");
145//!
146//! let b = factorial2(20);
147//! cg::callgrind::dump_stats("factorial2");
148//!
149//! assert_eq!(a,b);
150//! cg::callgrind::dump_stats(None);
151//! ```
152//!
153//! ### Overhead
154//! from [Valgrind docs](https://valgrind.org/docs/manual/manual-core-adv.html)
155//! > The code added to your binary has negligible performance impact: on x86, amd64, ppc32, ppc64 and ARM,
156//!  the overhead is 6 simple integer instructions and is probably undetectable except in tight loops.
157//!
158//! > ... the code does nothing when not run on Valgrind, so you are not forced to run your program
159//! under Valgrind just because you use the macros in this file.
160//!
161//! Although your loops should be very tight (like a well-executed dance move) to notice any impact,
162//! keep in mind that:
163//! - Wrapping each macros in a function implies function call overhead regardless of the run mode. This can potentially impact the performance of your Rust program.
164//! See [linker-plugin-lto](https://github.com/2dav/crabgrind/tree/linker-plugin-lto) branch for a possible workaround.
165//! - Functions that return `std::result::Result` involve branching, which can also have an impact on performance.
166//! - Functions that take strings as parameters internally convert them to `std::ffi::CString`, which can introduce additional overhead.
167use std::ffi::c_void;
168mod bindings;
169
170macro_rules! raw_call {
171    ($f:ident) => { raw_call!($f,) };
172    ($f:ident, $($args:tt)*) => {{
173        unsafe{ bindings::$f($($args)*) }
174    }};
175}
176
177/// Current run mode
178///
179/// see [`run_mode()`]
180#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
181pub enum RunMode {
182    /// on the real CPU
183    Native,
184    /// under Valgrind emulation
185    Valgrind,
186    /// under multiple layers of Valgrind emulation
187    ValgrindInValgrind(usize),
188}
189
190/// Returns the [`RunMode`] app running in
191///
192/// # Example
193/// ```no_run
194/// use crabgrind::RunMode;
195///
196/// match crabgrind::run_mode(){
197///     RunMode::Native                 => println!("native CPU"),
198///     RunMode::Valgrind               => println!("hey, Valgrind!"),
199///     RunMode::ValgrindInValgrind(n)  => println!("Valgrind layers: {n}"),
200/// }
201/// ```
202/// # Implementation
203/// `RUNNING_ON_VALGRIND`
204#[inline]
205pub fn run_mode() -> RunMode {
206    match unsafe { bindings::running_on_valgrind() } {
207        0 => RunMode::Native,
208        1 => RunMode::Valgrind,
209        x => RunMode::ValgrindInValgrind(x),
210    }
211}
212
213#[doc(hidden)]
214pub fn __print(msg: String) {
215    let cstr = std::ffi::CString::new(msg).unwrap();
216    raw_call!(vg_print, cstr.as_ptr());
217}
218
219/// Prints to the Valgrind's log.
220///
221/// Accepts format string similar to [`std::println!`].
222///
223/// # Example
224/// ```no_run
225/// if !matches!(crabgrind::run_mode(), crabgrind::RunMode::Native){
226///     crabgrind::print!("hello {}", "Valgrind");
227/// }
228/// ```
229///
230/// # Implementation
231/// `VALGRIND_PRINTF` wrapped with the fixed `"%s"` format.
232///
233/// # Panics
234/// If format string contains null-byte in any position.
235#[macro_export]
236macro_rules! print{
237    ($($arg:tt)+) => { $crate::__print(format!("{}",format_args!($($arg)+)));}
238}
239
240/// Prints to the Valgrind's log, with a newline.
241///
242/// Accepts format string similar to [`std::println!`].
243///
244/// # Example
245/// ```no_run
246/// use crabgrind as cg;
247///
248/// cg::println!("current mode: {:?}", cg::run_mode());
249/// ```
250///
251/// # Implementation
252/// `VALGRIND_PRINTF` wrapped with the fixed `"%s"` format.
253///
254/// # Panics
255/// If format string contains null-byte in any position.
256#[macro_export]
257macro_rules! println{
258    ($($arg:tt)+) => { $crate::__print(format!("{}\n",format_args!($($arg)+)));}
259}
260
261#[doc(hidden)]
262#[inline(always)]
263pub fn __print_stacktrace(msg: String) {
264    let cstr = std::ffi::CString::new(msg).unwrap();
265    raw_call!(vg_print_backtrace, cstr.as_ptr());
266}
267
268/// Prints to the Valgrind's log, with the current stacktrace attached.
269///
270/// Accepts format string similar to [`std::println!`].
271///
272/// # Example
273/// ```no_run
274/// use crabgrind as cg;
275///
276/// #[inline(never)]
277/// fn print_trace(){
278///     let mode = cg::run_mode();
279///     cg::print_stacktrace!("current mode: {mode:?}");
280/// }
281///
282/// print_trace();
283/// ```
284///
285/// # Implementation
286/// `VALGRIND_PRINTF_BACKTRACE` wrapped with the fixed `"%s"` format.
287///
288/// # Panics
289/// If format string contains null-byte in any position.
290#[macro_export]
291macro_rules! print_stacktrace{
292    ($($arg:tt)+) => { $crate::__print_stacktrace(format!("{}\n",format_args!($($arg)+)));}
293}
294
295/// Execute arbitrary Valgrind [Monitor command](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.gdbserver-commandhandling)
296///
297/// # Example
298/// ```no_run
299/// use crabgrind as cg;
300///
301/// let heap = String::from("alloca");
302///
303/// if cg::monitor_command("snapshot mem.snapshot").is_ok(){
304///     println!("snapshot is saved to \"mem.snapshot\"");
305/// }
306/// ```
307///
308/// # Implementation
309/// `VALGRIND_MONITOR_COMMAND`
310///
311/// # Panics
312/// If command string contains null-byte in any position.
313#[inline]
314pub fn monitor_command(cmd: impl AsRef<str>) -> std::io::Result<()> {
315    let cmd = std::ffi::CString::new(cmd.as_ref()).unwrap();
316    if raw_call!(vg_monitor_command, cmd.as_ptr()) {
317        Err(std::io::ErrorKind::NotFound.into())
318    } else {
319        Ok(())
320    }
321}
322
323/// Disable error reporting for this thread
324///
325/// Behaves in a stack like way, so you can safely call this multiple times provided that
326/// [`enable_error_reporting()`] is called the same number of times to re-enable reporting.  
327///
328/// The first call of this macro disables reporting.  Subsequent calls have no effect except
329/// to increase the number of [`enable_error_reporting()`] calls needed to re-enable reporting.  
330///
331/// Child threads do not inherit this setting from their parents -- they are always created with
332/// reporting enabled.
333///
334/// # Example
335/// ```no_run
336/// use crabgrind as cg;
337///
338/// cg::disable_error_reporting();
339///
340/// unsafe {
341///     let b = Box::new([0]);
342///     println!("{}", b.get_unchecked(1));
343/// };
344/// assert_eq!(cg::count_errors(), 0);
345/// ```
346///
347/// # Implementation
348/// `VALGRIND_DISABLE_ERROR_REPORTING`
349#[inline]
350pub fn disable_error_reporting() {
351    raw_call!(vg_disable_error_reporting);
352}
353
354/// Re-enable error reporting for this thread
355///
356/// see [`disable_error_reporting()`] docs
357///
358/// # Implementation
359/// `VALGRIND_ENABLE_ERROR_REPORTING`
360#[inline]
361pub fn enable_error_reporting() {
362    raw_call!(vg_enable_error_reporting);
363}
364
365/// Returns the number of errors found so far by Valgrind
366///
367/// # Example
368/// ```no_run
369/// use crabgrind as cg;
370///
371/// unsafe {
372///     let b = Box::new([0]);
373///     println!("{}", b.get_unchecked(1));
374/// };
375///
376/// assert_eq!(cg::count_errors(), 1);
377/// ```
378///
379/// # Implementation
380/// `VALGRIND_COUNT_ERRORS`
381#[inline]
382pub fn count_errors() -> usize {
383    raw_call!(vg_count_errors)
384}
385
386/// Change the value of a dynamic command line option.
387///
388/// see [`official docs`](https://valgrind.org/docs/manual/manual-core.html#manual-core.dynopts)
389/// for details.
390///
391/// # Example
392/// ```no_run
393/// use crabgrind as cg;
394///
395/// cg::change_cli_option("--leak-check=no");
396/// std::mem::forget(String::from("see you in the void"));
397/// ```
398///
399/// # Implementation
400/// `VALGRIND_CLO_CHANGE`
401///
402/// # Panics
403/// If command string contains null-byte in any position.
404#[inline]
405pub fn change_cli_option(opt: impl AsRef<str>) {
406    let cstr = std::ffi::CString::new(opt.as_ref()).unwrap();
407    raw_call!(vg_clo_change, cstr.as_ptr());
408}
409
410pub mod valgrind {
411    //! [`Valgrind requests`](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq)
412    use std::os::unix::prelude::RawFd;
413
414    use super::*;
415
416    pub type ThreadId = usize;
417
418    /// Discards translations of code in the specified address range
419    ///
420    /// see [official docs](https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq)
421    /// for details.
422    ///
423    /// # Implementation
424    /// `VALGRIND_DISCARD_TRANSLATIONS`
425    #[inline]
426    pub fn discard_translations(addr: *mut c_void, len: usize) {
427        raw_call!(vg_discard_translations, addr, len);
428    }
429
430    /// Load PDB debug info for Wine PE image_map
431    ///
432    /// # Implementation
433    /// `VALGRIND_LOAD_PDB_DEBUGINFO`
434    #[inline]
435    pub fn load_pdb_debuginfo(fd: RawFd, ptr: *mut c_void, total_size: usize, delta: usize) {
436        raw_call!(vg_load_pdb_debuginfo, fd, ptr, total_size, delta);
437    }
438
439    /// Map a code address to a source file name and line number
440    ///
441    /// `buf64` must point to a 64-byte buffer in the caller's address space.  
442    /// The result will be dumped in there and is guaranteed to be zero terminated.  
443    /// If no info is found, the first byte is set to zero.
444    ///
445    /// # Implementation
446    /// `VALGRIND_MAP_IP_TO_SRCLOC`
447    #[inline]
448    pub fn map_ip_to_srcloc(addr: *mut c_void, buf64: *mut c_void) -> usize {
449        raw_call!(vg_map_ip_to_srcloc, addr, buf64)
450    }
451
452    extern "C" fn _closure_adapter<F>(tid: ThreadId, f: *mut c_void)
453    where
454        F: FnMut(ThreadId),
455    {
456        if let Err(err) = std::panic::catch_unwind(|| unsafe {
457            debug_assert!(!f.is_null(), "closure pointer is null");
458            debug_assert_eq!(
459                f as usize & (std::mem::align_of::<F>() - 1),
460                0,
461                "unexpected closure pointer"
462            );
463
464            (*f.cast::<F>())(tid)
465        }) {
466            let panic_info = err
467                .downcast::<String>()
468                .map(|v| *v)
469                .or_else(|e| e.downcast::<&str>().map(|v| v.to_string()))
470                .unwrap_or_else(|_| "unknown panic source".to_string());
471
472            eprintln!("closure code panicked with: {panic_info:?}");
473
474            std::process::abort();
475        }
476    }
477
478    /// Runs a closure on the real CPU.
479    ///
480    /// Closure receives a [`ThreadId`] as the parameter, that is the Valgrind's notion of thread
481    /// identifier and there may not be relationship between [`ThreadId`] and rust's [`std::thread::ThreadId`].
482    ///
483    /// Refer to the `valgrind.h` for details and limitations.
484    ///
485    /// # Example
486    /// ```no_run
487    /// use crabgrind as cg;
488    ///
489    /// let mut thread_id = 0;
490    /// cg::valgrind::non_simd_call(|tid| {
491    ///     thread_id = tid;
492    /// });
493    /// println!("{thread_id}");
494    /// ```
495    ///
496    /// # Implementation
497    /// `VALGRIND_NON_SIMD_CALL1`
498    ///
499    /// # Panics
500    /// It's safe to panic in the closure code in that this won't cause a UB on stack unwinding.
501    #[inline]
502    pub fn non_simd_call<F>(f: F)
503    where
504        F: FnMut(ThreadId),
505    {
506        let boxed = Box::into_raw(Box::new(f));
507        raw_call!(vg_non_simd_call1, _closure_adapter::<F>, boxed.cast());
508        let _ = unsafe { Box::from_raw(boxed) };
509    }
510}
511
512pub mod callgrind {
513    //! [`Callgrind requests`](https://courses.cs.vt.edu/~cs3214/fall2011/projects/valgrind/valgrind-3.4.0/docs/html/cl-manual.html#cl-manual.clientrequests)
514    use super::*;
515
516    /// Dump current state of cost centers, and zero them afterwards
517    ///
518    /// If `reason` parameter is specified, this string will be written as a description field into
519    /// the profile data dump.
520    ///
521    /// # Example
522    /// ```no_run
523    /// use crabgrind as cg;
524    ///
525    /// fn factorial1(num: u128) -> u128 {
526    ///     match num {
527    ///         0 => 1,
528    ///         1 => 1,
529    ///         _ => factorial1(num - 1) * num,
530    ///     }
531    /// }
532    ///
533    /// fn factorial2(num: u128) -> u128 {
534    ///     (1..=num).product()
535    /// }
536    ///
537    /// cg::callgrind::zero_stats();
538    ///
539    /// let a = factorial1(20);
540    /// cg::callgrind::dump_stats("factorial1");
541    ///
542    /// let b = factorial2(20);
543    /// cg::callgrind::dump_stats("factorial2");
544    ///
545    /// assert_eq!(a,b);
546    /// cg::callgrind::dump_stats(None);
547    /// ```
548    ///
549    /// # Implementation
550    /// `CALLGRIND_DUMP_STATS` or `CALLGRIND_DUMP_STATS_AT`
551    ///
552    /// # Panics
553    /// If `reason` is specified and contains null-byte in any position.
554    #[inline]
555    pub fn dump_stats<'a>(reason: impl Into<Option<&'a str>>) {
556        match reason.into() {
557            None => raw_call!(cl_dump_stats),
558            Some(reason) => {
559                let cstr = std::ffi::CString::new(reason).unwrap();
560                raw_call!(cl_dump_stats_at, cstr.as_ptr())
561            }
562        };
563    }
564
565    /// Zero current stats
566    ///
567    /// # Implementation
568    /// `CALLGRIND_ZERO_STATS`
569    #[inline]
570    pub fn zero_stats() {
571        raw_call!(cl_zero_stats);
572    }
573
574    /// Toggles collection state
575    ///
576    /// The collection state specifies whether the happening of events should be noted or if
577    /// they are to be ignored. Events are noted by increment of counters in a cost center.
578    ///
579    /// # Example
580    /// run with `valgrind --tool==callgrind --collect-atstart=no ...`
581    /// ```no_run
582    /// use crabgrind as cg;
583    ///
584    /// let xs = (0..10 << 10).into_iter().collect::<Vec<u32>>();
585    ///
586    /// cg::callgrind::toggle_collect();
587    /// let i = xs.binary_search(&(10 << 10 >> 1));
588    /// cg::callgrind::toggle_collect();
589    /// ```
590    ///
591    /// # Implementation
592    /// `CALLGRIND_TOGGLE_COLLECT`
593    #[inline]
594    pub fn toggle_collect() {
595        raw_call!(cl_toggle_collect);
596    }
597
598    /// Start full callgrind instrumentation if not already switched on
599    ///
600    /// When cache simulation is done, it will flush the simulated cache;
601    /// this will lead to an artificial cache warmup phase afterwards with cache misses which
602    /// would not have happened in reality.
603    ///
604    /// Use this to bypass Callgrind aggregation for uninteresting code parts.
605    /// To start Callgrind in this mode to ignore the setup phase, use the option `--instr-atstart=no`.
606    ///
607    /// # Example
608    /// ```no_run
609    /// use crabgrind as cg;
610    ///
611    /// let xs = (0..10 << 10).into_iter().collect::<Vec<u32>>();
612    ///
613    /// cg::callgrind::start_instrumentation();
614    /// let i = xs.binary_search(&(10 << 10 >> 1));
615    /// cg::callgrind::dump_stats(None);
616    /// ```
617    /// also see documentation for [`stop_instrumentation()`]
618    ///
619    /// # Implementation
620    /// `CALLGRIND_START_INSTRUMENTATION`
621    #[inline]
622    pub fn start_instrumentation() {
623        raw_call!(cl_start_instrumentation);
624    }
625
626    /// Stop full callgrind instrumentation if not already switched off
627    ///
628    /// This flushes Valgrinds translation cache, and does no additional instrumentation afterwards,
629    /// which effectivly will run at the same speed as the "none" tool (ie. at minimal slowdown).
630    ///
631    /// also see documentation for [`start_instrumentation()`]
632    ///
633    /// # Implementation
634    /// `CALLGRIND_STOP_INSTRUMENTATION`
635    #[inline]
636    pub fn stop_instrumentation() {
637        raw_call!(cl_stop_instrumentation);
638    }
639}
640
641pub mod cachegrind {
642    //! [`Cachegrind requests`](https://valgrind.org/docs/manual/cg-manual.html#cg-manual.clientrequests)
643    use super::*;
644
645    /// Start full cachegrind instrumentation if not already switched on
646    ///
647    /// When cache simulation is done, it will flush the simulated cache;
648    /// this will lead to an artificial cache warmup phase afterwards with cache misses which
649    /// would not have happened in reality.
650    ///
651    /// Use this to bypass Cachegrind aggregation for uninteresting code parts.
652    /// To start Callgrind in this mode to ignore the setup phase, use the option `--instr-at-start=no`.
653    ///
654    /// # Example
655    /// ```no_run
656    /// use crabgrind as cg;
657    ///
658    /// let xs = (0..10 << 10).into_iter().collect::<Vec<u32>>();
659    ///
660    /// cg::cachegrind::start_instrumentation();
661    /// let i = xs.binary_search(&(10 << 10 >> 1));
662    /// cg::cachegrind::stop_instrumentation();
663    /// ```
664    /// also see documentation for [`cachegrind::stop_instrumentation()`]
665    ///
666    /// # Implementation
667    /// `CACHEGRIND_START_INSTRUMENTATION`
668    #[inline]
669    pub fn start_instrumentation() {
670        raw_call!(cg_start_instrumentation);
671    }
672
673    /// Stop full cachegrind instrumentation if not already switched off
674    ///
675    /// This flushes Valgrind's translation cache, and does no additional instrumentation afterwards,
676    /// which effectively will run at the same speed as the "none" tool (ie. at minimal slowdown).
677    ///
678    /// also see documentation for [`cachegrind::start_instrumentation()`]
679    ///
680    /// # Implementation
681    /// `CACHEGRIND_STOP_INSTRUMENTATION`
682    #[inline]
683    pub fn stop_instrumentation() {
684        raw_call!(cg_stop_instrumentation);
685    }
686}
687
688pub mod memcheck {
689    //! [`Memcheck requests`](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.clientreqs)
690    use super::*;
691    pub use bindings::LeakCount;
692
693    pub type BlockDescHandle = u32;
694
695    #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
696    #[non_exhaustive]
697    pub enum Error {
698        InvalidHandle,
699        NotAddressable(usize),
700        NoValgrind,
701        UnalignedArrays,
702    }
703
704    impl std::error::Error for Error {}
705    unsafe impl Send for Error {}
706    unsafe impl Sync for Error {}
707    impl std::fmt::Display for Error {
708        #[inline]
709        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
710            match self {
711                Error::InvalidHandle => f.write_str("Invalid memory block description handle"),
712                Error::NotAddressable(addr) => {
713                    write!(f, "Memory starting from 0x{addr:X} is not addressable")
714                }
715                Error::NoValgrind => f.write_str("Not running under Valgrind"),
716                Error::UnalignedArrays => {
717                    f.write_str("[previously indicated unaligned arrays;  these are now allowed]")
718                }
719            }
720        }
721    }
722
723    pub type Result<T = ()> = std::result::Result<T, Error>;
724
725    #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
726    pub enum LeakCheck {
727        Full,
728        New,
729        Quick,
730        Added,
731        Changed,
732    }
733
734    #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
735    pub enum MemState {
736        NoAccess,
737        Undefined,
738        Defined,
739        DefinedIfAddressable,
740    }
741
742    /// Mark memory state for an address range
743    ///
744    /// # Memory mark option
745    /// **MemState::NoAccess**
746    /// - mark address ranges as completely inaccessible
747    ///
748    /// **MemState::Defined**
749    /// - mark address ranges as accessible but containing undefined data
750    ///
751    /// **MemState::Undefined**
752    /// - mark address ranges as accessible and containing defined data
753    ///
754    /// **MemState::DefinedIfAddressable**
755    /// - same as `MemState::Defined` but only affects those bytes that are already addressable
756    ///
757    /// # Implementation
758    /// - [MemState::NoAccess] `VALGRIND_MAKE_MEM_NOACCESS`
759    /// - [MemState::Undefined] `VALGRIND_MAKE_MEM_UNDEFINED`
760    /// - [MemState::Defined] `VALGRIND_MAKE_MEM_DEFINED`
761    /// - [MemState::DefinedIfAddressable] `VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE`
762    #[inline]
763    pub fn mark_mem(addr: *mut c_void, len: usize, mark: MemState) -> Result {
764        let ret = match mark {
765            MemState::NoAccess => raw_call!(mc_make_mem_noaccess, addr, len),
766            MemState::Undefined => raw_call!(mc_make_mem_undefined, addr, len),
767            MemState::Defined => raw_call!(mc_make_mem_defined, addr, len),
768            MemState::DefinedIfAddressable => {
769                raw_call!(mc_make_mem_defined_if_addressable, addr, len)
770            }
771        };
772        if ret == -1 {
773            Ok(())
774        } else {
775            Err(Error::NoValgrind)
776        }
777    }
778
779    /// Create a block-description handle
780    ///
781    /// The description is an ascii string which is included in any messages pertaining to
782    /// addresses within the specified memory range.  Has no other effect on the properties of
783    /// the memory range.
784    ///
785    /// # Implementation
786    /// `VALGRIND_CREATE_BLOCK`
787    ///
788    /// # Panics
789    /// If string contains null-byte in any position.
790    #[inline]
791    pub fn new_block_description(
792        addr: *mut c_void,
793        len: usize,
794        desc: impl AsRef<str>,
795    ) -> BlockDescHandle {
796        let cstr = std::ffi::CString::new(desc.as_ref()).unwrap();
797        raw_call!(mc_create_block, addr, len, cstr.as_ptr())
798    }
799
800    /// Discard a block-description-handle
801    ///
802    /// # Implementation
803    /// `VALGRIND_DISCARD`
804    #[inline]
805    pub fn discard(handle: BlockDescHandle) -> Result {
806        if raw_call!(mc_discard, handle) == 0 {
807            Ok(())
808        } else {
809            Err(Error::InvalidHandle)
810        }
811    }
812
813    /// Check that memory range is addressable
814    ///
815    /// If suitable addressibility is not established, Valgrind prints an error message and returns
816    /// the address of the first offending byte.
817    ///
818    /// # Implementation
819    /// `VALGRIND_CHECK_MEM_IS_ADDRESSABLE`
820    #[inline]
821    pub fn is_addressable(addr: *mut c_void, len: usize) -> Result {
822        match raw_call!(mc_check_mem_is_addressable, addr, len) {
823            0 => Ok(()),
824            addr => Err(Error::NotAddressable(addr)),
825        }
826    }
827
828    /// Check that memory range is addressable and defined
829    ///
830    /// If suitable addressibility and definedness are not established, Valgrind prints an error
831    /// message and returns the address of the first offending byte.
832    ///
833    /// # Implementation
834    /// `VALGRIND_CHECK_MEM_IS_DEFINED`
835    #[inline]
836    pub fn is_defined(addr: *mut c_void, len: usize) -> Result {
837        match raw_call!(mc_check_mem_is_defined, addr, len) {
838            0 => Ok(()),
839            addr => Err(Error::NotAddressable(addr)),
840        }
841    }
842
843    /// Do a memory leak check
844    ///
845    /// # Memory check option
846    /// **LeakCheck::Full**
847    /// - Do a full memory leak check (like --leak-check=full) mid-execution. This is useful for
848    /// incrementally checking for leaks between arbitrary places in the program's execution.
849    ///
850    /// **LeakCheck::New**
851    /// - Same as `LeakCheck::Full` but only showing the entries since the previous leak search. It has no return value.
852    ///
853    /// **LeakCheck::Quick**
854    /// - Do a summary memory leak check (like --leak-check=summary) mid-execution.
855    ///
856    /// **LeakCheck::Added**
857    /// - Same as `LeakCheck::Full` but only showing the entries for which there was an increase in
858    /// leaked bytes or leaked number of blocks since the previous leak search.
859    ///
860    /// **LeakCheck::Changed**
861    /// - Same as `LeakCheck::Added` but showing entries with increased or decreased leaked
862    /// bytes/blocks since previous leak search.
863    ///
864    /// # Implementation
865    /// - [LeakCheck::Full]  `VALGRIND_DO_LEAK_CHECK`
866    /// - [LeakCheck::New]  `VALGRIND_DO_NEW_LEAK_CHECK`
867    /// - [LeakCheck::Quick]  `VALGRIND_DO_QUICK_LEAK_CHECK`
868    /// - [LeakCheck::Added]  `VALGRIND_DO_ADDED_LEAK_CHECK`
869    /// - [LeakCheck::Changed]  `VALGRIND_DO_CHANGED_LEAK_CHECK`
870    #[inline]
871    pub fn leak_check(mode: LeakCheck) {
872        match mode {
873            LeakCheck::Full => raw_call!(mc_do_leak_check),
874            LeakCheck::New => raw_call!(mc_do_new_leak_check),
875            LeakCheck::Quick => raw_call!(mc_do_quick_leak_check),
876            LeakCheck::Added => raw_call!(mc_do_added_leak_check),
877            LeakCheck::Changed => raw_call!(mc_do_changed_leak_check),
878        };
879    }
880
881    /// Return number of leaked bytes found by all previous leak checks
882    ///
883    /// # Implementation
884    /// `VALGRIND_COUNT_LEAKS`
885    #[inline]
886    pub fn leaks_count() -> LeakCount {
887        raw_call!(mc_count_leaks)
888    }
889
890    /// Return number of leaked blocks found by all previous leak checks
891    ///
892    /// # Implementation
893    /// `VALGRIND_COUNT_LEAK_BLOCKS`
894    #[inline]
895    pub fn block_leaks_count() -> LeakCount {
896        raw_call!(mc_count_leak_blocks)
897    }
898
899    /// Get the validity data for address range
900    ///
901    /// # Implementation
902    /// `VALGRIND_GET_VBITS`
903    #[inline]
904    pub fn vbits(addr: *mut c_void, bits: *const u8, nbytes: usize) -> Result {
905        match raw_call!(mc_get_vbits, addr, bits, nbytes) {
906            0 => Err(Error::NoValgrind),
907            1 => Ok(()),
908            2 => Err(Error::UnalignedArrays),
909            3 => Err(Error::NotAddressable(0)),
910            x => unreachable!("Unexpected return code {}", x),
911        }
912    }
913
914    /// Set the validity data for address range
915    ///
916    /// # Implementation
917    /// `VALGRIND_SET_VBITS`
918    #[inline]
919    pub fn set_vbits(addr: *mut c_void, bits: *const u8, nbytes: usize) -> Result {
920        match raw_call!(mc_set_vbits, addr, bits, nbytes) {
921            0 => Err(Error::NoValgrind),
922            1 => Ok(()),
923            2 => Err(Error::UnalignedArrays),
924            3 => Err(Error::NotAddressable(0)),
925            x => unreachable!("Unexpected return code {}", x),
926        }
927    }
928
929    /// Disable reporting of addressing errors in the specified address range
930    ///
931    /// # Implementation
932    /// `VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE`
933    #[inline]
934    pub fn disable_error_reporting(addr: *mut c_void, len: usize) {
935        raw_call!(mc_disable_addr_error_reporting_in_range, addr, len);
936    }
937
938    /// Re-enable reporting of addressing errors in the specified address range
939    ///
940    /// # Implementation
941    /// `VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE`
942    #[inline]
943    pub fn enable_error_reporting(addr: *mut c_void, len: usize) {
944        raw_call!(mc_enable_addr_error_reporting_in_range, addr, len);
945    }
946
947    pub mod alloc {
948        //! Heap memory functionality
949        use super::super::*;
950
951        /// Marks a region of memory as having been allocated by a `malloc()`-like function
952        ///
953        /// See the comments in `valgrind.h` for information on how to use it.
954        ///
955        /// # Implementation
956        /// `VALGRIND_MALLOCLIKE_BLOCK`
957        #[inline]
958        pub fn malloc(addr: *mut c_void, size: usize, rz: usize, is_zeroed: bool) {
959            raw_call!(vg_malloclike_block, addr, size, rz, is_zeroed);
960        }
961
962        /// Partner to [`malloc()`]
963        ///
964        /// See the comments in `valgrind.h` for information on how to use it.
965        ///
966        /// # Implementation
967        /// `VALGRIND_FREELIKE_BLOCK`
968        #[inline]
969        pub fn free(addr: *mut c_void, rz: usize) {
970            raw_call!(vg_freelike_block, addr, rz);
971        }
972
973        /// Informs Memcheck about reallocation
974        ///
975        /// See the comments in `valgrind.h` for information on how to use it.
976        ///
977        /// # Implementation
978        /// `VALGRIND_RESIZEINPLACE_BLOCK`
979        #[inline]
980        pub fn resize_inplace(addr: *mut c_void, old_size: usize, new_size: usize, rz: usize) {
981            raw_call!(vg_resizeinplace_block, addr, old_size, new_size, rz);
982        }
983    }
984
985    pub mod mempool {
986        //! Memory pools functionality
987        //!
988        //! refer to [`Memory pools`](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
989        //! Valgrind manual.
990        use super::super::*;
991
992        /// `VALGRIND_MEMPOOL_AUTO_FREE`
993        pub const AUTO_FREE: u32 = 1;
994
995        /// `VALGRIND_MEMPOOL_METAPOOL`
996        pub const METAPOOL: u32 = 2;
997
998        /// Create a memory pool
999        ///
1000        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1001        ///
1002        /// # Implementation
1003        /// `VALGRIND_CREATE_MEMPOOL` or `VALGRIND_CREATE_MEMPOOL_EXT`
1004        #[inline]
1005        pub fn create(
1006            pool: *mut c_void,
1007            rz: usize,
1008            is_zeroed: bool,
1009            flags: impl Into<Option<u32>>,
1010        ) {
1011            match flags.into() {
1012                None => raw_call!(vg_create_mempool, pool, rz, is_zeroed),
1013                Some(flags) => raw_call!(vg_create_mempool_ext, pool, rz, is_zeroed, flags),
1014            };
1015        }
1016
1017        /// Destroy a memory pool
1018        ///
1019        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1020        ///
1021        /// # Implementation
1022        /// `VALGRIND_DESTROY_MEMPOOL`
1023        #[inline]
1024        pub fn destroy(pool: *mut c_void) {
1025            raw_call!(vg_destroy_mempool, pool);
1026        }
1027
1028        /// Associate a piece of memory with a memory pool
1029        ///
1030        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1031        ///
1032        /// # Implementation
1033        /// `VALGRIND_MEMPOOL_ALLOC`
1034        #[inline]
1035        pub fn alloc(pool: *mut c_void, addr: *mut c_void, size: usize) {
1036            raw_call!(vg_mempool_alloc, pool, addr, size);
1037        }
1038
1039        /// Disassociate a piece of memory from a memory pool
1040        ///
1041        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1042        ///
1043        /// # Implementation
1044        /// `VALGRIND_MEMPOOL_FREE`
1045        #[inline]
1046        pub fn free(pool: *mut c_void, addr: *mut c_void) {
1047            raw_call!(vg_mempool_free, pool, addr);
1048        }
1049
1050        /// Disassociate any pieces outside a particular range
1051        ///
1052        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1053        ///
1054        /// # Implementation
1055        /// `VALGRIND_MEMPOOL_TRIM`
1056        #[inline]
1057        pub fn trim(pool: *mut c_void, addr: *mut c_void, size: usize) {
1058            raw_call!(vg_mempool_trim, pool, addr, size);
1059        }
1060
1061        /// Resize and/or move a piece associated with a memory pool
1062        ///
1063        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1064        ///
1065        /// # Implementation
1066        /// `VALGRIND_MOVE_MEMPOOL`
1067        #[inline]
1068        pub fn move_to(pool_a: *mut c_void, pool_b: *mut c_void) {
1069            raw_call!(vg_move_mempool, pool_a, pool_b);
1070        }
1071
1072        /// Resize and/or move a piece associated with a memory pool
1073        ///
1074        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1075        ///
1076        /// # Implementation
1077        /// `VALGRIND_MEMPOOL_CHANGE`
1078        #[inline]
1079        pub fn change(pool: *mut c_void, addr_a: *mut c_void, addr_b: *mut c_void, size: usize) {
1080            raw_call!(vg_mempool_change, pool, addr_a, addr_b, size);
1081        }
1082
1083        /// Check mempool existence
1084        ///
1085        /// refer to [Memory Pools: describing and working with custom allocators](https://valgrind.org/docs/manual/mc-manual.html#mc-manual.mempools)
1086        ///
1087        /// # Implementation
1088        /// `VALGRIND_MEMPOOL_EXISTS`
1089        #[inline]
1090        pub fn is_exists(pool: *mut c_void) -> bool {
1091            raw_call!(vg_mempool_exists, pool)
1092        }
1093    }
1094
1095    pub mod stack {
1096        //! Stack memory functionality
1097        use super::super::*;
1098
1099        pub type StackId = usize;
1100
1101        /// Mark a piece of memory as being a stack
1102        ///
1103        /// # Implementation
1104        /// `VALGRIND_STACK_REGISTER`
1105        #[inline]
1106        pub fn register(lowest: *mut c_void, highest: *mut c_void) -> StackId {
1107            raw_call!(vg_stack_register, lowest, highest)
1108        }
1109
1110        /// Unmark the piece of memory associated with a [`StackId`] as being a stack
1111        ///
1112        /// # Implementation
1113        /// `VALGRIND_STACK_DEREGISTER`
1114        #[inline]
1115        pub fn deregister(id: StackId) {
1116            raw_call!(vg_stack_deregister, id);
1117        }
1118
1119        /// Change the start and end address of the [`StackId`]
1120        ///
1121        /// # Implementation
1122        /// `VALGRIND_STACK_CHANGE`
1123        #[inline]
1124        pub fn change(id: StackId, new_lowest: *mut c_void, new_highest: *mut c_void) {
1125            raw_call!(vg_stack_change, id, new_lowest, new_highest);
1126        }
1127    }
1128}
1129
1130pub mod helgrind {
1131    //! [`Helgrind requests`](https://valgrind.org/docs/manual/hg-manual.html#hg-manual.client-requests)
1132    use super::*;
1133
1134    #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
1135    pub enum Annotation {
1136        HappensBefore,
1137        HappensAfter,
1138        New(usize),
1139        RwLockCreate,
1140        RwLockDestroy,
1141        /// 'true' for a writer lock
1142        RwLockAcquired(bool),
1143        RwLockReleased,
1144    }
1145
1146    /// Let `Helgrind` forget everything it know about the specified memory range
1147    ///
1148    /// # Implementation
1149    /// `VALGRIND_HG_CLEAN_MEMORY`
1150    #[inline]
1151    pub fn clean_memory(addr: *mut c_void, len: usize) {
1152        raw_call!(hg_clean_memory, addr, len);
1153    }
1154
1155    /// Annotations useful for debugging
1156    ///
1157    /// # Annotation options
1158    /// **Annotation::RwLockCreate**
1159    /// - Report that a lock has just been created at address LOCK
1160    ///
1161    /// **Annotation::RwLockDestroy**
1162    /// - Report that the lock at address LOCK is about to be destroyed
1163    ///
1164    /// **Annotation::RwLockAcquired**
1165    /// - Report that the lock at address LOCK has just been acquired
1166    ///
1167    /// **Annotation::RwLockReleased**
1168    /// - Report that the lock at address LOCK is about to be released
1169    ///
1170    /// **Annotation::HappensAfter** **Annotation::HappensBefore**
1171    /// - If threads `T1 .. Tn` all do ANNOTATE_HAPPENS_BEFORE(obj) and later (w.r.t. some
1172    /// notional global clock for the computation) thread `Tm` does ANNOTATE_HAPPENS_AFTER(obj),
1173    /// then `Helgrind` will regard all memory accesses done by `T1 .. Tn` before the ..BEFORE..
1174    /// call as happening-before all memory accesses done by `Tm` after the ..AFTER.. call.  
1175    /// Hence `Helgrind` won't complain about races if `Tm's` accesses afterwards are to the same
1176    /// locations as accesses before by any of `T1 .. Tn`.
1177    ///
1178    /// **Annotation::New**
1179    /// - Report that a new memory at "address" of size "size" has been allocated
1180    ///
1181    ///
1182    /// # Implementation
1183    /// - Annotation::RwLockCreate `ANNOTATE_RWLOCK_CREATE`
1184    /// - Annotation::RwLockDestroy `ANNOTATE_RWLOCK_DESTROY`
1185    /// - Annotation::RwLockAcquired `ANNOTATE_RWLOCK_ACQUIRED`
1186    /// - Annotation::RwLockReleased `ANNOTATE_RWLOCK_RELEASED`
1187    /// - Annotation::HappensAfter `ANNOTATE_HAPPENS_AFTER`
1188    /// - Annotation::HappensBefore `ANNOTATE_HAPPENS_BEFORE`
1189    /// - Annotation::New `ANNOTATE_NEW_MEMORY`
1190    #[inline]
1191    pub fn annotate_memory(addr: *mut c_void, rel: Annotation) {
1192        match rel {
1193            Annotation::RwLockCreate => raw_call!(hg_rwlock_create, addr),
1194            Annotation::RwLockDestroy => raw_call!(hg_rwlock_destroy, addr),
1195            Annotation::RwLockAcquired(is_wl) => raw_call!(hg_rwlock_acquired, addr, is_wl),
1196            Annotation::RwLockReleased => raw_call!(hg_rwlock_released, addr),
1197            Annotation::HappensAfter => raw_call!(hg_annotate_happens_after, addr),
1198            Annotation::HappensBefore => raw_call!(hg_annotate_happens_before, addr),
1199            Annotation::New(size) => raw_call!(hg_annotate_new_memory, addr, size),
1200        };
1201    }
1202}
1203
1204pub mod dhat {
1205    //! [`DHAT manual`](https://valgrind.org/docs/manual/dh-manual.html)
1206    use super::*;
1207
1208    /// Override default block size
1209    ///
1210    /// The size of the blocks that measure and display access counts is limited to 1024 bytes.
1211    /// This is done to limit the performance overhead and also to keep the size of the generated output reasonable.
1212    /// However, it is possible to override this limit using client requests.
1213    /// The use-case for this is to first run DHAT normally, and then identify any large blocks that you would like to further investigate with access count histograms.
1214    /// The macro should be placed immediately after the call to the allocator, and use the pointer returned by the allocator.
1215    ///
1216    /// # Implementation
1217    /// `DHAT_HISTOGRAM_MEMORY`
1218    #[inline]
1219    pub fn histogram_memory(addr: *mut c_void) {
1220        raw_call!(dh_histogram_memory, addr);
1221    }
1222}
1223
1224#[cfg(test)]
1225mod tests {
1226    use crate::{self as cg, valgrind::ThreadId};
1227
1228    #[test]
1229    fn test_run_mode_under_valgrind() {
1230        assert_eq!(cg::RunMode::Valgrind, cg::run_mode());
1231    }
1232
1233    #[test]
1234    fn print_macros_wont_fail() {
1235        // we are fine as long as it's not crashing
1236        let m = "crabgrind";
1237        cg::print!("{m}");
1238        cg::println!("het, {m}");
1239        cg::print_stacktrace!("{m}");
1240    }
1241
1242    #[test]
1243    fn ok_monitor_command() {
1244        // we are fine as long as it's not crashing
1245        cg::monitor_command("v.info all_errors").unwrap();
1246    }
1247
1248    #[test]
1249    fn wrong_monitor_command() {
1250        assert!(cg::monitor_command("hey valgringo").is_err());
1251    }
1252
1253    #[test]
1254    fn count_errors() {
1255        unsafe {
1256            let uninit = std::mem::MaybeUninit::<u8>::uninit();
1257            if uninit.assume_init() > 0 {
1258                unreachable!();
1259            }
1260        }
1261
1262        assert_eq!(cg::count_errors(), 1);
1263    }
1264
1265    #[test]
1266    fn disable_error_reporting() {
1267        cg::disable_error_reporting();
1268
1269        unsafe {
1270            let uninit = std::mem::MaybeUninit::<u8>::uninit();
1271            if uninit.assume_init() > 0 {
1272                unreachable!();
1273            }
1274        }
1275
1276        assert_eq!(cg::count_errors(), 0);
1277    }
1278
1279    #[test]
1280    fn non_simd_call() {
1281        let mut tid = ThreadId::MAX;
1282        cg::valgrind::non_simd_call(|id| {
1283            tid = id;
1284        });
1285        assert_ne!(tid, ThreadId::MAX);
1286    }
1287
1288    #[test]
1289    fn change_cli_option() {
1290        cg::change_cli_option("--leak-check=no");
1291        std::mem::forget(String::from("leaked"));
1292    }
1293}