Conversation
|
Welcome to Node.js, and thank you for your first contribution! Before review, please take a moment to read:
Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal. |
You will get this message on any new PR until a commit from a PR that you have submitted has actually landed in the default Your other PR #65521 is approved, but it seems to have stalled. |
This comment was marked as resolved.
This comment was marked as resolved.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #66088 +/- ##
==========================================
- Coverage 90.28% 90.27% -0.01%
==========================================
Files 789 789
Lines 271625 271633 +8
Branches 51849 51852 +3
==========================================
- Hits 245228 245210 -18
- Misses 16863 16903 +40
+ Partials 9534 9520 -14
🚀 New features to boost your workflow:
|
Resetting a ZstdCompress stream while a frame is still in progress dropped the frame state, but any bytes already written out stayed at the start of the output stream. The next frame was then appended to that fragment, so the resulting stream could not be decompressed. The failure was silent: the compressor reported no error at all. zstd requires internal buffers to be fully flushed before a new compression job starts. Track whether the current frame has completed and throw ERR_ZLIB_INCOMPLETE_FRAME from reset() otherwise. Signed-off-by: bun-unsafe <bun-unsafe@users.noreply.github.com>
49d577d to
f6cd7c6
Compare
|
@MikeMcC399 |
|
@MikeMcC399 |
Calling
reset()on a zstd compressor while a frame is still in progress leftthe stream in a state where it produced output that could not be decompressed,
without reporting anything. This makes
reset()throw instead.Fixes #66087.
The problem
reset()drops the state of the frame currently being compressed. Any bytesthat were already written out — by
flush(), or by an earlierwrite()thatfilled the output buffer — cannot be taken back, so they stay at the start of
the output stream. The next frame is then appended to that fragment, and the
result decodes as corruption.
Without the
reset()the same input produces 22 bytes and decodes tohelloworld, becauseend()continues the existing frame (8 bytes) instead ofstarting a new one (14 bytes).
zstd requires buffers to be fully flushed before a new compression job starts —
see
ZSTD_compressStream2indeps/zstd/lib/zstd.h:ZSTD_compressStream2returns non-zero while a frame is unfinished, which ishow a caller is meant to detect this. The compressor context was not keeping
track of that.
The fix
ZstdCompressContextnow records whether the current frame has completed. Aframe is complete once
ZSTD_compressStream2has been called withZSTD_e_endand returned 0.
ResetStream()refuses withERR_ZLIB_INCOMPLETE_FRAMEotherwise:
This follows the existing behaviour for the other invalid reset case, where
reset()during a write already throws.After the change the repro above reports the error instead of emitting a
corrupt stream.
What is not changed
reset()before any write still works.flush()followed byend()still works and still produces a valid stream.reset()on a finished frame is unaffected.Tests
test/parallel/test-zlib-zstd-reset-incomplete-frame.jscovers the resetmid-frame case, the valid
flush()+end()sequence, and the validreset()before writing.Ran against a
--debug-node --debug-symbolsbuild on Linux x86_64:Existing zlib tests:
Notes
gzip and brotli hit the same class of problem for the same call sequence (their
output is undecodable too). I kept this PR to zstd because that is where the
failure is completely silent, but I am happy to look at the other two codecs
separately if that is wanted.