Skip to content

Commit ca0c0eb

Browse files
committed
Merge #263: Correctly free null pointers
7cf1a90 chore: Update safety comments (Christian Lewe) 7e4f4c3 fix: NOP when freeing NULL pointer (Christian Lewe) Pull request description: Fixes #262 ACKs for top commit: apoelstra: ACK 7cf1a90; successfully ran local tests; thanks! These are great improvements to the docs Tree-SHA512: f93d0e954dbd1e62e0492243e559143cb0d832dd675108f6313d2aead3c3e225057d381755335bb6c3f517121db763c8101ef472c14c0eac993986984c547c7a
2 parents ce9eca8 + 7cf1a90 commit ca0c0eb

File tree

1 file changed

+65
-20
lines changed

1 file changed

+65
-20
lines changed

simplicity-sys/src/alloc.rs

+65-20
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,53 @@ type AllocatorFn = unsafe fn(Layout) -> *mut u8;
1919
/// Allocate `size_bytes` many bytes using the `allocator` function
2020
/// and return a pointer.
2121
///
22-
/// # Safety
22+
/// # Panics
2323
///
24-
/// `allocator` must be [`alloc::alloc`] or [`alloc::alloc_zeroed`].
24+
/// This function panics if `size_bytes` + [`MIN_ALIGN`],
25+
/// rounded up to next multiple of [`MIN_ALIGN`],
26+
/// is greater than [`isize::MAX`] (allocated too many bytes).
2527
///
26-
/// Allocated bytes must be freed using [`rust_free`].
28+
/// # Safety
29+
///
30+
/// - `allocator` must be [`alloc::alloc`] or [`alloc::alloc_zeroed`].
31+
/// - Allocated bytes must be freed using [`rust_free`].
2732
unsafe fn allocate(size_bytes: usize, allocator: AllocatorFn) -> *mut u8 {
28-
assert!(MIN_ALIGN >= mem::align_of::<usize>());
29-
assert!(MIN_ALIGN >= mem::align_of::<&usize>());
30-
assert!(MIN_ALIGN >= mem::size_of::<usize>());
33+
assert!(mem::align_of::<usize>() <= MIN_ALIGN);
34+
assert!(mem::align_of::<&usize>() <= MIN_ALIGN);
35+
assert!(mem::size_of::<usize>() <= MIN_ALIGN);
3136

32-
// Allocate MIN_ALIGN + size_bytes many bytes
33-
// Panic if too many bytes are tried to be allocated
34-
let size_prefixed_bytes = MIN_ALIGN + size_bytes;
35-
let layout = Layout::from_size_align(size_prefixed_bytes, MIN_ALIGN).unwrap();
37+
// We allocate a sequence of N bytes (size_bytes) that will hold the actual data,
38+
// prefixed by a sequence of MIN_ALIGN bytes that hold the number `MIN_ALIGN + N`
39+
// (number of allocated bytes).
40+
// The prefix needs to be offset by MIN_ALIGN to ensure correct alignment of the next bytes.
41+
// Finally, we return a pointer after this prefix for the caller to use.
42+
//
43+
// MIN_ALIGN MIN_ALIGN + 2
44+
// | |
45+
// +---------------+-------+-------+-----+-------+
46+
// | MIN_ALIGN + N | byte0 | byte1 | ... | byteN |
47+
// +---------------+-------+-------+-----+-------+
48+
// | ^ | |
49+
// 0 | MIN_ALIGN + 1 MIN_ALIGN + N
50+
// WE RETURN THIS POINTER
51+
//
52+
let size_prefixed_bytes = MIN_ALIGN.saturating_add(size_bytes);
53+
// PANIC: Allocated too many bytes (documented above).
54+
let layout =
55+
Layout::from_size_align(size_prefixed_bytes, MIN_ALIGN).expect("allocated too many bytes");
56+
// SAFETY: `layout` is nonzero.
3657
let ptr_prefix = allocator(layout);
3758
if ptr_prefix.is_null() {
59+
// Abort execution if allocation failed.
3860
alloc::handle_alloc_error(layout);
3961
}
40-
// Write the number of allocated bytes to memory
62+
// Write number of allocated bytes into prefix.
63+
//
64+
// SAFETY: prefix is valid for writes and well-aligned.
4165
(ptr_prefix as *mut usize).write(size_prefixed_bytes);
42-
// Return a pointer to the size_bytes many allocated bytes behind the counter
43-
// We need to offset the pointer by MIN_ALIGN to keep alignment
44-
// This means there is a gap of MIN_ALIGN - sizeof(size_t) many unused bytes
45-
// We asserted that MIN_ALIGN >= sizeof(size_t), so this gap is nonnegative
66+
// Return pointer behind prefix.
67+
//
68+
// SAFETY: `ptr_prefix` and `ptr_prefix + MIN_ALIGN` are part of same allocated object.
4669
ptr_prefix.add(MIN_ALIGN)
4770
}
4871

@@ -53,6 +76,7 @@ unsafe fn allocate(size_bytes: usize, allocator: AllocatorFn) -> *mut u8 {
5376
/// Allocated bytes must be freed using [`rust_free`].
5477
#[no_mangle]
5578
pub unsafe extern "C" fn rust_malloc(size_bytes: usize) -> *mut u8 {
79+
// SAFETY: Allocator is `alloc::alloc`.
5680
allocate(size_bytes, alloc::alloc)
5781
}
5882

@@ -64,23 +88,44 @@ pub unsafe extern "C" fn rust_malloc(size_bytes: usize) -> *mut u8 {
6488
#[no_mangle]
6589
pub unsafe extern "C" fn rust_calloc(num: usize, size: usize) -> *mut u8 {
6690
let size_bytes = num * size;
91+
// SAFETY: Allocator is `alloc_alloc_zeroed`.
6792
allocate(size_bytes, alloc::alloc_zeroed)
6893
}
6994

7095
/// Free allocated bytes at `ptr_bytes`.
7196
///
7297
/// # Safety
7398
///
74-
/// Bytes must have been allocated using [`rust_malloc`] or [`rust_calloc`].
99+
/// - `ptr_bytes` must have been allocated using [`rust_malloc`] or [`rust_calloc`].
100+
/// - If `ptr_bytes` is a `NULL` pointer, then this function is a NO-OP.
75101
#[no_mangle]
76102
pub unsafe extern "C" fn rust_free(ptr_bytes: *mut u8) {
77-
// Move MIN_ALIGN many bytes back in memory
78-
// and read the number of allocated bytes
103+
if ptr_bytes.is_null() {
104+
return;
105+
}
106+
107+
// We got a pointer to an allocation from `rust_malloc` or `rust_calloc`,
108+
// so the memory looks as follows.
109+
// There is a prefix of `MIN_ALIGN` bytes in front of the pointer we got.
110+
// This prefix holds the total number of allocated bytes.
111+
// We free this number of bytes to free the entire sequence.
112+
//
113+
// MIN_ALIGN MIN_ALIGN + 2
114+
// | |
115+
// +---------------+-------+-------+-----+-------+
116+
// | MIN_ALIGN + N | byte0 | byte1 | ... | byteN |
117+
// +---------------+-------+-------+-----+-------+
118+
// | ^ | |
119+
// 0 | MIN_ALIGN + 1 MIN_ALIGN + N
120+
// WE GOT THIS POINTER
121+
//
122+
// SAFETY: `ptr_bytes` and `ptr_bytes - MIN_ALIGN` are part of same allocated object.
79123
let ptr_prefix = ptr_bytes.sub(MIN_ALIGN);
124+
// SAFETY: prefix is valid for reads and well-aligned.
80125
let size_prefixed_bytes = (ptr_prefix as *mut usize).read();
81-
// Free the allocated bytes including the counter
82-
// Panic if the number of bytes overflows
126+
// INFALLIBLE: This layout was already allocated, so there is no overflow.
83127
let layout = Layout::from_size_align(size_prefixed_bytes, MIN_ALIGN).unwrap();
128+
// SAFETY: `ptr_prefix` was allocated via same allocator with same layout.
84129
alloc::dealloc(ptr_prefix, layout)
85130
}
86131

0 commit comments

Comments
 (0)