use super::*;
use core::ptr::NonNull;
use mnemosyne_backend::MemoryBackendWrapper;
use mnemosyne_core::constants::{
MAX_ALLOC_SIZE, PAGE_SHIFT, PAGE_SIZE, PAGES_PER_SEGMENT, SEGMENT_SIZE,
};
use mnemosyne_core::policy::StandardPolicy;
use mnemosyne_core::types::{Block, Segment};
#[test]
fn usable_size_returns_block_size_for_small_allocations() {
for &(req_size, req_align) in &[(8usize, 8usize), (16, 8), (32, 16), (64, 8), (1024, 8)] {
let ptr =
unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(req_size, req_align) };
assert!(
!ptr.is_null(),
"alloc({req_size}, {req_align}) returned null"
);
let reported = unsafe { usable_size(ptr) };
assert!(
reported >= req_size,
"usable_size({req_size}, {req_align}) = {reported} is below the request"
);
assert!(
reported >= req_align,
"usable_size({req_size}, {req_align}) = {reported} is below the adjusted minimum (alignment)"
);
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let page = unsafe { &(*segment).pages[page_index] };
assert_eq!(
reported, page.block_size,
"usable_size disagrees with the page's recorded block_size"
);
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
}
}
#[test]
fn usable_size_never_under_reports_across_every_size_class() {
use mnemosyne_core::NUM_SIZE_CLASSES;
use mnemosyne_core::size_class::class_to_size;
for class in 0..NUM_SIZE_CLASSES {
let class_max = class_to_size(class);
let prev_max = if class == 0 {
0
} else {
class_to_size(class - 1)
};
for &req in &[prev_max + 1, class_max] {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(req, 8) };
assert!(
!ptr.is_null(),
"alloc({req}) returned null for class {class}"
);
let reported = unsafe { usable_size(ptr) };
assert!(
reported >= req,
"usable_size under-reported for class {class}: requested {req}, got {reported}"
);
assert_eq!(
reported, class_max,
"usable_size for request {req} (class {class}) should equal class max {class_max}"
);
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
}
}
}
#[test]
fn usable_size_returns_payload_remainder_for_huge_allocations() {
let request = 4 * 1024 * 1024;
for &align in &[8usize, 64 * 1024, 1024 * 1024, SEGMENT_SIZE] {
let ptr = unsafe {
mnemosyne_arena::allocate_large_or_huge::<MemoryBackendWrapper>(request, align, true)
};
assert!(!ptr.is_null(), "huge allocation failed for align {align}");
let reported = unsafe { usable_size(ptr) };
assert!(
reported >= request,
"usable_size = {reported} is below the requested huge size {request} for align {align}"
);
let recovered = unsafe { *((ptr as *mut *mut Segment).sub(1)) };
let _released = unsafe {
mnemosyne_arena::deallocate_large_or_huge::<MemoryBackendWrapper>(ptr, recovered)
};
}
}
#[test]
fn usable_size_does_not_over_report_past_mapping_end_for_huge_allocations() {
let request = 4 * 1024 * 1024;
for &align in &[8usize, 64 * 1024, 1024 * 1024, SEGMENT_SIZE] {
let ptr = unsafe {
mnemosyne_arena::allocate_large_or_huge::<MemoryBackendWrapper>(request, align, true)
};
assert!(!ptr.is_null(), "huge allocation failed for align {align}");
let recovered = unsafe { *((ptr as *mut *mut Segment).sub(1)) };
let huge_size = unsafe { (*recovered).pages[0].block_size };
let raw_ptr = unsafe { (*recovered).raw_alloc_ptr } as usize;
let mapping_end = raw_ptr + huge_size;
let actual_remaining = mapping_end - ptr as usize;
let reported = unsafe { usable_size(ptr) };
assert!(
reported <= actual_remaining,
"usable_size {} exceeds remaining mapping {} (raw_ptr={:#x}, ptr={:?}, huge_size={}) for align {align}",
reported,
actual_remaining,
raw_ptr,
ptr,
huge_size,
);
assert!(
reported >= request,
"usable_size {} is below requested {} for align {align}",
reported,
request,
);
let _released = unsafe {
mnemosyne_arena::deallocate_large_or_huge::<MemoryBackendWrapper>(ptr, recovered)
};
}
}
#[test]
fn usable_size_returns_zero_for_null_pointer() {
let reported = unsafe { usable_size(core::ptr::null_mut()) };
assert_eq!(reported, 0);
}
#[test]
fn small_alloc_returns_block_aligned_ptr_outside_metadata_page() {
for &(req_size, req_align) in &[(8usize, 8usize), (16, 8), (32, 16), (64, 8), (1024, 8)] {
let ptr =
unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(req_size, req_align) };
assert!(
!ptr.is_null(),
"alloc({req_size}, {req_align}) returned null"
);
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
assert!(
page_index >= 1,
"alloc({req_size}, {req_align}) ptr {ptr:?} landed in metadata Page 0"
);
assert!(
page_index < PAGES_PER_SEGMENT,
"alloc({req_size}, {req_align}) page_index {page_index} >= PAGES_PER_SEGMENT"
);
let page = unsafe { &(*segment).pages[page_index] };
assert!(
page.block_size > 0,
"alloc({req_size}, {req_align}) targeted an uninitialized page"
);
let offset = ptr_val & (PAGE_SIZE - 1);
assert_eq!(
offset % page.block_size,
0,
"alloc({req_size}, {req_align}) ptr is not aligned to block stride {} of its size class",
page.block_size,
);
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
}
}
#[test]
fn reentrant_current_segment_local_free_uses_metadata_fast_path() {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(32, 8) };
assert!(
!ptr.is_null(),
"reentrant local-free setup allocation failed"
);
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let page = unsafe { &mut (*segment).pages[page_index] };
assert_eq!(page.alloc_count, 1);
assert!(
page.thread_free.is_empty(),
"thread_free list should start empty before reentrant free"
);
MemoryBackendWrapper::with_allocator(|_| {
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
});
assert_eq!(page.alloc_count, 0);
assert!(
page.thread_free.is_empty(),
"current-segment local free should not enqueue into page-local thread_free"
);
assert_eq!(page.free.map(NonNull::as_ptr), Some(ptr as *mut Block));
}
#[test]
fn current_segment_free_keeps_occupancy_mask_conservative() {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(32, 8) };
assert!(!ptr.is_null(), "current-segment mask allocation failed");
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let mask = 1u32 << page_index;
assert!(
unsafe { (*segment).is_current },
"test allocation must come from the current slicing segment"
);
assert_ne!(
unsafe { (*segment).page_occupied_mask } & mask,
0,
"allocation must mark the owning page occupied"
);
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
assert_eq!(unsafe { (*segment).pages[page_index].alloc_count }, 0);
assert_ne!(
unsafe { (*segment).page_occupied_mask } & mask,
0,
"current-segment free keeps a conservative mask bit for hot reuse"
);
}
#[test]
fn thread_alloc_rejects_invalid_alignment_requests() {
for &align in &[0usize, 3, 6, 12, SEGMENT_SIZE * 2] {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(64, align) };
assert!(
ptr.is_null(),
"invalid alignment {align} should be rejected"
);
}
}
#[test]
fn thread_alloc_rejects_zero_size_requests() {
for &align in &[1usize, 8, 16, PAGE_SIZE] {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(0, align) };
assert!(ptr.is_null(), "zero-size allocation should be rejected");
}
}
#[test]
fn thread_alloc_rejects_size_above_layout_bound() {
let ptr =
unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(MAX_ALLOC_SIZE + 1, 8) };
assert!(
ptr.is_null(),
"above-MAX_ALLOC_SIZE thread_alloc returned {ptr:?}"
);
}
#[test]
fn thread_alloc_layout_uses_layout_validated_fast_entry() {
let ptr = unsafe { thread_alloc_layout::<StandardPolicy, MemoryBackendWrapper>(64, 8) };
assert!(
!ptr.is_null(),
"Layout-validated thread_alloc fast entry returned null"
);
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
let oversized = unsafe {
thread_alloc_layout::<StandardPolicy, MemoryBackendWrapper>(64, SEGMENT_SIZE * 2)
};
assert!(
oversized.is_null(),
"Layout-validated oversized alignment returned {oversized:?}"
);
}
#[test]
fn thread_alloc_cold_charges_one_defrag_operation_per_page_refill() {
let _guard = crate::local_alloc::TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let worker = std::thread::spawn(|| {
let before = MemoryBackendWrapper::with_allocator(|alloc| {
assert_eq!(
alloc.page_refills, 0,
"fresh worker allocator should start with no page refills"
);
alloc.defrag_counter
})
.expect("fresh worker allocator slot must initialize");
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(8192, 8) };
assert!(!ptr.is_null(), "8192-byte allocation failed");
let after = MemoryBackendWrapper::with_allocator(|alloc| {
(alloc.defrag_counter, alloc.page_refills)
})
.expect("worker allocator slot must remain accessible");
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
(before, after.0, after.1)
});
let (before, after, refills) = worker
.join()
.expect("defrag accounting worker thread panicked");
assert_eq!(refills, 1, "single cold allocation should refill one page");
assert_eq!(
after,
before + 1,
"single page refill should charge exactly one defrag operation"
);
}
#[test]
fn hardened_policy_round_trip_alloc_free() {
use mnemosyne_core::policy::HardenedPolicy;
let _guard = crate::local_alloc::TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let ptr = unsafe { thread_alloc::<HardenedPolicy, MemoryBackendWrapper>(32, 8) };
assert!(!ptr.is_null(), "HardenedPolicy small allocation failed");
let slice = unsafe { core::slice::from_raw_parts(ptr, 32) };
for &byte in slice {
assert_eq!(
byte, 0,
"HardenedPolicy allocation was not zero-initialized"
);
}
unsafe {
core::ptr::write_bytes(ptr, 0x42, 32);
}
unsafe {
thread_free::<HardenedPolicy, MemoryBackendWrapper>(ptr);
}
}
#[test]
fn hardened_policy_detects_freelist_tamper() {
use mnemosyne_core::policy::HardenedPolicy;
let _guard = crate::local_alloc::TEST_LOCK
.lock()
.expect("local allocator test lock was poisoned");
let ptr1 = unsafe { thread_alloc::<HardenedPolicy, MemoryBackendWrapper>(16, 8) };
let ptr2 = unsafe { thread_alloc::<HardenedPolicy, MemoryBackendWrapper>(16, 8) };
assert!(!ptr1.is_null());
assert!(!ptr2.is_null());
unsafe {
thread_free::<HardenedPolicy, MemoryBackendWrapper>(ptr1);
thread_free::<HardenedPolicy, MemoryBackendWrapper>(ptr2);
}
let val2 = ptr2 as *mut usize;
unsafe {
let original_val = *val2;
*val2 = original_val ^ 0x08;
}
let ptr3 = unsafe { thread_alloc::<HardenedPolicy, MemoryBackendWrapper>(16, 8) };
assert_eq!(ptr3, ptr2);
let ptr_val = ptr3 as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let page = unsafe { (*segment).pages.get_unchecked(page_index) };
let free_head = page.free.map(|p| p.as_ptr() as usize);
assert_ne!(
free_head,
Some(ptr1 as usize),
"HardenedPolicy failed to obscure/randomize the tampered pointer"
);
}
#[test]
fn test_dealloc_path() {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(1024, 8) };
assert!(!ptr.is_null());
unsafe { thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr) };
}
#[test]
fn test_double_free_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_DOUBLE_FREE_ABORT_TEST").is_ok() {
unsafe {
let ptr = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_double_free_aborts_process")
.arg("--exact")
.env("RUN_DOUBLE_FREE_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_reclaim_overflow_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_RECLAIM_OVERFLOW_ABORT_TEST").is_ok() {
unsafe {
let ptr = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let page = &mut (*segment).pages[page_index];
page.set_alloc_count_for_segment(segment, page_index, 0);
let block = ptr as *mut Block;
page.thread_free
.push::<StandardPolicy>(NonNull::new_unchecked(block));
page.reclaim_thread_free::<StandardPolicy>();
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_reclaim_overflow_aborts_process")
.arg("--exact")
.env("RUN_RECLAIM_OVERFLOW_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_cross_thread_double_free_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_CROSS_THREAD_DOUBLE_FREE_ABORT_TEST").is_ok() {
let ptr = unsafe { thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8) };
let ptr_val = ptr as usize;
let handle = std::thread::spawn(move || unsafe {
let ptr = ptr_val as *mut u8;
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
});
let _ = handle.join();
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_cross_thread_double_free_aborts_process")
.arg("--exact")
.env("RUN_CROSS_THREAD_DOUBLE_FREE_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_local_immediate_double_free_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_LOCAL_IMMEDIATE_DOUBLE_FREE_ABORT_TEST").is_ok() {
unsafe {
let ptr1 = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
let _ptr2 = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr1);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr1);
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_local_immediate_double_free_aborts_process")
.arg("--exact")
.env("RUN_LOCAL_IMMEDIATE_DOUBLE_FREE_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_cpu_cache_double_free_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_CPU_CACHE_DOUBLE_FREE_ABORT_TEST").is_ok() {
unsafe {
crate::per_cpu::PER_CPU_CACHE_ENABLED
.store(true, core::sync::atomic::Ordering::Relaxed);
crate::per_cpu::enable_cpu_cache();
let ptr = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
let ptr_val = ptr as usize;
let handle = std::thread::spawn(move || {
let ptr = ptr_val as *mut u8;
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
});
let _ = handle.join();
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_cpu_cache_double_free_aborts_process")
.arg("--exact")
.env("RUN_CPU_CACHE_DOUBLE_FREE_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_large_alloc_metadata_corruption_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_LARGE_ALLOC_METADATA_CORRUPTION_ABORT_TEST").is_ok() {
unsafe {
let ptr = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(65536, 8);
assert!(!ptr.is_null());
let metadata_slot = (ptr as *mut usize).sub(1);
metadata_slot.write(0x1337);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_large_alloc_metadata_corruption_aborts_process")
.arg("--exact")
.env("RUN_LARGE_ALLOC_METADATA_CORRUPTION_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_large_alloc_segment_invariant_corruption_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_LARGE_ALLOC_SEGMENT_INVARIANT_CORRUPTION_ABORT_TEST").is_ok() {
unsafe {
let ptr = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(65536, 8);
assert!(!ptr.is_null());
let segment_ptr = *((ptr as *mut *mut Segment).sub(1));
(*segment_ptr).raw_alloc_ptr = core::ptr::null_mut();
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_large_alloc_segment_invariant_corruption_aborts_process")
.arg("--exact")
.env(
"RUN_LARGE_ALLOC_SEGMENT_INVARIANT_CORRUPTION_ABORT_TEST",
"1",
)
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_free_list_corruption_out_of_bounds_aborts_process() {
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_FREE_LIST_CORRUPTION_OUT_OF_BOUNDS_ABORT_TEST").is_ok() {
unsafe {
let ptr = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
assert!(!ptr.is_null());
let ptr_val = ptr as usize;
let segment_addr = ptr_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
thread_free::<StandardPolicy, MemoryBackendWrapper>(ptr);
let cookie = (*segment).keys[page_index];
let corrupt_block = ptr as *mut Block;
let bad_ptr = 0x12345678 as *mut Block;
(*corrupt_block).set_next::<StandardPolicy>(NonNull::new(bad_ptr), cookie);
let _ptr_new = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
let _ptr_another = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_free_list_corruption_out_of_bounds_aborts_process")
.arg("--exact")
.env("RUN_FREE_LIST_CORRUPTION_OUT_OF_BOUNDS_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}
#[test]
fn test_thread_free_cycle_aborts_process() {
use mnemosyne_core::policy::AllocPolicy;
use std::env;
use std::process::Command;
use std::string::String;
if env::var("RUN_THREAD_FREE_CYCLE_ABORT_TEST").is_ok() {
unsafe {
let ptr1 = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
let ptr2 = thread_alloc::<StandardPolicy, MemoryBackendWrapper>(16, 8);
let ptr1_val = ptr1 as usize;
let segment_addr = ptr1_val & !(SEGMENT_SIZE - 1);
let segment = segment_addr as *mut Segment;
let page_index = (ptr1_val >> PAGE_SHIFT) & (PAGES_PER_SEGMENT - 1);
let page = &mut (*segment).pages[page_index];
let block1 = ptr1 as *mut Block;
let block2 = ptr2 as *mut Block;
let cookie = if StandardPolicy::ENABLE_FREE_LIST_ENCRYPTION {
(*segment).keys[page_index]
} else {
0
};
page.thread_free
.push::<StandardPolicy>(NonNull::new_unchecked(block2));
page.thread_free
.push::<StandardPolicy>(NonNull::new_unchecked(block1));
(*block2).set_next::<StandardPolicy>(NonNull::new(block1), cookie);
page.reclaim_thread_free::<StandardPolicy>();
}
return;
}
let current_exe = env::current_exe().unwrap();
let output = Command::new(current_exe)
.arg("tests::test_thread_free_cycle_aborts_process")
.arg("--exact")
.env("RUN_THREAD_FREE_CYCLE_ABORT_TEST", "1")
.output()
.unwrap();
if output.status.success() {
std::println!(
"Subprocess stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
std::println!(
"Subprocess stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
panic!("Subprocess succeeded but was expected to abort!");
}
}