Skip to content

Commit

Permalink
add alloc_hater and fuzz tests (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
dataphract authored Aug 16, 2022
1 parent 8fbfdbc commit be172ce
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 18 deletions.
28 changes: 16 additions & 12 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ jobs:
matrix:
features:
- sptr
- sptr,alloc
- unstable
- unstable,alloc
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
Expand All @@ -37,30 +35,37 @@ jobs:
toolchain: nightly
override: true
- run: rustup component add clippy
# alloc disabled
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --no-default-features --features=${{ matrix.features }} -- -D warnings
args: --no-default-features --features=${{ matrix.features }} -- -D warnings
# alloc enabled
- uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace --all-targets --no-default-features --features=sptr,alloc -- -D warnings

test_stable:
name: test (stable)
runs-on: ubuntu-latest
strategy:
matrix:
features:
- sptr
- sptr,alloc
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
# alloc disabled
- uses: actions-rs/cargo@v1
with:
command: test
args: --no-default-features --features=sptr
# alloc enabled
- uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --no-default-features --features=${{ matrix.features }}
args: --workspace --no-default-features --features=sptr,alloc

test_nightly:
name: test (nightly)
Expand All @@ -69,17 +74,16 @@ jobs:
matrix:
features:
- sptr
- sptr,alloc
- unstable
- unstable,alloc
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
# alloc disabled
- uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --no-default-features --features=${{ matrix.features }}
args: --no-default-features --features=${{ matrix.features }}
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ keywords = ["allocator", "no_std"]
all-features = true
rustdoc-args = ["--cfg", "docs_rs"]

[workspace]
members = [
"acid_alloc_hater",
"alloc_hater",
]

[features]
default = ["sptr"]

Expand Down
10 changes: 10 additions & 0 deletions acid_alloc_hater/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "acid_alloc_hater"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
acid_alloc = { path = "../", features = ["alloc", "sptr"] }
alloc_hater = { path = "../alloc_hater" }
37 changes: 37 additions & 0 deletions acid_alloc_hater/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#![deny(unsafe_op_in_unsafe_fn)]

use std::{alloc::Layout, ops::Range, ptr::NonNull};

use acid_alloc::{AllocInitError, Buddy, Global};
use alloc_hater::Subject;

pub struct BuddySubject<const BLK_SIZE: usize, const LEVELS: usize>(
Buddy<BLK_SIZE, LEVELS, Global>,
);

impl<const BLK_SIZE: usize, const LEVELS: usize> BuddySubject<BLK_SIZE, LEVELS> {
pub fn new(num_blocks: usize) -> Result<Self, AllocInitError> {
let b = Buddy::try_new(num_blocks)?;
Ok(BuddySubject(b))
}

pub fn new_with_offset_gaps(
num_blocks: usize,
gaps: impl IntoIterator<Item = Range<usize>>,
) -> Result<Self, AllocInitError> {
let b = Buddy::try_new_with_offset_gaps(num_blocks, gaps)?;
Ok(BuddySubject(b))
}
}

impl<const BLK_SIZE: usize, const LEVELS: usize> Subject for BuddySubject<BLK_SIZE, LEVELS> {
type AllocError = acid_alloc::AllocError;

fn allocate(&mut self, layout: Layout) -> Result<NonNull<[u8]>, Self::AllocError> {
self.0.allocate(layout)
}

unsafe fn deallocate(&mut self, ptr: NonNull<u8>, _layout: std::alloc::Layout) {
unsafe { self.0.deallocate(ptr) };
}
}
10 changes: 10 additions & 0 deletions alloc_hater/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "alloc_hater"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
arbitrary = { version = "1.1.3", features = ["derive"] }
quickcheck = "1.0.3"
169 changes: 169 additions & 0 deletions alloc_hater/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//! A small library for ~~hating on~~ evaluating the correctness of allocators.
#![deny(unsafe_op_in_unsafe_fn)]

use core::{alloc::Layout, mem::MaybeUninit, ptr::NonNull, slice};
use std::cmp;

#[derive(arbitrary::Arbitrary)]
enum AllocatorOpTag {
Alloc,
Dealloc,
}

#[derive(Clone, Debug)]
pub enum AllocatorOp {
Alloc(Layout),
Dealloc(usize),
}

impl arbitrary::Arbitrary<'_> for AllocatorOp {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let tag = AllocatorOpTag::arbitrary(u)?;

let op = match tag {
AllocatorOpTag::Alloc => {
// Select a random bit index and shift to obtain a power of two.
let align_shift = u8::arbitrary(u)? % usize::BITS as u8;
let align: usize = 1 << align_shift;
assert!(align.is_power_of_two());

// Clamp size to prevent Layout creation errors.
let size = cmp::min(usize::arbitrary(u)?, isize::MAX as usize - (align - 1));

let layout = match Layout::from_size_align(size, align) {
Ok(l) => l,
Err(_) => {
panic!("invalid layout params: size=0x{size:X} align=0x{align:X}");
}
};

AllocatorOp::Alloc(layout)
}

AllocatorOpTag::Dealloc => AllocatorOp::Dealloc(usize::arbitrary(u)?),
};

Ok(op)
}
}

pub trait Subject {
type AllocError;

/// Allocates a block of memory according to `layout`.
fn allocate(&mut self, layout: Layout) -> Result<NonNull<[u8]>, Self::AllocError>;

/// Deallocates the block of memory with layout `layout` pointed to by `ptr`.
///
/// # Safety
///
/// `ptr` must denote a block of memory currently allocated by this
/// allocator, and it must have been allocated with `layout`.
unsafe fn deallocate(&mut self, ptr: NonNull<u8>, layout: Layout);
}

struct Block {
// A pointer to the allocated region.
ptr: NonNull<[u8]>,
// The original allocation layout.
layout: Layout,
// The unique ID of the last operation that wrote to this allocation.
id: u64,
}

unsafe fn paint(ptr: NonNull<[u8]>, id: u64) {
let slice: &mut [MaybeUninit<u8>] =
unsafe { slice::from_raw_parts_mut(ptr.cast().as_ptr(), ptr.len()) };
let id_bytes = id.to_le_bytes().into_iter().cycle();

for (byte, value) in slice.iter_mut().zip(id_bytes) {
byte.write(value);
}
}

impl Block {
unsafe fn init(ptr: NonNull<[u8]>, layout: Layout, id: u64) -> Block {
unsafe { paint(ptr, id) };

Block { ptr, layout, id }
}

unsafe fn paint(&mut self, id: u64) {
unsafe { paint(self.ptr, id) };
}

// Safety: must be initialized
unsafe fn verify(&self) -> bool {
let slice: &[u8] = unsafe { self.ptr.as_ref() };
let id_bytes = self.id.to_le_bytes().into_iter().cycle();

for (byte, value) in slice.iter().zip(id_bytes) {
if *byte != value {
return false;
}
}

true
}
}

pub struct Evaluator<S: Subject> {
subject: S,
}

#[derive(Clone, Debug)]
pub struct Failed {
pub completed: Vec<AllocatorOp>,
pub failed_op: AllocatorOp,
}

impl<S: Subject> Evaluator<S> {
pub fn new(subject: S) -> Evaluator<S> {
Evaluator { subject }
}

pub fn evaluate(&mut self, ops: impl IntoIterator<Item = AllocatorOp>) -> Result<(), Failed> {
let mut completed = Vec::new();
let mut blocks = Vec::new();

for (op_id, op) in ops.into_iter().enumerate() {
match op {
AllocatorOp::Alloc(layout) => {
let ptr = match self.subject.allocate(layout) {
Ok(p) => p,
Err(_) => continue,
};

let id: u64 = op_id.try_into().unwrap();
let block = unsafe { Block::init(ptr, layout, id) };
blocks.push(block);
}

AllocatorOp::Dealloc(raw_idx) => {
if blocks.is_empty() {
continue;
}

let idx = raw_idx % blocks.len();
let mut block = blocks.swap_remove(idx);
if unsafe { !block.verify() } {
return Err(Failed {
completed,
failed_op: op,
});
}

let id: u64 = op_id.try_into().unwrap();
unsafe {
block.paint(id);
self.subject.deallocate(block.ptr.cast(), block.layout);
}
}
}

completed.push(op);
}

Ok(())
}
}
3 changes: 3 additions & 0 deletions fuzz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
target
corpus
artifacts
35 changes: 35 additions & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "acid_alloc_fuzz"
version = "0.0.0"
authors = ["Automatically generated"]
publish = false
edition = "2018"

[package.metadata]
cargo-fuzz = true

[dependencies]
acid_alloc_hater = { path = "../acid_alloc_hater" }
alloc_hater = { path = "../alloc_hater" }
arbitrary = { version = "1.1.3", features = ["derive"] }
libfuzzer-sys = "0.4"

[dependencies.acid_alloc]
path = ".."
features = ["alloc", "sptr"]

# Prevent this from interfering with workspaces
[workspace]
members = ["."]

[[bin]]
name = "buddy_contiguous"
path = "fuzz_targets/buddy_contiguous.rs"
test = false
doc = false

[[bin]]
name = "buddy_discontiguous"
path = "fuzz_targets/buddy_discontiguous.rs"
test = false
doc = false
37 changes: 37 additions & 0 deletions fuzz/fuzz_targets/buddy_contiguous.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#![no_main]
#![feature(allocator_api)]

use acid_alloc_hater::BuddySubject;
use alloc_hater::AllocatorOp;
use arbitrary::{Arbitrary, Unstructured};
use libfuzzer_sys::fuzz_target;

const BLK_SIZE: usize = 16384;
const LEVELS: usize = 8;

const MAX_BLOCKS: usize = 1024;

#[derive(Clone, Debug)]
struct Args {
num_blocks: usize,
ops: Vec<AllocatorOp>,
}

impl Arbitrary<'_> for Args {
fn arbitrary(un: &mut Unstructured) -> arbitrary::Result<Args> {
let num_blocks = usize::arbitrary(un)? % MAX_BLOCKS;
let ops = Vec::arbitrary(un)?;

Ok(Args { num_blocks, ops })
}
}

fuzz_target!(|args: Args| {
let buddy: BuddySubject<BLK_SIZE, LEVELS> = match BuddySubject::new(args.num_blocks) {
Ok(a) => a,
Err(_) => return,
};

let mut eval = alloc_hater::Evaluator::new(buddy);
eval.evaluate(args.ops).unwrap();
});
Loading

0 comments on commit be172ce

Please sign in to comment.