From f077d71e8be283361b96423b006969038b1b43b1 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 21 Jul 2026 07:57:10 +0200 Subject: [PATCH] refactor(decompress): move legacy threshold policy to Rust Keep the target-specific ZSTD_LEGACY_SUPPORT macro in the C translation unit, but pass its scalar value through a Rust policy bridge before legacy dispatch. Rust now accepts only the historical supported range 1 through 7 and normalizes all other values, including a missing projection, to disabled. ABI assertions and focused tests preserve the compile-time configuration contract. Test Plan: Not run in this atomic commit; the capped serial Rust, native, smoke, and original test-suite verification follows. --- lib/decompress/zstd_decompress.c | 25 +++++++++++---- rust/src/zstd_decompress.rs | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 5d4ec0474..7d1f335f7 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -187,6 +187,15 @@ typedef char ZSTD_rust_decompress_stack_callbacks_layout[ size_t ZSTD_rust_decompress_stack_action( const ZSTD_rustDecompressStackProjection* projection, const ZSTD_rustDecompressStackCallbacks* callbacks); +typedef struct { + unsigned configured_level; +} ZSTD_rustLegacySupportProjection; +typedef char ZSTD_rust_legacy_support_projection_layout[ + (offsetof(ZSTD_rustLegacySupportProjection, configured_level) == 0 + && sizeof(ZSTD_rustLegacySupportProjection) == sizeof(unsigned)) + ? 1 : -1]; +unsigned ZSTD_rust_legacy_support_policy( + const ZSTD_rustLegacySupportProjection* projection); unsigned ZSTD_rust_legacy_support(void); #if ZSTD_TRACE @@ -352,15 +361,17 @@ void ZSTD_rust_dctx_trace_view(ZSTD_DCtx* dctx, } /* The Rust dispatcher has all version-specific legacy implementations. Keep - * the active C configuration as a leaf so test builds, which compile every - * Rust legacy module but vary this C macro per target, retain the historical - * support boundary. */ + * the active C configuration as a scalar leaf so test builds, which compile + * every Rust legacy module but vary this C macro per target, retain the + * historical support boundary. Rust owns normalization of that scalar. */ unsigned ZSTD_rust_legacy_support(void) { -#if defined(ZSTD_LEGACY_SUPPORT) && (ZSTD_LEGACY_SUPPORT >= 1) \ - && (ZSTD_LEGACY_SUPPORT <= 7) - return ZSTD_LEGACY_SUPPORT; + ZSTD_rustLegacySupportProjection const projection = { +#if defined(ZSTD_LEGACY_SUPPORT) + ZSTD_LEGACY_SUPPORT #else - return 0; + 0 #endif + }; + return ZSTD_rust_legacy_support_policy(&projection); } diff --git a/rust/src/zstd_decompress.rs b/rust/src/zstd_decompress.rs index 849228541..85505cd57 100644 --- a/rust/src/zstd_decompress.rs +++ b/rust/src/zstd_decompress.rs @@ -299,6 +299,17 @@ struct ZSTD_rustDctxView { dctx_size: usize, } +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ZSTD_rustLegacySupportProjection { + pub configured_level: c_uint, +} + +const _: () = { + assert!(offset_of!(ZSTD_rustLegacySupportProjection, configured_level) == 0); + assert!(size_of::() == size_of::()); +}; + const _: () = { assert!(offset_of!(ZSTD_rustDctxView, dctx) == 0); assert!(offset_of!(ZSTD_rustDctxView, static_size) == 29 * size_of::()); @@ -645,6 +656,50 @@ unsafe fn configured_legacy_support() -> u32 { } } +/// Normalize the C build's legacy-support threshold before dispatch. +/// +/// C supplies the active compile-time value because test builds intentionally +/// compile every legacy module while varying `ZSTD_LEGACY_SUPPORT`. Rust owns +/// the public policy that accepts levels 1 through 7 and treats every other +/// value as legacy-disabled. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_legacy_support_policy( + projection: *const ZSTD_rustLegacySupportProjection, +) -> c_uint { + let Some(projection) = (unsafe { projection.as_ref() }) else { + return 0; + }; + let configured = projection.configured_level; + if (1..=7).contains(&configured) { + configured + } else { + 0 + } +} + +#[cfg(test)] +mod legacy_support_policy_tests { + use super::*; + + #[test] + fn preserves_supported_thresholds_and_disables_other_values() { + for (configured, expected) in [(0, 0), (1, 1), (4, 4), (7, 7), (8, 0), (c_uint::MAX, 0)] { + let projection = ZSTD_rustLegacySupportProjection { + configured_level: configured, + }; + assert_eq!( + unsafe { ZSTD_rust_legacy_support_policy(&projection) }, + expected + ); + } + } + + #[test] + fn rejects_missing_configuration_projection() { + assert_eq!(unsafe { ZSTD_rust_legacy_support_policy(ptr::null()) }, 0); + } +} + /* This is the Rust equivalent of ZSTD_isLegacy(). The v0.1 magic is the one * historical exception: its bytes are checked as the little-endian constant * 0x1EB52FFD, while v0.2 through v0.7 use the usual 0xFD2FB52N values. */