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}