Skip to content

Commit d007fb3

Browse files
committed
fix(odb): Fix memory leak if git_odb_add_backend fails
`CustomOdbBackend` does not drop the inner `Backend` value, leaving it to libgit2 calling the backend's `free` function. This function cannot be called if the `git_odb_add_backend` call fails. Now, `Odb::add_custom_backend` first creates the inner `Backend` value and only when the value has successfully been passed to libgit2 do we stop managing the memory.
1 parent ae91c48 commit d007fb3

File tree

3 files changed

+304
-0
lines changed

3 files changed

+304
-0
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ mod util;
690690

691691
pub mod build;
692692
pub mod cert;
693+
pub mod odb_backend;
693694
pub mod oid_array;
694695
pub mod opts;
695696
pub mod string_array;

src/odb.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::util::Binding;
1212
use crate::{
1313
raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress,
1414
};
15+
use crate::odb_backend::{CustomOdbBackend, OdbBackend};
1516

1617
/// A structure to represent a git object database
1718
pub struct Odb<'repo> {
@@ -269,6 +270,18 @@ impl<'repo> Odb<'repo> {
269270
Ok(Mempack::from_raw(mempack))
270271
}
271272
}
273+
274+
/// Adds a custom backend to this odb with the given priority.
275+
/// Returns a handle to the backend.
276+
///
277+
/// `backend` will be dropped when this Odb is dropped.
278+
pub fn add_custom_backend<'odb, B: OdbBackend + 'odb>(&'odb self, backend: B, priority: i32) -> Result<CustomOdbBackend<'odb, B>, Error> {
279+
let mut inner = CustomOdbBackend::new_inner(backend);
280+
unsafe {
281+
try_call!(raw::git_odb_add_backend(self.raw, ptr::from_mut(inner.as_mut()).cast(), priority as c_int));
282+
Ok(CustomOdbBackend::new(inner))
283+
}
284+
}
272285
}
273286

274287
/// An object from the Object Database.

src/odb_backend.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
//! Custom backends for [`Odb`]s.
2+
use crate::util::Binding;
3+
use crate::{raw, Error, ErrorClass, ErrorCode, ObjectType, Oid};
4+
use libc::{c_int, size_t};
5+
use std::ffi::c_void;
6+
use std::marker::PhantomData;
7+
use std::mem::ManuallyDrop;
8+
use std::{ptr, slice};
9+
10+
pub trait OdbBackend {
11+
fn supported_operations(&self) -> SupportedOperations;
12+
13+
fn read(
14+
&mut self,
15+
ctx: &OdbBackendContext,
16+
oid: Oid,
17+
out: &mut OdbBackendAllocation,
18+
) -> Result<ObjectType, Error> {
19+
(ctx, oid, out);
20+
unimplemented!("OdbBackend::read")
21+
}
22+
// TODO: fn read_prefix(&mut self, ctx: &OdbBackendContext);
23+
fn read_header(&mut self, ctx: &OdbBackendContext, oid: Oid) -> Result<OdbHeader, Error> {
24+
(ctx, oid);
25+
unimplemented!("OdbBackend::read")
26+
}
27+
// TODO: fn write()
28+
// TODO: fn writestream()
29+
// TODO: fn readstream()
30+
fn exists(&mut self, ctx: &OdbBackendContext, oid: Oid) -> Result<bool, Error>;
31+
// TODO: fn exists_prefix()
32+
// TODO: fn refresh()
33+
// TODO: fn foreach()
34+
// TODO: fn writepack()
35+
// TODO: fn writemidx()
36+
// TODO: fn freshen()
37+
}
38+
39+
bitflags::bitflags! {
40+
pub struct SupportedOperations: u32 {
41+
/// The backend supports the [`OdbBackend::read`] method.
42+
const READ = 1;
43+
/// The backend supports the [`OdbBackend::read_prefix`] method.
44+
const READ_PREFIX = 1 << 1;
45+
/// The backend supports the [`OdbBackend::read_header`] method.
46+
const READ_HEADER = 1 << 2;
47+
/// The backend supports the [`OdbBackend::write`] method.
48+
const WRITE = 1 << 3;
49+
/// The backend supports the [`OdbBackend::writestream`] method.
50+
const WRITESTREAM = 1 << 4;
51+
/// The backend supports the [`OdbBackend::readstream`] method.
52+
const READSTREAM = 1 << 5;
53+
/// The backend supports the [`OdbBackend::exists`] method.
54+
const EXISTS = 1 << 6;
55+
/// The backend supports the [`OdbBackend::exists_prefix`] method.
56+
const EXISTS_PREFIX = 1 << 7;
57+
/// The backend supports the [`OdbBackend::refresh`] method.
58+
const REFRESH = 1 << 7;
59+
/// The backend supports the [`OdbBackend::foreach`] method.
60+
const FOREACH = 1 << 8;
61+
/// The backend supports the [`OdbBackend::writepack`] method.
62+
const WRITEPACK = 1 << 9;
63+
/// The backend supports the [`OdbBackend::writemidx`] method.
64+
const WRITEMIDX = 1 << 10;
65+
/// The backend supports the [`OdbBackend::freshen`] method.
66+
const FRESHEN = 1 << 11;
67+
}
68+
}
69+
70+
pub struct OdbHeader {
71+
pub size: usize,
72+
pub object_type: ObjectType,
73+
}
74+
75+
pub struct OdbBackendAllocation {
76+
backend_ptr: *mut raw::git_odb_backend,
77+
raw: *mut c_void,
78+
size: usize,
79+
}
80+
impl OdbBackendAllocation {
81+
pub fn as_mut(&mut self) -> &mut [u8] {
82+
unsafe { slice::from_raw_parts_mut(self.raw.cast(), self.size) }
83+
}
84+
}
85+
impl Drop for OdbBackendAllocation {
86+
fn drop(&mut self) {
87+
unsafe {
88+
raw::git_odb_backend_data_free(self.backend_ptr, self.raw);
89+
}
90+
}
91+
}
92+
93+
pub struct OdbBackendRead {}
94+
95+
pub struct OdbBackendContext {
96+
backend_ptr: *mut raw::git_odb_backend,
97+
}
98+
impl OdbBackendContext {
99+
/// Creates an instance of `OdbBackendAllocation` that points to `null`.
100+
/// Its size will be 0.
101+
pub fn null_alloc(&self) -> OdbBackendAllocation {
102+
OdbBackendAllocation {
103+
backend_ptr: self.backend_ptr,
104+
raw: ptr::null_mut(),
105+
size: 0,
106+
}
107+
}
108+
109+
/// Attempts to allocate a buffer of size `size`.
110+
///
111+
/// # Return value
112+
/// `Some(allocation)` if the allocation succeeded.
113+
/// `None` otherwise. This usually indicates that there is not enough memory.
114+
pub fn alloc(&self, size: usize) -> Option<OdbBackendAllocation> {
115+
let data = unsafe { raw::git_odb_backend_data_alloc(self.backend_ptr, size as size_t) };
116+
if data.is_null() {
117+
return None;
118+
}
119+
Some(OdbBackendAllocation {
120+
backend_ptr: self.backend_ptr,
121+
raw: data,
122+
size,
123+
})
124+
}
125+
126+
/// Attempts to allocate a buffer of size `size`, returning an error when that fails.
127+
/// Essentially the same as [`alloc`], but returns a [`Result`].
128+
///
129+
/// # Return value
130+
/// `Ok(allocation)` if the allocation succeeded.
131+
/// `Err(error)` otherwise. The error is always a `GenericError` of class `NoMemory`.
132+
pub fn try_alloc(&self, size: usize) -> Result<OdbBackendAllocation, Error> {
133+
self.alloc(size).ok_or_else(|| {
134+
Error::new(
135+
ErrorCode::GenericError,
136+
ErrorClass::NoMemory,
137+
"out of memory",
138+
)
139+
})
140+
}
141+
}
142+
143+
/// A handle to an [`OdbBackend`] that has been added to an [`Odb`](crate::Odb).
144+
pub struct CustomOdbBackend<'a, B: OdbBackend> {
145+
// NOTE: Any pointer in this field must be both non-null and properly aligned.
146+
raw: NonNull<Backend<B>>,
147+
phantom: PhantomData<fn() -> &'a ()>,
148+
}
149+
150+
impl<'a, B: OdbBackend> CustomOdbBackend<'a, B> {
151+
pub(crate) fn new_inner(backend: B) -> Box<Backend<B>> {
152+
let mut parent = raw::git_odb_backend {
153+
version: raw::GIT_ODB_BACKEND_VERSION,
154+
odb: ptr::null_mut(),
155+
read: None,
156+
read_prefix: None,
157+
read_header: None,
158+
write: None,
159+
writestream: None,
160+
readstream: None,
161+
exists: None,
162+
exists_prefix: None,
163+
refresh: None,
164+
foreach: None,
165+
writepack: None,
166+
writemidx: None,
167+
freshen: None,
168+
free: None,
169+
};
170+
Self::set_operations(backend.supported_operations(), &mut parent);
171+
172+
Box::new(Backend {
173+
parent,
174+
inner: backend,
175+
})
176+
}
177+
pub(crate) fn new(backend: Box<Backend<B>>) -> Self {
178+
// SAFETY: Box::into_raw guarantees that the pointer is properly aligned and non-null
179+
let backend = Box::into_raw(backend);
180+
let backend = unsafe { NonNull::new_unchecked(backend) };
181+
Self {
182+
raw: backend,
183+
phantom: PhantomData,
184+
}
185+
}
186+
fn set_operations(
187+
supported_operations: SupportedOperations,
188+
backend: &mut raw::git_odb_backend,
189+
) {
190+
macro_rules! op_if {
191+
($name:ident if $flag:ident) => {
192+
backend.$name = supported_operations
193+
.contains(SupportedOperations::$flag)
194+
.then_some(Backend::<B>::$name)
195+
};
196+
}
197+
op_if!(read if READ);
198+
op_if!(read_header if READ_HEADER);
199+
op_if!(exists if EXISTS);
200+
201+
backend.free = Some(Backend::<B>::free);
202+
}
203+
204+
pub(crate) fn as_git_odb_backend(&self) -> *mut raw::git_odb_backend {
205+
self.raw.cast()
206+
}
207+
}
208+
209+
#[repr(C)]
210+
pub(crate) struct Backend<B> {
211+
parent: raw::git_odb_backend,
212+
inner: B,
213+
}
214+
impl<B: OdbBackend> Backend<B> {
215+
extern "C" fn read(
216+
data_ptr: *mut *mut c_void,
217+
size_ptr: *mut size_t,
218+
otype_ptr: *mut raw::git_object_t,
219+
backend_ptr: *mut raw::git_odb_backend,
220+
oid_ptr: *const raw::git_oid,
221+
) -> raw::git_error_code {
222+
let backend = unsafe { backend_ptr.cast::<Self>().as_mut().unwrap() };
223+
let data = unsafe { data_ptr.as_mut().unwrap() };
224+
let size = unsafe { size_ptr.as_mut().unwrap() };
225+
let object_type = unsafe { otype_ptr.as_mut().unwrap() };
226+
let oid = unsafe { Oid::from_raw(oid_ptr) };
227+
228+
let context = OdbBackendContext { backend_ptr };
229+
230+
let mut allocation = ManuallyDrop::new(context.null_alloc());
231+
232+
let output = match backend.inner.read(&context, oid, &mut allocation) {
233+
Err(e) => {
234+
ManuallyDrop::into_inner(allocation);
235+
return e.raw_code();
236+
}
237+
Ok(o) => o,
238+
};
239+
240+
*size = allocation.size;
241+
*data = allocation.raw;
242+
*object_type = output.raw();
243+
244+
raw::GIT_OK
245+
}
246+
extern "C" fn read_header(
247+
size_ptr: *mut size_t,
248+
otype_ptr: *mut raw::git_object_t,
249+
backend_ptr: *mut raw::git_odb_backend,
250+
oid_ptr: *const raw::git_oid,
251+
) -> raw::git_error_code {
252+
let size = unsafe { size_ptr.as_mut().unwrap() };
253+
let otype = unsafe { otype_ptr.as_mut().unwrap() };
254+
let backend = unsafe { backend_ptr.cast::<Backend<B>>().as_mut().unwrap() };
255+
let oid = unsafe { Oid::from_raw(oid_ptr) };
256+
257+
let context = OdbBackendContext { backend_ptr };
258+
259+
let header = match backend.inner.read_header(&context, oid) {
260+
Err(e) => unsafe { return e.raw_set_git_error() },
261+
Ok(header) => header,
262+
};
263+
*size = header.size;
264+
*otype = header.object_type.raw();
265+
raw::GIT_OK
266+
}
267+
268+
extern "C" fn free(backend: *mut raw::git_odb_backend) {
269+
let inner = unsafe { Box::from_raw(backend.cast::<Self>()) };
270+
drop(inner);
271+
}
272+
273+
extern "C" fn exists(
274+
backend_ptr: *mut raw::git_odb_backend,
275+
oid_ptr: *const raw::git_oid,
276+
) -> c_int {
277+
let backend = unsafe { backend_ptr.cast::<Backend<B>>().as_mut().unwrap() };
278+
let oid = unsafe { Oid::from_raw(oid_ptr) };
279+
let context = OdbBackendContext { backend_ptr };
280+
let exists = match backend.inner.exists(&context, oid) {
281+
Err(e) => return unsafe { e.raw_set_git_error() },
282+
Ok(x) => x,
283+
};
284+
if exists {
285+
1
286+
} else {
287+
0
288+
}
289+
}
290+
}

0 commit comments

Comments
 (0)