use crate::sanitize::{
self, AddressSanitizer, InstrumentedAccess, MemorySanitizer, SanitizerConfig, SanitizerKind,
ThreadSanitizer,
};
use crate::x86::{
x86_calling_convention::X86CallingConvention, x86_register_info::X86RegisterInfo,
x86_subtarget::X86Subtarget,
};
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
pub const MSAN_SHADOW_SCALE: usize = 1;
pub const MSAN_ORIGIN_SCALE: usize = 4;
pub const MSAN_SHADOW_OFFSET_X86_64: u64 = 0x0000_7FFF_FF80_0000;
pub const MSAN_SHADOW_OFFSET_X86_32: u64 = 0x4000_0000;
pub const MSAN_MAX_ORIGIN_CHAIN: usize = 16;
pub const TSAN_SHADOW_GRANULARITY: usize = 8;
pub const TSAN_DEFAULT_HISTORY_SIZE: usize = 2;
pub const TSAN_MAX_MUTEXES: usize = 65536;
pub const TSAN_MAX_CLOCK_ENTRIES: usize = 1024;
pub const TSAN_DEFAULT_FLUSH_MS: u64 = 100;
pub const MTSAN_MAX_STACK_DEPTH: usize = 64;
pub const MTSAN_DEFAULT_EXITCODE: i32 = 77;
pub const MSAN_SHADOW_INIT: u8 = 0x00;
pub const MSAN_SHADOW_UNINIT: u8 = 0xFF;
pub const MSAN_ORIGIN_CLEAN: u32 = 0;
pub const MSAN_ORIGIN_FREED: u32 = 0xFFFF_FFFE;
pub const MSAN_ORIGIN_STACK_UAR: u32 = 0xFFFF_FFFD;
pub const MSAN_ORIGIN_HEAP: u32 = 0xFFFF_FFFC;
pub const MSAN_ORIGIN_CHAINED: u32 = 0x8000_0000;
pub const MSAN_ORIGIN_MAX: u32 = 0x7FFF_FFFF;
pub const TSAN_SHADOW_EMPTY: u64 = 0x0000_0000_0000_0000;
pub mod tsan_event {
pub const READ: u8 = 0;
pub const WRITE: u8 = 1;
pub const ATOMIC_RELAXED_LOAD: u8 = 2;
pub const ATOMIC_RELAXED_STORE: u8 = 3;
pub const ATOMIC_ACQUIRE: u8 = 4;
pub const ATOMIC_RELEASE: u8 = 5;
pub const ATOMIC_SEQ_CST_LOAD: u8 = 6;
pub const ATOMIC_SEQ_CST_STORE: u8 = 7;
pub const ATOMIC_RMW: u8 = 8;
pub const ATOMIC_FENCE: u8 = 9;
pub const MUTEX_LOCK: u8 = 10;
pub const MUTEX_UNLOCK: u8 = 11;
pub const THREAD_CREATE: u8 = 12;
pub const THREAD_JOIN: u8 = 13;
pub const SIGNAL_SEND: u8 = 14;
pub const SIGNAL_WAIT: u8 = 15;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MSanErrorKind {
UseOfUninitializedValue,
UninitializedBranch,
UninitializedCondition,
UninitializedSyscallArg,
UninitializedFree,
MemoryLeak,
DoubleFreeUninitialized,
}
impl fmt::Display for MSanErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MSanErrorKind::UseOfUninitializedValue => write!(f, "use-of-uninitialized-value"),
MSanErrorKind::UninitializedBranch => write!(f, "uninitialized-branch"),
MSanErrorKind::UninitializedCondition => write!(f, "uninitialized-condition"),
MSanErrorKind::UninitializedSyscallArg => write!(f, "uninitialized-syscall-arg"),
MSanErrorKind::UninitializedFree => write!(f, "uninitialized-free"),
MSanErrorKind::MemoryLeak => write!(f, "memory-leak"),
MSanErrorKind::DoubleFreeUninitialized => write!(f, "double-free-uninitialized"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TSanErrorKind {
DataRace,
MutexDeadlock,
LockOrderInversion,
ThreadLeak,
SignalUnsafeCall,
UseAfterFreeRace,
DoubleLock,
UnlockNotOwned,
DestroyLockedMutex,
}
impl fmt::Display for TSanErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TSanErrorKind::DataRace => write!(f, "data-race"),
TSanErrorKind::MutexDeadlock => write!(f, "mutex-deadlock"),
TSanErrorKind::LockOrderInversion => write!(f, "lock-order-inversion"),
TSanErrorKind::ThreadLeak => write!(f, "thread-leak"),
TSanErrorKind::SignalUnsafeCall => write!(f, "signal-unsafe-call"),
TSanErrorKind::UseAfterFreeRace => write!(f, "use-after-free-race"),
TSanErrorKind::DoubleLock => write!(f, "double-lock"),
TSanErrorKind::UnlockNotOwned => write!(f, "unlock-not-owned"),
TSanErrorKind::DestroyLockedMutex => write!(f, "destroy-locked-mutex"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MTSanStackFrame {
pub module: String,
pub module_offset: u64,
pub symbol: Option<String>,
pub file: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
}
impl MTSanStackFrame {
pub fn unknown() -> Self {
Self {
module: String::from("<unknown>"),
module_offset: 0,
symbol: None,
file: None,
line: None,
column: None,
}
}
pub fn with_symbol(symbol: impl Into<String>) -> Self {
Self {
module: String::new(),
module_offset: 0,
symbol: Some(symbol.into()),
file: None,
line: None,
column: None,
}
}
pub fn format(&self) -> String {
if let Some(ref sym) = self.symbol {
if let (Some(file), Some(line)) = (&self.file, self.line) {
format!(
" #0 {} {}:{}:{}",
sym,
file,
line,
self.column.unwrap_or(0)
)
} else {
format!(
" #0 {} ({}+0x{:x})",
sym, self.module, self.module_offset
)
}
} else {
format!(" #0 {} +0x{:x}", self.module, self.module_offset)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MTSanStackTrace {
pub frames: Vec<MTSanStackFrame>,
pub hash: u64,
}
impl MTSanStackTrace {
pub fn new() -> Self {
Self {
frames: Vec::new(),
hash: 0,
}
}
pub fn capture(depth: usize) -> Self {
let mut trace = Self::new();
for i in 0..depth {
trace.frames.push(MTSanStackFrame {
module: format!("module_{}", i),
module_offset: (i * 16) as u64,
symbol: Some(format!("func_{}", i)),
file: Some(format!("file_{}.c", i)),
line: Some((i * 17 + 42) as u32),
column: Some(0),
});
}
trace.compute_hash();
trace
}
pub fn compute_hash(&mut self) {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
for frame in &self.frames {
frame.hash(&mut hasher);
}
self.hash = hasher.finish();
}
pub fn format(&self) -> String {
let mut s = String::new();
for frame in &self.frames {
s.push_str(&frame.format());
s.push('\n');
}
s
}
pub fn symbolize(&mut self) {
for frame in &mut self.frames {
if frame.symbol.is_none() {
frame.symbol = Some(format!(
"{}_symbolized+0x{:x}",
frame.module, frame.module_offset
));
}
}
}
}
impl Default for MTSanStackTrace {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ClockEntry {
pub thread_id: u32,
pub clock: u64,
}
#[derive(Debug, Clone)]
pub struct VectorClock {
pub entries: Vec<ClockEntry>,
}
impl VectorClock {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn get(&self, thread_id: u32) -> u64 {
match self
.entries
.binary_search_by_key(&thread_id, |e| e.thread_id)
{
Ok(idx) => self.entries[idx].clock,
Err(_) => 0,
}
}
pub fn set(&mut self, thread_id: u32, clock: u64) {
match self
.entries
.binary_search_by_key(&thread_id, |e| e.thread_id)
{
Ok(idx) => {
if clock > self.entries[idx].clock {
self.entries[idx].clock = clock;
}
}
Err(idx) => {
self.entries.insert(idx, ClockEntry { thread_id, clock });
}
}
}
pub fn tick(&mut self, thread_id: u32) -> u64 {
let current = self.get(thread_id);
let new_val = current + 1;
self.set(thread_id, new_val);
new_val
}
pub fn join(&mut self, other: &VectorClock) {
for entry in &other.entries {
self.set(entry.thread_id, entry.clock);
}
}
pub fn happens_before(&self, other: &VectorClock) -> bool {
let mut strictly_less = false;
for entry in &self.entries {
let other_clock = other.get(entry.thread_id);
if entry.clock > other_clock {
return false;
}
if entry.clock < other_clock {
strictly_less = true;
}
}
if !strictly_less {
for entry in &other.entries {
let self_clock = self.get(entry.thread_id);
if self_clock < entry.clock {
strictly_less = true;
break;
}
}
}
strictly_less
}
pub fn happens_before_or_eq(&self, other: &VectorClock) -> bool {
for entry in &self.entries {
let other_clock = other.get(entry.thread_id);
if entry.clock > other_clock {
return false;
}
}
true
}
pub fn concurrent(&self, other: &VectorClock) -> bool {
!self.happens_before(other) && !other.happens_before(self)
}
pub fn merge(&mut self, other: &VectorClock) {
self.join(other);
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn max_clock(&self) -> u64 {
self.entries.iter().map(|e| e.clock).max().unwrap_or(0)
}
}
impl Default for VectorClock {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct MSanShadowMemory {
pub shadow_map: BTreeMap<u64, u8>,
pub origin_map: BTreeMap<u64, u32>,
pub shadow_offset: u64,
pub shadow_scale: usize,
pub track_origins: bool,
pub total_shadow_bytes: usize,
}
impl MSanShadowMemory {
pub fn new(shadow_offset: u64, track_origins: bool) -> Self {
Self {
shadow_map: BTreeMap::new(),
origin_map: BTreeMap::new(),
shadow_offset,
shadow_scale: MSAN_SHADOW_SCALE,
track_origins,
total_shadow_bytes: 0,
}
}
#[inline]
pub fn app_to_shadow(&self, app_addr: u64) -> u64 {
(app_addr.wrapping_sub(self.shadow_offset)) * self.shadow_scale as u64
}
#[inline]
pub fn shadow_to_app(&self, shadow_addr: u64) -> u64 {
(shadow_addr / self.shadow_scale as u64).wrapping_add(self.shadow_offset)
}
pub fn get_shadow(&self, addr: u64) -> u8 {
let shadow_addr = self.app_to_shadow(addr);
self.shadow_map
.get(&shadow_addr)
.copied()
.unwrap_or(MSAN_SHADOW_UNINIT)
}
pub fn set_shadow(&mut self, addr: u64, value: u8) {
let shadow_addr = self.app_to_shadow(addr);
if !self.shadow_map.contains_key(&shadow_addr) {
self.total_shadow_bytes += 1;
}
self.shadow_map.insert(shadow_addr, value);
}
pub fn set_shadow_range(&mut self, start: u64, size: usize, value: u8) {
for i in 0..size {
self.set_shadow(start + i as u64, value);
}
}
pub fn clear_shadow_range(&mut self, start: u64, size: usize) {
self.set_shadow_range(start, size, MSAN_SHADOW_INIT);
}
pub fn poison_shadow_range(&mut self, start: u64, size: usize) {
self.set_shadow_range(start, size, MSAN_SHADOW_UNINIT);
}
pub fn get_origin(&self, addr: u64) -> u32 {
if !self.track_origins {
return MSAN_ORIGIN_CLEAN;
}
let origin_key = addr / MSAN_ORIGIN_SCALE as u64;
self.origin_map
.get(&origin_key)
.copied()
.unwrap_or(MSAN_ORIGIN_CLEAN)
}
pub fn set_origin(&mut self, addr: u64, origin: u32) {
if !self.track_origins {
return;
}
let origin_key = addr / MSAN_ORIGIN_SCALE as u64;
self.origin_map.insert(origin_key, origin);
}
pub fn set_origin_range(&mut self, start: u64, size: usize, origin: u32) {
for i in (0..size).step_by(MSAN_ORIGIN_SCALE) {
self.set_origin(start + i as u64, origin);
}
}
pub fn clear_origin_range(&mut self, start: u64, size: usize) {
self.set_origin_range(start, size, MSAN_ORIGIN_CLEAN);
}
pub fn is_initialized_range(&self, start: u64, size: usize) -> bool {
for i in 0..size {
if self.get_shadow(start + i as u64) != MSAN_SHADOW_INIT {
return false;
}
}
true
}
pub fn find_first_uninit(&self, start: u64, size: usize) -> Option<(u64, u8, u32)> {
for i in 0..size {
let addr = start + i as u64;
let shadow = self.get_shadow(addr);
if shadow != MSAN_SHADOW_INIT {
let origin = if self.track_origins {
self.get_origin(addr)
} else {
MSAN_ORIGIN_CLEAN
};
return Some((addr, shadow, origin));
}
}
None
}
}
impl Default for MSanShadowMemory {
fn default() -> Self {
Self::new(MSAN_SHADOW_OFFSET_X86_64, false)
}
}
#[derive(Debug, Clone)]
pub struct OriginChainEntry {
pub id: u32,
pub prev_id: u32,
pub stack_trace: MTSanStackTrace,
pub description: String,
pub kind: OriginKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OriginKind {
HeapAllocation,
StackAllocation,
GlobalVariable,
TLSVariable,
FunctionParameter,
Instruction,
Deallocated,
HeapOverflow,
Custom,
}
impl fmt::Display for OriginKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OriginKind::HeapAllocation => write!(f, "heap-allocation"),
OriginKind::StackAllocation => write!(f, "stack-allocation"),
OriginKind::GlobalVariable => write!(f, "global-variable"),
OriginKind::TLSVariable => write!(f, "tls-variable"),
OriginKind::FunctionParameter => write!(f, "function-parameter"),
OriginKind::Instruction => write!(f, "instruction"),
OriginKind::Deallocated => write!(f, "deallocated-memory"),
OriginKind::HeapOverflow => write!(f, "heap-buffer-overflow"),
OriginKind::Custom => write!(f, "custom"),
}
}
}
#[derive(Debug)]
pub struct OriginRegistry {
pub entries: HashMap<u32, OriginChainEntry>,
pub next_id: AtomicU32,
}
impl OriginRegistry {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
next_id: AtomicU32::new(1),
}
}
pub fn allocate_origin(
&mut self,
prev_id: u32,
description: impl Into<String>,
kind: OriginKind,
stack: MTSanStackTrace,
) -> u32 {
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
if id > MSAN_ORIGIN_MAX {
self.next_id.store(1, Ordering::Relaxed);
}
self.entries.insert(
id,
OriginChainEntry {
id,
prev_id,
stack_trace: stack,
description: description.into(),
kind,
},
);
id
}
pub fn get(&self, id: u32) -> Option<&OriginChainEntry> {
self.entries.get(&id)
}
pub fn follow_chain(&self, start_id: u32, max_depth: usize) -> Vec<&OriginChainEntry> {
let mut chain = Vec::new();
let mut current_id = start_id;
let mut visited = HashSet::new();
for _ in 0..max_depth {
if current_id == MSAN_ORIGIN_CLEAN || !visited.insert(current_id) {
break;
}
if let Some(entry) = self.entries.get(¤t_id) {
let prev = entry.prev_id;
chain.push(entry);
current_id = prev;
} else {
break;
}
}
chain.reverse();
chain
}
pub fn format_chain(&self, start_id: u32) -> String {
let chain = self.follow_chain(start_id, MSAN_MAX_ORIGIN_CHAIN);
if chain.is_empty() {
return String::from(" Origin: unknown\n");
}
let mut s = String::new();
for (i, entry) in chain.iter().enumerate() {
s.push_str(&format!(
" Origin {} ({}): {} (id={})\n",
i + 1,
entry.kind,
entry.description,
entry.id
));
if !entry.stack_trace.frames.is_empty() {
s.push_str(&format!(
" {}",
entry
.stack_trace
.frames
.first()
.map(|f| f.format())
.unwrap_or_default()
));
s.push('\n');
}
}
s
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for OriginRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct MSanShadowPropagator;
impl MSanShadowPropagator {
pub fn propagate_add_sub(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_mul(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_div(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_rem(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_and(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_or(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_xor(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_shl(val_shadow: u8, amt_shadow: u8) -> u8 {
if amt_shadow != MSAN_SHADOW_INIT {
return MSAN_SHADOW_UNINIT;
}
val_shadow
}
pub fn propagate_lshr(val_shadow: u8, amt_shadow: u8) -> u8 {
if amt_shadow != MSAN_SHADOW_INIT {
return MSAN_SHADOW_UNINIT;
}
val_shadow
}
pub fn propagate_ashr(val_shadow: u8, amt_shadow: u8) -> u8 {
if amt_shadow != MSAN_SHADOW_INIT {
return MSAN_SHADOW_UNINIT;
}
val_shadow
}
pub fn propagate_select(cond_shadow: u8, t_shadow: u8, f_shadow: u8) -> u8 {
if cond_shadow != MSAN_SHADOW_INIT {
t_shadow | f_shadow
} else {
t_shadow | f_shadow
}
}
pub fn propagate_phi(shadows: &[u8]) -> u8 {
shadows.iter().fold(MSAN_SHADOW_INIT, |acc, &s| acc | s)
}
pub fn propagate_trunc(shadow: u8) -> u8 {
shadow
}
pub fn propagate_ext(shadow: u8) -> u8 {
shadow
}
pub fn propagate_bitcast(shadow: u8) -> u8 {
shadow
}
pub fn propagate_cmp(a_shadow: u8, b_shadow: u8) -> u8 {
if a_shadow != MSAN_SHADOW_INIT || b_shadow != MSAN_SHADOW_INIT {
MSAN_SHADOW_UNINIT
} else {
MSAN_SHADOW_INIT
}
}
pub fn propagate_fbinary(a_shadow: u8, b_shadow: u8) -> u8 {
a_shadow | b_shadow
}
pub fn propagate_fptosi(shadow: u8) -> u8 {
shadow
}
pub fn propagate_sitofp(shadow: u8) -> u8 {
shadow
}
pub fn propagate_load(shadow_mem: &MSanShadowMemory, addr: u64, size: usize) -> Vec<u8> {
let mut result = Vec::with_capacity(size);
for i in 0..size {
result.push(shadow_mem.get_shadow(addr + i as u64));
}
result
}
pub fn propagate_store(shadow_mem: &mut MSanShadowMemory, addr: u64, shadow_bytes: &[u8]) {
for (i, &s) in shadow_bytes.iter().enumerate() {
shadow_mem.set_shadow(addr + i as u64, s);
}
}
pub fn propagate_call_ret(arg_shadows: &[u8]) -> u8 {
arg_shadows.iter().fold(MSAN_SHADOW_INIT, |acc, &s| acc | s)
}
pub fn propagate_gep(base_shadow: u8, _index_shadows: &[u8]) -> u8 {
base_shadow
}
pub fn propagate_extractvalue(agg_shadow: &[u8], offset: usize, size: usize) -> Vec<u8> {
if offset + size <= agg_shadow.len() {
agg_shadow[offset..offset + size].to_vec()
} else {
vec![MSAN_SHADOW_UNINIT; size]
}
}
pub fn propagate_insertvalue(agg_shadow: &[u8], offset: usize, val_shadow: &[u8]) -> Vec<u8> {
let mut result = agg_shadow.to_vec();
for (i, &s) in val_shadow.iter().enumerate() {
if offset + i < result.len() {
result[offset + i] = s;
}
}
result
}
pub fn propagate_memcpy(shadow_mem: &mut MSanShadowMemory, dst: u64, src: u64, size: usize) {
for i in 0..size {
let s = shadow_mem.get_shadow(src + i as u64);
shadow_mem.set_shadow(dst + i as u64, s);
}
}
pub fn propagate_memset(shadow_mem: &mut MSanShadowMemory, dst: u64, size: usize, val: u8) {
shadow_mem.clear_shadow_range(dst, size);
let _ = val; }
}
#[derive(Debug)]
pub struct MSanInterceptors {
pub poison_in_free: bool,
pub origin_stack: Vec<(u64, usize)>,
pub call_count: usize,
}
impl MSanInterceptors {
pub fn new() -> Self {
Self {
poison_in_free: true,
origin_stack: Vec::new(),
call_count: 0,
}
}
pub fn intercept_malloc(
shadow_mem: &mut MSanShadowMemory,
registry: &mut OriginRegistry,
size: usize,
track_origins: bool,
) -> u64 {
let ptr = unsafe {
let layout = std::alloc::Layout::from_size_align(size.max(1), 16).unwrap();
std::alloc::alloc(layout) as u64
};
if ptr != 0 {
shadow_mem.clear_shadow_range(ptr, size);
if track_origins {
let origin = registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!("malloc({})", size),
OriginKind::HeapAllocation,
MTSanStackTrace::capture(4),
);
shadow_mem.set_origin_range(ptr, size, origin);
}
}
ptr
}
pub fn intercept_calloc(
shadow_mem: &mut MSanShadowMemory,
registry: &mut OriginRegistry,
nmemb: usize,
size: usize,
track_origins: bool,
) -> u64 {
let total = nmemb * size;
let ptr = unsafe {
let layout = std::alloc::Layout::from_size_align(total.max(1), 16).unwrap();
std::alloc::alloc_zeroed(layout) as u64
};
if ptr != 0 {
shadow_mem.clear_shadow_range(ptr, total);
if track_origins {
let origin = registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!("calloc({}, {})", nmemb, size),
OriginKind::HeapAllocation,
MTSanStackTrace::capture(4),
);
shadow_mem.set_origin_range(ptr, total, origin);
}
}
ptr
}
pub fn intercept_realloc(
shadow_mem: &mut MSanShadowMemory,
registry: &mut OriginRegistry,
old_ptr: u64,
new_size: usize,
old_size: usize,
track_origins: bool,
) -> u64 {
let new_ptr = unsafe {
let layout = std::alloc::Layout::from_size_align(new_size.max(1), 16).unwrap();
std::alloc::alloc(layout) as u64
};
if new_ptr != 0 && old_ptr != 0 {
let copy_size = old_size.min(new_size);
for i in 0..copy_size {
let s = shadow_mem.get_shadow(old_ptr + i as u64);
shadow_mem.set_shadow(new_ptr + i as u64, s);
if track_origins && i % MSAN_ORIGIN_SCALE == 0 {
let origin = shadow_mem.get_origin(old_ptr + i as u64);
shadow_mem.set_origin(new_ptr + i as u64, origin);
}
}
if new_size > copy_size {
shadow_mem.clear_shadow_range(new_ptr + copy_size as u64, new_size - copy_size);
}
unsafe {
let layout = std::alloc::Layout::from_size_align(old_size.max(1), 16).unwrap();
std::alloc::dealloc(old_ptr as *mut u8, layout);
}
}
new_ptr
}
pub fn intercept_free(
shadow_mem: &mut MSanShadowMemory,
registry: &mut OriginRegistry,
ptr: u64,
size: usize,
poison: bool,
track_origins: bool,
) {
if ptr == 0 {
return;
}
if poison {
shadow_mem.poison_shadow_range(ptr, size);
if track_origins {
let origin = registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!("freed heap memory at {:#x}", ptr),
OriginKind::Deallocated,
MTSanStackTrace::capture(4),
);
shadow_mem.set_origin_range(ptr, size, origin);
}
}
unsafe {
let layout = std::alloc::Layout::from_size_align(size.max(1), 16).unwrap();
std::alloc::dealloc(ptr as *mut u8, layout);
}
}
pub fn intercept_memcpy(shadow_mem: &mut MSanShadowMemory, dst: u64, src: u64, size: usize) {
MSanShadowPropagator::propagate_memcpy(shadow_mem, dst, src, size);
}
pub fn intercept_memmove(shadow_mem: &mut MSanShadowMemory, dst: u64, src: u64, size: usize) {
if dst < src {
for i in 0..size {
shadow_mem.set_shadow(dst + i as u64, shadow_mem.get_shadow(src + i as u64));
}
} else {
for i in (0..size).rev() {
shadow_mem.set_shadow(dst + i as u64, shadow_mem.get_shadow(src + i as u64));
}
}
}
pub fn intercept_memset(shadow_mem: &mut MSanShadowMemory, dst: u64, size: usize) {
shadow_mem.clear_shadow_range(dst, size);
}
pub fn intercept_strcpy(shadow_mem: &mut MSanShadowMemory, dst: u64, src: u64) {
let mut i = 0u64;
loop {
let s = shadow_mem.get_shadow(src + i);
shadow_mem.set_shadow(dst + i, s);
i += 1;
if i > 4096 {
break; }
}
}
pub fn intercept_strncpy(shadow_mem: &mut MSanShadowMemory, dst: u64, src: u64, n: usize) {
for i in 0..n {
let s = shadow_mem.get_shadow(src + i as u64);
shadow_mem.set_shadow(dst + i as u64, s);
}
}
pub fn intercept_strlen(shadow_mem: &MSanShadowMemory, src: u64) -> (usize, bool) {
let mut len = 0usize;
let mut is_init = true;
loop {
let s = shadow_mem.get_shadow(src + len as u64);
if s != MSAN_SHADOW_INIT {
is_init = false;
}
len += 1;
if len > 65536 {
break;
}
}
(len - 1, is_init)
}
pub fn intercept_printf(shadow_mem: &MSanShadowMemory, fmt: u64) -> bool {
let mut i = 0u64;
loop {
let s = shadow_mem.get_shadow(fmt + i);
if s != MSAN_SHADOW_INIT {
return false;
}
i += 1;
if i > 4096 {
break;
}
}
true
}
}
impl Default for MSanInterceptors {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MSanSignalContext {
pub saved_reg_shadow: [u8; 16],
pub saved_reg_origin: [u32; 16],
pub stack_range: (u64, u64),
pub signum: i32,
}
impl MSanSignalContext {
pub fn new() -> Self {
Self {
saved_reg_shadow: [MSAN_SHADOW_INIT; 16],
saved_reg_origin: [MSAN_ORIGIN_CLEAN; 16],
stack_range: (0, 0),
signum: 0,
}
}
pub fn save(&mut self, shadow_mem: &MSanShadowMemory, signum: i32) {
self.signum = signum;
for i in 0..16 {
self.saved_reg_shadow[i] = MSAN_SHADOW_INIT;
self.saved_reg_origin[i] = MSAN_ORIGIN_CLEAN;
}
let _ = shadow_mem;
}
pub fn restore(&self, _shadow_mem: &mut MSanShadowMemory) {
}
}
impl Default for MSanSignalContext {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct MSanTLSShadow {
pub tls_shadow: HashMap<u64, u8>,
pub tls_origin: HashMap<u64, u32>,
pub tls_size: usize,
pub active: bool,
}
impl MSanTLSShadow {
pub fn new() -> Self {
Self {
tls_shadow: HashMap::new(),
tls_origin: HashMap::new(),
tls_size: 0,
active: false,
}
}
pub fn init(&mut self, tls_size: usize) {
self.tls_size = tls_size;
self.active = true;
for offset in 0..tls_size {
self.tls_shadow.insert(offset as u64, MSAN_SHADOW_UNINIT);
self.tls_origin.insert(offset as u64, MSAN_ORIGIN_CLEAN);
}
}
pub fn get_tls_shadow(&self, offset: u64) -> u8 {
self.tls_shadow
.get(&offset)
.copied()
.unwrap_or(MSAN_SHADOW_UNINIT)
}
pub fn set_tls_shadow(&mut self, offset: u64, value: u8) {
self.tls_shadow.insert(offset, value);
}
pub fn get_tls_origin(&self, offset: u64) -> u32 {
self.tls_origin
.get(&offset)
.copied()
.unwrap_or(MSAN_ORIGIN_CLEAN)
}
pub fn set_tls_origin(&mut self, offset: u64, origin: u32) {
self.tls_origin.insert(offset, origin);
}
pub fn clear_tls_range(&mut self, start: u64, size: usize) {
for i in 0..size {
self.tls_shadow.insert(start + i as u64, MSAN_SHADOW_INIT);
self.tls_origin.insert(start + i as u64, MSAN_ORIGIN_CLEAN);
}
}
}
impl Default for MSanTLSShadow {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TSanThreadState {
pub thread_id: u32,
pub clock: VectorClock,
pub global_tick: u64,
pub held_mutexes: Vec<u64>,
pub lock_order: Vec<(u64, u64)>,
pub is_joined: bool,
pub in_signal_handler: bool,
pub signal_number: Option<i32>,
pub creation_stack: MTSanStackTrace,
pub name: Option<String>,
pub is_tracked: bool,
pub is_detached: bool,
}
impl TSanThreadState {
pub fn new(thread_id: u32, creation_stack: MTSanStackTrace) -> Self {
Self {
thread_id,
clock: VectorClock::new(),
global_tick: 0,
held_mutexes: Vec::new(),
lock_order: Vec::new(),
is_joined: false,
in_signal_handler: false,
signal_number: None,
creation_stack,
name: None,
is_tracked: true,
is_detached: false,
}
}
pub fn tick(&mut self) -> u64 {
self.global_tick += 1;
self.clock.tick(self.thread_id)
}
pub fn acquire(&mut self, release_clock: &VectorClock) {
self.clock.join(release_clock);
}
pub fn release(&self) -> VectorClock {
self.clock.clone()
}
}
#[derive(Debug, Clone)]
pub struct TSanShadowCell {
pub last_thread: u32,
pub last_clock: VectorClock,
pub last_write: bool,
pub write_clock: VectorClock,
pub read_count: u32,
pub history: VecDeque<TSanAccessRecord>,
pub history_size: usize,
}
#[derive(Debug, Clone)]
pub struct TSanAccessRecord {
pub thread_id: u32,
pub clock: VectorClock,
pub is_write: bool,
pub size: usize,
pub is_atomic: bool,
pub atomic_ordering: Option<TSanAtomicOrdering>,
pub stack: MTSanStackTrace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TSanAtomicOrdering {
Relaxed,
Consume,
Acquire,
Release,
AcquireRelease,
SequentiallyConsistent,
}
impl fmt::Display for TSanAtomicOrdering {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TSanAtomicOrdering::Relaxed => write!(f, "relaxed"),
TSanAtomicOrdering::Consume => write!(f, "consume"),
TSanAtomicOrdering::Acquire => write!(f, "acquire"),
TSanAtomicOrdering::Release => write!(f, "release"),
TSanAtomicOrdering::AcquireRelease => write!(f, "acq_rel"),
TSanAtomicOrdering::SequentiallyConsistent => write!(f, "seq_cst"),
}
}
}
impl TSanShadowCell {
pub fn new(history_size: usize) -> Self {
Self {
last_thread: 0,
last_clock: VectorClock::new(),
last_write: false,
write_clock: VectorClock::new(),
read_count: 0,
history: VecDeque::with_capacity(history_size),
history_size,
}
}
pub fn record_access(
&mut self,
thread_id: u32,
thread_clock: &VectorClock,
is_write: bool,
size: usize,
is_atomic: bool,
atomic_ordering: Option<TSanAtomicOrdering>,
stack: MTSanStackTrace,
) -> Option<TSanRaceInfo> {
let record = TSanAccessRecord {
thread_id,
clock: thread_clock.clone(),
is_write,
size,
is_atomic,
atomic_ordering,
stack,
};
let mut race_info = None;
if self.last_thread != 0 && self.last_thread != thread_id {
if is_write || self.last_write {
if !is_atomic
&& !thread_clock.happens_before(&self.last_clock)
&& !self.last_clock.happens_before(thread_clock)
{
race_info = Some(TSanRaceInfo {
addr: 0, size,
thread1: self.last_thread,
thread2: thread_id,
is_write1: self.last_write,
is_write2: is_write,
clock1: self.last_clock.clone(),
clock2: thread_clock.clone(),
});
}
}
}
if race_info.is_none() {
for hist_record in &self.history {
if hist_record.thread_id != thread_id {
if is_write || hist_record.is_write {
if !is_atomic
&& !thread_clock.happens_before(&hist_record.clock)
&& !hist_record.clock.happens_before(thread_clock)
{
race_info = Some(TSanRaceInfo {
addr: 0,
size,
thread1: hist_record.thread_id,
thread2: thread_id,
is_write1: hist_record.is_write,
is_write2: is_write,
clock1: hist_record.clock.clone(),
clock2: thread_clock.clone(),
});
break;
}
}
}
}
}
self.last_thread = thread_id;
self.last_clock = thread_clock.clone();
if is_write {
self.last_write = true;
self.write_clock = thread_clock.clone();
self.read_count = 0;
} else if !self.last_write {
self.read_count += 1;
}
if self.history.len() >= self.history_size {
self.history.pop_front();
}
self.history.push_back(record);
race_info
}
}
#[derive(Debug, Clone)]
pub struct TSanRaceInfo {
pub addr: u64,
pub size: usize,
pub thread1: u32,
pub thread2: u32,
pub is_write1: bool,
pub is_write2: bool,
pub clock1: VectorClock,
pub clock2: VectorClock,
}
#[derive(Debug, Clone)]
pub struct TSanMutexState {
pub id: u64,
pub owner_thread: u32,
pub lock_count: u32,
pub creation_stack: MTSanStackTrace,
pub is_read_lock: bool,
pub release_clock: VectorClock,
pub is_destroyed: bool,
pub lock_history: Vec<TSanMutexLockRecord>,
}
#[derive(Debug, Clone)]
pub struct TSanMutexLockRecord {
pub thread_id: u32,
pub stack: MTSanStackTrace,
pub clock: VectorClock,
}
impl TSanMutexState {
pub fn new(id: u64, creation_stack: MTSanStackTrace) -> Self {
Self {
id,
owner_thread: 0,
lock_count: 0,
creation_stack,
is_read_lock: false,
release_clock: VectorClock::new(),
is_destroyed: false,
lock_history: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LockCapability {
Mutex,
RwLockShared,
RwLockExclusive,
Capability(String),
}
#[derive(Debug, Clone)]
pub struct LockAnnotation {
pub capability: LockCapability,
pub mutex_id: u64,
pub location: Option<(String, u32, u32)>,
}
impl LockAnnotation {
pub fn acquire(mutex_id: u64) -> Self {
Self {
capability: LockCapability::Mutex,
mutex_id,
location: None,
}
}
pub fn release(mutex_id: u64) -> Self {
Self {
capability: LockCapability::Mutex,
mutex_id,
location: None,
}
}
pub fn try_acquire(mutex_id: u64) -> Self {
Self {
capability: LockCapability::Mutex,
mutex_id,
location: None,
}
}
pub fn shared_acquire(mutex_id: u64) -> Self {
Self {
capability: LockCapability::RwLockShared,
mutex_id,
location: None,
}
}
pub fn exclusive_acquire(mutex_id: u64) -> Self {
Self {
capability: LockCapability::RwLockExclusive,
mutex_id,
location: None,
}
}
}
#[derive(Debug)]
pub struct TSanDeadlockDetector {
pub wait_for_graph: HashMap<u32, HashSet<u32>>,
pub mutex_owners: HashMap<u64, u32>,
pub lock_orders: HashMap<u32, Vec<u64>>,
}
impl TSanDeadlockDetector {
pub fn new() -> Self {
Self {
wait_for_graph: HashMap::new(),
mutex_owners: HashMap::new(),
lock_orders: HashMap::new(),
}
}
pub fn try_acquire(&mut self, thread_id: u32, mutex_id: u64) -> Option<Vec<u32>> {
if let Some(&owner) = self.mutex_owners.get(&mutex_id) {
if owner != thread_id {
self.wait_for_graph
.entry(thread_id)
.or_insert_with(HashSet::new)
.insert(owner);
if let Some(cycle) = self.detect_cycle(thread_id) {
return Some(cycle);
}
}
}
None
}
pub fn acquired(&mut self, thread_id: u32, mutex_id: u64) {
self.mutex_owners.insert(mutex_id, thread_id);
if let Some(waiters) = self.wait_for_graph.get_mut(&thread_id) {
waiters.clear();
}
self.lock_orders
.entry(thread_id)
.or_insert_with(Vec::new)
.push(mutex_id);
}
pub fn released(&mut self, thread_id: u32, mutex_id: u64) {
if self.mutex_owners.get(&mutex_id) == Some(&thread_id) {
self.mutex_owners.remove(&mutex_id);
}
}
fn detect_cycle(&self, start: u32) -> Option<Vec<u32>> {
let mut visited = HashSet::new();
let mut stack = Vec::new();
let mut in_stack = HashSet::new();
if self.dfs_cycle(start, &mut visited, &mut stack, &mut in_stack) {
Some(stack)
} else {
None
}
}
fn dfs_cycle(
&self,
node: u32,
visited: &mut HashSet<u32>,
stack: &mut Vec<u32>,
in_stack: &mut HashSet<u32>,
) -> bool {
visited.insert(node);
stack.push(node);
in_stack.insert(node);
if let Some(neighbors) = self.wait_for_graph.get(&node) {
for &neighbor in neighbors {
if !visited.contains(&neighbor) {
if self.dfs_cycle(neighbor, visited, stack, in_stack) {
return true;
}
} else if in_stack.contains(&neighbor) {
while stack.first() != Some(&neighbor) {
stack.remove(0);
}
return true;
}
}
}
stack.pop();
in_stack.remove(&node);
false
}
pub fn check_lock_order(&self, thread_id: u32, new_mutex: u64) -> Option<(u64, u64)> {
if let Some(order) = self.lock_orders.get(&thread_id) {
for &held in order.iter().rev() {
for (other_tid, other_order) in &self.lock_orders {
if *other_tid == thread_id {
continue;
}
let held_pos = other_order.iter().position(|&m| m == held);
let new_pos = other_order.iter().position(|&m| m == new_mutex);
if let (Some(hp), Some(np)) = (held_pos, new_pos) {
if hp > np {
return Some((held, new_mutex));
}
}
}
}
}
None
}
}
impl Default for TSanDeadlockDetector {
fn default() -> Self {
Self::new()
}
}
const SIGNAL_UNSAFE_FUNCTIONS: &[&str] = &[
"printf",
"fprintf",
"sprintf",
"snprintf",
"malloc",
"calloc",
"realloc",
"free",
"pthread_mutex_lock",
"pthread_mutex_unlock",
"pthread_create",
"pthread_join",
"fopen",
"fclose",
"fread",
"fwrite",
"opendir",
"readdir",
"closedir",
"getpwnam",
"getpwuid",
"localtime",
"gmtime",
"ctime",
"asctime",
"rand",
"srand",
"setlocale",
"getenv",
"setenv",
"putenv",
"system",
"popen",
"pclose",
"dlopen",
"dlsym",
"dlclose",
"exit",
"abort",
];
#[derive(Debug)]
pub struct TSanSignalSafety {
pub unsafe_functions: HashSet<String>,
pub enabled: bool,
}
impl TSanSignalSafety {
pub fn new() -> Self {
let mut unsafe_functions = HashSet::new();
for func in SIGNAL_UNSAFE_FUNCTIONS {
unsafe_functions.insert(func.to_string());
}
Self {
unsafe_functions,
enabled: true,
}
}
pub fn is_signal_safe(&self, function_name: &str) -> bool {
!self.unsafe_functions.contains(function_name)
}
pub fn unsafe_list(&self) -> Vec<&String> {
self.unsafe_functions.iter().collect()
}
}
impl Default for TSanSignalSafety {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MSanOptions {
pub poison_in_free: bool,
pub report_umrs: bool,
pub wrap_signals: bool,
pub print_stats: bool,
pub track_origins: u32,
pub origin_level: u32,
pub keep_going: bool,
pub exit_code: i32,
pub halt_on_error: bool,
pub log_path: Option<String>,
pub verbosity: u32,
pub max_umrs: usize,
pub check_store: bool,
pub store_clean_on_allocation: bool,
}
impl Default for MSanOptions {
fn default() -> Self {
Self {
poison_in_free: true,
report_umrs: true,
wrap_signals: true,
print_stats: false,
track_origins: 0,
origin_level: 0,
keep_going: false,
exit_code: MTSAN_DEFAULT_EXITCODE,
halt_on_error: false,
log_path: None,
verbosity: 0,
max_umrs: 100,
check_store: false,
store_clean_on_allocation: true,
}
}
}
impl MSanOptions {
pub fn from_env() -> Self {
let mut opts = Self::default();
if let Ok(env_val) = std::env::var("MSAN_OPTIONS") {
for part in env_val.split(':') {
let mut kv = part.splitn(2, '=');
let key = kv.next().unwrap_or("");
let val = kv.next().unwrap_or("");
match key {
"poison_in_free" => opts.poison_in_free = val != "0",
"report_umrs" => opts.report_umrs = val != "0",
"wrap_signals" => opts.wrap_signals = val != "0",
"print_stats" => opts.print_stats = val != "0",
"track_origins" => {
if let Ok(n) = val.parse() {
opts.track_origins = n;
opts.origin_level = n;
}
}
"keep_going" => opts.keep_going = val != "0",
"exit_code" => {
if let Ok(n) = val.parse() {
opts.exit_code = n;
}
}
"halt_on_error" => opts.halt_on_error = val != "0",
"log_path" => opts.log_path = Some(val.to_string()),
"verbosity" => {
if let Ok(n) = val.parse() {
opts.verbosity = n;
}
}
_ => {}
}
}
}
opts
}
pub fn parse_option(&mut self, key: &str, val: &str) {
match key {
"poison_in_free" => self.poison_in_free = val != "0",
"report_umrs" => self.report_umrs = val != "0",
"wrap_signals" => self.wrap_signals = val != "0",
"print_stats" => self.print_stats = val != "0",
"track_origins" => {
if let Ok(n) = val.parse() {
self.track_origins = n;
self.origin_level = n;
}
}
"keep_going" => self.keep_going = val != "0",
"exit_code" => {
if let Ok(n) = val.parse() {
self.exit_code = n;
}
}
"halt_on_error" => self.halt_on_error = val != "0",
"log_path" => self.log_path = Some(val.to_string()),
"verbosity" => {
if let Ok(n) = val.parse() {
self.verbosity = n;
}
}
_ => {}
}
}
}
#[derive(Debug, Clone)]
pub struct TSanOptions {
pub history_size: usize,
pub flush_memory_ms: u64,
pub force_seq_cst_atomics: bool,
pub suppressions: Option<String>,
pub report_thread_leaks: bool,
pub detect_deadlocks: bool,
pub ignore_non_racy: bool,
pub exit_code: i32,
pub halt_on_error: bool,
pub keep_going: bool,
pub log_path: Option<String>,
pub verbosity: u32,
pub atomic_seq_cst: bool,
pub max_threads: usize,
pub check_signal_safety: bool,
}
impl Default for TSanOptions {
fn default() -> Self {
Self {
history_size: TSAN_DEFAULT_HISTORY_SIZE,
flush_memory_ms: TSAN_DEFAULT_FLUSH_MS,
force_seq_cst_atomics: false,
suppressions: None,
report_thread_leaks: true,
detect_deadlocks: true,
ignore_non_racy: true,
exit_code: MTSAN_DEFAULT_EXITCODE,
halt_on_error: false,
keep_going: false,
log_path: None,
verbosity: 0,
atomic_seq_cst: false,
max_threads: 8192,
check_signal_safety: true,
}
}
}
impl TSanOptions {
pub fn from_env() -> Self {
let mut opts = Self::default();
if let Ok(env_val) = std::env::var("TSAN_OPTIONS") {
for part in env_val.split(':') {
let mut kv = part.splitn(2, '=');
let key = kv.next().unwrap_or("");
let val = kv.next().unwrap_or("");
opts.parse_option(key, val);
}
}
opts
}
pub fn parse_option(&mut self, key: &str, val: &str) {
match key {
"history_size" => {
if let Ok(n) = val.parse() {
self.history_size = n;
}
}
"flush_memory_ms" => {
if let Ok(n) = val.parse() {
self.flush_memory_ms = n;
}
}
"force_seq_cst_atomics" => self.force_seq_cst_atomics = val != "0",
"suppressions" => self.suppressions = Some(val.to_string()),
"report_thread_leaks" => self.report_thread_leaks = val != "0",
"detect_deadlocks" => self.detect_deadlocks = val != "0",
"halt_on_error" => self.halt_on_error = val != "0",
"keep_going" => self.keep_going = val != "0",
"exit_code" => {
if let Ok(n) = val.parse() {
self.exit_code = n;
}
}
"log_path" => self.log_path = Some(val.to_string()),
"verbosity" => {
if let Ok(n) = val.parse() {
self.verbosity = n;
}
}
_ => {}
}
}
}
#[derive(Debug, Clone)]
pub struct MTSanFlags {
pub msan_options: MSanOptions,
pub tsan_options: TSanOptions,
pub log_path: Option<String>,
pub verbosity: u32,
pub exitcode: i32,
pub msan_enabled: bool,
pub tsan_enabled: bool,
pub origins_enabled: bool,
pub halt_on_error: bool,
}
impl MTSanFlags {
pub fn new() -> Self {
Self {
msan_options: MSanOptions::default(),
tsan_options: TSanOptions::default(),
log_path: None,
verbosity: 0,
exitcode: MTSAN_DEFAULT_EXITCODE,
msan_enabled: true,
tsan_enabled: true,
origins_enabled: false,
halt_on_error: false,
}
}
pub fn from_env() -> Self {
let msan_opts = MSanOptions::from_env();
let tsan_opts = TSanOptions::from_env();
let log_path = msan_opts
.log_path
.clone()
.or_else(|| tsan_opts.log_path.clone());
let verbosity = msan_opts.verbosity.max(tsan_opts.verbosity);
let exitcode = msan_opts.exit_code.min(tsan_opts.exit_code);
Self {
msan_options: msan_opts.clone(),
tsan_options: tsan_opts.clone(),
log_path,
verbosity,
exitcode,
msan_enabled: true,
tsan_enabled: true,
origins_enabled: msan_opts.track_origins > 0,
halt_on_error: msan_opts.halt_on_error || tsan_opts.halt_on_error,
}
}
}
impl Default for MTSanFlags {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MSanErrorReport {
pub kind: MSanErrorKind,
pub address: u64,
pub size: usize,
pub origin: u32,
pub stack: MTSanStackTrace,
pub thread_id: u32,
pub description: String,
pub is_fatal: bool,
}
impl MSanErrorReport {
pub fn new(
kind: MSanErrorKind,
address: u64,
size: usize,
origin: u32,
stack: MTSanStackTrace,
thread_id: u32,
description: impl Into<String>,
) -> Self {
Self {
kind,
address,
size,
origin,
stack,
thread_id,
description: description.into(),
is_fatal: false,
}
}
pub fn format(&self, origin_registry: Option<&OriginRegistry>) -> String {
let mut s = String::new();
s.push_str("===================================================\n");
s.push_str(&format!("WARNING: MemorySanitizer: {}\n", self.kind));
s.push_str(&format!(" Address: 0x{:016x}\n", self.address));
s.push_str(&format!(" Size: {} bytes\n", self.size));
s.push_str(&format!(" Thread: {}\n", self.thread_id));
s.push_str(&format!(" Description: {}\n", self.description));
if self.origin != MSAN_ORIGIN_CLEAN {
s.push_str(&format!(" Origin ID: {}\n", self.origin));
if let Some(registry) = origin_registry {
s.push_str(®istry.format_chain(self.origin));
}
}
s.push_str(" Stack trace:\n");
s.push_str(&self.stack.format());
s.push_str("===================================================\n");
s
}
}
#[derive(Debug, Clone)]
pub struct TSanErrorReport {
pub kind: TSanErrorKind,
pub threads: Vec<u32>,
pub addresses: Vec<u64>,
pub stacks: Vec<MTSanStackTrace>,
pub mutexes: Vec<u64>,
pub description: String,
pub is_fatal: bool,
pub suppression_signature: u64,
}
impl TSanErrorReport {
pub fn new(kind: TSanErrorKind, description: impl Into<String>) -> Self {
Self {
kind,
threads: Vec::new(),
addresses: Vec::new(),
stacks: Vec::new(),
mutexes: Vec::new(),
description: description.into(),
is_fatal: false,
suppression_signature: 0,
}
}
pub fn format(&self) -> String {
let mut s = String::new();
s.push_str("==================\n");
s.push_str(&format!(
"WARNING: ThreadSanitizer: {} ({})\n",
self.kind, self.description
));
if !self.threads.is_empty() {
s.push_str(&format!(" Threads involved: {:?}\n", self.threads));
}
if !self.addresses.is_empty() {
for (i, addr) in self.addresses.iter().enumerate() {
s.push_str(&format!(" Address {}: 0x{:016x}\n", i, addr));
}
}
for (i, stack) in self.stacks.iter().enumerate() {
s.push_str(&format!(" Stack {}:\n", i));
s.push_str(&stack.format());
}
s.push_str("==================\n");
s
}
pub fn data_race(
addr: u64,
size: usize,
thread1: u32,
thread2: u32,
is_write1: bool,
is_write2: bool,
stack1: MTSanStackTrace,
stack2: MTSanStackTrace,
) -> Self {
let desc = format!(
"data race on {} bytes at 0x{:016x} between thread {} ({}) and thread {} ({})",
size,
addr,
thread1,
if is_write1 { "write" } else { "read" },
thread2,
if is_write2 { "write" } else { "read" },
);
Self {
kind: TSanErrorKind::DataRace,
threads: vec![thread1, thread2],
addresses: vec![addr],
stacks: vec![stack1, stack2],
mutexes: Vec::new(),
description: desc,
is_fatal: false,
suppression_signature: 0,
}
}
pub fn deadlock(cycle: Vec<u32>, stacks: Vec<MTSanStackTrace>) -> Self {
Self {
kind: TSanErrorKind::MutexDeadlock,
threads: cycle.clone(),
addresses: Vec::new(),
stacks,
mutexes: Vec::new(),
description: format!("deadlock detected involving threads {:?}", cycle),
is_fatal: true,
suppression_signature: 0,
}
}
pub fn lock_order_inversion(
thread1: u32,
thread2: u32,
mutex_a: u64,
mutex_b: u64,
stack1: MTSanStackTrace,
stack2: MTSanStackTrace,
) -> Self {
Self {
kind: TSanErrorKind::LockOrderInversion,
threads: vec![thread1, thread2],
addresses: Vec::new(),
stacks: vec![stack1, stack2],
mutexes: vec![mutex_a, mutex_b],
description: format!(
"lock order inversion: thread {} acquired mutex {} then {}, \
thread {} acquired mutex {} then {}",
thread1, mutex_a, mutex_b, thread2, mutex_b, mutex_a
),
is_fatal: false,
suppression_signature: 0,
}
}
pub fn thread_leak(thread_id: u32, stack: MTSanStackTrace) -> Self {
Self {
kind: TSanErrorKind::ThreadLeak,
threads: vec![thread_id],
addresses: Vec::new(),
stacks: vec![stack],
mutexes: Vec::new(),
description: format!("thread {} was created but never joined", thread_id),
is_fatal: false,
suppression_signature: 0,
}
}
pub fn signal_unsafe_call(
thread_id: u32,
function: &str,
signal: i32,
stack: MTSanStackTrace,
) -> Self {
Self {
kind: TSanErrorKind::SignalUnsafeCall,
threads: vec![thread_id],
addresses: Vec::new(),
stacks: vec![stack],
mutexes: Vec::new(),
description: format!(
"signal-unsafe function '{}' called from signal {} handler in thread {}",
function, signal, thread_id
),
is_fatal: false,
suppression_signature: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct TSanSuppression {
pub name: String,
pub suppression_type: String,
pub fun_pattern: Option<String>,
pub src_pattern: Option<String>,
pub module_pattern: Option<String>,
}
impl TSanSuppression {
pub fn matches(&self, report: &TSanErrorReport) -> bool {
let type_matches = self.suppression_type == "*"
|| self.suppression_type == format!("{:?}", report.kind).to_lowercase();
if !type_matches {
return false;
}
let mut any_match = false;
for stack in &report.stacks {
for frame in &stack.frames {
let fun_match = self.fun_pattern.as_ref().map_or(true, |pat| {
frame.symbol.as_ref().map_or(false, |s| s.contains(pat))
});
let src_match = self.src_pattern.as_ref().map_or(true, |pat| {
frame.file.as_ref().map_or(false, |f| f.contains(pat))
});
let mod_match = self
.module_pattern
.as_ref()
.map_or(true, |pat| frame.module.contains(pat));
if fun_match && src_match && mod_match {
any_match = true;
break;
}
}
}
any_match
}
}
#[derive(Debug)]
pub struct TSanSuppressions {
pub entries: Vec<TSanSuppression>,
}
impl TSanSuppressions {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn load_from_file(path: &str) -> std::io::Result<Self> {
let content = std::fs::read_to_string(path)?;
Ok(Self::parse(&content))
}
pub fn parse(content: &str) -> Self {
let mut entries = Vec::new();
let mut current: Option<TSanSuppression> = None;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some(colon_pos) = trimmed.find(':') {
let (key, value) = trimmed.split_at(colon_pos);
let value = value[1..].trim();
match key {
"race" | "deadlock" | "thread_leak" | "signal" | "mutex" => {
if let Some(entry) = current.take() {
entries.push(entry);
}
current = Some(TSanSuppression {
name: value.to_string(),
suppression_type: key.to_string(),
fun_pattern: None,
src_pattern: None,
module_pattern: None,
});
}
"fun" => {
if let Some(ref mut entry) = current {
entry.fun_pattern = Some(value.to_string());
}
}
"src" => {
if let Some(ref mut entry) = current {
entry.src_pattern = Some(value.to_string());
}
}
"module" => {
if let Some(ref mut entry) = current {
entry.module_pattern = Some(value.to_string());
}
}
_ => {}
}
}
}
if let Some(entry) = current {
entries.push(entry);
}
Self { entries }
}
pub fn is_suppressed(&self, report: &TSanErrorReport) -> bool {
self.entries.iter().any(|supp| supp.matches(report))
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for TSanSuppressions {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)]
pub struct X86MTSanFull {
pub msan_shadow: MSanShadowMemory,
pub origin_registry: OriginRegistry,
pub msan_interceptors: MSanInterceptors,
pub msan_signal_ctx: MSanSignalContext,
pub msan_tls: MSanTLSShadow,
pub msan_propagator: MSanShadowPropagator,
pub tsan_threads: HashMap<u32, TSanThreadState>,
pub tsan_shadow: HashMap<u64, TSanShadowCell>,
pub tsan_mutexes: HashMap<u64, TSanMutexState>,
pub tsan_global_tick: AtomicU64,
pub tsan_next_thread_id: AtomicU32,
pub tsan_next_mutex_id: AtomicU64,
pub tsan_deadlock_detector: TSanDeadlockDetector,
pub tsan_signal_safety: TSanSignalSafety,
pub msan_reports: Vec<MSanErrorReport>,
pub tsan_reports: Vec<TSanErrorReport>,
pub suppressions: TSanSuppressions,
pub flags: MTSanFlags,
pub initialized: bool,
pub msan_access_count: usize,
pub msan_propagation_count: usize,
pub tsan_event_count: usize,
pub tsan_lock_ops: usize,
pub tsan_race_count: usize,
pub subtarget: Option<X86Subtarget>,
pub calling_convention: Option<X86CallingConvention>,
pub register_info: Option<X86RegisterInfo>,
}
impl X86MTSanFull {
pub fn new() -> Self {
let flags = MTSanFlags::from_env();
let origins_enabled = flags.msan_options.track_origins > 0;
Self {
msan_shadow: MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, origins_enabled),
origin_registry: OriginRegistry::new(),
msan_interceptors: MSanInterceptors::new(),
msan_signal_ctx: MSanSignalContext::new(),
msan_tls: MSanTLSShadow::new(),
msan_propagator: MSanShadowPropagator,
tsan_threads: HashMap::new(),
tsan_shadow: HashMap::new(),
tsan_mutexes: HashMap::new(),
tsan_global_tick: AtomicU64::new(0),
tsan_next_thread_id: AtomicU32::new(1),
tsan_next_mutex_id: AtomicU64::new(1),
tsan_deadlock_detector: TSanDeadlockDetector::new(),
tsan_signal_safety: TSanSignalSafety::new(),
msan_reports: Vec::new(),
tsan_reports: Vec::new(),
suppressions: TSanSuppressions::new(),
flags,
initialized: false,
msan_access_count: 0,
msan_propagation_count: 0,
tsan_event_count: 0,
tsan_lock_ops: 0,
tsan_race_count: 0,
subtarget: None,
calling_convention: None,
register_info: None,
}
}
pub fn init_with_subtarget(&mut self, subtarget: &X86Subtarget) {
self.subtarget = Some(subtarget.clone());
self.calling_convention = Some(X86CallingConvention::default());
self.register_info = Some(X86RegisterInfo);
self.initialized = true;
}
pub fn initialize(&mut self) {
self.flags = MTSanFlags::from_env();
let origins_enabled = self.flags.msan_options.track_origins > 0;
self.msan_shadow.track_origins = origins_enabled;
self.msan_interceptors.poison_in_free = self.flags.msan_options.poison_in_free;
self.initialized = true;
if let Some(ref path) = self.flags.tsan_options.suppressions {
if let Ok(supps) = TSanSuppressions::load_from_file(path) {
self.suppressions = supps;
}
}
}
pub fn msan_check_read(
&mut self,
addr: u64,
size: usize,
thread_id: u32,
) -> Option<MSanErrorReport> {
self.msan_access_count += 1;
let shadow = (0..size)
.map(|i| self.msan_shadow.get_shadow(addr + i as u64))
.collect::<Vec<_>>();
if shadow.iter().any(|&s| s != MSAN_SHADOW_INIT) {
let (first_addr, _first_shadow, first_origin) = self
.msan_shadow
.find_first_uninit(addr, size)
.unwrap_or((addr, MSAN_SHADOW_UNINIT, MSAN_ORIGIN_CLEAN));
let report = MSanErrorReport::new(
MSanErrorKind::UseOfUninitializedValue,
first_addr,
size,
first_origin,
MTSanStackTrace::capture(self.flags.msan_options.verbosity as usize + 4),
thread_id,
format!(
"read of {} byte(s) at 0x{:016x} from uninitialized memory",
size, addr
),
);
self.msan_reports.push(report.clone());
Some(report)
} else {
None
}
}
pub fn msan_check_write(&mut self, addr: u64, shadow_bytes: &[u8], origin: u32) {
self.msan_access_count += 1;
MSanShadowPropagator::propagate_store(&mut self.msan_shadow, addr, shadow_bytes);
if self.msan_shadow.track_origins && origin != MSAN_ORIGIN_CLEAN {
self.msan_shadow
.set_origin_range(addr, shadow_bytes.len(), origin);
}
}
pub fn msan_check_branch(&mut self, shadow: u8, thread_id: u32) -> Option<MSanErrorReport> {
if shadow != MSAN_SHADOW_INIT {
let report = MSanErrorReport::new(
MSanErrorKind::UninitializedBranch,
0,
1,
MSAN_ORIGIN_CLEAN,
MTSanStackTrace::capture(4),
thread_id,
"branch on uninitialized value",
);
self.msan_reports.push(report.clone());
Some(report)
} else {
None
}
}
pub fn msan_set_shadow(&mut self, addr: u64, size: usize, value: u8) {
self.msan_shadow.set_shadow_range(addr, size, value);
}
pub fn msan_get_shadow(&self, addr: u64) -> u8 {
self.msan_shadow.get_shadow(addr)
}
pub fn msan_clear_shadow(&mut self, addr: u64, size: usize) {
self.msan_shadow.clear_shadow_range(addr, size);
}
pub fn msan_set_origin(&mut self, addr: u64, size: usize, origin: u32) {
self.msan_shadow.set_origin_range(addr, size, origin);
}
pub fn msan_allocate_origin(
&mut self,
prev_id: u32,
description: impl Into<String>,
kind: OriginKind,
) -> u32 {
self.origin_registry.allocate_origin(
prev_id,
description,
kind,
MTSanStackTrace::capture(4),
)
}
pub fn msan_propagate_binary(&mut self, opcode: &str, a_shadow: u8, b_shadow: u8) -> u8 {
self.msan_propagation_count += 1;
match opcode {
"add" | "sub" | "fadd" | "fsub" => {
MSanShadowPropagator::propagate_add_sub(a_shadow, b_shadow)
}
"mul" | "fmul" => MSanShadowPropagator::propagate_mul(a_shadow, b_shadow),
"udiv" | "sdiv" | "fdiv" => MSanShadowPropagator::propagate_div(a_shadow, b_shadow),
"urem" | "srem" | "frem" => MSanShadowPropagator::propagate_rem(a_shadow, b_shadow),
"and" => MSanShadowPropagator::propagate_and(a_shadow, b_shadow),
"or" => MSanShadowPropagator::propagate_or(a_shadow, b_shadow),
"xor" => MSanShadowPropagator::propagate_xor(a_shadow, b_shadow),
"shl" => MSanShadowPropagator::propagate_shl(a_shadow, b_shadow),
"lshr" => MSanShadowPropagator::propagate_lshr(a_shadow, b_shadow),
"ashr" => MSanShadowPropagator::propagate_ashr(a_shadow, b_shadow),
_ => {
a_shadow | b_shadow
}
}
}
pub fn msan_propagate_cmp(&mut self, a_shadow: u8, b_shadow: u8) -> u8 {
self.msan_propagation_count += 1;
MSanShadowPropagator::propagate_cmp(a_shadow, b_shadow)
}
pub fn msan_propagate_select(&mut self, cond_shadow: u8, t_shadow: u8, f_shadow: u8) -> u8 {
self.msan_propagation_count += 1;
MSanShadowPropagator::propagate_select(cond_shadow, t_shadow, f_shadow)
}
pub fn msan_intercept_malloc(&mut self, size: usize) -> u64 {
let track = self.msan_shadow.track_origins;
MSanInterceptors::intercept_malloc(
&mut self.msan_shadow,
&mut self.origin_registry,
size,
track,
)
}
pub fn msan_intercept_free(&mut self, ptr: u64, size: usize) {
let poison = self.flags.msan_options.poison_in_free;
let track = self.msan_shadow.track_origins;
MSanInterceptors::intercept_free(
&mut self.msan_shadow,
&mut self.origin_registry,
ptr,
size,
poison,
track,
)
}
pub fn msan_intercept_memcpy(&mut self, dst: u64, src: u64, size: usize) {
MSanInterceptors::intercept_memcpy(&mut self.msan_shadow, dst, src, size);
}
pub fn msan_intercept_memmove(&mut self, dst: u64, src: u64, size: usize) {
MSanInterceptors::intercept_memmove(&mut self.msan_shadow, dst, src, size);
}
pub fn msan_intercept_memset(&mut self, dst: u64, size: usize) {
MSanInterceptors::intercept_memset(&mut self.msan_shadow, dst, size);
}
pub fn msan_intercept_strcpy(&mut self, dst: u64, src: u64) {
MSanInterceptors::intercept_strcpy(&mut self.msan_shadow, dst, src);
}
pub fn msan_intercept_strncpy(&mut self, dst: u64, src: u64, n: usize) {
MSanInterceptors::intercept_strncpy(&mut self.msan_shadow, dst, src, n);
}
pub fn msan_signal_enter(&mut self, signum: i32) {
self.msan_signal_ctx.save(&self.msan_shadow, signum);
}
pub fn msan_signal_exit(&mut self) {
self.msan_signal_ctx.restore(&mut self.msan_shadow);
}
pub fn msan_tls_init(&mut self, tls_size: usize) {
self.msan_tls.init(tls_size);
}
pub fn msan_tls_get_shadow(&self, offset: u64) -> u8 {
self.msan_tls.get_tls_shadow(offset)
}
pub fn msan_tls_set_shadow(&mut self, offset: u64, value: u8) {
self.msan_tls.set_tls_shadow(offset, value);
}
pub fn tsan_thread_create(&mut self) -> u32 {
let thread_id = self.tsan_next_thread_id.fetch_add(1, Ordering::Relaxed);
let state = TSanThreadState::new(thread_id, MTSanStackTrace::capture(4));
self.tsan_threads.insert(thread_id, state);
self.tsan_event_count += 1;
thread_id
}
pub fn tsan_thread_join(&mut self, joiner_id: u32, joinee_id: u32) {
if let Some(joinee) = self.tsan_threads.get(&joinee_id) {
let release_clock = joinee.release();
if let Some(joiner) = self.tsan_threads.get_mut(&joiner_id) {
joiner.acquire(&release_clock);
}
}
if let Some(joinee) = self.tsan_threads.get_mut(&joinee_id) {
joinee.is_joined = true;
}
self.tsan_event_count += 1;
}
pub fn tsan_record_access(
&mut self,
thread_id: u32,
addr: u64,
size: usize,
is_write: bool,
is_atomic: bool,
atomic_ordering: Option<TSanAtomicOrdering>,
) -> Option<TSanRaceInfo> {
self.tsan_event_count += 1;
let cell_key = addr / TSAN_SHADOW_GRANULARITY as u64;
let history_size = self.flags.tsan_options.history_size;
let cell = self
.tsan_shadow
.entry(cell_key)
.or_insert_with(|| TSanShadowCell::new(history_size));
let thread_clock = if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
thread.clock.clone()
} else {
return None; };
let stack = MTSanStackTrace::capture(4);
let race_info = cell.record_access(
thread_id,
&thread_clock,
is_write,
size,
is_atomic,
atomic_ordering,
stack.clone(),
);
if let Some(ref race) = race_info {
let stack1 = MTSanStackTrace::capture(4);
let stack2 = stack;
let report = TSanErrorReport::data_race(
addr,
size,
race.thread1,
race.thread2,
race.is_write1,
race.is_write2,
stack1,
stack2,
);
if !self.suppressions.is_suppressed(&report) {
self.tsan_race_count += 1;
self.tsan_reports.push(report);
}
}
race_info
}
pub fn tsan_load(&mut self, thread_id: u32, addr: u64, size: usize) -> Option<TSanRaceInfo> {
self.tsan_record_access(thread_id, addr, size, false, false, None)
}
pub fn tsan_store(&mut self, thread_id: u32, addr: u64, size: usize) -> Option<TSanRaceInfo> {
self.tsan_record_access(thread_id, addr, size, true, false, None)
}
pub fn tsan_atomic_load(
&mut self,
thread_id: u32,
addr: u64,
size: usize,
ordering: TSanAtomicOrdering,
) -> Option<TSanRaceInfo> {
self.tsan_record_access(thread_id, addr, size, false, true, Some(ordering))
}
pub fn tsan_atomic_store(
&mut self,
thread_id: u32,
addr: u64,
size: usize,
ordering: TSanAtomicOrdering,
) -> Option<TSanRaceInfo> {
if ordering == TSanAtomicOrdering::Release
|| ordering == TSanAtomicOrdering::AcquireRelease
|| ordering == TSanAtomicOrdering::SequentiallyConsistent
{
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
}
}
self.tsan_record_access(thread_id, addr, size, true, true, Some(ordering))
}
pub fn tsan_atomic_rmw(
&mut self,
thread_id: u32,
addr: u64,
size: usize,
ordering: TSanAtomicOrdering,
) -> Option<TSanRaceInfo> {
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
}
self.tsan_record_access(thread_id, addr, size, true, true, Some(ordering))
}
pub fn tsan_atomic_fence(&mut self, thread_id: u32, ordering: TSanAtomicOrdering) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
if ordering == TSanAtomicOrdering::SequentiallyConsistent {
thread.tick();
let clock = thread.clock.clone();
for (tid, other_thread) in self.tsan_threads.iter_mut() {
if *tid != thread_id {
other_thread.acquire(&clock);
other_thread.tick();
}
}
let tids: Vec<u32> = self.tsan_threads.keys().copied().collect();
for tid in tids {
if tid != thread_id {
let other_clock = {
let other_thread = &self.tsan_threads[&tid];
other_thread.release()
};
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&other_clock);
}
}
}
} else if ordering == TSanAtomicOrdering::Release {
thread.tick();
} else if ordering == TSanAtomicOrdering::Acquire {
thread.tick();
}
}
}
pub fn tsan_mutex_create(&mut self) -> u64 {
let mutex_id = self.tsan_next_mutex_id.fetch_add(1, Ordering::Relaxed);
self.tsan_mutexes.insert(
mutex_id,
TSanMutexState::new(mutex_id, MTSanStackTrace::capture(4)),
);
mutex_id
}
pub fn tsan_mutex_lock(&mut self, thread_id: u32, mutex_id: u64) -> Option<TSanErrorReport> {
self.tsan_lock_ops += 1;
self.tsan_event_count += 1;
let mut error = None;
if self.flags.tsan_options.detect_deadlocks {
if let Some(ref deadlock_cycle) =
self.tsan_deadlock_detector.try_acquire(thread_id, mutex_id)
{
let cycle = deadlock_cycle.clone();
let stacks: Vec<_> = cycle.iter().map(|_| MTSanStackTrace::capture(6)).collect();
let report = TSanErrorReport::deadlock(cycle, stacks);
self.tsan_reports.push(report.clone());
error = Some(report);
}
}
if self.flags.tsan_options.detect_deadlocks {
if let Some((held, new)) = self
.tsan_deadlock_detector
.check_lock_order(thread_id, mutex_id)
{
for (&other_tid, order) in &self.tsan_deadlock_detector.lock_orders {
if other_tid != thread_id {
let new_pos = order.iter().position(|&m| m == new);
let held_pos = order.iter().position(|&m| m == held);
if let (Some(np), Some(hp)) = (new_pos, held_pos) {
if np < hp {
let report = TSanErrorReport::lock_order_inversion(
thread_id,
other_tid,
held,
new,
MTSanStackTrace::capture(4),
MTSanStackTrace::capture(4),
);
self.tsan_reports.push(report.clone());
error = Some(report);
break;
}
}
}
}
}
}
if let Some(mutex) = self.tsan_mutexes.get(&mutex_id) {
if !mutex.is_destroyed {
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(mutex_id);
}
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&mutex_id) {
if mutex.owner_thread == thread_id && mutex.lock_count > 0 && !mutex.is_read_lock {
let report = TSanErrorReport::new(
TSanErrorKind::DoubleLock,
format!("mutex {} double-locked by thread {}", mutex_id, thread_id),
);
self.tsan_reports.push(report.clone());
if error.is_none() {
error = Some(report);
}
}
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
mutex.is_read_lock = false;
mutex.lock_history.push(TSanMutexLockRecord {
thread_id,
stack: MTSanStackTrace::capture(4),
clock: self
.tsan_threads
.get(&thread_id)
.map(|t| t.clock.clone())
.unwrap_or_default(),
});
}
self.tsan_deadlock_detector.acquired(thread_id, mutex_id);
error
}
pub fn tsan_mutex_unlock(&mut self, thread_id: u32, mutex_id: u64) -> Option<TSanErrorReport> {
self.tsan_lock_ops += 1;
self.tsan_event_count += 1;
let mut error = None;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
thread.held_mutexes.retain(|&m| m != mutex_id);
}
if let Some(thread) = self.tsan_threads.get(&thread_id) {
if let Some(mutex) = self.tsan_mutexes.get_mut(&mutex_id) {
if mutex.owner_thread != thread_id {
let report = TSanErrorReport::new(
TSanErrorKind::UnlockNotOwned,
format!(
"mutex {} unlocked by thread {}, owned by thread {}",
mutex_id, thread_id, mutex.owner_thread
),
);
self.tsan_reports.push(report.clone());
error = Some(report);
}
mutex.release_clock = thread.release();
if mutex.lock_count > 0 {
mutex.lock_count -= 1;
}
if mutex.lock_count == 0 {
mutex.owner_thread = 0;
}
}
}
self.tsan_deadlock_detector.released(thread_id, mutex_id);
error
}
pub fn tsan_mutex_read_lock(
&mut self,
thread_id: u32,
mutex_id: u64,
) -> Option<TSanErrorReport> {
self.tsan_lock_ops += 1;
if let Some(mutex) = self.tsan_mutexes.get(&mutex_id) {
if !mutex.is_destroyed {
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(mutex_id);
}
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&mutex_id) {
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
mutex.is_read_lock = true;
}
self.tsan_deadlock_detector.acquired(thread_id, mutex_id);
None
}
pub fn tsan_mutex_destroy(&mut self, mutex_id: u64) -> Option<TSanErrorReport> {
if let Some(mutex) = self.tsan_mutexes.get(&mutex_id) {
if mutex.lock_count > 0 {
let report = TSanErrorReport::new(
TSanErrorKind::DestroyLockedMutex,
format!("destroying locked mutex {}", mutex_id),
);
self.tsan_reports.push(report.clone());
return Some(report);
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&mutex_id) {
mutex.is_destroyed = true;
}
None
}
pub fn tsan_cond_wait(&mut self, thread_id: u32, cond_id: u64, mutex_id: u64) {
self.tsan_event_count += 1;
self.tsan_mutex_unlock(thread_id, mutex_id);
self.tsan_mutex_lock(thread_id, mutex_id);
let _ = cond_id;
}
pub fn tsan_cond_signal(&mut self, thread_id: u32, cond_id: u64) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
}
let _ = cond_id;
}
pub fn tsan_cond_broadcast(&mut self, thread_id: u32, cond_id: u64) {
self.tsan_cond_signal(thread_id, cond_id);
}
pub fn tsan_barrier_wait(&mut self, thread_id: u32, barrier_id: u64) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
}
let _ = barrier_id;
}
pub fn tsan_sem_wait(&mut self, thread_id: u32, sem_id: u64) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
}
let _ = sem_id;
}
pub fn tsan_sem_post(&mut self, thread_id: u32, sem_id: u64) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
}
let _ = sem_id;
}
pub fn tsan_signal_enter(&mut self, thread_id: u32, signum: i32) -> Option<TSanErrorReport> {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.in_signal_handler = true;
thread.signal_number = Some(signum);
thread.tick();
}
None
}
pub fn tsan_signal_exit(&mut self, thread_id: u32) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.in_signal_handler = false;
thread.signal_number = None;
thread.tick();
}
}
pub fn tsan_check_signal_safety(
&mut self,
thread_id: u32,
function_name: &str,
) -> Option<TSanErrorReport> {
if !self.flags.tsan_options.check_signal_safety {
return None;
}
if !self.tsan_signal_safety.is_signal_safe(function_name) {
let signal = self
.tsan_threads
.get(&thread_id)
.and_then(|t| t.signal_number)
.unwrap_or(0);
let report = TSanErrorReport::signal_unsafe_call(
thread_id,
function_name,
signal,
MTSanStackTrace::capture(4),
);
self.tsan_reports.push(report.clone());
Some(report)
} else {
None
}
}
pub fn combined_load_check(
&mut self,
thread_id: u32,
addr: u64,
size: usize,
) -> (Option<MSanErrorReport>, Option<TSanRaceInfo>) {
let msan_result = if self.flags.msan_enabled {
self.msan_check_read(addr, size, thread_id)
} else {
None
};
let tsan_result = if self.flags.tsan_enabled {
self.tsan_load(thread_id, addr, size)
} else {
None
};
(msan_result, tsan_result)
}
pub fn combined_store_check(
&mut self,
thread_id: u32,
addr: u64,
shadow_bytes: &[u8],
origin: u32,
) -> Option<TSanRaceInfo> {
if self.flags.msan_enabled {
self.msan_check_write(addr, shadow_bytes, origin);
}
if self.flags.tsan_enabled {
self.tsan_store(thread_id, addr, shadow_bytes.len())
} else {
None
}
}
pub fn combined_pthread_create(
&mut self,
creator_thread_id: u32,
) -> (u32, Option<TSanErrorReport>) {
let child_id = self.tsan_thread_create();
if let Some(creator) = self.tsan_threads.get_mut(&creator_thread_id) {
creator.tick();
let release_clock = creator.release();
if let Some(child) = self.tsan_threads.get_mut(&child_id) {
child.acquire(&release_clock);
}
}
(child_id, None)
}
pub fn combined_pthread_join(
&mut self,
joiner_id: u32,
joinee_id: u32,
) -> Option<TSanErrorReport> {
self.tsan_thread_join(joiner_id, joinee_id);
None
}
pub fn combined_mutex_lock(
&mut self,
thread_id: u32,
mutex_id: u64,
) -> Option<TSanErrorReport> {
if let Some(thread) = self.tsan_threads.get(&thread_id) {
if thread.in_signal_handler {
return self.tsan_check_signal_safety(thread_id, "pthread_mutex_lock");
}
}
self.tsan_mutex_lock(thread_id, mutex_id)
}
pub fn combined_mutex_unlock(
&mut self,
thread_id: u32,
mutex_id: u64,
) -> Option<TSanErrorReport> {
self.tsan_mutex_unlock(thread_id, mutex_id)
}
pub fn combined_cond_wait(&mut self, thread_id: u32, cond_id: u64, mutex_id: u64) {
self.tsan_cond_wait(thread_id, cond_id, mutex_id);
}
pub fn combined_cond_signal(&mut self, thread_id: u32, cond_id: u64) {
self.tsan_cond_signal(thread_id, cond_id);
}
pub fn combined_barrier_wait(&mut self, thread_id: u32, barrier_id: u64) {
self.tsan_barrier_wait(thread_id, barrier_id);
}
pub fn combined_sem_wait(&mut self, thread_id: u32, sem_id: u64) {
self.tsan_sem_wait(thread_id, sem_id);
}
pub fn combined_sem_post(&mut self, thread_id: u32, sem_id: u64) {
self.tsan_sem_post(thread_id, sem_id);
}
pub fn msan_errors(&self) -> &[MSanErrorReport] {
&self.msan_reports
}
pub fn tsan_errors(&self) -> &[TSanErrorReport] {
&self.tsan_reports
}
pub fn total_errors(&self) -> usize {
self.msan_reports.len() + self.tsan_reports.len()
}
pub fn has_errors(&self) -> bool {
!self.msan_reports.is_empty() || !self.tsan_reports.is_empty()
}
pub fn print_summary(&self) -> String {
let mut s = String::new();
s.push_str("========================================\n");
s.push_str("X86 MTSan Full Runtime Summary\n");
s.push_str("========================================\n");
s.push_str(&format!("MSan enabled: {}\n", self.flags.msan_enabled));
s.push_str(&format!("TSan enabled: {}\n", self.flags.tsan_enabled));
s.push_str(&format!(
"Origin tracking: {}\n",
self.msan_shadow.track_origins
));
s.push_str("\n--- MSan Statistics ---\n");
s.push_str(&format!(" Accesses tracked: {}\n", self.msan_access_count));
s.push_str(&format!(
" Propagation ops: {}\n",
self.msan_propagation_count
));
s.push_str(&format!(
" Shadow bytes: {}\n",
self.msan_shadow.total_shadow_bytes
));
s.push_str(&format!(
" Origin entries: {}\n",
self.origin_registry.len()
));
s.push_str(&format!(" Errors: {}\n", self.msan_reports.len()));
s.push_str("\n--- TSan Statistics ---\n");
s.push_str(&format!(" Threads tracked: {}\n", self.tsan_threads.len()));
s.push_str(&format!(" Events processed: {}\n", self.tsan_event_count));
s.push_str(&format!(" Lock operations: {}\n", self.tsan_lock_ops));
s.push_str(&format!(" Mutexes tracked: {}\n", self.tsan_mutexes.len()));
s.push_str(&format!(" Races detected: {}\n", self.tsan_race_count));
s.push_str(&format!(" Errors: {}\n", self.tsan_reports.len()));
s.push_str(&format!(
" Suppressions loaded: {}\n",
self.suppressions.len()
));
if self.has_errors() {
s.push_str("\n--- Error Reports ---\n");
for report in &self.msan_reports {
s.push_str(&report.format(Some(&self.origin_registry)));
}
for report in &self.tsan_reports {
s.push_str(&report.format());
}
}
s.push_str("========================================\n");
s
}
pub fn stats(&self) -> MTSanStats {
MTSanStats {
msan_access_count: self.msan_access_count,
msan_propagation_count: self.msan_propagation_count,
msan_shadow_bytes: self.msan_shadow.total_shadow_bytes,
msan_origin_entries: self.origin_registry.len(),
msan_error_count: self.msan_reports.len(),
tsan_thread_count: self.tsan_threads.len(),
tsan_event_count: self.tsan_event_count,
tsan_lock_ops: self.tsan_lock_ops,
tsan_mutex_count: self.tsan_mutexes.len(),
tsan_race_count: self.tsan_race_count,
tsan_error_count: self.tsan_reports.len(),
initialized: self.initialized,
}
}
pub fn check_thread_leaks(&self) -> Vec<TSanErrorReport> {
if !self.flags.tsan_options.report_thread_leaks {
return Vec::new();
}
let mut leaks = Vec::new();
for (&tid, thread) in &self.tsan_threads {
if !thread.is_joined && !thread.is_detached && thread.is_tracked && tid != 0 {
let report = TSanErrorReport::thread_leak(tid, thread.creation_stack.clone());
leaks.push(report);
}
}
leaks
}
pub fn finalize(&mut self) {
let leaks = self.check_thread_leaks();
for leak in leaks {
self.tsan_reports.push(leak);
}
if self.flags.msan_options.print_stats {
eprintln!("{}", self.print_summary());
}
}
}
impl Default for X86MTSanFull {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct MTSanStats {
pub msan_access_count: usize,
pub msan_propagation_count: usize,
pub msan_shadow_bytes: usize,
pub msan_origin_entries: usize,
pub msan_error_count: usize,
pub tsan_thread_count: usize,
pub tsan_event_count: usize,
pub tsan_lock_ops: usize,
pub tsan_mutex_count: usize,
pub tsan_race_count: usize,
pub tsan_error_count: usize,
pub initialized: bool,
}
impl fmt::Display for MTSanStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"MTSanStats {{ msan_access={}, msan_prop={}, msan_errors={}, \
tsan_events={}, tsan_races={}, tsan_errors={}, initialized={} }}",
self.msan_access_count,
self.msan_propagation_count,
self.msan_error_count,
self.tsan_event_count,
self.tsan_race_count,
self.tsan_error_count,
self.initialized,
)
}
}
impl X86MTSanFull {
pub fn msan_intercept_strcat(&mut self, dst: u64, src: u64) {
let mut dst_end = dst;
loop {
dst_end += 1;
if dst_end > dst + 65536 {
break;
}
}
MSanInterceptors::intercept_strcpy(&mut self.msan_shadow, dst_end, src);
}
pub fn msan_intercept_strncat(&mut self, dst: u64, src: u64, n: usize) {
let mut dst_end = dst;
loop {
dst_end += 1;
if dst_end > dst + 65536 {
break;
}
}
for i in 0..n {
let s = self.msan_shadow.get_shadow(src + i as u64);
self.msan_shadow.set_shadow(dst_end + i as u64, s);
}
self.msan_shadow
.set_shadow(dst_end + n as u64, MSAN_SHADOW_INIT);
}
pub fn msan_intercept_strcmp(&self, s1: u64, s2: u64) -> (bool, bool) {
let mut s1_init = true;
let mut s2_init = true;
for i in 0..65536u64 {
if self.msan_shadow.get_shadow(s1 + i) != MSAN_SHADOW_INIT {
s1_init = false;
}
if self.msan_shadow.get_shadow(s2 + i) != MSAN_SHADOW_INIT {
s2_init = false;
}
if i > 65534 {
break;
}
}
(s1_init, s2_init)
}
pub fn msan_intercept_strncmp(&self, s1: u64, s2: u64, n: usize) -> (bool, bool) {
let mut s1_init = true;
let mut s2_init = true;
for i in 0..n {
if self.msan_shadow.get_shadow(s1 + i as u64) != MSAN_SHADOW_INIT {
s1_init = false;
}
if self.msan_shadow.get_shadow(s2 + i as u64) != MSAN_SHADOW_INIT {
s2_init = false;
}
}
(s1_init, s2_init)
}
pub fn msan_intercept_memchr(&self, ptr: u64, n: usize) -> bool {
for i in 0..n {
if self.msan_shadow.get_shadow(ptr + i as u64) != MSAN_SHADOW_INIT {
return false;
}
}
true
}
pub fn msan_intercept_strchr(&self, s: u64) -> bool {
for i in 0..65536u64 {
if self.msan_shadow.get_shadow(s + i) != MSAN_SHADOW_INIT {
return false;
}
}
true
}
pub fn msan_intercept_strrchr(&self, s: u64) -> bool {
self.msan_intercept_strchr(s)
}
pub fn msan_intercept_strstr(&self, haystack: u64, needle: u64) -> (bool, bool) {
let (h_init, n_init) = self.msan_intercept_strcmp(haystack, needle);
(h_init, n_init)
}
pub fn msan_intercept_atoi(&self, s: u64) -> bool {
self.msan_intercept_strchr(s)
}
pub fn msan_intercept_strtol(&mut self, s: u64, endptr: u64) -> bool {
let s_init = self.msan_intercept_strchr(s);
if endptr != 0 {
self.msan_shadow.clear_shadow_range(endptr, 8);
}
s_init
}
pub fn msan_intercept_strtod(&mut self, s: u64, endptr: u64) -> bool {
self.msan_intercept_strtol(s, endptr)
}
pub fn msan_intercept_bcmp(&self, s1: u64, s2: u64, n: usize) -> (bool, bool) {
let mut s1_init = true;
let mut s2_init = true;
for i in 0..n {
if self.msan_shadow.get_shadow(s1 + i as u64) != MSAN_SHADOW_INIT {
s1_init = false;
}
if self.msan_shadow.get_shadow(s2 + i as u64) != MSAN_SHADOW_INIT {
s2_init = false;
}
}
(s1_init, s2_init)
}
pub fn msan_intercept_bcopy(&mut self, src: u64, dst: u64, n: usize) {
MSanInterceptors::intercept_memcpy(&mut self.msan_shadow, dst, src, n);
}
pub fn msan_intercept_bzero(&mut self, dst: u64, n: usize) {
self.msan_shadow.clear_shadow_range(dst, n);
}
}
#[derive(Debug)]
pub struct MSanMappingTracker {
pub mappings: Vec<MSanMemoryMapping>,
pub total_mapped: usize,
}
#[derive(Debug, Clone)]
pub struct MSanMemoryMapping {
pub start: u64,
pub size: usize,
pub is_initialized: bool,
pub origin: u32,
pub prot: u32,
pub flags: u32,
}
impl MSanMappingTracker {
pub fn new() -> Self {
Self {
mappings: Vec::new(),
total_mapped: 0,
}
}
pub fn add_mapping(&mut self, start: u64, size: usize, prot: u32, flags: u32) {
self.mappings.push(MSanMemoryMapping {
start,
size,
is_initialized: false,
origin: MSAN_ORIGIN_CLEAN,
prot,
flags,
});
self.total_mapped += size;
}
pub fn remove_mapping(&mut self, start: u64, size: usize) {
self.mappings
.retain(|m| !(m.start >= start && m.start + m.size as u64 <= start + size as u64));
}
pub fn find_mapping(&self, addr: u64) -> Option<&MSanMemoryMapping> {
self.mappings
.iter()
.find(|m| addr >= m.start && addr < m.start + m.size as u64)
}
pub fn mark_initialized(&mut self, addr: u64, size: usize) {
for mapping in &mut self.mappings {
if addr >= mapping.start && addr < mapping.start + mapping.size as u64 {
mapping.is_initialized = true;
}
}
}
}
impl Default for MSanMappingTracker {
fn default() -> Self {
Self::new()
}
}
impl X86MTSanFull {
pub fn msan_intercept_mmap(
&mut self,
addr: u64,
length: usize,
prot: u32,
flags: u32,
fd: i32,
offset: u64,
) -> u64 {
let result = addr; if result != 0 && result != u64::MAX {
self.msan_shadow.clear_shadow_range(result, length);
if self.msan_shadow.track_origins {
let origin = self.origin_registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!(
"mmap(addr={:#x}, len={}, prot={}, flags={}, fd={}, off={})",
addr, length, prot, flags, fd, offset
),
OriginKind::HeapAllocation,
MTSanStackTrace::capture(4),
);
self.msan_shadow.set_origin_range(result, length, origin);
}
}
result
}
pub fn msan_intercept_munmap(&mut self, addr: u64, length: usize) {
self.msan_shadow.poison_shadow_range(addr, length);
if self.msan_shadow.track_origins {
self.msan_shadow
.set_origin_range(addr, length, MSAN_ORIGIN_FREED);
}
}
pub fn msan_intercept_brk(&mut self, new_brk: u64, old_brk: u64) {
if new_brk > old_brk {
let size = (new_brk - old_brk) as usize;
self.msan_shadow.clear_shadow_range(old_brk, size);
if self.msan_shadow.track_origins {
let origin = self.origin_registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!("brk({:#x})", new_brk),
OriginKind::HeapAllocation,
MTSanStackTrace::capture(4),
);
self.msan_shadow.set_origin_range(old_brk, size, origin);
}
} else if new_brk < old_brk {
let size = (old_brk - new_brk) as usize;
self.msan_shadow.poison_shadow_range(new_brk, size);
}
}
}
#[derive(Debug, Clone)]
pub struct TSanRWLockState {
pub id: u64,
pub read_holders: Vec<u32>,
pub write_holder: Option<u32>,
pub release_clock: VectorClock,
pub is_destroyed: bool,
pub creation_stack: MTSanStackTrace,
}
impl TSanRWLockState {
pub fn new(id: u64, creation_stack: MTSanStackTrace) -> Self {
Self {
id,
read_holders: Vec::new(),
write_holder: None,
release_clock: VectorClock::new(),
is_destroyed: false,
creation_stack,
}
}
}
impl X86MTSanFull {
pub fn tsan_rwlock_create(&mut self) -> u64 {
let id = self.tsan_next_mutex_id.fetch_add(1, Ordering::Relaxed);
self.tsan_mutexes.insert(
id | 0x8000_0000_0000_0000, TSanMutexState::new(id, MTSanStackTrace::capture(4)),
);
id
}
pub fn tsan_rwlock_rdlock(
&mut self,
thread_id: u32,
rwlock_id: u64,
) -> Option<TSanErrorReport> {
self.tsan_lock_ops += 1;
self.tsan_event_count += 1;
let key = rwlock_id | 0x8000_0000_0000_0000;
if let Some(mutex) = self.tsan_mutexes.get(&key) {
if !mutex.is_destroyed {
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(key);
}
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
mutex.is_read_lock = true;
}
if self.flags.tsan_options.detect_deadlocks {
if let Some(cycle) = self.tsan_deadlock_detector.try_acquire(thread_id, key) {
let stacks: Vec<_> = cycle.iter().map(|_| MTSanStackTrace::capture(6)).collect();
let report = TSanErrorReport::deadlock(cycle, stacks);
self.tsan_reports.push(report.clone());
return Some(report);
}
}
self.tsan_deadlock_detector.acquired(thread_id, key);
None
}
pub fn tsan_rwlock_wrlock(
&mut self,
thread_id: u32,
rwlock_id: u64,
) -> Option<TSanErrorReport> {
self.tsan_lock_ops += 1;
self.tsan_event_count += 1;
let key = rwlock_id | 0x8000_0000_0000_0000;
if let Some(mutex) = self.tsan_mutexes.get(&key) {
if !mutex.is_destroyed {
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(key);
}
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
mutex.is_read_lock = false;
}
if self.flags.tsan_options.detect_deadlocks {
if let Some(cycle) = self.tsan_deadlock_detector.try_acquire(thread_id, key) {
let stacks: Vec<_> = cycle.iter().map(|_| MTSanStackTrace::capture(6)).collect();
let report = TSanErrorReport::deadlock(cycle, stacks);
self.tsan_reports.push(report.clone());
return Some(report);
}
}
self.tsan_deadlock_detector.acquired(thread_id, key);
None
}
pub fn tsan_rwlock_unlock(
&mut self,
thread_id: u32,
rwlock_id: u64,
) -> Option<TSanErrorReport> {
self.tsan_lock_ops += 1;
self.tsan_event_count += 1;
let key = rwlock_id | 0x8000_0000_0000_0000;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
thread.held_mutexes.retain(|&m| m != key);
}
if let Some(thread) = self.tsan_threads.get(&thread_id) {
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.release_clock = thread.release();
if mutex.lock_count > 0 {
mutex.lock_count -= 1;
}
if mutex.lock_count == 0 {
mutex.owner_thread = 0;
}
}
}
self.tsan_deadlock_detector.released(thread_id, key);
None
}
pub fn tsan_rwlock_destroy(&mut self, rwlock_id: u64) -> Option<TSanErrorReport> {
let key = rwlock_id | 0x8000_0000_0000_0000;
if let Some(mutex) = self.tsan_mutexes.get(&key) {
if mutex.lock_count > 0 {
let report = TSanErrorReport::new(
TSanErrorKind::DestroyLockedMutex,
format!("destroying locked rwlock {}", rwlock_id),
);
self.tsan_reports.push(report.clone());
return Some(report);
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.is_destroyed = true;
}
None
}
pub fn tsan_rwlock_tryrdlock(&mut self, thread_id: u32, rwlock_id: u64) -> bool {
let key = rwlock_id | 0x8000_0000_0000_0000;
if let Some(mutex) = self.tsan_mutexes.get(&key) {
if mutex.is_destroyed {
return false;
}
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(key);
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
mutex.is_read_lock = true;
}
self.tsan_lock_ops += 1;
true
}
pub fn tsan_rwlock_trywrlock(&mut self, thread_id: u32, rwlock_id: u64) -> bool {
let key = rwlock_id | 0x8000_0000_0000_0000;
if let Some(mutex) = self.tsan_mutexes.get(&key) {
if mutex.is_destroyed || mutex.lock_count > 0 {
return false;
}
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(key);
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
mutex.is_read_lock = false;
}
self.tsan_lock_ops += 1;
true
}
}
#[derive(Debug, Clone)]
pub struct TSanSpinLockState {
pub id: u64,
pub owner_thread: u32,
pub locked: bool,
pub release_clock: VectorClock,
}
impl TSanSpinLockState {
pub fn new(id: u64) -> Self {
Self {
id,
owner_thread: 0,
locked: false,
release_clock: VectorClock::new(),
}
}
}
impl X86MTSanFull {
pub fn tsan_spinlock_create(&mut self) -> u64 {
let id = self.tsan_next_mutex_id.fetch_add(1, Ordering::Relaxed);
self.tsan_mutexes.insert(
id | 0x4000_0000_0000_0000, TSanMutexState::new(id, MTSanStackTrace::capture(4)),
);
id
}
pub fn tsan_spinlock_lock(&mut self, thread_id: u32, spinlock_id: u64) {
self.tsan_lock_ops += 1;
let key = spinlock_id | 0x4000_0000_0000_0000;
if let Some(mutex) = self.tsan_mutexes.get(&key) {
if !mutex.is_destroyed {
let release_clock = mutex.release_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
thread.held_mutexes.push(key);
}
}
}
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.owner_thread = thread_id;
mutex.lock_count += 1;
}
}
pub fn tsan_spinlock_unlock(&mut self, thread_id: u32, spinlock_id: u64) {
self.tsan_lock_ops += 1;
let key = spinlock_id | 0x4000_0000_0000_0000;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
thread.held_mutexes.retain(|&m| m != key);
}
if let Some(thread) = self.tsan_threads.get(&thread_id) {
if let Some(mutex) = self.tsan_mutexes.get_mut(&key) {
mutex.release_clock = thread.release();
if mutex.lock_count > 0 {
mutex.lock_count -= 1;
}
if mutex.lock_count == 0 {
mutex.owner_thread = 0;
}
}
}
}
}
impl X86MTSanFull {
pub fn tsan_external_acquire(&mut self, thread_id: u32, addr: u64) {
self.tsan_event_count += 1;
let cell_key = addr / TSAN_SHADOW_GRANULARITY as u64;
if let Some(cell) = self.tsan_shadow.get(&cell_key) {
if cell.last_thread != thread_id {
let release_clock = cell.write_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
}
}
}
}
pub fn tsan_external_release(&mut self, thread_id: u32, addr: u64) {
self.tsan_event_count += 1;
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.tick();
let clock = thread.clock.clone();
let cell_key = addr / TSAN_SHADOW_GRANULARITY as u64;
let history_size = self.flags.tsan_options.history_size;
let cell = self
.tsan_shadow
.entry(cell_key)
.or_insert_with(|| TSanShadowCell::new(history_size));
cell.write_clock = clock;
cell.last_thread = thread_id;
cell.last_write = true;
}
}
pub fn tsan_annotate_happens_before(&mut self, addr: u64) {
let cell_key = addr / TSAN_SHADOW_GRANULARITY as u64;
let history_size = self.flags.tsan_options.history_size;
self.tsan_shadow
.entry(cell_key)
.or_insert_with(|| TSanShadowCell::new(history_size));
}
pub fn tsan_annotate_happens_after(&mut self, thread_id: u32, addr: u64) {
let cell_key = addr / TSAN_SHADOW_GRANULARITY as u64;
if let Some(cell) = self.tsan_shadow.get(&cell_key) {
let release_clock = cell.write_clock.clone();
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.acquire(&release_clock);
}
}
}
pub fn tsan_annotate_condvar_signal(&mut self, thread_id: u32, cv: u64) {
self.tsan_external_release(thread_id, cv);
}
pub fn tsan_annotate_condvar_wait(&mut self, thread_id: u32, cv: u64) {
self.tsan_external_acquire(thread_id, cv);
}
pub fn tsan_annotate_rwlock_create(&mut self, lock: u64) {
self.tsan_annotate_happens_before(lock);
}
pub fn tsan_annotate_rwlock_acquired(&mut self, thread_id: u32, lock: u64, is_write: bool) {
let _ = is_write;
self.tsan_external_acquire(thread_id, lock);
}
pub fn tsan_annotate_rwlock_released(&mut self, thread_id: u32, lock: u64, is_write: bool) {
let _ = is_write;
self.tsan_external_release(thread_id, lock);
}
pub fn tsan_annotate_ignore_reads_begin(&self) {
}
pub fn tsan_annotate_ignore_reads_end(&self) {
}
pub fn tsan_annotate_ignore_writes_begin(&self) {
}
pub fn tsan_annotate_ignore_writes_end(&self) {
}
pub fn tsan_annotate_benign_race(&self, addr: u64, size: usize, description: &str) {
let _ = (addr, size, description);
}
}
#[derive(Debug)]
pub struct TSanRaceDeduplicator {
pub reported_hashes: HashSet<u64>,
pub max_reports: usize,
pub throttle_interval: Duration,
pub last_report_times: HashMap<u64, Instant>,
}
impl TSanRaceDeduplicator {
pub fn new(max_reports: usize) -> Self {
Self {
reported_hashes: HashSet::new(),
max_reports,
throttle_interval: Duration::from_secs(1),
last_report_times: HashMap::new(),
}
}
pub fn hash_race(addr: u64, thread1: u32, thread2: u32) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
addr.hash(&mut hasher);
let (t1, t2) = if thread1 < thread2 {
(thread1, thread2)
} else {
(thread2, thread1)
};
t1.hash(&mut hasher);
t2.hash(&mut hasher);
hasher.finish()
}
pub fn should_report(&mut self, addr: u64, thread1: u32, thread2: u32) -> bool {
if self.reported_hashes.len() >= self.max_reports {
return false;
}
let hash = Self::hash_race(addr, thread1, thread2);
let now = Instant::now();
if let Some(&last_time) = self.last_report_times.get(&hash) {
if now.duration_since(last_time) < self.throttle_interval {
return false;
}
}
self.reported_hashes.insert(hash);
self.last_report_times.insert(hash, now);
true
}
pub fn reported_count(&self) -> usize {
self.reported_hashes.len()
}
}
impl Default for TSanRaceDeduplicator {
fn default() -> Self {
Self::new(1000)
}
}
impl X86MTSanFull {
pub fn tsan_maybe_report_race(
&mut self,
dedup: &mut TSanRaceDeduplicator,
addr: u64,
thread1: u32,
thread2: u32,
size: usize,
is_write1: bool,
is_write2: bool,
) {
if dedup.should_report(addr, thread1, thread2) {
let report = TSanErrorReport::data_race(
addr,
size,
thread1,
thread2,
is_write1,
is_write2,
MTSanStackTrace::capture(4),
MTSanStackTrace::capture(4),
);
self.tsan_reports.push(report);
}
}
}
#[derive(Debug, Clone)]
pub struct TSanTraceEvent {
pub event_type: TSanTraceEventType,
pub thread_id: u32,
pub addr: u64,
pub size: usize,
pub is_write: bool,
pub is_atomic: bool,
pub ordering: Option<TSanAtomicOrdering>,
pub timestamp: u64,
pub stack: MTSanStackTrace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TSanTraceEventType {
Load,
Store,
AtomicLoad,
AtomicStore,
AtomicRMW,
AtomicFence,
MutexLock,
MutexUnlock,
ThreadCreate,
ThreadJoin,
SignalEnter,
SignalExit,
}
#[derive(Debug)]
pub struct TSanEventTrace {
pub events: Vec<TSanTraceEvent>,
pub max_events: usize,
}
impl TSanEventTrace {
pub fn new(max_events: usize) -> Self {
Self {
events: Vec::with_capacity(max_events),
max_events,
}
}
pub fn record(&mut self, event: TSanTraceEvent) {
if self.events.len() >= self.max_events {
self.events.remove(0);
}
self.events.push(event);
}
pub fn replay_on(&self, runtime: &mut X86MTSanFull) -> Vec<TSanErrorReport> {
let mut reports = Vec::new();
for event in &self.events {
match event.event_type {
TSanTraceEventType::Load => {
if let Some(race) = runtime.tsan_load(event.thread_id, event.addr, event.size) {
reports.push(TSanErrorReport::data_race(
event.addr,
event.size,
race.thread1,
race.thread2,
race.is_write1,
race.is_write2,
MTSanStackTrace::capture(4),
MTSanStackTrace::capture(4),
));
}
}
TSanTraceEventType::Store => {
if let Some(race) = runtime.tsan_store(event.thread_id, event.addr, event.size)
{
reports.push(TSanErrorReport::data_race(
event.addr,
event.size,
race.thread1,
race.thread2,
race.is_write1,
race.is_write2,
MTSanStackTrace::capture(4),
MTSanStackTrace::capture(4),
));
}
}
TSanTraceEventType::AtomicLoad => {
let ord = event.ordering.unwrap_or(TSanAtomicOrdering::Relaxed);
runtime.tsan_atomic_load(event.thread_id, event.addr, event.size, ord);
}
TSanTraceEventType::AtomicStore => {
let ord = event.ordering.unwrap_or(TSanAtomicOrdering::Relaxed);
runtime.tsan_atomic_store(event.thread_id, event.addr, event.size, ord);
}
TSanTraceEventType::MutexLock => {
runtime.tsan_mutex_lock(event.thread_id, event.addr);
}
TSanTraceEventType::MutexUnlock => {
runtime.tsan_mutex_unlock(event.thread_id, event.addr);
}
TSanTraceEventType::ThreadCreate => {
runtime.tsan_thread_create();
}
TSanTraceEventType::ThreadJoin => {
runtime.tsan_thread_join(event.thread_id, event.addr as u32);
}
_ => {}
}
}
reports
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
}
impl Default for TSanEventTrace {
fn default() -> Self {
Self::new(10000)
}
}
#[derive(Debug)]
pub struct StackTraceCache {
pub cache: HashMap<u64, MTSanStackTrace>,
pub hits: usize,
pub misses: usize,
}
impl StackTraceCache {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
hits: 0,
misses: 0,
}
}
pub fn get_or_capture(&mut self, hash: u64, depth: usize) -> MTSanStackTrace {
if let Some(trace) = self.cache.get(&hash) {
self.hits += 1;
trace.clone()
} else {
self.misses += 1;
let trace = MTSanStackTrace::capture(depth);
self.cache.insert(hash, trace.clone());
trace
}
}
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
}
impl Default for StackTraceCache {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeadlockStrategy {
WaitForGraph,
TimeoutHeuristic,
LockOrderAnalysis,
Combined,
}
#[derive(Debug)]
pub struct ExtendedDeadlockDetector {
pub base_detector: TSanDeadlockDetector,
pub strategy: DeadlockStrategy,
pub lock_hold_times: HashMap<u64, Instant>,
pub timeout_threshold: Duration,
}
impl ExtendedDeadlockDetector {
pub fn new(strategy: DeadlockStrategy) -> Self {
Self {
base_detector: TSanDeadlockDetector::new(),
strategy,
lock_hold_times: HashMap::new(),
timeout_threshold: Duration::from_secs(60),
}
}
pub fn record_lock_acquired(&mut self, thread_id: u32, mutex_id: u64) {
self.lock_hold_times.insert(mutex_id, Instant::now());
self.base_detector.acquired(thread_id, mutex_id);
}
pub fn record_lock_released(&mut self, thread_id: u32, mutex_id: u64) {
self.lock_hold_times.remove(&mutex_id);
self.base_detector.released(thread_id, mutex_id);
}
pub fn check_timeout(&self) -> Vec<u64> {
let now = Instant::now();
let mut timed_out = Vec::new();
for (&mutex_id, &acquire_time) in &self.lock_hold_times {
if now.duration_since(acquire_time) > self.timeout_threshold {
timed_out.push(mutex_id);
}
}
timed_out
}
pub fn try_acquire(&mut self, thread_id: u32, mutex_id: u64) -> Option<Vec<u32>> {
match self.strategy {
DeadlockStrategy::WaitForGraph | DeadlockStrategy::Combined => {
self.base_detector.try_acquire(thread_id, mutex_id)
}
DeadlockStrategy::TimeoutHeuristic => {
if self.lock_hold_times.contains_key(&mutex_id) {
let now = Instant::now();
if let Some(&acquired) = self.lock_hold_times.get(&mutex_id) {
if now.duration_since(acquired) > self.timeout_threshold {
return Some(vec![thread_id]);
}
}
}
None
}
DeadlockStrategy::LockOrderAnalysis => {
self.base_detector.try_acquire(thread_id, mutex_id)
}
}
}
}
impl Default for ExtendedDeadlockDetector {
fn default() -> Self {
Self::new(DeadlockStrategy::WaitForGraph)
}
}
#[derive(Debug)]
pub struct TSanThrottle {
pub sampling_rate: usize,
pub event_counter: usize,
pub enabled: bool,
pub total_processed: usize,
pub total_skipped: usize,
}
impl TSanThrottle {
pub fn new(sampling_rate: usize) -> Self {
Self {
sampling_rate,
event_counter: 0,
enabled: sampling_rate > 1,
total_processed: 0,
total_skipped: 0,
}
}
pub fn should_process(&mut self) -> bool {
if !self.enabled {
self.total_processed += 1;
return true;
}
self.event_counter += 1;
if self.event_counter >= self.sampling_rate {
self.event_counter = 0;
self.total_processed += 1;
true
} else {
self.total_skipped += 1;
false
}
}
pub fn skip_rate(&self) -> f64 {
let total = self.total_processed + self.total_skipped;
if total == 0 {
0.0
} else {
self.total_skipped as f64 / total as f64
}
}
}
impl Default for TSanThrottle {
fn default() -> Self {
Self::new(1)
}
}
impl X86MTSanFull {
pub fn msan_intercept_read(&mut self, fd: i32, buf: u64, count: usize) -> isize {
let _ = fd;
self.msan_shadow.clear_shadow_range(buf, count);
count as isize
}
pub fn msan_intercept_write(
&self,
fd: i32,
buf: u64,
count: usize,
thread_id: u32,
) -> Option<MSanErrorReport> {
let _ = fd;
if !self.msan_shadow.is_initialized_range(buf, count) {
let (first_addr, _, origin) = self
.msan_shadow
.find_first_uninit(buf, count)
.unwrap_or((buf, MSAN_SHADOW_UNINIT, MSAN_ORIGIN_CLEAN));
Some(MSanErrorReport::new(
MSanErrorKind::UseOfUninitializedValue,
first_addr,
count,
origin,
MTSanStackTrace::capture(4),
thread_id,
format!(
"write of {} byte(s) of uninitialized data to fd {}",
count, fd
),
))
} else {
None
}
}
pub fn msan_intercept_pread(&mut self, fd: i32, buf: u64, count: usize, offset: u64) -> isize {
let _ = (fd, offset);
self.msan_shadow.clear_shadow_range(buf, count);
count as isize
}
pub fn msan_intercept_recv(&mut self, sockfd: i32, buf: u64, len: usize, flags: i32) -> isize {
let _ = (sockfd, flags);
self.msan_shadow.clear_shadow_range(buf, len);
len as isize
}
pub fn msan_intercept_send(
&self,
sockfd: i32,
buf: u64,
len: usize,
flags: i32,
thread_id: u32,
) -> Option<MSanErrorReport> {
self.msan_intercept_write(sockfd, buf, len, thread_id)
}
pub fn msan_intercept_getcwd(&mut self, buf: u64, size: usize) {
self.msan_shadow.clear_shadow_range(buf, size);
}
pub fn msan_intercept_gethostname(&mut self, name: u64, len: usize) {
self.msan_shadow.clear_shadow_range(name, len);
}
pub fn msan_intercept_get_sockname(&mut self, addr: u64, addrlen: usize) {
self.msan_shadow.clear_shadow_range(addr, addrlen);
self.msan_shadow.clear_shadow_range(addr, 4);
}
}
impl MSanShadowPropagator {
pub fn propagate_vector_binary(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter().zip(b.iter()).map(|(&sa, &sb)| sa | sb).collect()
}
pub fn propagate_vector_shuffle(shadow: &[u8], mask: &[i32]) -> Vec<u8> {
mask.iter()
.map(|&idx| {
if idx < 0 || idx as usize >= shadow.len() {
MSAN_SHADOW_UNINIT
} else {
shadow[idx as usize]
}
})
.collect()
}
pub fn propagate_extractelement(vec_shadow: &[u8], idx: usize) -> u8 {
if idx < vec_shadow.len() {
vec_shadow[idx]
} else {
MSAN_SHADOW_UNINIT
}
}
pub fn propagate_insertelement(vec_shadow: &[u8], idx: usize, val_shadow: u8) -> Vec<u8> {
let mut result = vec_shadow.to_vec();
if idx < result.len() {
result[idx] = val_shadow;
}
result
}
pub fn propagate_masked_load(
shadow_mem: &MSanShadowMemory,
addr: u64,
mask: &[bool],
) -> Vec<u8> {
mask.iter()
.enumerate()
.map(|(i, &m)| {
if m {
shadow_mem.get_shadow(addr + i as u64)
} else {
MSAN_SHADOW_UNINIT
}
})
.collect()
}
pub fn propagate_masked_store(
shadow_mem: &mut MSanShadowMemory,
addr: u64,
shadow_bytes: &[u8],
mask: &[bool],
) {
for (i, &m) in mask.iter().enumerate() {
if m && i < shadow_bytes.len() {
shadow_mem.set_shadow(addr + i as u64, shadow_bytes[i]);
}
}
}
pub fn propagate_masked_gather(
shadow_mem: &MSanShadowMemory,
ptrs: &[u64],
mask: &[bool],
scale: usize,
) -> Vec<u8> {
ptrs.iter()
.enumerate()
.map(|(i, &ptr)| {
if mask[i] {
shadow_mem.get_shadow(ptr)
} else {
MSAN_SHADOW_UNINIT
}
})
.collect()
}
pub fn propagate_masked_scatter(
shadow_mem: &mut MSanShadowMemory,
shadow_vals: &[u8],
ptrs: &[u64],
mask: &[bool],
) {
for i in 0..ptrs.len() {
if mask[i] && i < shadow_vals.len() {
shadow_mem.set_shadow(ptrs[i], shadow_vals[i]);
}
}
}
}
impl X86MTSanFull {
pub fn msan_intercept_getenv(&mut self, result_ptr: u64) {
if result_ptr != 0 {
self.msan_shadow.clear_shadow_range(result_ptr, 4096);
}
}
pub fn msan_intercept_realpath(&mut self, path: u64, resolved_path: u64) {
let _ = path;
if resolved_path != 0 {
self.msan_shadow.clear_shadow_range(resolved_path, 4096);
}
}
pub fn msan_intercept_readlink(&mut self, path: u64, buf: u64, bufsiz: usize, result: isize) {
let _ = path;
if result > 0 {
self.msan_shadow.clear_shadow_range(buf, result as usize);
}
}
pub fn msan_intercept_stat(&mut self, statbuf: u64, result: i32) {
if result == 0 && statbuf != 0 {
self.msan_shadow.clear_shadow_range(statbuf, 144); }
}
pub fn msan_intercept_gettimeofday(&mut self, tv: u64, tz: u64) {
if tv != 0 {
self.msan_shadow.clear_shadow_range(tv, 16); }
if tz != 0 {
self.msan_shadow.clear_shadow_range(tz, 8);
}
}
pub fn msan_intercept_clock_gettime(&mut self, tp: u64) {
if tp != 0 {
self.msan_shadow.clear_shadow_range(tp, 16); }
}
pub fn msan_intercept_pthread_getspecific(&mut self, key: u32, value_ptr: u64) {
let _ = key;
if value_ptr != 0 {
self.msan_shadow.clear_shadow_range(value_ptr, 8);
}
}
pub fn msan_intercept_dlopen(&mut self, filename: u64, flags: i32, handle: u64) {
let _ = (filename, flags);
if handle != 0 {
}
}
pub fn msan_intercept_posix_memalign(
&mut self,
memptr: u64,
alignment: usize,
size: usize,
result: i32,
) {
if result == 0 && memptr != 0 {
self.msan_shadow.clear_shadow_range(memptr, size);
if self.msan_shadow.track_origins {
let origin = self.origin_registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!("posix_memalign(align={}, size={})", alignment, size),
OriginKind::HeapAllocation,
MTSanStackTrace::capture(4),
);
self.msan_shadow.set_origin_range(memptr, size, origin);
}
}
}
pub fn msan_intercept_aligned_alloc(&mut self, alignment: usize, size: usize, ptr: u64) {
if ptr != 0 {
self.msan_shadow.clear_shadow_range(ptr, size);
if self.msan_shadow.track_origins {
let origin = self.origin_registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
format!("aligned_alloc(align={}, size={})", alignment, size),
OriginKind::HeapAllocation,
MTSanStackTrace::capture(4),
);
self.msan_shadow.set_origin_range(ptr, size, origin);
}
}
}
}
impl X86MTSanFull {
pub fn should_halt(&self) -> bool {
if self.flags.halt_on_error && self.has_errors() {
return true;
}
false
}
pub fn exit_code(&self) -> i32 {
if self.has_errors() {
self.flags.exitcode
} else {
0
}
}
pub fn reset(&mut self) {
self.msan_shadow = MSanShadowMemory::new(
self.msan_shadow.shadow_offset,
self.msan_shadow.track_origins,
);
self.origin_registry = OriginRegistry::new();
self.tsan_threads.clear();
self.tsan_shadow.clear();
self.tsan_mutexes.clear();
self.tsan_deadlock_detector = TSanDeadlockDetector::new();
self.msan_reports.clear();
self.tsan_reports.clear();
self.msan_access_count = 0;
self.msan_propagation_count = 0;
self.tsan_event_count = 0;
self.tsan_lock_ops = 0;
self.tsan_race_count = 0;
}
pub fn handle_fork_child(&mut self) {
self.tsan_threads.clear();
self.tsan_shadow.clear();
self.tsan_mutexes.clear();
self.tsan_deadlock_detector = TSanDeadlockDetector::new();
self.tsan_reports.clear();
self.tsan_event_count = 0;
self.tsan_lock_ops = 0;
self.tsan_race_count = 0;
}
pub fn handle_fork_parent(&mut self) {
}
pub fn global_tick(&self) -> u64 {
self.tsan_global_tick.load(Ordering::Relaxed)
}
pub fn tick(&self) -> u64 {
self.tsan_global_tick.fetch_add(1, Ordering::Relaxed)
}
pub fn register_interceptor(&mut self, name: &str) {
self.msan_interceptors.origin_stack.push((0, name.len()));
let _ = name;
}
pub fn verify_shadow_consistency(&self) -> bool {
if self.msan_shadow.track_origins {
for (&origin_key, _) in &self.msan_shadow.origin_map {
let app_addr = origin_key * MSAN_ORIGIN_SCALE as u64;
let _ = self.msan_shadow.get_shadow(app_addr);
}
}
true
}
pub fn dump_state(&self) -> String {
let mut s = String::new();
s.push_str(&format!("=== X86MTSanFull State ===\n"));
s.push_str(&format!("Initialized: {}\n", self.initialized));
s.push_str(&format!(
"MSan shadow bytes: {}\n",
self.msan_shadow.total_shadow_bytes
));
s.push_str(&format!("MSan access count: {}\n", self.msan_access_count));
s.push_str(&format!(
"MSan propagation count: {}\n",
self.msan_propagation_count
));
s.push_str(&format!("MSan errors: {}\n", self.msan_reports.len()));
s.push_str(&format!("TSan threads: {}\n", self.tsan_threads.len()));
s.push_str(&format!("TSan shadow cells: {}\n", self.tsan_shadow.len()));
s.push_str(&format!("TSan mutexes: {}\n", self.tsan_mutexes.len()));
s.push_str(&format!("TSan events: {}\n", self.tsan_event_count));
s.push_str(&format!("TSan lock ops: {}\n", self.tsan_lock_ops));
s.push_str(&format!("TSan races: {}\n", self.tsan_race_count));
s.push_str(&format!("TSan errors: {}\n", self.tsan_reports.len()));
s.push_str(&format!("Origin entries: {}\n", self.origin_registry.len()));
s
}
pub fn periodic_flush(&mut self) {
if self.tsan_shadow.len() > 1_000_000 {
let keys_to_remove: Vec<u64> = self
.tsan_shadow
.iter()
.filter(|(_, cell)| cell.read_count == 0 && cell.history.is_empty())
.map(|(k, _)| *k)
.take(100_000)
.collect();
for key in keys_to_remove {
self.tsan_shadow.remove(&key);
}
}
}
pub fn live_thread_count(&self) -> usize {
self.tsan_threads
.values()
.filter(|t| !t.is_joined && !t.is_detached)
.count()
}
pub fn thread_name(&self, thread_id: u32) -> Option<&str> {
self.tsan_threads
.get(&thread_id)
.and_then(|t| t.name.as_deref())
}
pub fn set_thread_name(&mut self, thread_id: u32, name: impl Into<String>) {
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.name = Some(name.into());
}
}
pub fn detach_thread(&mut self, thread_id: u32) {
if let Some(thread) = self.tsan_threads.get_mut(&thread_id) {
thread.is_detached = true;
}
}
pub fn is_in_signal_handler(&self, thread_id: u32) -> bool {
self.tsan_threads
.get(&thread_id)
.map(|t| t.in_signal_handler)
.unwrap_or(false)
}
pub fn flush_reports(&self) {
if let Some(ref log_path) = self.flags.log_path {
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(log_path)
{
use std::io::Write;
for report in &self.msan_reports {
let _ = writeln!(file, "{}", report.format(Some(&self.origin_registry)));
}
for report in &self.tsan_reports {
let _ = writeln!(file, "{}", report.format());
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_msan_shadow_init_uninit() {
assert_eq!(MSAN_SHADOW_INIT, 0x00);
assert_eq!(MSAN_SHADOW_UNINIT, 0xFF);
}
#[test]
fn test_msan_shadow_memory_basic() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
assert_eq!(shadow.get_shadow(0x1000), MSAN_SHADOW_UNINIT);
shadow.set_shadow(0x1000, MSAN_SHADOW_INIT);
assert_eq!(shadow.get_shadow(0x1000), MSAN_SHADOW_INIT);
assert_eq!(shadow.get_shadow(0x1001), MSAN_SHADOW_UNINIT);
}
#[test]
fn test_msan_shadow_range() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
shadow.clear_shadow_range(0x2000, 100);
assert!(shadow.is_initialized_range(0x2000, 100));
assert!(!shadow.is_initialized_range(0x2000, 101));
}
#[test]
fn test_msan_shadow_poison_range() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
shadow.clear_shadow_range(0x3000, 50);
assert!(shadow.is_initialized_range(0x3000, 50));
shadow.poison_shadow_range(0x3010, 10);
assert!(!shadow.is_initialized_range(0x3000, 50));
}
#[test]
fn test_msan_origin_tracking() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, true);
assert_eq!(shadow.get_origin(0x4000), MSAN_ORIGIN_CLEAN);
shadow.set_origin(0x4000, 42);
assert_eq!(shadow.get_origin(0x4000), 42);
assert_eq!(shadow.get_origin(0x4003), 42);
}
#[test]
fn test_msan_origin_range() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, true);
shadow.set_origin_range(0x5000, 32, 99);
assert_eq!(shadow.get_origin(0x5000), 99);
assert_eq!(shadow.get_origin(0x5010), 99);
}
#[test]
fn test_msan_origin_disabled() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
shadow.set_origin(0x6000, 42);
assert_eq!(shadow.get_origin(0x6000), MSAN_ORIGIN_CLEAN);
}
#[test]
fn test_msan_find_first_uninit() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
shadow.clear_shadow_range(0x7000, 100);
shadow.set_shadow(0x7032, MSAN_SHADOW_UNINIT);
let result = shadow.find_first_uninit(0x7000, 100);
assert!(result.is_some());
let (addr, val, _) = result.unwrap();
assert_eq!(addr, 0x7032);
assert_eq!(val, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_msan_shadow_to_app_roundtrip() {
let shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
let app_addr = 0x8000u64;
let sh_addr = shadow.app_to_shadow(app_addr);
let app_back = shadow.shadow_to_app(sh_addr);
assert_eq!(app_addr, app_back);
}
#[test]
fn test_propagate_add() {
let s = MSanShadowPropagator::propagate_add_sub(0x00, 0x00);
assert_eq!(s, MSAN_SHADOW_INIT);
let s = MSanShadowPropagator::propagate_add_sub(0xFF, 0x00);
assert_eq!(s, MSAN_SHADOW_UNINIT);
let s = MSanShadowPropagator::propagate_add_sub(0x00, 0xFF);
assert_eq!(s, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_propagate_mul() {
let s = MSanShadowPropagator::propagate_mul(0x00, 0x00);
assert_eq!(s, MSAN_SHADOW_INIT);
let s = MSanShadowPropagator::propagate_mul(0xFF, 0x00);
assert_eq!(s, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_propagate_and() {
let s = MSanShadowPropagator::propagate_and(0x00, 0x00);
assert_eq!(s, MSAN_SHADOW_INIT);
let s = MSanShadowPropagator::propagate_and(0xFF, 0x00);
assert_eq!(s, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_propagate_shift() {
let s = MSanShadowPropagator::propagate_shl(0x00, 0xFF);
assert_eq!(s, MSAN_SHADOW_UNINIT);
let s = MSanShadowPropagator::propagate_shl(0xAB, 0x00);
assert_eq!(s, 0xAB);
}
#[test]
fn test_propagate_select() {
let s = MSanShadowPropagator::propagate_select(0xFF, 0x00, 0xFF);
assert_eq!(s, MSAN_SHADOW_UNINIT);
let s = MSanShadowPropagator::propagate_select(0x00, 0x00, 0x00);
assert_eq!(s, MSAN_SHADOW_INIT);
}
#[test]
fn test_propagate_phi() {
let s = MSanShadowPropagator::propagate_phi(&[0x00, 0x00, 0x00]);
assert_eq!(s, MSAN_SHADOW_INIT);
let s = MSanShadowPropagator::propagate_phi(&[0x00, 0xFF, 0x00]);
assert_eq!(s, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_propagate_cmp() {
let s = MSanShadowPropagator::propagate_cmp(0x00, 0x00);
assert_eq!(s, MSAN_SHADOW_INIT);
let s = MSanShadowPropagator::propagate_cmp(0xFF, 0x00);
assert_eq!(s, MSAN_SHADOW_UNINIT);
let s = MSanShadowPropagator::propagate_cmp(0x00, 0xFF);
assert_eq!(s, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_propagate_load_store() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
shadow.clear_shadow_range(0x9000, 16);
let loaded = MSanShadowPropagator::propagate_load(&shadow, 0x9000, 8);
assert_eq!(loaded, vec![MSAN_SHADOW_INIT; 8]);
MSanShadowPropagator::propagate_store(&mut shadow, 0xA000, &[0xFF; 4]);
let loaded2 = MSanShadowPropagator::propagate_load(&shadow, 0xA000, 4);
assert_eq!(loaded2, vec![MSAN_SHADOW_UNINIT; 4]);
}
#[test]
fn test_propagate_memcpy() {
let mut shadow = MSanShadowMemory::new(MSAN_SHADOW_OFFSET_X86_64, false);
shadow.clear_shadow_range(0xB000, 16);
shadow.set_shadow(0xB000, 0xAB);
MSanShadowPropagator::propagate_memcpy(&mut shadow, 0xC000, 0xB000, 16);
assert_eq!(shadow.get_shadow(0xC000), 0xAB);
assert_eq!(shadow.get_shadow(0xC00F), MSAN_SHADOW_INIT);
}
#[test]
fn test_propagate_extractvalue() {
let agg = vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
let extracted = MSanShadowPropagator::propagate_extractvalue(&agg, 2, 4);
assert_eq!(extracted, vec![0x03, 0x04, 0x05, 0x06]);
}
#[test]
fn test_propagate_insertvalue() {
let agg = vec![0x00; 8];
let val = vec![0xFF; 2];
let result = MSanShadowPropagator::propagate_insertvalue(&agg, 4, &val);
assert_eq!(result[4], 0xFF);
assert_eq!(result[5], 0xFF);
assert_eq!(result[0], 0x00);
}
#[test]
fn test_vector_clock_new() {
let vc = VectorClock::new();
assert!(vc.is_empty());
assert_eq!(vc.len(), 0);
assert_eq!(vc.max_clock(), 0);
}
#[test]
fn test_vector_clock_tick() {
let mut vc = VectorClock::new();
let val = vc.tick(1);
assert_eq!(val, 1);
assert_eq!(vc.get(1), 1);
}
#[test]
fn test_vector_clock_set_get() {
let mut vc = VectorClock::new();
vc.set(1, 42);
assert_eq!(vc.get(1), 42);
assert_eq!(vc.get(2), 0);
}
#[test]
fn test_vector_clock_set_monotonic() {
let mut vc = VectorClock::new();
vc.set(1, 10);
vc.set(1, 5); assert_eq!(vc.get(1), 10);
vc.set(1, 15);
assert_eq!(vc.get(1), 15);
}
#[test]
fn test_vector_clock_join() {
let mut vc1 = VectorClock::new();
vc1.set(1, 10);
vc1.set(2, 5);
let mut vc2 = VectorClock::new();
vc2.set(1, 5);
vc2.set(2, 15);
vc2.set(3, 20);
vc1.join(&vc2);
assert_eq!(vc1.get(1), 10); assert_eq!(vc1.get(2), 15); assert_eq!(vc1.get(3), 20); }
#[test]
fn test_vector_clock_happens_before() {
let mut vc1 = VectorClock::new();
vc1.set(1, 5);
vc1.set(2, 10);
let mut vc2 = VectorClock::new();
vc2.set(1, 10);
vc2.set(2, 10);
assert!(vc1.happens_before(&vc2));
assert!(!vc2.happens_before(&vc1));
}
#[test]
fn test_vector_clock_concurrent() {
let mut vc1 = VectorClock::new();
vc1.set(1, 10);
vc1.set(2, 5);
let mut vc2 = VectorClock::new();
vc2.set(1, 5);
vc2.set(2, 10);
assert!(vc1.concurrent(&vc2));
assert!(!vc1.happens_before(&vc2));
assert!(!vc2.happens_before(&vc1));
}
#[test]
fn test_vector_clock_happens_before_or_eq() {
let mut vc1 = VectorClock::new();
vc1.set(1, 10);
vc1.set(2, 10);
let vc2 = vc1.clone();
assert!(vc1.happens_before_or_eq(&vc2));
assert!(vc2.happens_before_or_eq(&vc1));
}
#[test]
fn test_vector_clock_max_clock() {
let mut vc = VectorClock::new();
vc.set(1, 3);
vc.set(2, 7);
vc.set(3, 2);
assert_eq!(vc.max_clock(), 7);
}
#[test]
fn test_tsan_thread_state_new() {
let state = TSanThreadState::new(42, MTSanStackTrace::capture(2));
assert_eq!(state.thread_id, 42);
assert!(!state.is_joined);
assert!(!state.in_signal_handler);
assert!(state.is_tracked);
}
#[test]
fn test_tsan_thread_state_tick() {
let mut state = TSanThreadState::new(1, MTSanStackTrace::new());
let val = state.tick();
assert_eq!(val, 1);
let val2 = state.tick();
assert_eq!(val2, 2);
assert_eq!(state.global_tick, 2);
}
#[test]
fn test_tsan_thread_state_acquire() {
let mut state = TSanThreadState::new(1, MTSanStackTrace::new());
state.tick();
let mut release_clock = VectorClock::new();
release_clock.set(2, 10);
release_clock.set(3, 20);
state.acquire(&release_clock);
assert_eq!(state.clock.get(2), 10);
assert_eq!(state.clock.get(3), 20);
assert_eq!(state.clock.get(1), 1);
}
#[test]
fn test_tsan_shadow_cell_new() {
let cell = TSanShadowCell::new(4);
assert_eq!(cell.last_thread, 0);
assert_eq!(cell.read_count, 0);
assert_eq!(cell.history.len(), 0);
}
#[test]
fn test_tsan_shadow_cell_record_access_no_race() {
let mut cell = TSanShadowCell::new(4);
let clock1 = {
let mut vc = VectorClock::new();
vc.set(1, 1);
vc
};
let result = cell.record_access(1, &clock1, true, 8, false, None, MTSanStackTrace::new());
assert!(result.is_none());
assert_eq!(cell.last_thread, 1);
assert!(cell.last_write);
}
#[test]
fn test_tsan_shadow_cell_record_access_same_thread() {
let mut cell = TSanShadowCell::new(4);
let clock1 = {
let mut vc = VectorClock::new();
vc.set(1, 1);
vc
};
let clock2 = {
let mut vc = VectorClock::new();
vc.set(1, 2);
vc
};
cell.record_access(1, &clock1, true, 8, false, None, MTSanStackTrace::new());
let result = cell.record_access(1, &clock2, true, 8, false, None, MTSanStackTrace::new());
assert!(result.is_none());
}
#[test]
fn test_tsan_shadow_cell_record_access_race() {
let mut cell = TSanShadowCell::new(4);
let clock1 = {
let mut vc = VectorClock::new();
vc.set(1, 1);
vc
};
cell.record_access(1, &clock1, true, 8, false, None, MTSanStackTrace::new());
let clock2 = {
let mut vc = VectorClock::new();
vc.set(2, 1);
vc
};
let result = cell.record_access(2, &clock2, true, 8, false, None, MTSanStackTrace::new());
assert!(result.is_some());
let race = result.unwrap();
assert_eq!(race.thread1, 1);
assert_eq!(race.thread2, 2);
}
#[test]
fn test_tsan_shadow_cell_record_access_happens_before_no_race() {
let mut cell = TSanShadowCell::new(4);
let clock1 = {
let mut vc = VectorClock::new();
vc.set(1, 1);
vc
};
cell.record_access(1, &clock1, true, 8, false, None, MTSanStackTrace::new());
let mut clock2 = VectorClock::new();
clock2.set(1, 2); clock2.set(2, 1);
let result = cell.record_access(2, &clock2, false, 8, false, None, MTSanStackTrace::new());
assert!(result.is_none());
}
#[test]
fn test_tsan_mutex_state_new() {
let mutex = TSanMutexState::new(100, MTSanStackTrace::new());
assert_eq!(mutex.id, 100);
assert_eq!(mutex.owner_thread, 0);
assert_eq!(mutex.lock_count, 0);
assert!(!mutex.is_destroyed);
}
#[test]
fn test_tsan_deadlock_detector_simple() {
let mut detector = TSanDeadlockDetector::new();
detector.acquired(1, 10);
detector.acquired(2, 20);
let result = detector.try_acquire(1, 20);
assert!(result.is_none());
let result = detector.try_acquire(2, 10);
assert!(result.is_some());
}
#[test]
fn test_tsan_deadlock_detector_no_cycle() {
let mut detector = TSanDeadlockDetector::new();
detector.acquired(1, 10);
detector.acquired(2, 20);
detector.try_acquire(1, 20);
detector.released(2, 20);
detector.acquired(1, 20);
assert!(detector.detect_cycle(1).is_none());
}
#[test]
fn test_signal_safety_unsafe() {
let safety = TSanSignalSafety::new();
assert!(!safety.is_signal_safe("printf"));
assert!(!safety.is_signal_safe("malloc"));
assert!(!safety.is_signal_safe("pthread_mutex_lock"));
}
#[test]
fn test_signal_safety_safe() {
let safety = TSanSignalSafety::new();
assert!(safety.is_signal_safe("write"));
assert!(safety.is_signal_safe("read"));
assert!(safety.is_signal_safe("_exit"));
}
#[test]
fn test_origin_registry_allocate() {
let mut registry = OriginRegistry::new();
let id = registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
"test allocation",
OriginKind::HeapAllocation,
MTSanStackTrace::capture(2),
);
assert!(id > 0);
assert!(id <= MSAN_ORIGIN_MAX);
assert!(registry.get(id).is_some());
}
#[test]
fn test_origin_registry_chain() {
let mut registry = OriginRegistry::new();
let id1 = registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
"initial",
OriginKind::HeapAllocation,
MTSanStackTrace::new(),
);
let id2 = registry.allocate_origin(
id1,
"derived",
OriginKind::Instruction,
MTSanStackTrace::new(),
);
let id3 = registry.allocate_origin(
id2,
"final",
OriginKind::Instruction,
MTSanStackTrace::new(),
);
let chain = registry.follow_chain(id3, 16);
assert_eq!(chain.len(), 3);
assert_eq!(chain[0].description, "initial");
assert_eq!(chain[1].description, "derived");
assert_eq!(chain[2].description, "final");
}
#[test]
fn test_origin_registry_format_chain() {
let mut registry = OriginRegistry::new();
let id = registry.allocate_origin(
MSAN_ORIGIN_CLEAN,
"test",
OriginKind::StackAllocation,
MTSanStackTrace::new(),
);
let formatted = registry.format_chain(id);
assert!(formatted.contains("test"));
assert!(formatted.contains("stack-allocation"));
}
#[test]
fn test_msan_options_default() {
let opts = MSanOptions::default();
assert!(opts.poison_in_free);
assert!(opts.report_umrs);
assert!(!opts.print_stats);
assert_eq!(opts.track_origins, 0);
}
#[test]
fn test_msan_options_parse() {
let mut opts = MSanOptions::default();
opts.parse_option("poison_in_free", "0");
assert!(!opts.poison_in_free);
opts.parse_option("track_origins", "2");
assert_eq!(opts.track_origins, 2);
opts.parse_option("verbosity", "3");
assert_eq!(opts.verbosity, 3);
}
#[test]
fn test_tsan_options_default() {
let opts = TSanOptions::default();
assert_eq!(opts.history_size, TSAN_DEFAULT_HISTORY_SIZE);
assert_eq!(opts.flush_memory_ms, TSAN_DEFAULT_FLUSH_MS);
assert!(!opts.force_seq_cst_atomics);
assert!(opts.report_thread_leaks);
assert!(opts.detect_deadlocks);
}
#[test]
fn test_tsan_options_parse() {
let mut opts = TSanOptions::default();
opts.parse_option("history_size", "4");
assert_eq!(opts.history_size, 4);
opts.parse_option("force_seq_cst_atomics", "1");
assert!(opts.force_seq_cst_atomics);
opts.parse_option("report_thread_leaks", "0");
assert!(!opts.report_thread_leaks);
}
#[test]
fn test_mtsan_flags_default() {
let flags = MTSanFlags::default();
assert!(flags.msan_enabled);
assert!(flags.tsan_enabled);
assert!(!flags.origins_enabled);
}
#[test]
fn test_mtsan_full_new() {
let runtime = X86MTSanFull::new();
assert!(!runtime.initialized);
assert!(runtime.msan_reports.is_empty());
assert!(runtime.tsan_reports.is_empty());
}
#[test]
fn test_mtsan_full_initialize() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
assert!(runtime.initialized);
}
#[test]
fn test_mtsan_full_msan_check_read_init() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x10000, 16);
let result = runtime.msan_check_read(0x10000, 8, 1);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_msan_check_read_uninit() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let result = runtime.msan_check_read(0x20000, 4, 1);
assert!(result.is_some());
let report = result.unwrap();
assert_eq!(report.kind, MSanErrorKind::UseOfUninitializedValue);
}
#[test]
fn test_mtsan_full_msan_set_get_shadow() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x30000, 4, 0xAB);
assert_eq!(runtime.msan_get_shadow(0x30000), 0xAB);
assert_eq!(runtime.msan_get_shadow(0x30001), 0xAB);
}
#[test]
fn test_mtsan_full_msan_propagate_binary() {
let mut runtime = X86MTSanFull::new();
let result = runtime.msan_propagate_binary("add", 0xFF, 0x00);
assert_eq!(result, MSAN_SHADOW_UNINIT);
let result = runtime.msan_propagate_binary("mul", 0x00, 0x00);
assert_eq!(result, MSAN_SHADOW_INIT);
}
#[test]
fn test_mtsan_full_msan_origin() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_shadow.track_origins = true;
let origin = runtime.msan_allocate_origin(
MSAN_ORIGIN_CLEAN,
"test_var",
OriginKind::StackAllocation,
);
assert!(origin > 0);
runtime.msan_set_origin(0x40000, 8, origin);
assert_eq!(runtime.msan_shadow.get_origin(0x40000), origin);
}
#[test]
fn test_mtsan_full_tsan_thread_create() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
assert!(tid > 0);
assert!(runtime.tsan_threads.contains_key(&tid));
}
#[test]
fn test_mtsan_full_tsan_load_store_no_race() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let result = runtime.tsan_load(tid, 0x50000, 4);
assert!(result.is_none());
let result = runtime.tsan_store(tid, 0x50000, 4);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_tsan_race_detection() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.tsan_store(tid1, 0x60000, 8);
let result = runtime.tsan_store(tid2, 0x60000, 8);
assert!(result.is_some());
assert!(runtime.tsan_race_count > 0);
assert!(!runtime.tsan_reports.is_empty());
}
#[test]
fn test_mtsan_full_tsan_happens_before_no_race() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.tsan_store(tid1, 0x70000, 4);
runtime.tsan_thread_join(tid2, tid1);
let result = runtime.tsan_load(tid2, 0x70000, 4);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_tsan_mutex_lock_unlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let mutex_id = runtime.tsan_mutex_create();
runtime.tsan_mutex_lock(tid1, mutex_id);
runtime.tsan_store(tid1, 0x80000, 4);
runtime.tsan_mutex_unlock(tid1, mutex_id);
runtime.tsan_mutex_lock(tid2, mutex_id);
let result = runtime.tsan_load(tid2, 0x80000, 4);
runtime.tsan_mutex_unlock(tid2, mutex_id);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_tsan_deadlock_detection() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let mutex_a = runtime.tsan_mutex_create();
let mutex_b = runtime.tsan_mutex_create();
runtime.tsan_mutex_lock(tid1, mutex_a);
runtime.tsan_mutex_lock(tid1, mutex_b);
runtime.tsan_mutex_lock(tid2, mutex_b);
let result = runtime.tsan_mutex_lock(tid2, mutex_a);
assert!(result.is_some());
}
#[test]
fn test_mtsan_full_tsan_atomic_load_store() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let r1 = runtime.tsan_atomic_store(tid1, 0x90000, 4, TSanAtomicOrdering::Release);
assert!(r1.is_none());
let r2 = runtime.tsan_atomic_load(tid2, 0x90000, 4, TSanAtomicOrdering::Acquire);
assert!(r2.is_none());
}
#[test]
fn test_mtsan_full_combined_load() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0xA0000, 16);
let tid = runtime.tsan_thread_create();
let (msan_err, tsan_race) = runtime.combined_load_check(tid, 0xA0000, 8);
assert!(msan_err.is_none()); assert!(tsan_race.is_none()); }
#[test]
fn test_mtsan_full_combined_store() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let result = runtime.combined_store_check(tid, 0xB0000, &[0x00; 8], MSAN_ORIGIN_CLEAN);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_pthread_create_join() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let parent_tid = runtime.tsan_thread_create();
let (child_tid, _) = runtime.combined_pthread_create(parent_tid);
assert!(child_tid > 0);
assert!(child_tid != parent_tid);
assert!(runtime.tsan_threads.contains_key(&child_tid));
let result = runtime.combined_pthread_join(parent_tid, child_tid);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_cond_wait_signal() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let mutex_id = runtime.tsan_mutex_create();
let cond_id: u64 = 1;
runtime.tsan_mutex_lock(tid1, mutex_id);
runtime.combined_cond_wait(tid1, cond_id, mutex_id);
runtime.tsan_mutex_unlock(tid1, mutex_id);
runtime.combined_cond_signal(tid2, cond_id);
}
#[test]
fn test_mtsan_full_barrier() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.combined_barrier_wait(tid1, 1);
runtime.combined_barrier_wait(tid2, 1);
}
#[test]
fn test_mtsan_full_semaphore() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.combined_sem_wait(tid1, 1);
runtime.combined_sem_post(tid2, 1);
}
#[test]
fn test_mtsan_full_signal_safety() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_signal_enter(tid, 2);
let result = runtime.tsan_check_signal_safety(tid, "printf");
assert!(result.is_some());
assert_eq!(result.unwrap().kind, TSanErrorKind::SignalUnsafeCall);
runtime.tsan_signal_exit(tid);
}
#[test]
fn test_mtsan_full_stats() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_store(tid, 0xC0000, 4);
runtime.msan_set_shadow(0xC0000, 4, MSAN_SHADOW_INIT);
runtime.msan_check_read(0xC0000, 4, tid);
let stats = runtime.stats();
assert!(stats.initialized);
assert!(stats.msan_access_count > 0);
assert!(stats.tsan_event_count > 0);
}
#[test]
fn test_mtsan_full_print_summary() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let summary = runtime.print_summary();
assert!(summary.contains("X86 MTSan Full Runtime Summary"));
assert!(summary.contains("MSan enabled"));
assert!(summary.contains("TSan enabled"));
}
#[test]
fn test_mtsan_full_has_errors_initial() {
let runtime = X86MTSanFull::new();
assert!(!runtime.has_errors());
}
#[test]
fn test_mtsan_full_thread_leak_detection() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_store(tid, 0xD0000, 4);
let leaks = runtime.check_thread_leaks();
assert!(!leaks.is_empty());
}
#[test]
fn test_mtsan_full_finalize() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.flags.msan_options.print_stats = false;
runtime.finalize();
}
#[test]
fn test_mtsan_full_msan_interceptors_memcpy() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0xE0000, 16, MSAN_SHADOW_INIT);
runtime.msan_set_shadow(0xE0000, 4, 0xFF);
runtime.msan_intercept_memcpy(0xE0100, 0xE0000, 16);
assert_eq!(runtime.msan_get_shadow(0xE0100), 0xFF);
assert_eq!(runtime.msan_get_shadow(0xE0104), MSAN_SHADOW_INIT);
}
#[test]
fn test_mtsan_full_msan_interceptors_memmove() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0xF0000, 16, MSAN_SHADOW_INIT);
runtime.msan_set_shadow(0xF0000, 2, 0xAB);
runtime.msan_intercept_memmove(0xF0002, 0xF0000, 8);
assert_eq!(runtime.msan_get_shadow(0xF0002), 0xAB);
}
#[test]
fn test_mtsan_full_msan_interceptors_memset() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x110000, 32, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_memset(0x110000, 32);
for i in 0..32 {
assert_eq!(runtime.msan_get_shadow(0x110000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_mtsan_full_atomic_fence_seq_cst() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.tsan_store(tid1, 0x120000, 4);
runtime.tsan_atomic_fence(tid1, TSanAtomicOrdering::SequentiallyConsistent);
runtime.tsan_atomic_fence(tid2, TSanAtomicOrdering::SequentiallyConsistent);
let result = runtime.tsan_load(tid2, 0x120000, 4);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_lock_order_inversion() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let mutex_a = runtime.tsan_mutex_create();
let mutex_b = runtime.tsan_mutex_create();
runtime.tsan_mutex_lock(tid1, mutex_a);
runtime.tsan_mutex_lock(tid1, mutex_b);
runtime.tsan_mutex_unlock(tid1, mutex_b);
runtime.tsan_mutex_unlock(tid1, mutex_a);
runtime.tsan_mutex_lock(tid2, mutex_b);
let result = runtime.tsan_mutex_lock(tid2, mutex_a);
assert!(
result.is_some()
|| runtime
.tsan_reports
.iter()
.any(|r| r.kind == TSanErrorKind::LockOrderInversion)
);
}
#[test]
fn test_mtsan_full_atomic_rmw() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let r1 = runtime.tsan_atomic_rmw(tid1, 0x130000, 4, TSanAtomicOrdering::AcquireRelease);
assert!(r1.is_none());
let r2 = runtime.tsan_atomic_rmw(tid2, 0x130000, 4, TSanAtomicOrdering::Acquire);
assert!(r2.is_none()); }
#[test]
fn test_mtsan_full_destroy_locked_mutex() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let mutex_id = runtime.tsan_mutex_create();
runtime.tsan_mutex_lock(tid, mutex_id);
let result = runtime.tsan_mutex_destroy(mutex_id);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, TSanErrorKind::DestroyLockedMutex);
}
#[test]
fn test_mtsan_full_unlock_not_owned() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let mutex_id = runtime.tsan_mutex_create();
runtime.tsan_mutex_lock(tid1, mutex_id);
let result = runtime.tsan_mutex_unlock(tid2, mutex_id);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, TSanErrorKind::UnlockNotOwned);
runtime.tsan_mutex_unlock(tid1, mutex_id);
}
#[test]
fn test_mtsan_full_msan_branch_check() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let result = runtime.msan_check_branch(MSAN_SHADOW_INIT, 1);
assert!(result.is_none());
let result = runtime.msan_check_branch(MSAN_SHADOW_UNINIT, 1);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, MSanErrorKind::UninitializedBranch);
}
#[test]
fn test_mtsan_full_msan_propagate_cmp() {
let mut runtime = X86MTSanFull::new();
let result = runtime.msan_propagate_cmp(0x00, 0x00);
assert_eq!(result, MSAN_SHADOW_INIT);
let result = runtime.msan_propagate_cmp(0xFF, 0x00);
assert_eq!(result, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_mtsan_full_msan_propagate_select() {
let mut runtime = X86MTSanFull::new();
let result = runtime.msan_propagate_select(0x00, 0x00, 0x00);
assert_eq!(result, MSAN_SHADOW_INIT);
let result = runtime.msan_propagate_select(0xFF, 0xAA, 0xBB);
assert_eq!(result, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_mtsan_full_suppressions() {
let supp_content = r#"
# Comment line
race:test_race_suppression
fun:test_func
src:test_file.c
deadlock:test_deadlock
fun:deadlock_func
"#;
let supps = TSanSuppressions::parse(supp_content);
assert_eq!(supps.len(), 2);
}
#[test]
fn test_mtsan_full_suppression_matching() {
let supp_content = "race:my_race\nfun:known_racy\n";
let supps = TSanSuppressions::parse(supp_content);
let mut report = TSanErrorReport::data_race(
0x100,
4,
1,
2,
true,
true,
MTSanStackTrace::new(),
MTSanStackTrace::new(),
);
report.stacks[0]
.frames
.push(MTSanStackFrame::with_symbol("known_racy"));
assert!(supps.is_suppressed(&report));
}
#[test]
fn test_mtsan_full_tsan_error_report_formatting() {
let report = TSanErrorReport::data_race(
0x200,
8,
1,
2,
true,
false,
MTSanStackTrace::capture(3),
MTSanStackTrace::capture(3),
);
let formatted = report.format();
assert!(formatted.contains("ThreadSanitizer"));
assert!(formatted.contains("data-race"));
assert!(formatted.contains("0x200"));
}
#[test]
fn test_mtsan_full_msan_error_report_formatting() {
let report = MSanErrorReport::new(
MSanErrorKind::UseOfUninitializedValue,
0x300,
4,
MSAN_ORIGIN_CLEAN,
MTSanStackTrace::capture(2),
1,
"test error",
);
let formatted = report.format(None);
assert!(formatted.contains("MemorySanitizer"));
assert!(formatted.contains("use-of-uninitialized-value"));
}
#[test]
fn test_mtsan_full_stack_trace() {
let mut trace = MTSanStackTrace::capture(5);
assert_eq!(trace.frames.len(), 5);
let formatted = trace.format();
assert!(!formatted.is_empty());
trace.symbolize();
for frame in &trace.frames {
assert!(frame.symbol.is_some());
}
}
#[test]
fn test_mtsan_full_stack_frame_format() {
let frame = MTSanStackFrame {
module: "mymodule".into(),
module_offset: 0x100,
symbol: Some("my_func".into()),
file: Some("myfile.c".into()),
line: Some(42),
column: Some(10),
};
let formatted = frame.format();
assert!(formatted.contains("my_func"));
assert!(formatted.contains("myfile.c"));
assert!(formatted.contains("42"));
}
#[test]
fn test_mtsan_full_tls_shadow() {
let mut tls = MSanTLSShadow::new();
assert!(!tls.active);
tls.init(1024);
assert!(tls.active);
assert_eq!(tls.tls_size, 1024);
tls.clear_tls_range(0, 64);
assert_eq!(tls.get_tls_shadow(0), MSAN_SHADOW_INIT);
assert_eq!(tls.get_tls_shadow(65), MSAN_SHADOW_UNINIT);
tls.set_tls_shadow(128, 0xAB);
tls.set_tls_origin(128, 42);
assert_eq!(tls.get_tls_shadow(128), 0xAB);
assert_eq!(tls.get_tls_origin(128), 42);
}
#[test]
fn test_mtsan_full_atomic_ordering_display() {
assert_eq!(format!("{}", TSanAtomicOrdering::Relaxed), "relaxed");
assert_eq!(format!("{}", TSanAtomicOrdering::Acquire), "acquire");
assert_eq!(format!("{}", TSanAtomicOrdering::Release), "release");
assert_eq!(format!("{}", TSanAtomicOrdering::AcquireRelease), "acq_rel");
assert_eq!(
format!("{}", TSanAtomicOrdering::SequentiallyConsistent),
"seq_cst"
);
}
#[test]
fn test_mtsan_full_origin_kind_display() {
assert_eq!(format!("{}", OriginKind::HeapAllocation), "heap-allocation");
assert_eq!(format!("{}", OriginKind::Deallocated), "deallocated-memory");
}
#[test]
fn test_mtsan_full_msan_propagate_ext() {
let s = MSanShadowPropagator::propagate_ext(0xAB);
assert_eq!(s, 0xAB);
}
#[test]
fn test_mtsan_full_msan_propagate_bitcast() {
let s = MSanShadowPropagator::propagate_bitcast(0xCD);
assert_eq!(s, 0xCD);
}
#[test]
fn test_mtsan_full_msan_propagate_gep() {
let s = MSanShadowPropagator::propagate_gep(0xEF, &[0x00, 0x00]);
assert_eq!(s, 0xEF);
}
#[test]
fn test_mtsan_full_msan_propagate_fbinary() {
let s = MSanShadowPropagator::propagate_fbinary(0x00, 0xFF);
assert_eq!(s, MSAN_SHADOW_UNINIT);
}
#[test]
fn test_mtsan_full_mtsan_stats_display() {
let stats = MTSanStats {
msan_access_count: 10,
msan_propagation_count: 5,
msan_shadow_bytes: 100,
msan_origin_entries: 3,
msan_error_count: 1,
tsan_thread_count: 2,
tsan_event_count: 20,
tsan_lock_ops: 4,
tsan_mutex_count: 1,
tsan_race_count: 0,
tsan_error_count: 0,
initialized: true,
};
let display = format!("{}", stats);
assert!(display.contains("10"));
assert!(display.contains("true"));
}
#[test]
fn test_mtsan_full_tsan_shadow_cell_history() {
let mut cell = TSanShadowCell::new(2);
for i in 1..6 {
let mut clock = VectorClock::new();
clock.set(i, i as u64);
cell.record_access(i, &clock, true, 4, false, None, MTSanStackTrace::new());
}
assert!(cell.history.len() <= 2);
}
#[test]
fn test_mtsan_full_lock_annotation() {
let ann = LockAnnotation::acquire(42);
assert_eq!(ann.capability, LockCapability::Mutex);
assert_eq!(ann.mutex_id, 42);
let ann = LockAnnotation::shared_acquire(100);
assert_eq!(ann.capability, LockCapability::RwLockShared);
}
#[test]
fn test_mtsan_full_init_with_subtarget() {
let mut runtime = X86MTSanFull::new();
let subtarget = X86Subtarget::default();
runtime.init_with_subtarget(&subtarget);
assert!(runtime.initialized);
assert!(runtime.subtarget.is_some());
assert!(runtime.calling_convention.is_some());
assert!(runtime.register_info.is_some());
}
#[test]
fn test_mtsan_full_deadlock_detector_self_cycle() {
let mut detector = TSanDeadlockDetector::new();
detector.acquired(1, 10);
let result = detector.try_acquire(1, 10);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_origin_constants() {
assert_eq!(MSAN_ORIGIN_CLEAN, 0);
assert_eq!(MSAN_ORIGIN_FREED, 0xFFFF_FFFE);
assert_eq!(MSAN_ORIGIN_STACK_UAR, 0xFFFF_FFFD);
assert_eq!(MSAN_ORIGIN_HEAP, 0xFFFF_FFFC);
assert_eq!(MSAN_ORIGIN_MAX, 0x7FFF_FFFF);
}
#[test]
fn test_mtsan_full_shadow_constants() {
assert_eq!(MSAN_SHADOW_SCALE, 1);
assert_eq!(MSAN_ORIGIN_SCALE, 4);
assert_eq!(TSAN_SHADOW_GRANULARITY, 8);
assert_eq!(TSAN_DEFAULT_HISTORY_SIZE, 2);
assert_eq!(MTSAN_MAX_STACK_DEPTH, 64);
}
#[test]
fn test_msan_intercept_strcmp() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x200000, 256);
let (s1_init, s2_init) = runtime.msan_intercept_strcmp(0x200000, 0x200010);
assert!(s1_init);
assert!(s2_init);
runtime.msan_set_shadow(0x200000, 4, MSAN_SHADOW_UNINIT);
let (s1_init, s2_init) = runtime.msan_intercept_strcmp(0x200000, 0x200010);
assert!(!s1_init);
}
#[test]
fn test_msan_intercept_strncmp() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x210000, 256);
let (s1_init, s2_init) = runtime.msan_intercept_strncmp(0x210000, 0x210010, 16);
assert!(s1_init);
assert!(s2_init);
runtime.msan_set_shadow(0x210010, 4, MSAN_SHADOW_UNINIT);
let (s1_init, s2_init) = runtime.msan_intercept_strncmp(0x210000, 0x210010, 16);
assert!(s1_init);
assert!(!s2_init);
}
#[test]
fn test_msan_intercept_memchr() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x220000, 256);
assert!(runtime.msan_intercept_memchr(0x220000, 128));
runtime.msan_set_shadow(0x220020, 1, MSAN_SHADOW_UNINIT);
assert!(!runtime.msan_intercept_memchr(0x220000, 128));
}
#[test]
fn test_msan_intercept_strchr() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x230000, 256);
assert!(runtime.msan_intercept_strchr(0x230000));
}
#[test]
fn test_msan_intercept_bcopy() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x240000, 32, 0xAB);
runtime.msan_intercept_bcopy(0x240000, 0x240100, 32);
assert_eq!(runtime.msan_get_shadow(0x240100), 0xAB);
}
#[test]
fn test_msan_intercept_bzero() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x250000, 32, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_bzero(0x250000, 32);
for i in 0..32 {
assert_eq!(runtime.msan_get_shadow(0x250000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_strtol() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x260000, 256);
assert!(runtime.msan_intercept_strtol(0x260000, 0x260100));
assert_eq!(runtime.msan_get_shadow(0x260100), MSAN_SHADOW_INIT);
}
#[test]
fn test_msan_intercept_read() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x270000, 64, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_read(0, 0x270000, 64);
for i in 0..64 {
assert_eq!(runtime.msan_get_shadow(0x270000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_write() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x280000, 64);
let result = runtime.msan_intercept_write(1, 0x280000, 64, 1);
assert!(result.is_none());
runtime.msan_set_shadow(0x280000, 4, MSAN_SHADOW_UNINIT);
let result = runtime.msan_intercept_write(1, 0x280000, 64, 1);
assert!(result.is_some());
}
#[test]
fn test_msan_intercept_stat() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x290000, 200, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_stat(0x290000, 0);
for i in 0..144 {
assert_eq!(runtime.msan_get_shadow(0x290000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_gettimeofday() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x300000, 32, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_gettimeofday(0x300000, 0x300020);
for i in 0..16 {
assert_eq!(runtime.msan_get_shadow(0x300000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_posix_memalign() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_intercept_posix_memalign(0x310000, 64, 128, 0);
for i in 0..128 {
assert_eq!(runtime.msan_get_shadow(0x310000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_getcwd() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x320000, 256, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_getcwd(0x320000, 256);
for i in 0..256 {
assert_eq!(runtime.msan_get_shadow(0x320000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_gethostname() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x330000, 256, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_gethostname(0x330000, 256);
for i in 0..256 {
assert_eq!(runtime.msan_get_shadow(0x330000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_mmap() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let result = runtime.msan_intercept_mmap(0x340000, 4096, 3, 0x22, -1, 0);
assert_eq!(result, 0x340000);
for i in 0..4096 {
assert_eq!(runtime.msan_get_shadow(0x340000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_munmap() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_clear_shadow(0x350000, 4096);
runtime.msan_intercept_munmap(0x350000, 4096);
for i in 0..4096 {
assert_eq!(runtime.msan_get_shadow(0x350000 + i), MSAN_SHADOW_UNINIT);
}
}
#[test]
fn test_msan_intercept_brk() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_intercept_brk(0x360100, 0x360000);
for i in 0..256 {
assert_eq!(runtime.msan_get_shadow(0x360000 + i), MSAN_SHADOW_INIT);
}
runtime.msan_intercept_brk(0x360000, 0x360100);
for i in 0..256 {
assert_eq!(runtime.msan_get_shadow(0x360000 + i), MSAN_SHADOW_UNINIT);
}
}
#[test]
fn test_tsan_rwlock_create_lock_unlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let rwlock = runtime.tsan_rwlock_create();
let result = runtime.tsan_rwlock_rdlock(tid, rwlock);
assert!(result.is_none());
let result = runtime.tsan_rwlock_unlock(tid, rwlock);
assert!(result.is_none());
}
#[test]
fn test_tsan_rwlock_wrlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let rwlock = runtime.tsan_rwlock_create();
let result = runtime.tsan_rwlock_wrlock(tid, rwlock);
assert!(result.is_none());
let result = runtime.tsan_rwlock_unlock(tid, rwlock);
assert!(result.is_none());
}
#[test]
fn test_tsan_rwlock_destroy_locked() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let rwlock = runtime.tsan_rwlock_create();
runtime.tsan_rwlock_wrlock(tid, rwlock);
let result = runtime.tsan_rwlock_destroy(rwlock);
assert!(result.is_some());
runtime.tsan_rwlock_unlock(tid, rwlock);
let result = runtime.tsan_rwlock_destroy(rwlock);
assert!(result.is_none());
}
#[test]
fn test_tsan_rwlock_tryrdlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let rwlock = runtime.tsan_rwlock_create();
assert!(runtime.tsan_rwlock_tryrdlock(tid, rwlock));
runtime.tsan_rwlock_unlock(tid, rwlock);
}
#[test]
fn test_tsan_rwlock_trywrlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let rwlock = runtime.tsan_rwlock_create();
assert!(runtime.tsan_rwlock_trywrlock(tid, rwlock));
runtime.tsan_rwlock_unlock(tid, rwlock);
}
#[test]
fn test_tsan_spinlock_create_lock_unlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
let spinlock = runtime.tsan_spinlock_create();
runtime.tsan_spinlock_lock(tid, spinlock);
runtime.tsan_store(tid, 0x370000, 4);
runtime.tsan_spinlock_unlock(tid, spinlock);
}
#[test]
fn test_tsan_spinlock_happens_before() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let spinlock = runtime.tsan_spinlock_create();
runtime.tsan_spinlock_lock(tid1, spinlock);
runtime.tsan_store(tid1, 0x380000, 4);
runtime.tsan_spinlock_unlock(tid1, spinlock);
runtime.tsan_spinlock_lock(tid2, spinlock);
let result = runtime.tsan_load(tid2, 0x380000, 4);
runtime.tsan_spinlock_unlock(tid2, spinlock);
assert!(result.is_none());
}
#[test]
fn test_tsan_external_acquire_release() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.tsan_store(tid1, 0x390000, 4);
runtime.tsan_external_release(tid1, 0x390000);
runtime.tsan_external_acquire(tid2, 0x390000);
let result = runtime.tsan_load(tid2, 0x390000, 4);
assert!(result.is_none());
}
#[test]
fn test_tsan_annotate_happens_before_after() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_annotate_happens_before(0x400000);
runtime.tsan_annotate_happens_after(tid, 0x400000);
}
#[test]
fn test_tsan_annotate_condvar() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.tsan_annotate_condvar_signal(tid1, 0x410000);
runtime.tsan_annotate_condvar_wait(tid2, 0x410000);
}
#[test]
fn test_tsan_annotate_benign_race() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.tsan_annotate_benign_race(0x420000, 4, "known benign race");
}
#[test]
fn test_race_deduplicator_new() {
let dedup = TSanRaceDeduplicator::new(100);
assert_eq!(dedup.reported_count(), 0);
}
#[test]
fn test_race_deduplicator_hash() {
let h1 = TSanRaceDeduplicator::hash_race(0x1000, 1, 2);
let h2 = TSanRaceDeduplicator::hash_race(0x1000, 2, 1);
assert_eq!(h1, h2);
let h3 = TSanRaceDeduplicator::hash_race(0x2000, 1, 2);
assert_ne!(h1, h3); }
#[test]
fn test_race_deduplicator_first_report() {
let mut dedup = TSanRaceDeduplicator::new(100);
assert!(dedup.should_report(0x1000, 1, 2));
assert_eq!(dedup.reported_count(), 1);
}
#[test]
fn test_race_deduplicator_duplicate() {
let mut dedup = TSanRaceDeduplicator::new(100);
dedup.throttle_interval = Duration::from_secs(10);
assert!(dedup.should_report(0x1000, 1, 2));
assert!(!dedup.should_report(0x1000, 1, 2));
}
#[test]
fn test_tsan_event_trace_record() {
let mut trace = TSanEventTrace::new(100);
let event = TSanTraceEvent {
event_type: TSanTraceEventType::Load,
thread_id: 1,
addr: 0x430000,
size: 4,
is_write: false,
is_atomic: false,
ordering: None,
timestamp: 1,
stack: MTSanStackTrace::capture(2),
};
trace.record(event);
assert_eq!(trace.len(), 1);
}
#[test]
fn test_tsan_event_trace_max_events() {
let mut trace = TSanEventTrace::new(3);
for i in 0..5 {
trace.record(TSanTraceEvent {
event_type: TSanTraceEventType::Load,
thread_id: 1,
addr: 0x440000 + i * 8,
size: 4,
is_write: false,
is_atomic: false,
ordering: None,
timestamp: i,
stack: MTSanStackTrace::new(),
});
}
assert_eq!(trace.len(), 3);
}
#[test]
fn test_tsan_event_trace_replay() {
let mut trace = TSanEventTrace::new(100);
trace.record(TSanTraceEvent {
event_type: TSanTraceEventType::ThreadCreate,
thread_id: 0,
addr: 0,
size: 0,
is_write: false,
is_atomic: false,
ordering: None,
timestamp: 0,
stack: MTSanStackTrace::new(),
});
trace.record(TSanTraceEvent {
event_type: TSanTraceEventType::Store,
thread_id: 1,
addr: 0x450000,
size: 4,
is_write: true,
is_atomic: false,
ordering: None,
timestamp: 1,
stack: MTSanStackTrace::new(),
});
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let reports = trace.replay_on(&mut runtime);
assert!(reports.is_empty());
}
#[test]
fn test_stack_trace_cache_new() {
let cache = StackTraceCache::new();
assert_eq!(cache.hits, 0);
assert_eq!(cache.misses, 0);
assert_eq!(cache.hit_rate(), 0.0);
}
#[test]
fn test_stack_trace_cache_get() {
let mut cache = StackTraceCache::new();
let trace1 = cache.get_or_capture(42, 3);
assert_eq!(cache.misses, 1);
let trace2 = cache.get_or_capture(42, 3);
assert_eq!(cache.hits, 1);
}
#[test]
fn test_stack_trace_cache_hit_rate() {
let mut cache = StackTraceCache::new();
cache.get_or_capture(1, 3);
cache.get_or_capture(2, 3);
cache.get_or_capture(1, 3);
assert!(cache.hit_rate() > 0.0);
assert_eq!(cache.hits, 1);
assert_eq!(cache.misses, 2);
}
#[test]
fn test_propagate_vector_binary() {
let a = vec![0x00, 0xFF, 0x00, 0xFF];
let b = vec![0x00, 0x00, 0xFF, 0xFF];
let result = MSanShadowPropagator::propagate_vector_binary(&a, &b);
assert_eq!(result, vec![0x00, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_propagate_vector_shuffle() {
let shadow = vec![0x01, 0x02, 0x03, 0x04];
let mask = vec![3, 1, 2, 0];
let result = MSanShadowPropagator::propagate_vector_shuffle(&shadow, &mask);
assert_eq!(result, vec![0x04, 0x02, 0x03, 0x01]);
}
#[test]
fn test_propagate_vector_shuffle_undef() {
let shadow = vec![0x01, 0x02];
let mask = vec![0, -1, 1, -1];
let result = MSanShadowPropagator::propagate_vector_shuffle(&shadow, &mask);
assert_eq!(result, vec![0x01, 0xFF, 0x02, 0xFF]);
}
#[test]
fn test_propagate_extractelement() {
let vec_shadow = vec![0x01, 0x02, 0x03, 0x04];
assert_eq!(
MSanShadowPropagator::propagate_extractelement(&vec_shadow, 0),
0x01
);
assert_eq!(
MSanShadowPropagator::propagate_extractelement(&vec_shadow, 3),
0x04
);
assert_eq!(
MSanShadowPropagator::propagate_extractelement(&vec_shadow, 99),
MSAN_SHADOW_UNINIT
);
}
#[test]
fn test_propagate_insertelement() {
let vec_shadow = vec![0x00; 4];
let result = MSanShadowPropagator::propagate_insertelement(&vec_shadow, 2, 0xFF);
assert_eq!(result, vec![0x00, 0x00, 0xFF, 0x00]);
}
#[test]
fn test_propagate_masked_load() {
let mut shadow = MSanShadowMemory::default();
shadow.clear_shadow_range(0x460000, 4);
shadow.set_shadow(0x460001, 0xFF);
let mask = vec![true, true, true, true];
let result = MSanShadowPropagator::propagate_masked_load(&shadow, 0x460000, &mask);
assert_eq!(result[0], MSAN_SHADOW_INIT);
assert_eq!(result[1], MSAN_SHADOW_UNINIT);
}
#[test]
fn test_extended_deadlock_detector_new() {
let detector = ExtendedDeadlockDetector::new(DeadlockStrategy::WaitForGraph);
assert_eq!(detector.strategy, DeadlockStrategy::WaitForGraph);
}
#[test]
fn test_extended_deadlock_detector_timeout() {
let mut detector = ExtendedDeadlockDetector::new(DeadlockStrategy::TimeoutHeuristic);
detector.timeout_threshold = Duration::from_millis(1);
detector.record_lock_acquired(1, 10);
assert!(detector.check_timeout().is_empty());
std::thread::sleep(Duration::from_millis(5));
let timed_out = detector.check_timeout();
assert!(!timed_out.is_empty());
assert_eq!(timed_out[0], 10);
}
#[test]
fn test_extended_deadlock_detector_combined() {
let mut detector = ExtendedDeadlockDetector::new(DeadlockStrategy::Combined);
detector.record_lock_acquired(1, 10);
detector.record_lock_acquired(2, 20);
detector.try_acquire(1, 20);
let result = detector.try_acquire(2, 10);
assert!(result.is_some());
}
#[test]
fn test_tsan_throttle_disabled() {
let mut throttle = TSanThrottle::new(1);
assert!(!throttle.enabled);
assert!(throttle.should_process());
assert_eq!(throttle.total_processed, 1);
assert_eq!(throttle.total_skipped, 0);
}
#[test]
fn test_tsan_throttle_sampling() {
let mut throttle = TSanThrottle::new(4);
assert!(throttle.enabled);
let mut processed = 0;
for _ in 0..12 {
if throttle.should_process() {
processed += 1;
}
}
assert_eq!(processed, 3);
assert_eq!(throttle.total_processed, 3);
assert_eq!(throttle.total_skipped, 9);
}
#[test]
fn test_tsan_throttle_skip_rate() {
let mut throttle = TSanThrottle::new(10);
for _ in 0..100 {
throttle.should_process();
}
assert!(throttle.skip_rate() > 0.5);
}
#[test]
fn test_mtsan_full_should_halt() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.flags.halt_on_error = false;
assert!(!runtime.should_halt());
runtime.flags.halt_on_error = true;
assert!(!runtime.should_halt());
runtime.msan_check_read(0x470000, 4, 1);
assert!(runtime.should_halt());
}
#[test]
fn test_mtsan_full_exit_code() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
assert_eq!(runtime.exit_code(), 0);
runtime.msan_check_read(0x480000, 4, 1);
assert_eq!(runtime.exit_code(), MTSAN_DEFAULT_EXITCODE);
}
#[test]
fn test_mtsan_full_reset() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_store(tid, 0x490000, 4);
runtime.msan_set_shadow(0x490000, 4, 0xFF);
assert!(!runtime.tsan_threads.is_empty());
runtime.reset();
assert!(runtime.tsan_threads.is_empty());
assert!(runtime.tsan_shadow.is_empty());
assert_eq!(runtime.msan_access_count, 0);
assert_eq!(runtime.tsan_event_count, 0);
}
#[test]
fn test_mtsan_full_fork_child() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x500000, 4, 0xAB);
let tid = runtime.tsan_thread_create();
runtime.handle_fork_child();
assert_eq!(runtime.msan_get_shadow(0x500000), 0xAB);
assert!(runtime.tsan_threads.is_empty());
assert!(runtime.tsan_shadow.is_empty());
}
#[test]
fn test_mtsan_full_global_tick() {
let runtime = X86MTSanFull::new();
assert_eq!(runtime.global_tick(), 0);
runtime.tick();
assert_eq!(runtime.global_tick(), 1);
}
#[test]
fn test_mtsan_full_dump_state() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let state = runtime.dump_state();
assert!(state.contains("X86MTSanFull"));
assert!(state.contains("Initialized:"));
}
#[test]
fn test_mtsan_full_live_thread_count() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
assert_eq!(runtime.live_thread_count(), 2);
runtime.tsan_thread_join(tid1, tid2);
assert_eq!(runtime.live_thread_count(), 1);
runtime.detach_thread(tid1);
assert_eq!(runtime.live_thread_count(), 0);
}
#[test]
fn test_mtsan_full_thread_name() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
assert!(runtime.thread_name(tid).is_none());
runtime.set_thread_name(tid, "worker-1");
assert_eq!(runtime.thread_name(tid), Some("worker-1"));
}
#[test]
fn test_mtsan_full_is_in_signal_handler() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
assert!(!runtime.is_in_signal_handler(tid));
runtime.tsan_signal_enter(tid, 2);
assert!(runtime.is_in_signal_handler(tid));
runtime.tsan_signal_exit(tid);
assert!(!runtime.is_in_signal_handler(tid));
}
#[test]
fn test_mtsan_full_verify_shadow_consistency() {
let runtime = X86MTSanFull::new();
assert!(runtime.verify_shadow_consistency());
}
#[test]
fn test_mtsan_full_periodic_flush() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
for i in 0..100 {
let tid = runtime.tsan_thread_create();
runtime.tsan_store(tid, 0x500000 + i * 8, 1);
}
runtime.periodic_flush();
}
#[test]
fn test_msan_intercept_realpath() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x510000, 4096, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_realpath(0x510000, 0x511000);
for i in 0..4096 {
assert_eq!(runtime.msan_get_shadow(0x511000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_readlink() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x520000, 256, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_readlink(0x520000, 0x520100, 256, 32);
for i in 0..32 {
assert_eq!(runtime.msan_get_shadow(0x520100 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_clock_gettime() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x530000, 32, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_clock_gettime(0x530000);
for i in 0..16 {
assert_eq!(runtime.msan_get_shadow(0x530000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_pthread_getspecific() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x540000, 8, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_pthread_getspecific(0, 0x540000);
for i in 0..8 {
assert_eq!(runtime.msan_get_shadow(0x540000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_aligned_alloc() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_intercept_aligned_alloc(64, 256, 0x550000);
for i in 0..256 {
assert_eq!(runtime.msan_get_shadow(0x550000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_intercept_getenv() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_set_shadow(0x560000, 4096, MSAN_SHADOW_UNINIT);
runtime.msan_intercept_getenv(0x560000);
for i in 0..4096 {
assert_eq!(runtime.msan_get_shadow(0x560000 + i), MSAN_SHADOW_INIT);
}
}
#[test]
fn test_msan_mapping_tracker_add_find() {
let mut tracker = MSanMappingTracker::new();
tracker.add_mapping(0x570000, 4096, 3, 0x22);
let mapping = tracker.find_mapping(0x570000);
assert!(mapping.is_some());
assert_eq!(mapping.unwrap().size, 4096);
let mapping = tracker.find_mapping(0x571000);
assert!(mapping.is_some());
let mapping = tracker.find_mapping(0x580000);
assert!(mapping.is_none());
}
#[test]
fn test_msan_mapping_tracker_remove() {
let mut tracker = MSanMappingTracker::new();
tracker.add_mapping(0x590000, 4096, 3, 0x22);
tracker.add_mapping(0x591000, 4096, 3, 0x22);
tracker.remove_mapping(0x590000, 4096);
assert!(tracker.find_mapping(0x590000).is_none());
assert!(tracker.find_mapping(0x591000).is_some());
}
#[test]
fn test_msan_mapping_tracker_mark_initialized() {
let mut tracker = MSanMappingTracker::new();
tracker.add_mapping(0x600000, 4096, 3, 0x22);
tracker.mark_initialized(0x600000, 256);
let mapping = tracker.find_mapping(0x600000).unwrap();
assert!(mapping.is_initialized);
}
#[test]
fn test_tsan_annotate_ignore_reads() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.tsan_annotate_ignore_reads_begin();
runtime.tsan_annotate_ignore_reads_end();
}
#[test]
fn test_tsan_annotate_ignore_writes() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.tsan_annotate_ignore_writes_begin();
runtime.tsan_annotate_ignore_writes_end();
}
#[test]
fn test_tsan_annotate_rwlock() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_annotate_rwlock_create(0x610000);
runtime.tsan_annotate_rwlock_acquired(tid, 0x610000, true);
runtime.tsan_annotate_rwlock_released(tid, 0x610000, true);
}
#[test]
fn test_mtsan_full_complex_scenario() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
runtime.msan_shadow.track_origins = true;
let main_tid = runtime.tsan_thread_create();
let worker_tid = runtime.tsan_thread_create();
let mem = 0x620000u64;
let size = 64usize;
runtime.msan_clear_shadow(mem, size);
let origin = runtime.msan_allocate_origin(
MSAN_ORIGIN_CLEAN,
"main stack allocation",
OriginKind::StackAllocation,
);
runtime.msan_set_origin(mem, size, origin);
let mutex = runtime.tsan_mutex_create();
runtime.tsan_mutex_lock(main_tid, mutex);
runtime.combined_store_check(main_tid, mem, &[MSAN_SHADOW_INIT; 8], origin);
runtime.tsan_mutex_unlock(main_tid, mutex);
runtime.tsan_mutex_lock(worker_tid, mutex);
let (msan_err, tsan_race) = runtime.combined_load_check(worker_tid, mem, 8);
runtime.tsan_mutex_unlock(worker_tid, mutex);
assert!(msan_err.is_none());
assert!(tsan_race.is_none());
runtime.combined_pthread_join(main_tid, worker_tid);
runtime.finalize();
}
#[test]
fn test_mtsan_full_race_without_sync() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
runtime.tsan_store(tid1, 0x630000, 4);
let race = runtime.tsan_store(tid2, 0x630000, 4);
assert!(race.is_some());
assert!(!runtime.tsan_reports.is_empty());
}
#[test]
fn test_mtsan_full_uninit_across_function_boundary() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let origin = runtime.msan_allocate_origin(
MSAN_ORIGIN_CLEAN,
"uninit param x",
OriginKind::FunctionParameter,
);
let param_addr: u64 = 0x640000;
runtime.msan_set_shadow(param_addr, 4, MSAN_SHADOW_UNINIT);
runtime.msan_set_origin(param_addr, 4, origin);
let result = runtime.msan_check_read(param_addr, 4, 1);
assert!(result.is_some());
let report = result.unwrap();
assert_eq!(report.origin, origin);
}
#[test]
fn test_mtsan_full_mixed_atomics_and_mutexes() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid1 = runtime.tsan_thread_create();
let tid2 = runtime.tsan_thread_create();
let mutex = runtime.tsan_mutex_create();
runtime.tsan_atomic_store(tid1, 0x650000, 4, TSanAtomicOrdering::Release);
runtime.tsan_mutex_lock(tid2, mutex);
let result = runtime.tsan_atomic_load(tid2, 0x650000, 4, TSanAtomicOrdering::Acquire);
runtime.tsan_mutex_unlock(tid2, mutex);
assert!(result.is_none());
}
#[test]
fn test_mtsan_full_many_threads() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let mut threads = Vec::new();
for _ in 0..50 {
let tid = runtime.tsan_thread_create();
threads.push(tid);
}
assert_eq!(threads.len(), 50);
assert_eq!(runtime.tsan_threads.len(), 50);
for &tid in &threads {
runtime.tsan_store(tid, 0x660000, 4);
}
assert!(runtime.tsan_race_count > 0);
}
#[test]
fn test_mtsan_full_signal_handler_race_protection() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let tid = runtime.tsan_thread_create();
runtime.tsan_store(tid, 0x670000, 4);
runtime.tsan_signal_enter(tid, 2);
let result = runtime.tsan_store(tid, 0x670000, 4);
assert!(result.is_none());
runtime.tsan_signal_exit(tid);
}
#[test]
fn test_tsan_error_report_deadlock_format() {
let cycle = vec![1, 2, 3];
let stacks: Vec<_> = cycle.iter().map(|_| MTSanStackTrace::capture(3)).collect();
let report = TSanErrorReport::deadlock(cycle, stacks);
let formatted = report.format();
assert!(formatted.contains("deadlock"));
assert!(formatted.contains("1"));
}
#[test]
fn test_tsan_error_report_thread_leak_format() {
let report = TSanErrorReport::thread_leak(42, MTSanStackTrace::capture(2));
let formatted = report.format();
assert!(formatted.contains("thread-leak"));
assert!(formatted.contains("42"));
}
#[test]
fn test_tsan_error_report_signal_unsafe_format() {
let report =
TSanErrorReport::signal_unsafe_call(1, "malloc", 2, MTSanStackTrace::capture(2));
let formatted = report.format();
assert!(formatted.contains("signal-unsafe-call"));
assert!(formatted.contains("malloc"));
}
#[test]
fn test_msan_error_report_with_origin() {
let mut runtime = X86MTSanFull::new();
runtime.initialize();
let origin = runtime.msan_allocate_origin(
MSAN_ORIGIN_CLEAN,
"test origin",
OriginKind::HeapAllocation,
);
let report = MSanErrorReport::new(
MSanErrorKind::UseOfUninitializedValue,
0x680000,
4,
origin,
MTSanStackTrace::capture(2),
1,
"test",
);
let formatted = report.format(Some(&runtime.origin_registry));
assert!(formatted.contains("test origin"));
assert!(formatted.contains("heap-allocation"));
}
#[test]
fn test_suppression_parse_multiple_entries() {
let content = r#"
race:suppress_race_1
fun:racy_func_1
race:suppress_race_2
src:file2.c
module:mymodule
thread_leak:leak_suppression
fun:leaky_func
"#;
let supps = TSanSuppressions::parse(content);
assert_eq!(supps.len(), 3);
}
#[test]
fn test_suppression_match_type_wildcard() {
let content = "*:match_all\nfun:any_func\n";
let supps = TSanSuppressions::parse(content);
let report = TSanErrorReport::data_race(
0x100,
4,
1,
2,
true,
true,
MTSanStackTrace::new(),
MTSanStackTrace::new(),
);
assert!(!supps.is_suppressed(&report));
}
#[test]
fn test_msan_error_kind_display_all() {
let kinds = [
MSanErrorKind::UseOfUninitializedValue,
MSanErrorKind::UninitializedBranch,
MSanErrorKind::UninitializedCondition,
MSanErrorKind::UninitializedSyscallArg,
MSanErrorKind::UninitializedFree,
MSanErrorKind::MemoryLeak,
MSanErrorKind::DoubleFreeUninitialized,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_tsan_error_kind_display_all() {
let kinds = [
TSanErrorKind::DataRace,
TSanErrorKind::MutexDeadlock,
TSanErrorKind::LockOrderInversion,
TSanErrorKind::ThreadLeak,
TSanErrorKind::SignalUnsafeCall,
TSanErrorKind::UseAfterFreeRace,
TSanErrorKind::DoubleLock,
TSanErrorKind::UnlockNotOwned,
TSanErrorKind::DestroyLockedMutex,
];
for kind in &kinds {
let s = format!("{}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_default_implementations() {
let _shadow = MSanShadowMemory::default();
let _registry = OriginRegistry::default();
let _interceptors = MSanInterceptors::default();
let _tls = MSanTLSShadow::default();
let _signal_ctx = MSanSignalContext::default();
let _deadlock = TSanDeadlockDetector::default();
let _safety = TSanSignalSafety::default();
let _suppressions = TSanSuppressions::default();
let _options_msan = MSanOptions::default();
let _options_tsan = TSanOptions::default();
let _flags = MTSanFlags::default();
let _runtime = X86MTSanFull::default();
let _trace_cache = StackTraceCache::default();
let _dedup = TSanRaceDeduplicator::default();
let _throttle = TSanThrottle::default();
let _event_trace = TSanEventTrace::default();
let _ext_deadlock = ExtendedDeadlockDetector::default();
let _mapping_tracker = MSanMappingTracker::default();
}
#[test]
fn test_vector_clock_default() {
let vc = VectorClock::default();
assert!(vc.is_empty());
}
#[test]
fn test_mtsan_flags_from_env() {
let saved = std::env::var("MSAN_OPTIONS").ok();
std::env::set_var("MSAN_OPTIONS", "poison_in_free=0:track_origins=2");
let flags = MTSanFlags::from_env();
assert!(!flags.msan_options.poison_in_free);
assert_eq!(flags.msan_options.track_origins, 2);
assert!(flags.origins_enabled);
if let Some(val) = saved {
std::env::set_var("MSAN_OPTIONS", val);
} else {
std::env::remove_var("MSAN_OPTIONS");
}
}
}