diff --git a/src/codecs/dav1d.rs b/src/codecs/dav1d.rs index 2720a4c..3c6fc7a 100644 --- a/src/codecs/dav1d.rs +++ b/src/codecs/dav1d.rs @@ -39,12 +39,21 @@ pub struct Dav1d { } /// # Safety -/// C-callback function that does not perform any unsafe operations. +/// `cookie` must be the `Box<[u8]>` raw pointer that was passed to +/// `dav1d_data_wrap()` in `Dav1dDataWrapper::wrap()`. Dav1d guarantees this +/// callback is invoked exactly once per wrap, after the last internal +/// reference (including any retained `c->tile[].data` ref) is dropped. unsafe extern "C" fn avif_dav1d_free_callback( - _buf: *const u8, - _cookie: *mut ::std::os::raw::c_void, + buf: *const u8, + cookie: *mut ::std::os::raw::c_void, ) { - // Do nothing. The buffers are owned by the decoder. + // Reconstitute and drop the boxed slice that backs this Dav1dData. + // `cookie` is the slice length so we can rebuild the fat pointer. + let len = cookie as usize; + // # Safety: see function-level safety comment. + drop(unsafe { + Box::from_raw(std::ptr::slice_from_raw_parts_mut(buf as *mut u8, len)) + }); } // See https://code.videolan.org/videolan/dav1d/-/blob/9849ede1304da1443cfb4a86f197765081034205/include/dav1d/common.h#L55-59 @@ -114,18 +123,36 @@ impl Dav1dDataWrapper { } fn wrap(&mut self, payload: &[u8]) -> AvifResult<()> { - // # Safety: Calling a C function with valid parameters. + // dav1d may retain interior pointers into this buffer (e.g. in + // c->tile[].data) past the end of the current get_next_image() call, + // but `payload` is a non-persistent avifIO slice that the caller is + // free to invalidate immediately afterwards. Hand dav1d an owned copy + // whose lifetime is governed by dav1d's own ref-count and freed via + // avif_dav1d_free_callback(). + let owned: Box<[u8]> = payload.to_vec().into_boxed_slice(); + let len = owned.len(); + let ptr = Box::into_raw(owned) as *mut u8; + // # Safety: Calling a C function with valid parameters. Ownership of + // `ptr` is transferred to dav1d; it is reclaimed in + // avif_dav1d_free_callback(). match unsafe { dav1d_data_wrap( self.mut_ptr(), - payload.as_ptr(), - payload.len(), + ptr, + len, Some(avif_dav1d_free_callback), - /*cookie=*/ std::ptr::null_mut(), + /*cookie=*/ len as *mut ::std::os::raw::c_void, ) } { 0 => Ok(()), - res => AvifError::unknown_error(format!("dav1d_data_wrap returned {res}")), + res => { + // dav1d_data_wrap failed; reclaim the allocation ourselves. + // # Safety: we still own `ptr` because dav1d did not take it. + drop(unsafe { + Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) + }); + AvifError::unknown_error(format!("dav1d_data_wrap returned {res}")) + } } } }