Conversation
|
Review requested:
|
|
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. |
e61f146 to
ff40068
Compare
|
This seems reasonable as a mitigation, but I'm concerned that swallowing deserialization errors could hide real regressions in the internal report protocol. Could we make the frame validation stricter before deserializing instead? For example, checking for the expected inner V8 header after the length field may let us reject false positives as stdout without hiding genuine deserialization failures. Separating the report stream from user stdout still seems like the cleanest long-term fix, but stricter validation might be a smaller alternative. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #66273 +/- ##
==========================================
- Coverage 90.35% 90.34% -0.01%
==========================================
Files 789 789
Lines 273493 273506 +13
Branches 52281 52280 -1
==========================================
- Hits 247101 247091 -10
- Misses 16853 16891 +38
+ Partials 9539 9524 -15
🚀 New features to boost your workflow:
|
|
Thanks for the sharp review. You are right that a blanket catch could hide a real regression in the report protocol. I pushed a commit that validates the inner v8 header before deserializing, so stray stdout that only mimics a frame is rejected as output, while a genuine frame that fails to deserialize is left to surface instead of being swallowed. I also added a test for that second case. I agree that separating the report stream from user stdout is the cleanest long-term fix. |
8120db3 to
642807f
Compare
| const reported = await collectReported([ | ||
| plausibleSizeFalseHeader, | ||
| ...chunks, | ||
| ]); |
There was a problem hiding this comment.
Does this actually verify resync before drain()?
collectReported() calls fileTest.drain() after feeding all chunks, so this would still pass if the real frame stayed buffered until the child exited.
Could we assert before drain() instead, e.g.:
fileTest.parseMessage(plausibleSizeFalseHeader);
fileTest.parseMessage(chunks[0]);
assert.deepStrictEqual(reported.at(-1), reportedDiagnosticEvent);
If the inner-header check already tells us this is not a valid frame, perhaps we could consume one byte as stdout and immediately restart the header search instead of breaking.
The child test process sends framed report messages and raw user stdout over one pipe, using the bytes FF 0F to mark the start of a frame. User output can contain those same bytes, so #processRawBuffer could read a plausible size from stray stdout and hand the bytes to the v8 deserializer. The deserializer then threw. Because the call had no error handling, the exception aborted the whole test run. Read the frame before advancing the buffer and wrap the deserialize in a try/catch. When the read fails, leave the buffer untouched and stop parsing frames so #drainRawBuffer emits the stray byte as stdout and rescans for the next real header. This turns a fatal crash into recoverable stdout and preserves any real frames that follow the stray bytes. Fixes: nodejs#66164 Signed-off-by: Muhammad Faizan Uddin <faizan.uddin94@gmail.com>
Check that a framed payload starts with the inner v8 header before handing it to the deserializer, so stray stdout that mimics a frame is rejected as output while genuine deserialize failures still surface. Signed-off-by: Muhammad Faizan Uddin <faizan.uddin94@gmail.com>
Recovery from stray stdout that mimics a frame happened only at shutdown, inside #drainRawBuffer, so a real frame that followed the stray bytes was reported late. Move the recovery into #processRawBuffer. When the payload does not start with the inner v8 header, emit one byte as stdout and restart the header search right away, so the next real frame is reported live. Assert before drain() in the test so it verifies live recovery instead of recovery at shutdown. Signed-off-by: Muhammad Faizan Uddin <faizan.uddin94@gmail.com>
642807f to
2899307
Compare
|
Thanks for the review. I have addressed the remaining comment. Recovery is now live inside The test now asserts before I also rebased onto the latest main to pick up #66307. |
Summary
node --testcan crash the parent runner when a test writes bytes to stdout that look like a v8 report frame. A singleconsole.logof the wrong bytes aborts the whole run with an internal error that does not point at any test:Fixes: #66164
Root cause
The child test process multiplexes framed report messages and raw user stdout on one pipe. A frame starts with the two magic bytes
FF 0Ffollowed by a 4 byte size.#processRawBufferscans for that magic, reads the size and hands the payload to the v8 deserializer. User output can contain those same bytes, so stray stdout with a plausible size reaches the deserializer, which throws. The call had no error handling, so the exception aborted the entire run. The earlier>>> 0fix in #64706 hardened the size read only. This case has a valid looking size and fails one step later at the deserialize.Fix
Two small changes in
#processRawBuffer:readHeader()andreadValue()in try/catch. On failure, leave the buffer untouched and stop parsing frames. The existing#drainRawBufferno progress path then emits the stray byte as stdout and rescans for the next real header.This turns a fatal crash into recoverable stdout, keeps any real frames that follow the stray bytes and matches how the runner already treats false headers with an oversized size.
Tests
Added four cases to
test/parallel/test-runner-v8-deserializer.mjs:Known limitation (possible follow-up, out of scope here)
If stray bytes ever form a valid v8 payload, the deserialize would succeed and report a bogus item. This is pre-existing and extremely unlikely. Fully removing the ambiguity needs an escape mechanism or a separate channel for user stdout, which is a larger change. This PR removes the crash, which is the reported bug.