Skip to content

Commit 4017e69

Browse files
committed
improve usage of unsafe (#1231)
Inspired by #1231 (comment) . - make sure the type in question isn't use outside of its designated module - improve documentation around safety to make the underlying data structure more obvious.
1 parent 911c05f commit 4017e69

File tree

3 files changed

+34
-11
lines changed

3 files changed

+34
-11
lines changed

gix-pack/src/cache/delta/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ pub mod traverse;
1717
///
1818
pub mod from_offsets;
1919

20-
/// An item stored within the [`Tree`]
20+
/// An item stored within the [`Tree`] whose data is stored in a pack file, identified by
21+
/// the offset of its first (`offset`) and last (`next_offset`) bytes.
22+
///
23+
/// It represents either a root entry, or one that relies on a base to be resolvable,
24+
/// alongside associated `data` `T`.
2125
pub struct Item<T> {
2226
/// The offset into the pack file at which the pack entry's data is located.
2327
pub offset: crate::data::Offset,

gix-pack/src/cache/delta/traverse/resolve.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::{
1414
data::EntryRange,
1515
};
1616

17-
mod node {
17+
mod root {
1818
use crate::cache::delta::{traverse::util::ItemSliceSync, Item};
1919

2020
/// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`].
@@ -26,7 +26,10 @@ mod node {
2626
impl<'a, T: Send> Node<'a, T> {
2727
/// SAFETY: The child_items must be unique among between users of the `ItemSliceSync`.
2828
#[allow(unsafe_code)]
29-
pub(crate) unsafe fn new(item: &'a mut Item<T>, child_items: &'a ItemSliceSync<'a, Item<T>>) -> Self {
29+
pub(in crate::cache::delta::traverse) unsafe fn new(
30+
item: &'a mut Item<T>,
31+
child_items: &'a ItemSliceSync<'a, Item<T>>,
32+
) -> Self {
3033
Node { item, child_items }
3134
}
3235
}
@@ -68,7 +71,7 @@ mod node {
6871
}
6972
}
7073

71-
pub(crate) struct State<'items, F, MBFN, T: Send> {
74+
pub(in crate::cache::delta::traverse) struct State<'items, F, MBFN, T: Send> {
7275
pub delta_bytes: Vec<u8>,
7376
pub fully_resolved_delta_bytes: Vec<u8>,
7477
pub progress: Box<dyn Progress>,
@@ -78,7 +81,7 @@ pub(crate) struct State<'items, F, MBFN, T: Send> {
7881
}
7982

8083
#[allow(clippy::too_many_arguments)]
81-
pub(crate) fn deltas<T, F, MBFN, E, R>(
84+
pub(in crate::cache::delta::traverse) fn deltas<T, F, MBFN, E, R>(
8285
objects: gix_features::progress::StepShared,
8386
size: gix_features::progress::StepShared,
8487
item: &mut Item<T>,
@@ -118,9 +121,9 @@ where
118121
// each node is a base, and its children always start out as deltas which become a base after applying them.
119122
// These will be pushed onto our stack until all are processed
120123
let root_level = 0;
121-
// SAFETY: The child items are unique
124+
// SAFETY: The child items are unique, as `item` is the root of a tree of dependent child items.
122125
#[allow(unsafe_code)]
123-
let root_node = unsafe { node::Node::new(item, child_items) };
126+
let root_node = unsafe { root::Node::new(item, child_items) };
124127
let mut nodes: Vec<_> = vec![(root_level, root_node)];
125128
while let Some((level, mut base)) = nodes.pop() {
126129
if should_interrupt.load(Ordering::Relaxed) {
@@ -240,7 +243,7 @@ fn deltas_mt<T, F, MBFN, E, R>(
240243
objects: gix_features::progress::StepShared,
241244
size: gix_features::progress::StepShared,
242245
progress: &dyn Progress,
243-
nodes: Vec<(u16, node::Node<'_, T>)>,
246+
nodes: Vec<(u16, root::Node<'_, T>)>,
244247
resolve: F,
245248
resolve_data: &R,
246249
modify_base: MBFN,

gix-pack/src/cache/delta/traverse/util.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
use std::marker::PhantomData;
22

3-
pub(crate) struct ItemSliceSync<'a, T>
3+
/// SAFETY: This type is used to allow access to a size-optimized vec of items that form a
4+
/// tree, and we need to access it concurrently with each thread taking its own root node,
5+
/// and working its way through all the reachable leaves.
6+
///
7+
/// The tree was built by decoding a pack whose entries refer to its bases only by OFS_DELTA -
8+
/// they are pointing backwards only which assures bases have to be listed first, and that each entry
9+
/// only has a single parent.
10+
///
11+
/// REF_DELTA entries aren't supported here, and cause immediate failure - they are expected to have
12+
/// been resolved before as part of the thin-pack handling.
13+
///
14+
/// If we somehow would allow REF_DELTA entries to point to an in-pack object, then in theory malicious packs could
15+
/// cause all kinds of graphs as they can point anywhere in the pack, but they still can't link an entry to
16+
/// more than one base. And that's what one would really have to do for two threads to encounter the same child.
17+
///
18+
/// Thus I believe it's impossible for this data structure to end up in a place where it violates its assumption.
19+
pub(in crate::cache::delta::traverse) struct ItemSliceSync<'a, T>
420
where
521
T: Send,
622
{
@@ -14,7 +30,7 @@ impl<'a, T> ItemSliceSync<'a, T>
1430
where
1531
T: Send,
1632
{
17-
pub fn new(items: &'a mut [T]) -> Self {
33+
pub(in crate::cache::delta::traverse) fn new(items: &'a mut [T]) -> Self {
1834
ItemSliceSync {
1935
items: items.as_mut_ptr(),
2036
#[cfg(debug_assertions)]
@@ -25,7 +41,7 @@ where
2541

2642
// SAFETY: The index must point into the slice and must not be reused concurrently.
2743
#[allow(unsafe_code)]
28-
pub unsafe fn get_mut(&self, index: usize) -> &'a mut T {
44+
pub(in crate::cache::delta::traverse) unsafe fn get_mut(&self, index: usize) -> &'a mut T {
2945
#[cfg(debug_assertions)]
3046
if index >= self.len {
3147
panic!("index out of bounds: the len is {} but the index is {index}", self.len);

0 commit comments

Comments
 (0)