@@ -361,3 +361,108 @@ func TestReader_WriteTo(t *testing.T) {
361361 t .Fatal ("result does not match original" )
362362 }
363363}
364+
365+ // TestReader_DirectModeStaleData verifies that a zero-length uncompressed block
366+ // in direct mode does not cause stale pool data to be returned. Before the fix,
367+ // r.data was not cleared after a direct-mode decompress, so a subsequent
368+ // zero-length block would copy stale data from r.data.
369+ func TestReader_DirectModeStaleData (t * testing.T ) {
370+ // Build a minimal LZ4 frame containing:
371+ // 1. A normal uncompressed block with known data
372+ // 2. A zero-length uncompressed block (0x80000000)
373+ // 3. End-of-stream marker (0x00000000)
374+ //
375+ // First, compress real data to get a valid frame, then append the
376+ // zero-length block before the end marker.
377+ payload := []byte ("hello, world! this is test data for direct mode stale check." )
378+ var compressed bytes.Buffer
379+ zw := lz4 .NewWriter (& compressed )
380+ if err := zw .Apply (lz4 .ConcurrencyOption (- 1 ), lz4 .ChecksumOption (false )); err != nil {
381+ t .Fatal (err )
382+ }
383+ if _ , err := zw .Write (payload ); err != nil {
384+ t .Fatal (err )
385+ }
386+ if err := zw .Close (); err != nil {
387+ t .Fatal (err )
388+ }
389+
390+ // The frame ends with: [end mark: 00 00 00 00].
391+ // Insert a zero-length uncompressed block (0x80000000) before the end mark.
392+ raw := compressed .Bytes ()
393+ endMark := raw [len (raw )- 4 :] // last 4 bytes = end mark
394+ prefix := raw [:len (raw )- 4 ] // everything before end mark
395+ var frame bytes.Buffer
396+ frame .Write (prefix )
397+ frame .Write ([]byte {0x00 , 0x00 , 0x00 , 0x80 }) // zero-length uncompressed block
398+ frame .Write (endMark ) // end mark
399+
400+ // Read with a large buffer to force the direct path (buf >= block size)
401+ // and concurrency=1. The zero-length block triggers the bn==0 fallback
402+ // in Reader.Read, which would copy stale r.data if the fix is missing.
403+ zr := lz4 .NewReader (& frame )
404+ if err := zr .Apply (lz4 .ConcurrencyOption (- 1 )); err != nil {
405+ t .Fatal (err )
406+ }
407+ var result bytes.Buffer
408+ buf := make ([]byte , 64 * 1024 ) // >= default block size (64KB)
409+ for {
410+ n , readErr := zr .Read (buf )
411+ if n > 0 {
412+ result .Write (buf [:n ])
413+ }
414+ if readErr == io .EOF {
415+ break
416+ }
417+ if readErr != nil {
418+ t .Fatal (readErr )
419+ }
420+ }
421+ if ! bytes .Equal (result .Bytes (), payload ) {
422+ t .Fatalf ("decompressed data mismatch: got %d bytes %q, want %d bytes %q" ,
423+ result .Len (), result .Bytes (), len (payload ), payload )
424+ }
425+ }
426+
427+ func TestReader_BufferIssue (t * testing.T ) {
428+ for _ , opts := range [][]lz4.Option {
429+ nil ,
430+ _o (lz4 .ConcurrencyOption (2 )),
431+ } {
432+ label := fmt .Sprintf ("%v" , opts )
433+ t .Run (label , func (t * testing.T ) {
434+ pr , pw := io .Pipe ()
435+ go func (w * io.PipeWriter ) {
436+ defer w .Close ()
437+ file , err := os .Open ("testdata/bundle.00001.part00000.lz4" )
438+ if err != nil {
439+ w .CloseWithError (err )
440+ return
441+ }
442+ defer file .Close ()
443+ io .Copy (w , file )
444+ }(pw )
445+ data := make ([]byte , 1024 * 1024 * 4 )
446+ lz4Reader := lz4 .NewReader (pr )
447+ if err := lz4Reader .Apply (opts ... ); err != nil {
448+ t .Fatal (err )
449+ }
450+ var total int
451+ for {
452+ n , readErr := lz4Reader .Read (data )
453+ if n > 0 {
454+ total += n
455+ }
456+ if readErr == io .EOF {
457+ break
458+ }
459+ if readErr != nil {
460+ t .Fatal (readErr )
461+ }
462+ }
463+ if total != 128 * 1024 * 1024 {
464+ t .Fatalf ("incorrect number of bytes: got %d, want %d" , total , 128 * 1024 * 1024 )
465+ }
466+ })
467+ }
468+ }
0 commit comments