feat(decompress): move decoder allocation bridges into Rust
Decoder orchestration already crossed into Rust, but the C translation unit still owned the allocation bridge used by every decoder context and dictionary set. That split kept custom malloc/calloc/free behavior, allocator-pair validation, and the decoder-context storage lifecycle in C without focused Rust coverage. Move those bridges into Rust while keeping the C-defined `ZSTD_DCtx` layout and configuration-dependent platform, legacy, and trace leaves in C. Rust now validates custom allocator pairs, allocates and frees the opaque decoder storage, dispatches default allocations to libc, and preserves calloc's zero-fill contract for custom allocators. Focused tests cover default memory, custom callbacks, zeroing, null-safe free, and invalid callback pairs. Test Plan: - `cargo test --manifest-path rust/Cargo.toml --lib -- --test-threads=1` -- 447 passed - `cargo test --manifest-path rust/Cargo.toml --lib zstd_decompress -- --test-threads=1` -- 6 passed - `cargo clippy --manifest-path rust/Cargo.toml --lib -- -D warnings` -- passed - `cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check` -- passed - `make -B -C lib -j2 lib` -- passed - `make -B -C tests -j2 test-cli-tests` -- passed - `ZSTREAM_TESTTIME=-T2s make -B -C tests -j2 test-zstream` -- passed - `FUZZERTEST=-T5s make -B -C tests -j2 test-fuzzer` -- passed - `make -B -C tests/fuzz -j2 all` and `sequence_compression_api` -- passed - `make -C tests -j2 test-zstd` -- passed, including large-data cases
This commit is contained in:
+154
-5
@@ -301,8 +301,6 @@ struct ZSTD_rustDctxView {
|
||||
unsafe extern "C" {
|
||||
fn ZSTD_rust_dctx_view(dctx: *mut ZSTD_DCtx, out: *mut ZSTD_rustDctxView);
|
||||
fn ZSTD_rust_dctx_sizeof() -> usize;
|
||||
fn ZSTD_rust_dctx_alloc(custom_mem: ZSTD_customMem) -> *mut ZSTD_DCtx;
|
||||
fn ZSTD_rust_dctx_free_storage(dctx: *mut ZSTD_DCtx, custom_mem: ZSTD_customMem);
|
||||
fn ZSTD_rust_dctx_init_platform(dctx: *mut ZSTD_DCtx);
|
||||
fn ZSTD_rust_dctx_default_max_window_size() -> usize;
|
||||
fn ZSTD_rust_no_forward_progress_max() -> c_int;
|
||||
@@ -313,9 +311,6 @@ unsafe extern "C" {
|
||||
src: *const c_void,
|
||||
src_size: usize,
|
||||
) -> usize;
|
||||
fn ZSTD_rust_custom_malloc(size: usize, custom_mem: ZSTD_customMem) -> *mut c_void;
|
||||
fn ZSTD_rust_custom_calloc(size: usize, custom_mem: ZSTD_customMem) -> *mut c_void;
|
||||
fn ZSTD_rust_custom_free(allocation: *mut c_void, custom_mem: ZSTD_customMem);
|
||||
fn ZSTD_rust_create_ddict(
|
||||
dict: *const c_void,
|
||||
dict_size: usize,
|
||||
@@ -871,6 +866,160 @@ fn custom_mem_valid(custom_mem: ZSTD_customMem) -> bool {
|
||||
custom_mem.custom_alloc.is_some() == custom_mem.custom_free.is_some()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_dctx_alloc(custom_mem: ZSTD_customMem) -> *mut ZSTD_DCtx {
|
||||
if !custom_mem_valid(custom_mem) {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let dctx_size = unsafe { ZSTD_rust_dctx_sizeof() };
|
||||
unsafe { ZSTD_rust_custom_malloc(dctx_size, custom_mem) }.cast()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_dctx_free_storage(
|
||||
dctx: *mut ZSTD_DCtx,
|
||||
custom_mem: ZSTD_customMem,
|
||||
) {
|
||||
unsafe { ZSTD_rust_custom_free(dctx.cast(), custom_mem) };
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_custom_malloc(
|
||||
size: usize,
|
||||
custom_mem: ZSTD_customMem,
|
||||
) -> *mut c_void {
|
||||
match custom_mem.custom_alloc {
|
||||
Some(alloc) => unsafe { alloc(custom_mem.opaque, size) },
|
||||
None => unsafe { libc::malloc(size) },
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_custom_calloc(
|
||||
size: usize,
|
||||
custom_mem: ZSTD_customMem,
|
||||
) -> *mut c_void {
|
||||
match custom_mem.custom_alloc {
|
||||
Some(alloc) => {
|
||||
let allocation = unsafe { alloc(custom_mem.opaque, size) };
|
||||
if !allocation.is_null() {
|
||||
unsafe { ptr::write_bytes(allocation.cast::<u8>(), 0, size) };
|
||||
}
|
||||
allocation
|
||||
}
|
||||
None => unsafe { libc::calloc(1, size) },
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ZSTD_rust_custom_free(
|
||||
allocation: *mut c_void,
|
||||
custom_mem: ZSTD_customMem,
|
||||
) {
|
||||
if allocation.is_null() {
|
||||
return;
|
||||
}
|
||||
match custom_mem.custom_free {
|
||||
Some(free) => unsafe { free(custom_mem.opaque, allocation) },
|
||||
None => unsafe { libc::free(allocation) },
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod allocation_tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
struct AllocationStats {
|
||||
allocations: AtomicUsize,
|
||||
frees: AtomicUsize,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn tracking_alloc(opaque: *mut c_void, size: usize) -> *mut c_void {
|
||||
let stats = unsafe { &*opaque.cast::<AllocationStats>() };
|
||||
stats.allocations.fetch_add(1, Ordering::Relaxed);
|
||||
let allocation = unsafe { libc::malloc(size) };
|
||||
if !allocation.is_null() {
|
||||
unsafe { ptr::write_bytes(allocation.cast::<u8>(), 0xA5, size) };
|
||||
}
|
||||
allocation
|
||||
}
|
||||
|
||||
unsafe extern "C" fn tracking_free(opaque: *mut c_void, allocation: *mut c_void) {
|
||||
let stats = unsafe { &*opaque.cast::<AllocationStats>() };
|
||||
stats.frees.fetch_add(1, Ordering::Relaxed);
|
||||
unsafe { libc::free(allocation) };
|
||||
}
|
||||
|
||||
fn custom_mem(stats: &AllocationStats) -> ZSTD_customMem {
|
||||
ZSTD_customMem {
|
||||
custom_alloc: Some(tracking_alloc),
|
||||
custom_free: Some(tracking_free),
|
||||
opaque: (stats as *const AllocationStats).cast_mut().cast(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_allocation_uses_libc_and_zeroes_calloc() {
|
||||
let default_mem = default_custom_mem();
|
||||
unsafe {
|
||||
let allocation = ZSTD_rust_custom_malloc(32, default_mem);
|
||||
assert!(!allocation.is_null());
|
||||
ZSTD_rust_custom_free(allocation, default_mem);
|
||||
|
||||
let zeroed = ZSTD_rust_custom_calloc(32, default_mem);
|
||||
assert!(!zeroed.is_null());
|
||||
let bytes = std::slice::from_raw_parts(zeroed.cast::<u8>(), 32);
|
||||
assert!(bytes.iter().all(|&byte| byte == 0));
|
||||
ZSTD_rust_custom_free(zeroed, default_mem);
|
||||
ZSTD_rust_custom_free(ptr::null_mut(), default_mem);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_allocation_calls_callbacks_and_zeroes_calloc() {
|
||||
let stats = AllocationStats {
|
||||
allocations: AtomicUsize::new(0),
|
||||
frees: AtomicUsize::new(0),
|
||||
};
|
||||
let custom_mem = custom_mem(&stats);
|
||||
|
||||
unsafe {
|
||||
let allocation = ZSTD_rust_custom_malloc(32, custom_mem);
|
||||
assert!(!allocation.is_null());
|
||||
assert_eq!(stats.allocations.load(Ordering::Relaxed), 1);
|
||||
|
||||
let zeroed = ZSTD_rust_custom_calloc(32, custom_mem);
|
||||
assert!(!zeroed.is_null());
|
||||
assert_eq!(stats.allocations.load(Ordering::Relaxed), 2);
|
||||
let bytes = std::slice::from_raw_parts(zeroed.cast::<u8>(), 32);
|
||||
assert!(bytes.iter().all(|&byte| byte == 0));
|
||||
|
||||
ZSTD_rust_custom_free(allocation, custom_mem);
|
||||
ZSTD_rust_custom_free(zeroed, custom_mem);
|
||||
ZSTD_rust_custom_free(ptr::null_mut(), custom_mem);
|
||||
}
|
||||
assert_eq!(stats.frees.load(Ordering::Relaxed), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dctx_allocation_rejects_mismatched_callback_pairs() {
|
||||
let invalid_alloc_only = ZSTD_customMem {
|
||||
custom_alloc: Some(tracking_alloc),
|
||||
custom_free: None,
|
||||
opaque: ptr::null_mut(),
|
||||
};
|
||||
let invalid_free_only = ZSTD_customMem {
|
||||
custom_alloc: None,
|
||||
custom_free: Some(tracking_free),
|
||||
opaque: ptr::null_mut(),
|
||||
};
|
||||
|
||||
assert!(!custom_mem_valid(invalid_alloc_only));
|
||||
assert!(!custom_mem_valid(invalid_free_only));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_frame_header_ptr(view: &ZSTD_rustDctxView) -> *mut ZSTD_FrameHeader {
|
||||
view.f_params.cast()
|
||||
|
||||
Reference in New Issue
Block a user