Skip to content

Commit 115c993

Browse files
authored
Merge pull request #1137 from martinvonz/push-mruzqpwzzwmy
Document and restrict some unsafe code in gix-pack
2 parents 5d8b5f4 + c4807de commit 115c993

File tree

3 files changed

+89
-93
lines changed

3 files changed

+89
-93
lines changed

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

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use gix_features::{
99
};
1010

1111
use crate::{
12-
cache::delta::{traverse::util::ItemSliceSend, Item, Tree},
12+
cache::delta::{traverse::util::ItemSliceSync, Item, Tree},
1313
data::EntryRange,
1414
};
1515

@@ -133,25 +133,23 @@ where
133133
let object_progress = OwnShared::new(Mutable::new(object_progress));
134134

135135
let start = std::time::Instant::now();
136+
let child_items = ItemSliceSync::new(&mut self.child_items);
137+
let child_items = &child_items;
136138
in_parallel_with_slice(
137139
&mut self.root_items,
138140
thread_limit,
139141
{
140-
let child_items = ItemSliceSend::new(&mut self.child_items);
141142
{
142143
let object_progress = object_progress.clone();
143-
move |thread_index| {
144-
let _ = &child_items;
145-
resolve::State {
146-
delta_bytes: Vec::<u8>::with_capacity(4096),
147-
fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096),
148-
progress: Box::new(
149-
threading::lock(&object_progress).add_child(format!("thread {thread_index}")),
150-
),
151-
resolve: resolve.clone(),
152-
modify_base: inspect_object.clone(),
153-
child_items: child_items.clone(),
154-
}
144+
move |thread_index| resolve::State {
145+
delta_bytes: Vec::<u8>::with_capacity(4096),
146+
fully_resolved_delta_bytes: Vec::<u8>::with_capacity(4096),
147+
progress: Box::new(
148+
threading::lock(&object_progress).add_child(format!("thread {thread_index}")),
149+
),
150+
resolve: resolve.clone(),
151+
modify_base: inspect_object.clone(),
152+
child_items,
155153
}
156154
}
157155
},

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

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,81 @@ use gix_features::{progress::Progress, threading, zlib};
77

88
use crate::{
99
cache::delta::{
10-
traverse::{
11-
util::{ItemSliceSend, Node},
12-
Context, Error,
13-
},
10+
traverse::{util::ItemSliceSync, Context, Error},
1411
Item,
1512
},
1613
data,
1714
data::EntryRange,
1815
};
1916

17+
mod node {
18+
use crate::cache::delta::{traverse::util::ItemSliceSync, Item};
19+
20+
/// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`].
21+
pub(crate) struct Node<'a, T: Send> {
22+
item: &'a mut Item<T>,
23+
child_items: &'a ItemSliceSync<'a, Item<T>>,
24+
}
25+
26+
impl<'a, T: Send> Node<'a, T> {
27+
/// SAFETY: The child_items must be unique among between users of the `ItemSliceSync`.
28+
#[allow(unsafe_code)]
29+
pub(crate) unsafe fn new(item: &'a mut Item<T>, child_items: &'a ItemSliceSync<'a, Item<T>>) -> Self {
30+
Node { item, child_items }
31+
}
32+
}
33+
34+
impl<'a, T: Send> Node<'a, T> {
35+
/// Returns the offset into the pack at which the `Node`s data is located.
36+
pub fn offset(&self) -> u64 {
37+
self.item.offset
38+
}
39+
40+
/// Returns the slice into the data pack at which the pack entry is located.
41+
pub fn entry_slice(&self) -> crate::data::EntryRange {
42+
self.item.offset..self.item.next_offset
43+
}
44+
45+
/// Returns the node data associated with this node.
46+
pub fn data(&mut self) -> &mut T {
47+
&mut self.item.data
48+
}
49+
50+
/// Returns true if this node has children, e.g. is not a leaf in the tree.
51+
pub fn has_children(&self) -> bool {
52+
!self.item.children.is_empty()
53+
}
54+
55+
/// Transform this `Node` into an iterator over its children.
56+
///
57+
/// Children are `Node`s referring to pack entries whose base object is this pack entry.
58+
pub fn into_child_iter(self) -> impl Iterator<Item = Node<'a, T>> + 'a {
59+
let children = self.child_items;
60+
// SAFETY: The index is a valid index into the children array.
61+
// SAFETY: The resulting mutable pointer cannot be yielded by any other node.
62+
#[allow(unsafe_code)]
63+
self.item.children.iter().map(move |&index| Node {
64+
item: unsafe { children.get_mut(index as usize) },
65+
child_items: children,
66+
})
67+
}
68+
}
69+
}
70+
2071
pub(crate) struct State<'items, F, MBFN, T: Send> {
2172
pub delta_bytes: Vec<u8>,
2273
pub fully_resolved_delta_bytes: Vec<u8>,
2374
pub progress: Box<dyn Progress>,
2475
pub resolve: F,
2576
pub modify_base: MBFN,
26-
pub child_items: ItemSliceSend<'items, Item<T>>,
77+
pub child_items: &'items ItemSliceSync<'items, Item<T>>,
2778
}
2879

2980
#[allow(clippy::too_many_arguments)]
3081
pub(crate) fn deltas<T, F, MBFN, E, R>(
3182
objects: gix_features::progress::StepShared,
3283
size: gix_features::progress::StepShared,
33-
node: &mut Item<T>,
84+
item: &mut Item<T>,
3485
State {
3586
delta_bytes,
3687
fully_resolved_delta_bytes,
@@ -67,13 +118,10 @@ where
67118
// each node is a base, and its children always start out as deltas which become a base after applying them.
68119
// These will be pushed onto our stack until all are processed
69120
let root_level = 0;
70-
let mut nodes: Vec<_> = vec![(
71-
root_level,
72-
Node {
73-
item: node,
74-
child_items: child_items.clone(),
75-
},
76-
)];
121+
// SAFETY: The child items are unique
122+
#[allow(unsafe_code)]
123+
let root_node = unsafe { node::Node::new(item, child_items) };
124+
let mut nodes: Vec<_> = vec![(root_level, root_node)];
77125
while let Some((level, mut base)) = nodes.pop() {
78126
if should_interrupt.load(Ordering::Relaxed) {
79127
return Err(Error::Interrupted);
@@ -186,13 +234,13 @@ where
186234
/// system. Since this thread will take a controlling function, we may spawn one more than that. In threaded mode, we will finish
187235
/// all remaining work.
188236
#[allow(clippy::too_many_arguments)]
189-
pub(crate) fn deltas_mt<T, F, MBFN, E, R>(
237+
fn deltas_mt<T, F, MBFN, E, R>(
190238
mut threads_to_create: isize,
191239
decompressed_bytes_by_pack_offset: BTreeMap<u64, (data::Entry, u64, Vec<u8>)>,
192240
objects: gix_features::progress::StepShared,
193241
size: gix_features::progress::StepShared,
194242
progress: &dyn Progress,
195-
nodes: Vec<(u16, Node<'_, T>)>,
243+
nodes: Vec<(u16, node::Node<'_, T>)>,
196244
resolve: F,
197245
resolve_data: &R,
198246
modify_base: MBFN,
Lines changed: 14 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,36 @@
11
use std::marker::PhantomData;
22

3-
use crate::cache::delta::Item;
4-
5-
pub(crate) struct ItemSliceSend<'a, T>
3+
pub(crate) struct ItemSliceSync<'a, T>
64
where
75
T: Send,
86
{
97
items: *mut T,
108
phantom: PhantomData<&'a T>,
119
}
1210

13-
impl<'a, T> ItemSliceSend<'a, T>
11+
impl<'a, T> ItemSliceSync<'a, T>
1412
where
1513
T: Send,
1614
{
1715
pub fn new(items: &'a mut [T]) -> Self {
18-
ItemSliceSend {
16+
ItemSliceSync {
1917
items: items.as_mut_ptr(),
2018
phantom: PhantomData,
2119
}
2220
}
23-
}
2421

25-
/// SAFETY: This would be unsafe if this would ever be abused, but it's used internally and only in a way that assure that the pointers
26-
/// don't violate aliasing rules.
27-
impl<T> Clone for ItemSliceSend<'_, T>
28-
where
29-
T: Send,
30-
{
31-
fn clone(&self) -> Self {
32-
ItemSliceSend {
33-
items: self.items,
34-
phantom: self.phantom,
35-
}
22+
/// SAFETY: The index must point into the slice and must not be reused concurrently.
23+
#[allow(unsafe_code)]
24+
pub unsafe fn get_mut(&self, index: usize) -> &'a mut T {
25+
// SAFETY: The children array is alive by the 'a lifetime.
26+
unsafe { &mut *self.items.add(index) }
3627
}
3728
}
3829

39-
// SAFETY: T is `Send`, and we only ever access one T at a time. And, ptrs need that assurance, I wonder if it's always right.
30+
// SAFETY: T is `Send`, and we only use the pointer for creating new pointers.
4031
#[allow(unsafe_code)]
41-
unsafe impl<T> Send for ItemSliceSend<'_, T> where T: Send {}
42-
43-
/// An item returned by `iter_root_chunks`, allowing access to the `data` stored alongside nodes in a [`Tree`].
44-
pub(crate) struct Node<'a, T: Send> {
45-
pub item: &'a mut Item<T>,
46-
pub child_items: ItemSliceSend<'a, Item<T>>,
47-
}
48-
49-
impl<'a, T: Send> Node<'a, T> {
50-
/// Returns the offset into the pack at which the `Node`s data is located.
51-
pub fn offset(&self) -> u64 {
52-
self.item.offset
53-
}
54-
55-
/// Returns the slice into the data pack at which the pack entry is located.
56-
pub fn entry_slice(&self) -> crate::data::EntryRange {
57-
self.item.offset..self.item.next_offset
58-
}
59-
60-
/// Returns the node data associated with this node.
61-
pub fn data(&mut self) -> &mut T {
62-
&mut self.item.data
63-
}
64-
65-
/// Returns true if this node has children, e.g. is not a leaf in the tree.
66-
pub fn has_children(&self) -> bool {
67-
!self.item.children.is_empty()
68-
}
69-
70-
/// Transform this `Node` into an iterator over its children.
71-
///
72-
/// Children are `Node`s referring to pack entries whose base object is this pack entry.
73-
pub fn into_child_iter(self) -> impl Iterator<Item = Node<'a, T>> + 'a {
74-
let children = self.child_items;
75-
self.item.children.iter().map(move |&index| {
76-
// SAFETY: The children array is alive by the 'a lifetime.
77-
// SAFETY: The index is a valid index into the children array.
78-
// SAFETY: The resulting mutable pointer cannot be yielded by any other node.
79-
#[allow(unsafe_code)]
80-
Node {
81-
item: unsafe { &mut *children.items.add(index as usize) },
82-
child_items: children.clone(),
83-
}
84-
})
85-
}
86-
}
32+
unsafe impl<T> Send for ItemSliceSync<'_, T> where T: Send {}
33+
// SAFETY: T is `Send`, and as long as the user follows the contract of
34+
// `get_mut()`, we only ever access one T at a time.
35+
#[allow(unsafe_code)]
36+
unsafe impl<T> Sync for ItemSliceSync<'_, T> where T: Send {}

0 commit comments

Comments
 (0)