diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 38d7caec2..1ad5856eb 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -368,6 +368,25 @@ typedef char ZSTD_rust_create_cdict_state_layout[ == 3 * sizeof(void*) && sizeof(ZSTD_rust_createCDictState) == 4 * sizeof(void*)) ? 1 : -1]; +typedef void (*ZSTD_rust_freeCDictWorkspace_f)(void* context); +typedef void (*ZSTD_rust_freeCDictObject_f)(void* context); +typedef struct { + void* callbackContext; + int cdictInWorkspace; + ZSTD_rust_freeCDictWorkspace_f freeWorkspace; + ZSTD_rust_freeCDictObject_f freeObject; +} ZSTD_rust_freeCDictState; +size_t ZSTD_rust_freeCDict(const ZSTD_rust_freeCDictState* state); +typedef char ZSTD_rust_free_cdict_state_layout[ + (offsetof(ZSTD_rust_freeCDictState, callbackContext) == 0 + && offsetof(ZSTD_rust_freeCDictState, cdictInWorkspace) + == sizeof(void*) + && offsetof(ZSTD_rust_freeCDictState, freeWorkspace) + == 2 * sizeof(void*) + && offsetof(ZSTD_rust_freeCDictState, freeObject) + == 3 * sizeof(void*) + && sizeof(ZSTD_rust_freeCDictState) == 4 * sizeof(void*)) + ? 1 : -1]; typedef void (*ZSTD_rust_resetCCtxClearAllDicts_f)(void* context); typedef size_t (*ZSTD_rust_resetCCtxResetParams_f)(void* context); typedef struct { @@ -5547,17 +5566,27 @@ ZSTD_CDict* ZSTD_createCDict_byReference(const void* dict, size_t dictSize, int &state, dict, dictSize, compressionLevel, ZSTD_dlm_byRef); } +static void ZSTD_rust_freeCDict_freeWorkspace(void* context) +{ + ZSTD_CDict* const cdict = (ZSTD_CDict*)context; + ZSTD_cwksp_free(&cdict->workspace, cdict->customMem); +} + +static void ZSTD_rust_freeCDict_freeObject(void* context) +{ + ZSTD_CDict* const cdict = (ZSTD_CDict*)context; + ZSTD_customFree(cdict, cdict->customMem); +} + size_t ZSTD_freeCDict(ZSTD_CDict* cdict) { - if (cdict==NULL) return 0; /* support free on NULL */ - { ZSTD_customMem const cMem = cdict->customMem; - int cdictInWorkspace = ZSTD_cwksp_owns_buffer(&cdict->workspace, cdict); - ZSTD_cwksp_free(&cdict->workspace, cMem); - if (!cdictInWorkspace) { - ZSTD_customFree(cdict, cMem); - } - return 0; - } + ZSTD_rust_freeCDictState state; + state.callbackContext = cdict; + state.cdictInWorkspace = cdict == NULL ? 0 + : ZSTD_cwksp_owns_buffer(&cdict->workspace, cdict); + state.freeWorkspace = ZSTD_rust_freeCDict_freeWorkspace; + state.freeObject = ZSTD_rust_freeCDict_freeObject; + return ZSTD_rust_freeCDict(&state); } /*! ZSTD_initStaticCDict_advanced() : diff --git a/rust/src/zstd_compress_dictionary.rs b/rust/src/zstd_compress_dictionary.rs index 0cbfe03aa..397bc3674 100644 --- a/rust/src/zstd_compress_dictionary.rs +++ b/rust/src/zstd_compress_dictionary.rs @@ -437,6 +437,48 @@ pub unsafe extern "C" fn ZSTD_rust_createCDict( cdict } +type FreeCDictWorkspaceFn = unsafe extern "C" fn(*mut c_void); +type FreeCDictObjectFn = unsafe extern "C" fn(*mut c_void); + +/// Explicit projection for the public `ZSTD_freeCDict` wrapper. +/// +/// Rust owns null handling, workspace/object ordering, and the embedded-object +/// decision. C retains private workspace and allocator teardown callbacks. +#[repr(C)] +pub struct ZSTD_rust_freeCDictState { + callback_context: *mut c_void, + cdict_in_workspace: c_int, + free_workspace: FreeCDictWorkspaceFn, + free_object: FreeCDictObjectFn, +} + +const _: () = { + assert!(offset_of!(ZSTD_rust_freeCDictState, callback_context) == 0); + assert!(offset_of!(ZSTD_rust_freeCDictState, cdict_in_workspace) == size_of::()); + assert!(offset_of!(ZSTD_rust_freeCDictState, free_workspace) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_freeCDictState, free_object) == 3 * size_of::()); + assert!(size_of::() == size_of::<[usize; 4]>()); +}; + +/// Free private CDict storage and, when applicable, its outer object. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_freeCDict(state: *const ZSTD_rust_freeCDictState) -> usize { + if state.is_null() { + return ERROR(ZstdErrorCode::Generic); + } + let state = unsafe { &*state }; + if state.callback_context.is_null() { + return 0; + } + unsafe { + (state.free_workspace)(state.callback_context); + if state.cdict_in_workspace == 0 { + (state.free_object)(state.callback_context); + } + } + 0 +} + type CompressBeginUsingDictBeginFn = unsafe extern "C" fn(*mut c_void, *const c_void, usize, *const c_void, u64) -> usize; @@ -2834,6 +2876,69 @@ mod tests { assert!(probe.published_cdict.is_null()); } + #[derive(Default)] + struct FreeCDictProbe { + events: Vec<&'static str>, + } + + unsafe extern "C" fn free_cdict_test_workspace(context: *mut c_void) { + let probe = unsafe { &mut *context.cast::() }; + probe.events.push("workspace"); + } + + unsafe extern "C" fn free_cdict_test_object(context: *mut c_void) { + let probe = unsafe { &mut *context.cast::() }; + probe.events.push("object"); + } + + fn free_cdict_test_state( + probe: &mut FreeCDictProbe, + cdict_in_workspace: c_int, + ) -> ZSTD_rust_freeCDictState { + ZSTD_rust_freeCDictState { + callback_context: (probe as *mut FreeCDictProbe).cast(), + cdict_in_workspace, + free_workspace: free_cdict_test_workspace, + free_object: free_cdict_test_object, + } + } + + #[test] + fn free_cdict_frees_workspace_before_an_external_object() { + let mut probe = FreeCDictProbe::default(); + let state = free_cdict_test_state(&mut probe, 0); + + let result = unsafe { ZSTD_rust_freeCDict(&state) }; + + assert_eq!(result, 0); + assert_eq!(probe.events, ["workspace", "object"]); + } + + #[test] + fn free_cdict_skips_the_external_object_when_embedded_in_workspace() { + let mut probe = FreeCDictProbe::default(); + let state = free_cdict_test_state(&mut probe, 1); + + let result = unsafe { ZSTD_rust_freeCDict(&state) }; + + assert_eq!(result, 0); + assert_eq!(probe.events, ["workspace"]); + } + + #[test] + fn free_cdict_accepts_a_null_context() { + let state = ZSTD_rust_freeCDictState { + callback_context: ptr::null_mut(), + cdict_in_workspace: 0, + free_workspace: free_cdict_test_workspace, + free_object: free_cdict_test_object, + }; + + let result = unsafe { ZSTD_rust_freeCDict(&state) }; + + assert_eq!(result, 0); + } + #[derive(Default)] struct CompressBeginUsingDictProbe { events: Vec<&'static str>,