Skip to content

Commit 4b1acee

Browse files
committed
add safety comments to unsafe blocks
1 parent d8ede61 commit 4b1acee

8 files changed

Lines changed: 448 additions & 24 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
name = "fast_radix_trie"
33
version = "1.1.1-alpha"
44
authors = [
5-
"Takeru Ohta <phjgt308@gmail.com>",
65
"Evan Cameron <cameron.evan@gmail.com>",
6+
"Takeru Ohta <phjgt308@gmail.com>",
77
]
88
description = "Memory-efficient trie data structures based on radix tree"
99
homepage = "https://github.com/bluecatengineering/fast_radix_trie"

src/lib.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ impl BorrowedBytes for str {
114114
#[inline(always)]
115115
pub fn strip_prefix<'a>(haystack: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> {
116116
if memchr::arch::all::is_prefix(haystack, prefix) {
117-
// SAFETY: we know prefix is a prefix of haystack so len is less than haystack
117+
// Safety:
118+
// - `is_prefix` ensures that `prefix.len() <= haystack.len()`
119+
// - Therefore, slicing `haystack` from `prefix.len()` is guaranteed to be in bounds
118120
unsafe { Some(haystack.get_unchecked(prefix.len()..)) }
119121
} else {
120122
None
@@ -130,7 +132,12 @@ pub fn longest_common_prefix_by_byte(a: &[u8], b: &[u8]) -> (usize, Option<Order
130132
let cmp = if a.is_empty() || b.is_empty() || i >= min_len {
131133
None
132134
} else {
133-
// SAFETY: i is less than min_len
135+
// Safety:
136+
// - `i` is the count of matching elements from zip, so i <= min_len
137+
// - The condition `i >= min_len` is false, so i < min_len
138+
// - min_len <= a.len() and min_len <= b.len()
139+
// - Therefore i < a.len() and i < b.len()
140+
// - `get_unchecked(i)` is safe for both a and b
134141
unsafe { Some(a.get_unchecked(i).cmp(b.get_unchecked(i))) }
135142
};
136143
(i, cmp)
@@ -160,7 +167,11 @@ macro_rules! fn_lcp {
160167
}
161168
// process remaining bytes less than CHUNK_LEN - one at a time
162169
while i < min_len {
163-
// SAFETY: we know i is less than min_len
170+
// Safety:
171+
// - Loop condition ensures i < min_len
172+
// - min_len = min(a.len(), b.len())
173+
// - Therefore i < a.len() and i < b.len()
174+
// - `get_unchecked(i)` is safe for both a and b
164175
let a_byte = unsafe { a.get_unchecked(i) };
165176
let b_byte = unsafe { b.get_unchecked(i) };
166177
if a_byte != b_byte {

src/map.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,10 @@ mod tests {
947947
let results = t
948948
.common_prefixes(b"abc")
949949
.flat_map(|(k, v)| {
950+
// Safety:
951+
// - In this test, all keys are valid UTF-8 strings (inserted as &str)
952+
// - `from_utf8_unchecked` is safe here because we control the input data
953+
// - This is only used for debug printing in tests
950954
unsafe {
951955
println!("{:?}", core::str::from_utf8_unchecked(k));
952956
}

src/node.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,21 @@ pub struct Node<V> {
2525
pub(crate) _marker: PhantomData<V>,
2626
}
2727

28+
// Safety:
29+
// - A `Node<V>` is safe to send across threads if `V` is `Send`
30+
// - The raw pointer is managed by this struct and not exposed
2831
unsafe impl<V: Send> Send for Node<V> {}
32+
// Safety:
33+
// - A `Node<V>` is safe to share across threads if `V` is `Sync`
34+
// - The raw pointer is managed by this struct and not exposed
2935
unsafe impl<V: Sync> Sync for Node<V> {}
3036

3137
impl<V: Clone> Clone for Node<V> {
3238
fn clone(&self) -> Self {
3339
let mut new_ptr = self.ptr_data().allocate();
40+
// Safety:
41+
// - `new_ptr` is a freshly allocated and correctly aligned pointer from `allocate`
42+
// - `new_ptr.assume_init()` is safe because all parts of the node have been initialized
3443
unsafe {
3544
new_ptr.write_header(*self.header());
3645
new_ptr.write_label(self.label());
@@ -67,6 +76,9 @@ impl<V> Node<V> {
6776
children_len: children.len() as u8,
6877
};
6978
let mut ptr = header.ptr_data().allocate();
79+
// Safety:
80+
// - `ptr` is a freshly allocated and correctly aligned pointer from `allocate`
81+
// - All parts of the node are initialized before `assume_init` is called
7082
unsafe {
7183
ptr.write_header(header);
7284
ptr.write_label(label);
@@ -78,17 +90,29 @@ impl<V> Node<V> {
7890

7991
/// Returns the reference to the value of this node.
8092
pub fn value(&self) -> Option<&V> {
93+
// Safety:
94+
// - `self.ptr` points to an allocation with the same valid layout it was allocated with
95+
// - `value_ptr` correctly calculates the offset to the `Option<V>`
8196
unsafe { (self.ptr_data().value_ptr(self.ptr)).as_ref() }.as_ref()
8297
}
8398

8499
/// Returns the mutable reference to the value of this node.
85100
pub fn value_mut(&mut self) -> Option<&mut V> {
101+
// Safety:
102+
// - `self.ptr` points to an allocation with the same valid layout it was allocated with
103+
// - `value_ptr` correctly calculates the offset to the `Option<V>`
86104
unsafe { (self.ptr_data().value_ptr(self.ptr)).as_mut() }.as_mut()
87105
}
88106

89107
/// Returns mutable references to the node itself with its sibling and child
90108
pub fn as_mut(&mut self) -> NodeMut<'_, V> {
109+
// Safety:
110+
// - `self.ptr` points to an allocation with the same valid layout it was allocated with
111+
// - `value_ptr` correctly calculates the offset to the `Option<V>`
91112
let value = unsafe { self.ptr_data().value_ptr(self.ptr).as_mut() }.as_mut();
113+
// Safety:
114+
// - `self.ptr` points to an allocation with the same valid layout it was allocated with
115+
// - `children_mut_opt` correctly calculates the offset to the children array if it exists
92116
let children = unsafe { self.ptr_data().children_mut_opt(self.ptr) };
93117

94118
NodeMut {
@@ -100,6 +124,9 @@ impl<V> Node<V> {
100124

101125
/// Takes the value out of this node.
102126
pub fn take_value(&mut self) -> Option<V> {
127+
// Safety:
128+
// - `self.ptr` points to an allocation with the same valid layout it was allocated with
129+
// - `value_ptr` correctly calculates the offset to the `Option<V>`
103130
unsafe {
104131
let ptr = self.ptr_data().value_ptr(self.ptr);
105132
ptr.replace(None)
@@ -108,6 +135,9 @@ impl<V> Node<V> {
108135

109136
/// adds child at i and shifts elements right
110137
/// child index must be at i <= len, len can be 0
138+
// Safety:
139+
// - `i` must be a valid index to insert at, i.e. `i <= self.children_len()`
140+
// - `self.children_len()` must be less than `u8::MAX`
111141
pub(crate) unsafe fn add_child(&mut self, new_child: Node<V>, i: usize) {
112142
debug_assert!(
113143
i <= self.children_len(),
@@ -128,6 +158,13 @@ impl<V> Node<V> {
128158
let old_ptr_data = self.ptr_data();
129159
let value = self.take_value();
130160

161+
// Safety:
162+
// - `realloc` is safe because `self.ptr` points to a valid allocation with `old_ptr_data.layout`.
163+
// The new size is calculated correctly in `new_ptr_data.layout`
164+
// - `copy_to` is safe because `i` is a valid index, `num` is within bounds
165+
// and the source and destination pointers are within the newly allocated block
166+
// - `new_ptr.assume_init()` is safe because all parts of the node have been initialized
167+
// - the pointer is assigned using `forget()` because we reallocated
131168
unsafe {
132169
let raw_ptr = alloc::alloc::realloc(
133170
self.ptr.as_ptr().cast(),
@@ -200,6 +237,14 @@ impl<V> Node<V> {
200237
"When prefixing label, the size of allocation must increase"
201238
);
202239

240+
// Safety:
241+
// - `realloc` is safe because `self.ptr` points to a valid allocation with `old_ptr_data.layout`.
242+
// The new size is calculated correctly in `new_ptr_data.layout`
243+
// - `new_ptr.write_value(value)` is safe because the space is allocated
244+
// - `copy_from` and `copy_from_nonoverlapping` are safe because the source and destination pointers
245+
// are valid and within the allocated blocks, and the lengths within bounds
246+
// - `new_ptr.assume_init()` is safe because all parts of the node have been initialized
247+
// - the pointer is assigned using `forget()` because we reallocated
203248
unsafe {
204249
let raw_ptr = alloc::alloc::realloc(
205250
self.ptr.as_ptr().cast(),
@@ -253,6 +298,8 @@ impl<V> Node<V> {
253298

254299
/// removes child at i and shifts elements left
255300
/// node must have children already
301+
// Safety:
302+
// - `i` must be a valid index of a child, i.e. `i < self.children_len()`
256303
pub(crate) unsafe fn remove_child(&mut self, i: usize) -> Node<V> {
257304
debug_assert!(
258305
i < self.children_len(),
@@ -281,6 +328,13 @@ impl<V> Node<V> {
281328
ptr_data: new_ptr_data,
282329
};
283330

331+
// Safety:
332+
// - `old_ptr.children_ptr().add(i).read()` is safe because `i` is a valid index
333+
// - `copy_from` is safe because the source and destination pointers are valid and the length is correct
334+
// - `realloc` is safe because `self.ptr` points to a valid allocation with `old_layout`
335+
// - `new_ptr.write_value(value)` is safe because the space is allocated
336+
// - `new_ptr.assume_init()` is safe because all parts of the node have been initialized
337+
// - the pointer is assigned using `forget()` because we reallocated
284338
unsafe {
285339
// get child at i
286340
let removed_child = some!(old_ptr.children_ptr()).add(i).read();
@@ -314,6 +368,9 @@ impl<V> Node<V> {
314368
/// Sets the value of this node.
315369
pub fn set_value(&mut self, value: V) {
316370
// self.take_value();
371+
// Safety:
372+
// - `self.ptr` points to an allocation with the same valid layout it was allocated with
373+
// - `value_ptr` correctly calculates the offset to the `Option<V>`
317374
unsafe {
318375
let ptr = self.ptr_data().value_ptr(self.ptr);
319376
let _ = ptr.replace(Some(value));
@@ -324,13 +381,19 @@ impl<V> Node<V> {
324381
/// and setting current node to have the suffix child plus optional `new_child`
325382
/// returns index of new_child (if Some(new_child) was passed)
326383
/// otherwise 0
384+
/// # Safety:
385+
/// - `position` must be a valid index within the label, i.e., `position < self.label_len()`
327386
pub(crate) unsafe fn split_at(&mut self, position: usize, new_child: Option<Node<V>>) -> usize {
328387
debug_assert!(
329388
position < self.label_len(),
330389
"label offset must be within label bounds"
331390
);
332391
let value = self.take_value();
333392

393+
// Safety:
394+
// - `self.label().get_unchecked(position..)` is safe because `position` is checked to be within bounds
395+
// - A new node `child` is allocated and correctly initialized with the suffix of the label and the old children
396+
// - `copy_from_nonoverlapping` is safe because the source and destination are valid and do not overlap
334397
let child = unsafe {
335398
let suffix = self.label().get_unchecked(position..);
336399
let old_children_len = self.children_len();
@@ -363,6 +426,8 @@ impl<V> Node<V> {
363426
let new_layout = new_data.layout;
364427
let old_layout = self.ptr_data().layout;
365428

429+
// Safety:
430+
// - `realloc` is safe because `self.ptr` points to a valid allocation with `old_layout`
366431
let mut new_ptr = unsafe {
367432
let new_ptr =
368433
alloc::alloc::realloc(self.ptr.as_ptr().cast(), old_layout, new_layout.size())
@@ -375,6 +440,11 @@ impl<V> Node<V> {
375440
ptr_data: new_data,
376441
}
377442
};
443+
// Safety:
444+
// - `new_ptr` is a valid pointer to a newly allocated block of memory
445+
// - `new_ptr.write_children` is safe because the children array is valid and the space is allocated
446+
// - `new_ptr.assume_init()` is safe because all parts of the node have been initialized
447+
// - pointer is assigned to `self.ptr` using `forget()` because we `realloc`ated
378448
unsafe {
379449
new_ptr.write_header(new_hdr);
380450
// index of new_child

0 commit comments

Comments
 (0)