| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,6 +25,7 @@ const { | |||
| 25 | 25 | SafePromiseAllSettledReturnVoid, | |
| 26 | 26 | SafeSet, | |
| 27 | 27 | String, | |
| 28 | + StringFromCharCode, | ||
| 28 | 29 | StringPrototypeIndexOf, | |
| 29 | 30 | StringPrototypeSlice, | |
| 30 | 31 | StringPrototypeStartsWith, | |
@@ -261,6 +262,7 @@ class FileTest extends Test { | |||
| 261 | 262 | #rawBuffer = []; // Raw data waiting to be parsed | |
| 262 | 263 | #rawBufferSize = 0; | |
| 263 | 264 | #reportedChildren = 0; | |
| 265 | + #pendingPartialV8Header = false; | ||
| 264 | 266 | failedSubtests = false; | |
| 265 | 267 | ||
| 266 | 268 | constructor(options) { | |
@@ -352,6 +354,12 @@ class FileTest extends Test { | |||
| 352 | 354 | } | |
| 353 | 355 | parseMessage(readData) { | |
| 354 | 356 | let dataLength = TypedArrayPrototypeGetLength(readData); | |
| 357 | + if (this.#pendingPartialV8Header) { | ||
| 358 | + readData = Buffer.concat([TypedArrayPrototypeSubarray(v8Header, 0, 1), readData]); | ||
| 359 | + dataLength = TypedArrayPrototypeGetLength(readData); | ||
| 360 | + this.#pendingPartialV8Header = false; | ||
| 361 | + } | ||
| 362 | + | ||
| 355 | 363 | if (dataLength === 0) return; | |
| 356 | 364 | const partialV8Header = readData[dataLength - 1] === v8Header[0]; | |
| 357 | 365 | ||
@@ -362,22 +370,52 @@ class FileTest extends Test { | |||
| 362 | 370 | dataLength--; | |
| 363 | 371 | } | |
| 364 | 372 | ||
| 365 | - if (this.#rawBuffer[0] && TypedArrayPrototypeGetLength(this.#rawBuffer[0]) < kSerializedSizeHeader) { | ||
| 366 | - this.#rawBuffer[0] = Buffer.concat([this.#rawBuffer[0], readData]); | ||
| 367 | - } else { | ||
| 368 | - ArrayPrototypePush(this.#rawBuffer, readData); | ||
| 373 | + if (dataLength > 0) { | ||
| 374 | + if (this.#rawBuffer[0] && TypedArrayPrototypeGetLength(this.#rawBuffer[0]) < kSerializedSizeHeader) { | ||
| 375 | + this.#rawBuffer[0] = Buffer.concat([this.#rawBuffer[0], readData]); | ||
| 376 | + } else { | ||
| 377 | + ArrayPrototypePush(this.#rawBuffer, readData); | ||
| 378 | + } | ||
| 379 | + this.#rawBufferSize += dataLength; | ||
| 380 | + this.#processRawBuffer(); | ||
| 369 | 381 | } | |
| 370 | - this.#rawBufferSize += dataLength; | ||
| 371 | - this.#processRawBuffer(); | ||
| 372 | 382 | ||
| 373 | 383 | if (partialV8Header) { | |
| 374 | - ArrayPrototypePush(this.#rawBuffer, TypedArrayPrototypeSubarray(v8Header, 0, 1)); | ||
| 375 | - this.#rawBufferSize++; | ||
| 384 | + this.#pendingPartialV8Header = true; | ||
| 376 | 385 | } | |
| 377 | 386 | } | |
| 378 | 387 | #drainRawBuffer() { | |
| 388 | + if (this.#pendingPartialV8Header) { | ||
| 389 | + ArrayPrototypePush(this.#rawBuffer, TypedArrayPrototypeSubarray(v8Header, 0, 1)); | ||
| 390 | + this.#rawBufferSize++; | ||
| 391 | + this.#pendingPartialV8Header = false; | ||
| 392 | + } | ||
| 393 | + | ||
| 379 | 394 | while (this.#rawBuffer.length > 0) { | |
| 395 | + const prevBufferLength = this.#rawBuffer.length; | ||
| 396 | + const prevBufferSize = this.#rawBufferSize; | ||
| 380 | 397 | this.#processRawBuffer(); | |
| 398 | + | ||
| 399 | + if (this.#rawBuffer.length === prevBufferLength && | ||
| 400 | + this.#rawBufferSize === prevBufferSize) { | ||
| 401 | + const bufferHead = this.#rawBuffer[0]; | ||
| 402 | + this.addToReport({ | ||
| 403 | + __proto__: null, | ||
| 404 | + type: 'test:stdout', | ||
| 405 | + data: { | ||
| 406 | + __proto__: null, | ||
| 407 | + file: this.name, | ||
| 408 | + message: StringFromCharCode(bufferHead[0]), | ||
| 409 | + }, | ||
| 410 | + }); | ||
| 411 | + | ||
| 412 | + if (TypedArrayPrototypeGetLength(bufferHead) === 1) { | ||
| 413 | + ArrayPrototypeShift(this.#rawBuffer); | ||
| 414 | + } else { | ||
| 415 | + this.#rawBuffer[0] = TypedArrayPrototypeSubarray(bufferHead, 1); | ||
| 416 | + } | ||
| 417 | + this.#rawBufferSize--; | ||
| 418 | + } | ||
| 381 | 419 | } | |
| 382 | 420 | } | |
| 383 | 421 | #processRawBuffer() { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,12 +14,29 @@ async function toArray(chunks) { | |||
| 14 | 14 | return arr; | |
| 15 | 15 | } | |
| 16 | 16 | ||
| 17 | - const chunks = await toArray(serializer([ | ||
| 18 | - { type: 'test:diagnostic', data: { nesting: 0, details: {}, message: 'diagnostic' } }, | ||
| 19 | - ])); | ||
| 17 | + const diagnosticEvent = { | ||
| 18 | + type: 'test:diagnostic', | ||
| 19 | + data: { nesting: 0, details: {}, message: 'diagnostic' }, | ||
| 20 | + }; | ||
| 21 | + const chunks = await toArray(serializer([diagnosticEvent])); | ||
| 20 | 22 | const defaultSerializer = new DefaultSerializer(); | |
| 21 | 23 | defaultSerializer.writeHeader(); | |
| 22 | 24 | const headerLength = defaultSerializer.releaseBuffer().length; | |
| 25 | + const headerOnly = Buffer.from([0xff, 0x0f]); | ||
| 26 | + const oversizedLengthHeader = Buffer.from([0xff, 0x0f, 0x7f, 0xff, 0xff, 0xff]); | ||
| 27 | + const truncatedLengthHeader = Buffer.from([0xff, 0x0f, 0x00, 0x01, 0x00, 0x00]); | ||
| 28 | + // Expected stdout for oversizedLengthHeader: first byte is emitted via | ||
| 29 | + // String.fromCharCode (byte-by-byte fallback in #drainRawBuffer), remaining | ||
| 30 | + // bytes go through the nonSerialized UTF-8 decode path in #processRawBuffer. | ||
| 31 | + const oversizedLengthStdout = String.fromCharCode(oversizedLengthHeader[0]) + | ||
| 32 | + Buffer.from(oversizedLengthHeader.subarray(1)).toString('utf-8'); | ||
| 33 | + | ||
| 34 | + function collectStdout(reported) { | ||
| 35 | + return reported | ||
| 36 | + .filter((event) => event.type === 'test:stdout') | ||
| 37 | + .map((event) => event.data.message) | ||
| 38 | + .join(''); | ||
| 39 | + } | ||
| 23 | 40 | ||
| 24 | 41 | describe('v8 deserializer', common.mustCall(() => { | |
| 25 | 42 | let fileTest; | |
@@ -56,35 +73,86 @@ describe('v8 deserializer', common.mustCall(() => { | |||
| 56 | 73 | ||
| 57 | 74 | it('should deserialize a serialized chunk', async () => { | |
| 58 | 75 | const reported = await collectReported(chunks); | |
| 59 | - assert.deepStrictEqual(reported, [ | ||
| 60 | - { data: { nesting: 0, details: {}, message: 'diagnostic' }, type: 'test:diagnostic' }, | ||
| 61 | - ]); | ||
| 76 | + assert.deepStrictEqual(reported, [diagnosticEvent]); | ||
| 62 | 77 | }); | |
| 63 | 78 | ||
| 64 | 79 | it('should deserialize a serialized chunk after non-serialized chunk', async () => { | |
| 65 | 80 | const reported = await collectReported([Buffer.concat([Buffer.from('unknown'), ...chunks])]); | |
| 66 | 81 | assert.deepStrictEqual(reported, [ | |
| 67 | 82 | { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | |
| 68 | - { data: { nesting: 0, details: {}, message: 'diagnostic' }, type: 'test:diagnostic' }, | ||
| 83 | + diagnosticEvent, | ||
| 69 | 84 | ]); | |
| 70 | 85 | }); | |
| 71 | 86 | ||
| 72 | 87 | it('should deserialize a serialized chunk before non-serialized output', async () => { | |
| 73 | 88 | const reported = await collectReported([Buffer.concat([ ...chunks, Buffer.from('unknown')])]); | |
| 74 | 89 | assert.deepStrictEqual(reported, [ | |
| 75 | - { data: { nesting: 0, details: {}, message: 'diagnostic' }, type: 'test:diagnostic' }, | ||
| 90 | + diagnosticEvent, | ||
| 76 | 91 | { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | |
| 77 | 92 | ]); | |
| 78 | 93 | }); | |
| 79 | 94 | ||
| 95 | + it('should not hang when buffer starts with v8Header followed by oversized length', async () => { | ||
| 96 | + // Regression test for https://github.com/nodejs/node/issues/62693 | ||
| 97 | + // FF 0F is the v8 serializer header; the next 4 bytes are read as a | ||
| 98 | + // big-endian message size. 0x7FFFFFFF far exceeds any actual buffer | ||
| 99 | + // size, causing #processRawBuffer to make no progress and | ||
| 100 | + // #drainRawBuffer to loop forever without the no-progress guard. | ||
| 101 | + const reported = await collectReported([oversizedLengthHeader]); | ||
| 102 | + assert.partialDeepStrictEqual( | ||
| 103 | + reported, | ||
| 104 | + Array.from({ length: reported.length }, () => ({ type: 'test:stdout' })), | ||
| 105 | + ); | ||
| 106 | + assert.strictEqual(collectStdout(reported), oversizedLengthStdout); | ||
| 107 | + }); | ||
| 108 | + | ||
| 109 | + it('should flush incomplete v8 frame as stdout and keep prior valid data', async () => { | ||
| 110 | + // A valid non-serialized message followed by bytes that look like | ||
| 111 | + // a v8 header with a truncated/oversized length. | ||
| 112 | + const reported = await collectReported([ | ||
| 113 | + Buffer.from('hello'), | ||
| 114 | + truncatedLengthHeader, | ||
| 115 | + ]); | ||
| 116 | + assert.strictEqual(collectStdout(reported), `hello${truncatedLengthHeader.toString('latin1')}`); | ||
| 117 | + }); | ||
| 118 | + | ||
| 119 | + it('should flush v8Header-only bytes as stdout when stream ends', async () => { | ||
| 120 | + // Just the two-byte v8 header with no size field at all. | ||
| 121 | + const reported = await collectReported([headerOnly]); | ||
| 122 | + assert(reported.every((event) => event.type === 'test:stdout')); | ||
| 123 | + assert.strictEqual(collectStdout(reported), headerOnly.toString('latin1')); | ||
| 124 | + }); | ||
| 125 | + | ||
| 126 | + it('should resync and parse valid messages after false v8 header', async () => { | ||
| 127 | + // A false v8 header (FF 0F + oversized length) followed by a | ||
| 128 | + // legitimate serialized message. The parser must skip the corrupt | ||
| 129 | + // bytes and still deserialize the real message. | ||
| 130 | + const reported = await collectReported([ | ||
| 131 | + oversizedLengthHeader, | ||
| 132 | + ...chunks, | ||
| 133 | + ]); | ||
| 134 | + assert.deepStrictEqual(reported.at(-1), diagnosticEvent); | ||
| 135 | + assert.strictEqual(reported.filter((event) => event.type === 'test:diagnostic').length, 1); | ||
| 136 | + assert.strictEqual(collectStdout(reported), oversizedLengthStdout); | ||
| 137 | + }); | ||
| 138 | + | ||
| 139 | + it('should preserve a false v8 header split across chunks', async () => { | ||
| 140 | + const reported = await collectReported([ | ||
| 141 | + oversizedLengthHeader.subarray(0, 1), | ||
| 142 | + oversizedLengthHeader.subarray(1), | ||
| 143 | + ]); | ||
| 144 | + assert(reported.every((event) => event.type === 'test:stdout')); | ||
| 145 | + assert.strictEqual(collectStdout(reported), oversizedLengthStdout); | ||
| 146 | + }); | ||
| 147 | + | ||
| 80 | 148 | const headerPosition = headerLength * 2 + 4; | |
| 81 | 149 | for (let i = 0; i < headerPosition + 5; i++) { | |
| 82 | 150 | const message = `should deserialize a serialized message split into two chunks {...${i},${i + 1}...}`; | |
| 83 | 151 | it(message, async () => { | |
| 84 | 152 | const data = chunks[0]; | |
| 85 | 153 | const reported = await collectReported([data.subarray(0, i), data.subarray(i)]); | |
| 86 | 154 | assert.deepStrictEqual(reported, [ | |
| 87 | - { data: { nesting: 0, details: {}, message: 'diagnostic' }, type: 'test:diagnostic' }, | ||
| 155 | + diagnosticEvent, | ||
| 88 | 156 | ]); | |
| 89 | 157 | }); | |
| 90 | 158 | ||
@@ -96,7 +164,7 @@ describe('v8 deserializer', common.mustCall(() => { | |||
| 96 | 164 | ]); | |
| 97 | 165 | assert.deepStrictEqual(reported, [ | |
| 98 | 166 | { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | |
| 99 | - { data: { nesting: 0, details: {}, message: 'diagnostic' }, type: 'test:diagnostic' }, | ||
| 167 | + diagnosticEvent, | ||
| 100 | 168 | { data: { __proto__: null, file: 'filetest', message: 'unknown' }, type: 'test:stdout' }, | |
| 101 | 169 | ]); | |
| 102 | 170 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments