extern crate core;
extern crate libc;
extern crate object_alloc;
extern crate rand;
extern crate test;
extern crate twox_hash;
use self::core::fmt;
use self::core::fmt::{Debug, Formatter};
use self::test::black_box;
use self::object_alloc::ObjectAlloc;
union CorruptionTester<T: Copy> {
_min: [u8; MIN_SIZE],
_t: T,
}
pub struct CorruptionTesterDefault<T: Copy = [u8; MIN_SIZE]>(CorruptionTester<T>);
pub struct CorruptionTesterUnsafe<T: Copy = [u8; MIN_SIZE]>(CorruptionTester<T>);
use self::wrapper::*;
mod wrapper {
#[derive(PartialEq, Debug)]
pub enum State {
New,
Valid,
Dropped,
Invalid,
}
pub trait CorruptionTesterWrapper {
fn state(&self) -> State;
fn update_new(&mut self, check_state: bool);
}
}
const MIN_SIZE: usize = core::mem::size_of::<Header>() + 1;
#[derive(Debug)]
struct Header {
state_nonce: u32,
hash: u32,
}
#[derive(PartialEq)]
enum Initializer {
Default,
Unsafe,
}
const NONCE_NEW: u32 = 3_254_320_468;
const NONCE_VALID: u32 = 3_033_817_838;
const NONCE_DROPPED: u32 = 2_620_515_550;
impl<T: Copy> Default for CorruptionTesterDefault<T> {
fn default() -> CorruptionTesterDefault<T> {
use self::core::mem::uninitialized;
let mut tester = unsafe { uninitialized::<CorruptionTesterDefault<T>>() };
CorruptionTester::init(&mut tester.0, Initializer::Default);
assert_eq!(tester.0.state(Initializer::Default), State::New);
tester
}
}
pub unsafe fn unsafe_default<T: Copy>(ptr: *mut CorruptionTesterUnsafe<T>) {
let state = (*ptr).0.state(Initializer::Unsafe);
assert!(state == State::Invalid || state == State::Dropped);
CorruptionTester::init(&mut (*ptr).0, Initializer::Unsafe);
assert_eq!((*ptr).0.state(Initializer::Unsafe), State::New);
}
impl<T: Copy> Drop for CorruptionTesterDefault<T> {
fn drop(&mut self) {
self.0.drop(Initializer::Default);
}
}
impl<T: Copy> Drop for CorruptionTesterUnsafe<T> {
fn drop(&mut self) {
self.0.drop(Initializer::Unsafe);
}
}
impl<T: Copy> Debug for CorruptionTester<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let (hdr, slc) = self.to_header_and_slice();
write!(f, "{{header: {:?} hash: {:?}}}", hdr, slc)
}
}
impl<T: Copy> CorruptionTesterWrapper for CorruptionTesterDefault<T> {
fn state(&self) -> State {
self.0.state(Initializer::Default)
}
fn update_new(&mut self, check_state: bool) {
self.0.update_new(check_state, Initializer::Default);
}
}
impl<T: Copy> CorruptionTesterWrapper for CorruptionTesterUnsafe<T> {
fn state(&self) -> State {
self.0.state(Initializer::Unsafe)
}
fn update_new(&mut self, check_state: bool) {
self.0.update_new(check_state, Initializer::Unsafe);
}
}
impl<T: Copy> CorruptionTester<T> {
fn init(ptr: *mut CorruptionTester<T>, init: Initializer) {
unsafe {
{
use self::core::ptr::write;
let (hdr, slc) = (*ptr).to_header_and_slice_mut();
for elem in slc.iter_mut() {
*elem = self::rand::random();
}
let ptr_arg = match init {
Initializer::Default => 0,
Initializer::Unsafe => ptr as usize,
};
write(hdr,
Header {
state_nonce: NONCE_NEW,
hash: Self::hash(ptr_arg, NONCE_NEW, slc),
});
}
assert_eq!((*ptr).state(init), State::New);
}
}
fn state(&self, init: Initializer) -> State {
let (hdr, slc) = self.to_header_and_slice();
if hdr.state_nonce == NONCE_NEW {
let ptr = match init {
Initializer::Default => 0,
Initializer::Unsafe => self as *const CorruptionTester<T> as usize,
};
let hash = Self::hash(ptr, hdr.state_nonce, slc);
if hdr.hash == hash {
State::New
} else {
State::Invalid
}
} else {
let hash = Self::hash(self as *const CorruptionTester<T> as usize,
hdr.state_nonce,
slc);
let tuple = (hdr.state_nonce, hdr.hash);
if tuple == (NONCE_VALID, hash) {
State::Valid
} else if tuple == (NONCE_DROPPED, hash) {
State::Dropped
} else {
State::Invalid
}
}
}
fn update_new(&mut self, check_state: bool, init: Initializer) {
if check_state {
assert_eq!(self.state(init), State::New);
}
self.update_state(State::Valid);
}
fn update_state(&mut self, new_state: State) {
let nonce = match new_state {
State::Valid => NONCE_VALID,
State::Dropped => NONCE_DROPPED,
_ => unreachable!(),
};
let ptr = self as *mut CorruptionTester<T> as usize;
let (hdr, slc) = self.to_header_and_slice_mut();
hdr.state_nonce = nonce;
hdr.hash = Self::hash(ptr, nonce, slc);
}
fn drop(&mut self, init: Initializer) {
use std::thread::panicking;
if panicking() {
return;
}
assert!(!(self as *mut CorruptionTester<T>).is_null());
let state = self.state(init);
if state != State::Valid && state != State::New {
let addr = self as *mut CorruptionTester<T> as usize;
panic!("unexpected state {:?} for object {:?} at address {}",
state,
self,
addr);
}
self.update_state(State::Dropped);
black_box(self);
}
fn to_header_and_slice(&self) -> (&Header, &[u8]) {
use self::core::mem::{size_of, transmute};
use self::core::slice::from_raw_parts;
let size = size_of::<CorruptionTester<T>>();
let header_size = size_of::<Header>();
unsafe {
let ptr = self as *const CorruptionTester<T> as usize;
(transmute(ptr), from_raw_parts((ptr + header_size) as *const u8, (size - header_size)))
}
}
#[cfg_attr(feature="cargo-clippy", allow(wrong_self_convention))]
fn to_header_and_slice_mut(&mut self) -> (&mut Header, &mut [u8]) {
use self::core::mem::{size_of, transmute};
use self::core::slice::from_raw_parts_mut;
let size = size_of::<CorruptionTester<T>>();
let header_size = size_of::<Header>();
unsafe {
let ptr = self as *mut CorruptionTester<T> as usize;
(transmute(ptr),
from_raw_parts_mut((ptr + header_size) as *mut u8, (size - header_size)))
}
}
fn hash(ptr: usize, state_nonce: u32, random_bytes: &[u8]) -> u32 {
use self::twox_hash::XxHash;
use self::core::hash::Hasher;
let mut hasher = XxHash::with_seed(ptr as u64);
hasher.write_u32(state_nonce);
hasher.write(random_bytes);
hasher.finish() as u32
}
}
mod mapped {
#[cfg(unix)]
extern crate libc;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate winapi;
#[cfg(unix)]
lazy_static!{
static ref RANDOM_FD: i32 = unsafe {
use std::ffi::CString;
let cstr = CString::new("/dev/random").unwrap();
let fd = libc::open(cstr.as_ptr(), libc::O_WRONLY);
assert!(fd >= 0);
fd
};
}
#[cfg(unix)]
pub fn is_mapped_range(ptr: *mut u8, len: usize) -> bool {
unsafe {
let ret = libc::write(*RANDOM_FD, ptr as *const libc::c_void, len);
assert!(ret < 0 || (ret as usize) == len);
(ret as usize) == len
}
}
#[cfg(windows)]
pub fn is_mapped_range(ptr: *mut u8, len: usize) -> bool {
use core::mem::{uninitialized, size_of};
use self::kernel32::{K32QueryWorkingSet, GetCurrentProcess};
use self::winapi::c_void;
use self::winapi::basetsd::ULONG_PTR;
#[repr(C)]
struct WorkingSets {
num_entries: ULONG_PTR,
entries: [PSAPI_WORKING_SET_BLOCK; 1024],
}
unsafe {
let mut entries = uninitialized::<WorkingSets>();
let ptr = &mut entries as *mut _ as *mut c_void;
let size = size_of::<WorkingSets>() as u32;
assert_eq!(K32QueryWorkingSet(GetCurrentProcess(), ptr, size), 1);
}
false
}
#[cfg(test)]
mod tests {
extern crate core;
#[test]
fn test_is_mapped_range() {
use super::is_mapped_range;
use self::core::ptr::null_mut;
use self::core::mem::size_of;
let arr = Box::new([0 as usize; 100]);
unsafe {
let ptr = Box::into_raw(arr);
assert!(is_mapped_range(ptr as *mut u8, size_of::<[usize; 100]>()));
assert!(!is_mapped_range(null_mut(), 1));
Box::from_raw(ptr); }
}
}
}
use self::core::marker::PhantomData;
pub struct TestBuilder<T, O: ObjectAlloc<T>, F: Fn() -> O> {
new: F,
test_iters: usize,
qc_tests: Option<usize>,
_marker: PhantomData<(T, O)>,
}
impl<T, O: ObjectAlloc<T>, F: Fn() -> O> TestBuilder<T, O, F> {
pub fn new(new: F) -> TestBuilder<T, O, F> {
TestBuilder {
new,
test_iters: 100_000,
qc_tests: None,
_marker: PhantomData,
}
}
pub fn test_iters(mut self, iters: usize) -> TestBuilder<T, O, F> {
self.test_iters = iters;
self
}
pub fn quickcheck_tests(mut self, tests: usize) -> TestBuilder<T, O, F> {
self.qc_tests = Some(tests);
self
}
}
impl<T: Copy, O: ObjectAlloc<CorruptionTesterDefault<T>>, F: Fn() -> O>
TestBuilder<CorruptionTesterDefault<T>, O, F>
where T: Send + 'static,
O: 'static,
F: Send + Sync + 'static
{
pub fn quickcheck(self) {
self::quickcheck::test(self.new, self.qc_tests)
}
pub fn test(self) {
self.priv_test()
}
}
impl<T: Copy, O: ObjectAlloc<CorruptionTesterUnsafe<T>>, F: Fn() -> O>
TestBuilder<CorruptionTesterUnsafe<T>, O, F>
where T: Send + 'static,
O: 'static,
F: Send + Sync + 'static
{
pub fn quickcheck(self) {
self::quickcheck::test(self.new, self.qc_tests)
}
pub fn test(self) {
self.priv_test()
}
}
impl<C: CorruptionTesterWrapper, O: ObjectAlloc<C>, F: Fn() -> O> TestBuilder<C, O, F> {
fn priv_test(self) {
let mut tester = Tester::new((self.new)());
for _ in 0..self.test_iters {
if rand::random() && !tester.alloced.is_empty() {
tester.dealloc(rand::random());
} else {
tester.alloc();
}
}
tester.drop_and_check();
}
}
mod quickcheck {
extern crate core;
extern crate quickcheck;
use self::quickcheck::{Arbitrary, Gen, QuickCheck, Testable, TestResult};
use super::*;
use self::core::marker::PhantomData;
pub fn test<C, O, F>(new: F, tests: Option<usize>)
where C: CorruptionTesterWrapper + Send + 'static,
O: ObjectAlloc<C> + 'static,
F: Fn() -> O + Send + Sync + 'static
{
let mut qc = QuickCheck::new();
if let Some(tests) = tests {
qc = qc.tests(tests).max_tests(tests);
}
qc.quickcheck(TestGenerator(new, PhantomData));
}
struct TestGenerator<C, O, F>(F, PhantomData<C>)
where C: CorruptionTesterWrapper,
O: ObjectAlloc<C>,
F: Fn() -> O;
impl<C, O, F> Testable for TestGenerator<C, O, F>
where C: CorruptionTesterWrapper + Send + 'static,
O: ObjectAlloc<C> + 'static,
F: Fn() -> O + Send + 'static
{
fn result<G: Gen>(&self, g: &mut G) -> TestResult {
let ops = Vec::<AllocOp>::arbitrary(g);
run_alloc_sequence(Tester::new(self.0()), &ops)
}
}
#[derive(Copy, Clone, Debug)]
pub enum AllocOp {
Alloc,
Dealloc(usize),
}
impl Arbitrary for AllocOp {
fn arbitrary<G: Gen>(g: &mut G) -> AllocOp {
if g.gen() {
AllocOp::Alloc
} else {
AllocOp::Dealloc(g.gen())
}
}
}
fn run_alloc_sequence<C: CorruptionTesterWrapper, O: ObjectAlloc<C>>(mut tester: Tester<C, O>,
seq: &[AllocOp])
-> TestResult {
for op in seq {
match *op {
AllocOp::Alloc => tester.alloc(),
AllocOp::Dealloc(idx) => {
if idx < tester.alloced.len() {
tester.dealloc(idx);
} else {
tester.drop_and_check();
return TestResult::discard();
}
}
}
}
tester.drop_and_check();
TestResult::passed()
}
}
use std::collections::HashSet;
struct Tester<C: CorruptionTesterWrapper, O: ObjectAlloc<C>> {
alloc: O,
alloced: Vec<*mut C>,
alloced_set: HashSet<*mut C>,
freed: HashSet<*mut C>,
}
impl<C: CorruptionTesterWrapper, O: ObjectAlloc<C>> Tester<C, O> {
fn new(alloc: O) -> Tester<C, O> {
Tester {
alloc: alloc,
alloced: Vec::new(),
alloced_set: HashSet::new(),
freed: HashSet::new(),
}
}
fn alloc(&mut self) {
let obj = unsafe { self.alloc.alloc().unwrap() };
assert!(!self.alloced_set.contains(&obj));
if self.freed.remove(&obj) {
match unsafe { (*obj).state() } {
State::New => unsafe {
(*obj).update_new(false);
},
State::Valid => {}
state => {
panic!("newly-allocated object at {:?} in unexpected state {:?}",
obj,
state);
}
}
} else {
match unsafe { (*obj).state() } {
State::New => unsafe {
(*obj).update_new(false);
},
state => {
panic!("newly-allocated object at {:?} in unexpected state {:?}",
obj,
state);
}
}
}
self.alloced.push(obj);
self.alloced_set.insert(obj);
}
fn dealloc(&mut self, idx: usize) {
let len = self.alloced.len();
let obj: *mut C = self.alloced.swap_remove(idx % len);
assert_eq!(unsafe { (*obj).state() }, State::Valid);
self.alloced_set.remove(&obj);
self.freed.insert(obj);
unsafe {
self.alloc.dealloc(obj);
}
}
fn drop_and_check(mut self) {
use self::core::mem::drop;
while !self.alloced.is_empty() {
let idx = self.alloced.len() - 1;
self.dealloc(idx);
}
let Tester { alloc, freed, .. } = self;
drop(alloc);
for obj in freed {
use self::core::mem;
if !mapped::is_mapped_range(obj as *mut u8, mem::size_of::<C>()) {
continue;
}
match unsafe { (*obj).state() } {
State::Invalid | State::Dropped => {}
state => {
panic!("freed object at {:?} in unexpected state: {:?}", obj, state);
}
}
}
}
}
#[cfg(test)]
pub mod tests {
extern crate core;
extern crate rand;
extern crate test;
use super::*;
use self::core::{mem, ptr};
fn make_default_boxed() -> Box<CorruptionTesterDefault> {
Box::new(CorruptionTesterDefault::default())
}
fn make_unsafe_default_boxed() -> Box<CorruptionTesterUnsafe> {
unsafe {
let obj = mem::uninitialized::<CorruptionTesterUnsafe>();
let mut bx = Box::new(obj);
use self::core::ops::DerefMut;
unsafe_default(bx.deref_mut());
bx
}
}
fn test_corruption_tester_helper<C: CorruptionTesterWrapper>(mut obj: Box<C>) {
assert!(obj.state() == State::New);
obj.update_new(true);
assert!(obj.state() == State::Valid);
let ptr = Box::into_raw(obj);
unsafe {
mem::drop(Box::from_raw(ptr));
assert!((*ptr).state() == State::Dropped);
}
}
#[test]
fn test_corruption_tester() {
test_corruption_tester_helper(make_default_boxed());
test_corruption_tester_helper(make_unsafe_default_boxed());
}
fn test_corruption_tester_corruption_helper<C: CorruptionTesterWrapper>(mut a: Box<C>,
mut b: Box<C>) {
assert!(a.state() == State::New);
assert!(b.state() == State::New);
a.update_new(true);
b.update_new(true);
assert!(a.state() == State::Valid);
assert!(b.state() == State::Valid);
use self::core::ops::DerefMut;
unsafe { ptr::copy(a.deref_mut(), b.deref_mut(), 1) };
assert!(a.state() == State::Valid);
assert!(b.state() == State::Invalid);
mem::forget(b); }
#[test]
fn test_corruption_tester_corruption() {
test_corruption_tester_corruption_helper(make_default_boxed(), make_default_boxed());
test_corruption_tester_corruption_helper(make_unsafe_default_boxed(),
make_unsafe_default_boxed());
}
fn test_corruption_tester_corruption_panic_on_drop_helper<C: CorruptionTesterWrapper>() {
use self::core::mem::zeroed;
let _tester: C = unsafe { zeroed() };
}
#[test]
#[should_panic]
fn test_corruption_tester_corruption_panic_on_drop() {
test_corruption_tester_corruption_panic_on_drop_helper::<CorruptionTesterDefault>();
test_corruption_tester_corruption_panic_on_drop_helper::<CorruptionTesterUnsafe>();
}
}