diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 8c7819947..3e204936c 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -277,6 +277,7 @@ void ZSTD_rust_storeLastLiterals(SeqStore_t* seqStorePtr, void ZSTD_rust_resetSeqStore(SeqStore_t* ssPtr); size_t ZSTD_rust_fastSequenceLengthSum(const ZSTD_Sequence* seqBuf, size_t seqBufSize); +void ZSTD_rust_validateSeqStore(const SeqStore_t* seqStore, U32 minMatch); size_t ZSTD_rust_convertSequencesNoRepcodes( SeqDef* dstSeqs, const ZSTD_Sequence* inSeqs, size_t nbSequences); BlockSummary ZSTD_rust_get1BlockSummary(const ZSTD_Sequence* seqs, @@ -2348,15 +2349,7 @@ static size_t ZSTD_fastSequenceLengthSum(ZSTD_Sequence const* seqBuf, size_t seq static void ZSTD_validateSeqStore(const SeqStore_t* seqStore, const ZSTD_compressionParameters* cParams) { #if DEBUGLEVEL >= 1 - const SeqDef* seq = seqStore->sequencesStart; - const SeqDef* const seqEnd = seqStore->sequences; - size_t const matchLenLowerBound = cParams->minMatch == 3 ? 3 : 4; - for (; seq < seqEnd; ++seq) { - const ZSTD_SequenceLength seqLength = ZSTD_getSequenceLength(seqStore, seq); - assert(seqLength.matchLength >= matchLenLowerBound); - (void)seqLength; - (void)matchLenLowerBound; - } + ZSTD_rust_validateSeqStore(seqStore, (U32)cParams->minMatch); #else (void)seqStore; (void)cParams; diff --git a/rust/src/zstd_compress_stats.rs b/rust/src/zstd_compress_stats.rs index 53fc13948..58460870e 100644 --- a/rust/src/zstd_compress_stats.rs +++ b/rust/src/zstd_compress_stats.rs @@ -1151,6 +1151,31 @@ pub unsafe extern "C" fn ZSTD_rust_fastSequenceLengthSum( lit_len_sum.wrapping_add(match_len_sum) } +/// Validates the match lengths produced in a sequence store. This is the +/// Rust leaf for C's `ZSTD_validateSeqStore()`; the C wrapper retains the +/// `DEBUGLEVEL` gate so release builds keep the original no-op behavior. +#[no_mangle] +pub unsafe extern "C" fn ZSTD_rust_validateSeqStore( + seq_store: *const SeqStore_t, + min_match: c_uint, +) { + let seq_store = unsafe { &*seq_store }; + let match_len_lower_bound = if min_match == 3 { 3 } else { 4 }; + let nb_sequences = + unsafe { seq_store.sequences.offset_from(seq_store.sequencesStart) } as usize; + + for index in 0..nb_sequences { + let sequence = unsafe { *seq_store.sequencesStart.add(index) }; + let mut match_length = sequence.mlBase as usize + MINMATCH; + if index == seq_store.longLengthPos as usize + && seq_store.longLengthType == ZSTD_LLT_MATCH_LENGTH + { + match_length += 0x10000; + } + assert!(match_length >= match_len_lower_bound); + } +} + /// Returns whether all bytes in the input have the same value. /// /// This keeps the original C helper's precondition that `src` points to at @@ -3378,6 +3403,45 @@ mod tests { ); } + #[test] + fn validate_seq_store_accepts_ordinary_match_lengths() { + let mut fixture = TestSeqStore::new( + vec![ + SeqDef { + offBase: 1, + litLength: 0, + mlBase: 0, + }, + SeqDef { + offBase: 1, + litLength: 0, + mlBase: 1, + }, + ], + vec![], + ); + let seq_store = fixture.seq_store(); + + unsafe { ZSTD_rust_validateSeqStore(&seq_store, 3) }; + } + + #[test] + fn validate_seq_store_accounts_for_long_match_metadata() { + let mut fixture = TestSeqStore::new( + vec![SeqDef { + offBase: 1, + litLength: 0, + mlBase: 0, + }], + vec![], + ); + let mut seq_store = fixture.seq_store(); + seq_store.longLengthType = ZSTD_LLT_MATCH_LENGTH; + seq_store.longLengthPos = 0; + + unsafe { ZSTD_rust_validateSeqStore(&seq_store, 4) }; + } + #[test] fn post_process_rejects_external_count_overflow() { let mut out = [ZSTD_Sequence {