#![doc = include_str!("../../doc/memcheck.md")]
use super::{client_request, constants::memcheck::*};
use crate::{
ScopeGuard,
bindings::CG_MemcheckClientRequest as CR,
requests::{Scope, sealed::Sealed},
};
use core::{
ffi::{CStr, c_void},
marker::PhantomData,
};
pub type BlockHandle = usize;
pub type InvalidBlockHandle = BlockHandle;
#[doc = include_str!("../../doc/memcheck/UnaddressableBytes.md")]
pub type UnaddressableBytes = usize;
#[doc = include_str!("../../doc/memcheck/OffendingOffset.md")]
pub type OffendingOffset = usize;
#[doc(hidden)]
#[derive(Debug)]
pub struct DisabledReporting<'a>(PhantomData<&'a ()>);
impl Scope for DisabledReporting<'_> {
type Inner = (*const c_void, usize);
#[inline(always)]
fn enter((addr, size): Self::Inner) {
disable_error_reporting(addr.cast(), size);
}
#[inline(always)]
fn exit((addr, size): Self::Inner) {
enable_error_reporting(addr.cast(), size);
}
}
#[doc = include_str!("../../doc/memcheck/MemState.md")]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum MemState {
#[doc = include_str!("../../doc/memcheck/memstate/noaccess.md")]
NoAccess,
#[doc = include_str!("../../doc/memcheck/memstate/undefined.md")]
Undefined,
#[doc = include_str!("../../doc/memcheck/memstate/defined.md")]
Defined,
#[doc = include_str!("../../doc/memcheck/memstate/defined_if_addressable.md")]
DefinedIfAddressable,
}
#[doc = include_str!("../../doc/memcheck/LeakCheck.md")]
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum LeakCheck {
#[doc = include_str!("../../doc/memcheck/leakcheck/full.md")]
#[default]
Full,
#[doc = include_str!("../../doc/memcheck/leakcheck/added.md")]
Added,
#[doc = include_str!("../../doc/memcheck/leakcheck/quick.md")]
Quick,
#[doc = include_str!("../../doc/memcheck/leakcheck/changed.md")]
Changed,
#[doc = include_str!("../../doc/memcheck/leakcheck/new.md")]
New,
}
impl LeakCheck {
#[inline]
pub fn check(self) -> LeaksCount {
leak_check(self);
count_leaks()
}
#[inline]
pub fn check_blocks(self) -> LeaksCount {
leak_check(self);
count_leak_blocks()
}
}
#[doc = include_str!("../../doc/memcheck/LeaksCount.md")]
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub struct LeaksCount {
pub leaked: usize,
pub dubious: usize,
pub reachable: usize,
pub suppressed: usize,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum VBitsError {
NoValgrind,
LegacyAlignment,
Unaddressable,
Unknown(u8),
}
#[doc = include_str!("../../doc/memcheck/mark_memory.md")]
#[inline(always)]
pub fn mark_memory(
addr: *const c_void,
size: usize,
mark: MemState,
) -> Result<(), UnaddressableBytes> {
macro_rules! r {
($req:path) => {
client_request!($req, addr, size)
};
}
let result = match mark {
MemState::NoAccess => r!(CR::CG_VALGRIND_MAKE_MEM_NOACCESS),
MemState::Undefined => r!(CR::CG_VALGRIND_MAKE_MEM_UNDEFINED),
MemState::Defined => r!(CR::CG_VALGRIND_MAKE_MEM_DEFINED),
MemState::DefinedIfAddressable => r!(CR::CG_VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE),
};
(result == MAKE_MEM_OK).then_some(()).ok_or(result)
}
macro_rules! check_mem {
($req:path, $addr:expr, $size:expr) => {
match client_request!($req, $addr, $size) {
CHECK_MEM_OK => Ok(()),
x => Err(x - $addr as usize),
}
};
}
#[doc = include_str!("../../doc/memcheck/check_mem_addressable.md")]
#[inline(always)]
pub fn check_mem_addressable(addr: *const c_void, size: usize) -> Result<(), OffendingOffset> {
check_mem!(CR::CG_VALGRIND_CHECK_MEM_IS_ADDRESSABLE, addr, size)
}
#[doc = include_str!("../../doc/memcheck/check_mem_defined.md")]
#[inline(always)]
pub fn check_mem_defined(addr: *const c_void, size: usize) -> Result<(), OffendingOffset> {
check_mem!(CR::CG_VALGRIND_CHECK_MEM_IS_DEFINED, addr, size)
}
#[doc = include_str!("../../doc/memcheck/leak_check.md")]
#[inline(always)]
pub fn leak_check(check: LeakCheck) {
let (a1, a2) = match check {
LeakCheck::Full => LEAK_CHECK_FULL,
LeakCheck::Added => LEAK_CHECK_ADDED,
LeakCheck::Quick => LEAK_CHECK_QUICK,
LeakCheck::Changed => LEAK_CHECK_CHANGED,
LeakCheck::New => LEAK_CHECK_NEW,
};
client_request!(CR::CG_VALGRIND_DO_LEAK_CHECK, a1, a2);
}
#[doc = include_str!("../../doc/memcheck/count_leaks.md")]
#[inline(always)]
pub fn count_leaks() -> LeaksCount {
let mut leaks = LeaksCount::default();
client_request!(
CR::CG_VALGRIND_COUNT_LEAKS,
core::ptr::addr_of_mut!(leaks.leaked),
core::ptr::addr_of_mut!(leaks.dubious),
core::ptr::addr_of_mut!(leaks.reachable),
core::ptr::addr_of_mut!(leaks.suppressed)
);
leaks
}
#[doc = include_str!("../../doc/memcheck/count_leak_blocks.md")]
#[inline(always)]
pub fn count_leak_blocks() -> LeaksCount {
let mut leaks = LeaksCount::default();
client_request!(
CR::CG_VALGRIND_COUNT_LEAK_BLOCKS,
core::ptr::addr_of_mut!(leaks.leaked),
core::ptr::addr_of_mut!(leaks.dubious),
core::ptr::addr_of_mut!(leaks.reachable),
core::ptr::addr_of_mut!(leaks.suppressed)
);
leaks
}
macro_rules! vbits {
($req:path, $addr:expr, $slice:expr) => {
match client_request!($req, $addr, $slice.as_ptr(), $slice.len()) {
VBITS_OK => Ok(()),
VBITS_NO_VALGRIND => Err(VBitsError::NoValgrind),
VBITS_LEGACY => Err(VBitsError::LegacyAlignment),
VBITS_UNADDRESSABLE => Err(VBitsError::Unaddressable),
x => Err(VBitsError::Unknown(u8::try_from(x).expect("Return code must fit `u8`"))),
}
};
}
#[doc = include_str!("../../doc/memcheck/vbits.md")]
#[inline(always)]
pub fn vbits(addr: *const c_void, dest: &mut [u8]) -> Result<(), VBitsError> {
vbits!(CR::CG_VALGRIND_GET_VBITS, addr, dest)
}
#[doc = include_str!("../../doc/memcheck/set_vbits.md")]
#[inline(always)]
pub fn set_vbits(addr: *const c_void, vbits: &[u8]) -> Result<(), VBitsError> {
vbits!(CR::CG_VALGRIND_SET_VBITS, addr, vbits)
}
#[doc = include_str!("../../doc/memcheck/create_block.md")]
#[inline(always)]
pub fn create_block(addr: *const c_void, size: usize, desc: impl AsRef<CStr>) -> BlockHandle {
let desc = desc.as_ref().as_ptr();
client_request!(CR::CG_VALGRIND_CREATE_BLOCK, addr, size, desc)
}
#[doc = include_str!("../../doc/memcheck/discard_block.md")]
#[inline(always)]
pub fn discard_block(handle: BlockHandle) -> Result<(), InvalidBlockHandle> {
match client_request!(CR::CG_VALGRIND_DISCARD, handle) {
DISCARD_MEM_OK => Ok(()),
_ => Err(handle),
}
}
#[doc = include_str!("../../doc/memcheck/disable_reporting.md")]
#[inline(always)]
pub fn disable_reporting(bytes: &[u8]) -> ScopeGuard<DisabledReporting<'_>> {
ScopeGuard::new((bytes.as_ptr().cast(), bytes.len()))
}
#[doc = include_str!("../../doc/memcheck/enable_error_reporting.md")]
#[inline(always)]
pub fn enable_error_reporting(addr: *const c_void, size: usize) {
client_request!(CR::CG_VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE, addr, size);
}
#[doc = include_str!("../../doc/memcheck/disable_error_reporting.md")]
#[inline(always)]
pub fn disable_error_reporting(addr: *const c_void, size: usize) {
client_request!(CR::CG_VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE, addr, size);
}
impl core::fmt::Display for VBitsError {
#[inline(always)]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoValgrind => write!(f, "not running under Valgrind"),
Self::LegacyAlignment => write!(f, "legacy alignment issue"),
Self::Unaddressable => write!(f, "memory not addressable"),
Self::Unknown(x) => write!(f, "unknown VALGRIND_*_VBITS error code: {x}"),
}
}
}
impl core::error::Error for VBitsError {}
impl Sealed for DisabledReporting<'_> {}