Skip to content

Commit 9a0e6a1

Browse files
authored
Merge pull request #33 from alexcrichton/miri-and-fuzz-and-update
Perform some long-needed maintenance here
2 parents 0551725 + 8c4ff07 commit 9a0e6a1

File tree

14 files changed

+283
-114
lines changed

14 files changed

+283
-114
lines changed

.github/workflows/main.yml

+29-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
matrix:
1010
rust: [stable, beta, nightly]
1111
steps:
12-
- uses: actions/checkout@master
12+
- uses: actions/checkout@v4
1313
- name: Install Rust (
1414
run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }}
1515
- run: cargo test
@@ -24,7 +24,7 @@ jobs:
2424
name: Rustfmt
2525
runs-on: ubuntu-latest
2626
steps:
27-
- uses: actions/checkout@master
27+
- uses: actions/checkout@v4
2828
- name: Install Rust
2929
run: rustup update stable && rustup default stable && rustup component add rustfmt
3030
- run: cargo fmt -- --check
@@ -33,7 +33,7 @@ jobs:
3333
name: WebAssembly
3434
runs-on: ubuntu-latest
3535
steps:
36-
- uses: actions/checkout@master
36+
- uses: actions/checkout@v4
3737
- name: Install Rust
3838
run: rustup update stable && rustup default stable && rustup target add wasm32-unknown-unknown
3939
- run: cargo build --target wasm32-unknown-unknown
@@ -43,7 +43,32 @@ jobs:
4343
name: external-platform
4444
runs-on: ubuntu-latest
4545
steps:
46-
- uses: actions/checkout@master
46+
- uses: actions/checkout@v4
4747
- name: Install Rust
4848
run: rustup update stable && rustup default stable && rustup target add x86_64-fortanix-unknown-sgx
4949
- run: cargo build --target x86_64-fortanix-unknown-sgx
50+
51+
fuzz:
52+
name: Build Fuzzers
53+
runs-on: ubuntu-latest
54+
steps:
55+
- uses: actions/checkout@v4
56+
- name: Install Rust
57+
run: rustup update nightly && rustup default nightly
58+
- run: cargo install cargo-fuzz
59+
- run: cargo fuzz build --dev
60+
61+
miri:
62+
name: Miri
63+
runs-on: ubuntu-latest
64+
steps:
65+
- uses: actions/checkout@v4
66+
- name: Install Miri
67+
run: |
68+
rustup toolchain install nightly --component miri
69+
rustup override set nightly
70+
cargo miri setup
71+
- name: Test with Miri
72+
run: cargo miri test
73+
env:
74+
MIRIFLAGS: -Zmiri-tree-borrows

Cargo.toml

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ documentation = "https://docs.rs/dlmalloc"
1010
description = """
1111
A Rust port of the dlmalloc allocator
1212
"""
13+
edition.workspace = true
14+
15+
[workspace]
16+
members = ['fuzz']
17+
18+
[workspace.package]
19+
edition = '2021'
1320

1421
[package.metadata.docs.rs]
1522
features = ['global']
@@ -27,7 +34,8 @@ core = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-core
2734
compiler_builtins = { version = '0.1.0', optional = true }
2835

2936
[dev-dependencies]
30-
rand = "0.3"
37+
arbitrary = "1.3.2"
38+
rand = { version = "0.8", features = ['small_rng'] }
3139

3240
[profile.release]
3341
debug-assertions = true

fuzz/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
corpus
2+
artifacts

fuzz/Cargo.toml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "dlmalloc-fuzz"
3+
version = "0.0.1"
4+
publish = false
5+
edition.workspace = true
6+
7+
[package.metadata]
8+
cargo-fuzz = true
9+
10+
[dependencies]
11+
arbitrary = "1.3.2"
12+
dlmalloc = { path = '..' }
13+
libfuzzer-sys = "0.4.7"
14+
15+
[[bin]]
16+
name = "alloc"
17+
path = "fuzz_targets/alloc.rs"
18+
test = false
19+
bench = false

fuzz/fuzz_targets/alloc.rs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#![no_main]
2+
3+
use arbitrary::Unstructured;
4+
use libfuzzer_sys::fuzz_target;
5+
6+
fuzz_target!(|bytes: &[u8]| {
7+
let _ = dlmalloc_fuzz::run(&mut Unstructured::new(bytes));
8+
});

fuzz/src/lib.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use arbitrary::{Result, Unstructured};
2+
use dlmalloc::Dlmalloc;
3+
use std::cmp;
4+
5+
const MAX_ALLOCATED: usize = 100 << 20; // 100 MB
6+
7+
pub fn run(u: &mut Unstructured<'_>) -> Result<()> {
8+
let mut a = Dlmalloc::new();
9+
let mut ptrs = Vec::new();
10+
let mut allocated = 0;
11+
unsafe {
12+
while u.arbitrary()? {
13+
// If there are pointers to free then have a chance of deallocating
14+
// a pointer. Try not to deallocate things until there's a "large"
15+
// working set but afterwards give it a 50/50 chance of allocating
16+
// or deallocating.
17+
let free = match ptrs.len() {
18+
0 => false,
19+
0..=10_000 => u.ratio(1, 3)?,
20+
_ => u.arbitrary()?,
21+
};
22+
if free {
23+
let idx = u.choose_index(ptrs.len())?;
24+
let (ptr, size, align) = ptrs.swap_remove(idx);
25+
allocated -= size;
26+
a.free(ptr, size, align);
27+
continue;
28+
}
29+
30+
// 1/100 chance of reallocating a pointer to a different size.
31+
if ptrs.len() > 0 && u.ratio(1, 100)? {
32+
let idx = u.choose_index(ptrs.len())?;
33+
let (ptr, size, align) = ptrs.swap_remove(idx);
34+
35+
// Arbitrarily choose whether to make this allocation either
36+
// twice as large or half as small.
37+
let new_size = if u.arbitrary()? {
38+
u.int_in_range(size..=size * 2)?
39+
} else if size > 10 {
40+
u.int_in_range(size / 2..=size)?
41+
} else {
42+
continue;
43+
};
44+
if allocated + new_size - size > MAX_ALLOCATED {
45+
ptrs.push((ptr, size, align));
46+
continue;
47+
}
48+
allocated -= size;
49+
allocated += new_size;
50+
51+
// Perform the `realloc` and assert that all bytes were copied.
52+
let mut tmp = Vec::new();
53+
for i in 0..cmp::min(size, new_size) {
54+
tmp.push(*ptr.offset(i as isize));
55+
}
56+
let ptr = a.realloc(ptr, size, align, new_size);
57+
assert!(!ptr.is_null());
58+
for (i, byte) in tmp.iter().enumerate() {
59+
assert_eq!(*byte, *ptr.offset(i as isize));
60+
}
61+
ptrs.push((ptr, new_size, align));
62+
}
63+
64+
// Aribtrarily choose a size to allocate as well as an alignment.
65+
// Enable small sizes with standard alignment happening a fair bit.
66+
let size = if u.arbitrary()? {
67+
u.int_in_range(1..=128)?
68+
} else {
69+
u.int_in_range(1..=128 * 1024)?
70+
};
71+
let align = if u.ratio(1, 10)? {
72+
1 << u.int_in_range(3..=8)?
73+
} else {
74+
8
75+
};
76+
77+
if size + allocated > MAX_ALLOCATED {
78+
continue;
79+
}
80+
allocated += size;
81+
82+
// Choose arbitrarily between a zero-allocated chunk and a normal
83+
// allocated chunk.
84+
let zero = u.ratio(1, 50)?;
85+
let ptr = if zero {
86+
a.calloc(size, align)
87+
} else {
88+
a.malloc(size, align)
89+
};
90+
for i in 0..size {
91+
if zero {
92+
assert_eq!(*ptr.offset(i as isize), 0);
93+
}
94+
*ptr.offset(i as isize) = 0xce;
95+
}
96+
ptrs.push((ptr, size, align));
97+
}
98+
99+
// Deallocate everythign when we're done.
100+
for (ptr, size, align) in ptrs {
101+
a.free(ptr, size, align);
102+
}
103+
104+
a.destroy();
105+
}
106+
107+
Ok(())
108+
}

src/dlmalloc.rs

+45-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use core::cmp;
77
use core::mem;
88
use core::ptr;
99

10-
use Allocator;
10+
use crate::Allocator;
1111

1212
pub struct Dlmalloc<A> {
1313
smallmap: u32,
@@ -91,22 +91,22 @@ impl<A> Dlmalloc<A> {
9191
Dlmalloc {
9292
smallmap: 0,
9393
treemap: 0,
94-
smallbins: [0 as *mut _; (NSMALLBINS + 1) * 2],
95-
treebins: [0 as *mut _; NTREEBINS],
94+
smallbins: [ptr::null_mut(); (NSMALLBINS + 1) * 2],
95+
treebins: [ptr::null_mut(); NTREEBINS],
9696
dvsize: 0,
9797
topsize: 0,
98-
dv: 0 as *mut _,
99-
top: 0 as *mut _,
98+
dv: ptr::null_mut(),
99+
top: ptr::null_mut(),
100100
footprint: 0,
101101
max_footprint: 0,
102102
seg: Segment {
103-
base: 0 as *mut _,
103+
base: ptr::null_mut(),
104104
size: 0,
105-
next: 0 as *mut _,
105+
next: ptr::null_mut(),
106106
flags: 0,
107107
},
108108
trim_check: 0,
109-
least_addr: 0 as *mut _,
109+
least_addr: ptr::null_mut(),
110110
release_checks: 0,
111111
system_allocator,
112112
}
@@ -201,7 +201,7 @@ impl<A: Allocator> Dlmalloc<A> {
201201
}
202202

203203
fn top_foot_size(&self) -> usize {
204-
self.align_offset_usize(Chunk::mem_offset() as usize)
204+
self.align_offset_usize(Chunk::mem_offset())
205205
+ self.pad_request(mem::size_of::<Segment>())
206206
+ self.min_chunk_size()
207207
}
@@ -799,7 +799,7 @@ impl<A: Allocator> Dlmalloc<A> {
799799
let nextp = Chunk::plus_offset(p, mem::size_of::<usize>());
800800
(*p).head = Chunk::fencepost_head();
801801
nfences += 1;
802-
if (&(*nextp).head as *const usize as *mut u8) < old_end {
802+
if ptr::addr_of!((*nextp).head).cast::<u8>() < old_end {
803803
p = nextp;
804804
} else {
805805
break;
@@ -940,13 +940,15 @@ impl<A: Allocator> Dlmalloc<A> {
940940
}
941941

942942
unsafe fn smallbin_at(&mut self, idx: u32) -> *mut Chunk {
943-
debug_assert!(((idx * 2) as usize) < self.smallbins.len());
944-
&mut *self.smallbins.get_unchecked_mut((idx as usize) * 2) as *mut *mut Chunk as *mut Chunk
943+
let idx = usize::try_from(idx * 2).unwrap();
944+
debug_assert!(idx < self.smallbins.len());
945+
self.smallbins.as_mut_ptr().add(idx).cast()
945946
}
946947

947948
unsafe fn treebin_at(&mut self, idx: u32) -> *mut *mut TreeChunk {
948-
debug_assert!((idx as usize) < self.treebins.len());
949-
&mut *self.treebins.get_unchecked_mut(idx as usize)
949+
let idx = usize::try_from(idx).unwrap();
950+
debug_assert!(idx < self.treebins.len());
951+
self.treebins.as_mut_ptr().add(idx)
950952
}
951953

952954
fn compute_tree_index(&self, size: usize) -> u32 {
@@ -1642,6 +1644,26 @@ impl<A: Allocator> Dlmalloc<A> {
16421644
unsafe fn traverse_and_check(&self) -> usize {
16431645
0
16441646
}
1647+
1648+
pub unsafe fn trim(&mut self, pad: usize) -> bool {
1649+
self.sys_trim(pad)
1650+
}
1651+
1652+
pub unsafe fn destroy(mut self) -> usize {
1653+
let mut freed = 0;
1654+
let mut sp = &mut self.seg as *mut Segment;
1655+
while !sp.is_null() {
1656+
let base = (*sp).base;
1657+
let size = (*sp).size;
1658+
let can_free = !base.is_null() && !Segment::is_extern(sp);
1659+
sp = (*sp).next;
1660+
1661+
if can_free && self.system_allocator.free(base, size) {
1662+
freed += size;
1663+
}
1664+
}
1665+
freed
1666+
}
16451667
}
16461668

16471669
const PINUSE: usize = 1 << 0;
@@ -1719,19 +1741,19 @@ impl Chunk {
17191741
}
17201742

17211743
unsafe fn plus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk {
1722-
(me as *mut u8).offset(offset as isize) as *mut Chunk
1744+
me.cast::<u8>().add(offset).cast()
17231745
}
17241746

17251747
unsafe fn minus_offset(me: *mut Chunk, offset: usize) -> *mut Chunk {
1726-
(me as *mut u8).offset(-(offset as isize)) as *mut Chunk
1748+
me.cast::<u8>().offset(-(offset as isize)).cast()
17271749
}
17281750

17291751
unsafe fn to_mem(me: *mut Chunk) -> *mut u8 {
1730-
(me as *mut u8).offset(Chunk::mem_offset())
1752+
me.cast::<u8>().add(Chunk::mem_offset())
17311753
}
17321754

1733-
fn mem_offset() -> isize {
1734-
2 * (mem::size_of::<usize>() as isize)
1755+
fn mem_offset() -> usize {
1756+
2 * mem::size_of::<usize>()
17351757
}
17361758

17371759
unsafe fn from_mem(mem: *mut u8) -> *mut Chunk {
@@ -1750,7 +1772,7 @@ impl TreeChunk {
17501772
}
17511773

17521774
unsafe fn chunk(me: *mut TreeChunk) -> *mut Chunk {
1753-
&mut (*me).chunk
1775+
ptr::addr_of_mut!((*me).chunk)
17541776
}
17551777

17561778
unsafe fn next(me: *mut TreeChunk) -> *mut TreeChunk {
@@ -1782,14 +1804,14 @@ impl Segment {
17821804
}
17831805

17841806
unsafe fn top(seg: *mut Segment) -> *mut u8 {
1785-
(*seg).base.offset((*seg).size as isize)
1807+
(*seg).base.add((*seg).size)
17861808
}
17871809
}
17881810

17891811
#[cfg(test)]
17901812
mod tests {
17911813
use super::*;
1792-
use System;
1814+
use crate::System;
17931815

17941816
// Prime the allocator with some allocations such that there will be free
17951817
// chunks in the treemap
@@ -1817,6 +1839,7 @@ mod tests {
18171839
}
18181840

18191841
#[test]
1842+
#[cfg(not(miri))]
18201843
// Test allocating the maximum request size with a non-empty treemap
18211844
fn treemap_alloc_max() {
18221845
let mut a = Dlmalloc::new(System::new());

src/dummy.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use crate::Allocator;
12
use core::ptr;
2-
use Allocator;
33

44
pub struct System {
55
_priv: (),

0 commit comments

Comments
 (0)