| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 89aba0c commit f33dba7
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -28,23 +28,21 @@ MaybeLocal<String> MakeString(Isolate* isolate, | |||
| 28 | 28 | const char* data, | |
| 29 | 29 | size_t length, | |
| 30 | 30 | enum encoding encoding) { | |
| 31 | - MaybeLocal<Value> ret; | ||
| 32 | - if (encoding == UTF8) { | ||
| 33 | - MaybeLocal<String> utf8_string; | ||
| 34 | - if (length <= static_cast<size_t>(v8::String::kMaxLength)) { | ||
| 35 | - utf8_string = String::NewFromUtf8( | ||
| 36 | - isolate, data, v8::NewStringType::kNormal, length); | ||
| 37 | - } | ||
| 38 | - if (utf8_string.IsEmpty()) { | ||
| 39 | - isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate)); | ||
| 40 | - return MaybeLocal<String>(); | ||
| 41 | - } else { | ||
| 42 | - return utf8_string; | ||
| 43 | - } | ||
| 44 | - } else { | ||
| 45 | - ret = StringBytes::Encode(isolate, data, length, encoding); | ||
| 31 | + // StringBytes::Encode() would report an over-long UTF-8 input as | ||
| 32 | + // ERR_BUFFER_TOO_LARGE (or clamp it); keep reporting it the way this | ||
| 33 | + // decoder always has. | ||
| 34 | + if (encoding == UTF8 && length > static_cast<size_t>(v8::String::kMaxLength)) | ||
| 35 | + [[unlikely]] { | ||
| 36 | + isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate)); | ||
| 37 | + return MaybeLocal<String>(); | ||
| 46 | 38 | } | |
| 47 | 39 | ||
| 40 | + // For UTF-8 this takes the simdutf-backed ASCII / Latin-1 / UTF-16 paths and | ||
| 41 | + // only falls back to v8::String::NewFromUtf8() (the previous unconditional | ||
| 42 | + // path here) for input containing invalid sequences, so U+FFFD replacement | ||
| 43 | + // is unchanged. | ||
| 44 | + MaybeLocal<Value> ret = StringBytes::Encode(isolate, data, length, encoding); | ||
| 45 | + | ||
| 48 | 46 | if (ret.IsEmpty()) { | |
| 49 | 47 | return {}; | |
| 50 | 48 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,81 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + // The UTF-8 StringDecoder shares its byte->string conversion with | ||
| 3 | + // Buffer#toString(): ASCII, Latin-1-representable and general inputs take | ||
| 4 | + // different (SIMD) paths depending on content and size, and invalid input | ||
| 5 | + // falls back to a replacing decoder. This test pins the decoder's output for | ||
| 6 | + // inputs that cross those size thresholds, for chunkings that split multibyte | ||
| 7 | + // sequences, and for invalid bytes embedded in otherwise large valid input. | ||
| 8 | + require('../common'); | ||
| 9 | + const assert = require('assert'); | ||
| 10 | + const { StringDecoder } = require('string_decoder'); | ||
| 11 | + | ||
| 12 | + function decodeInChunks(buf, chunkSize) { | ||
| 13 | + const decoder = new StringDecoder('utf8'); | ||
| 14 | + let out = ''; | ||
| 15 | + for (let i = 0; i < buf.length; i += chunkSize) { | ||
| 16 | + out += decoder.write(buf.subarray(i, i + chunkSize)); | ||
| 17 | + } | ||
| 18 | + return out + decoder.end(); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + function check(str, label) { | ||
| 22 | + const buf = Buffer.from(str, 'utf8'); | ||
| 23 | + // Sanity: the expectation itself round-trips. | ||
| 24 | + assert.strictEqual(buf.toString('utf8'), str, `${label}: toString`); | ||
| 25 | + for (const chunkSize of [1, 2, 3, 4, 5, 7, 31, 32, 33, 255, 256, 257, | ||
| 26 | + 4095, 4096, 65536, buf.length]) { | ||
| 27 | + if (chunkSize > buf.length) continue; | ||
| 28 | + // Keep the test fast: byte-sized chunks only for the smaller inputs. | ||
| 29 | + if (buf.length > 100_000 && chunkSize < 4095) continue; | ||
| 30 | + assert.strictEqual(decodeInChunks(buf, chunkSize), str, | ||
| 31 | + `${label}: chunkSize=${chunkSize}`); | ||
| 32 | + } | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + const sizes = [31, 32, 33, 255, 256, 257, 4096, 70000, (1 << 20) + 5]; | ||
| 36 | + for (const size of sizes) { | ||
| 37 | + check('a'.repeat(size), `ascii ${size}`); | ||
| 38 | + // Latin-1 range only (one-byte string in V8, two bytes each in UTF-8). | ||
| 39 | + check('é'.repeat(size), `latin1 ${size}`); | ||
| 40 | + // ASCII with a single Latin-1 character at the end / start. | ||
| 41 | + check('a'.repeat(size - 1) + 'ÿ', `ascii+latin1 tail ${size}`); | ||
| 42 | + check('Ä' + 'a'.repeat(size - 1), `latin1 head+ascii ${size}`); | ||
| 43 | + // BMP beyond Latin-1 (three-byte sequences). | ||
| 44 | + check('日'.repeat(size), `cjk ${size}`); | ||
| 45 | + // Mixed, including astral plane characters (surrogate pairs, 4 bytes). | ||
| 46 | + check(('abé日\u{1F600}').repeat(Math.ceil(size / 6)), `mixed ${size}`); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + // Invalid bytes inside otherwise valid input of every size class must still be | ||
| 50 | + // replaced with U+FFFD exactly as before, regardless of chunking. | ||
| 51 | + for (const size of [8, 40, 300, 5000, (1 << 20) + 5]) { | ||
| 52 | + const valid = Buffer.from('a'.repeat(size)); | ||
| 53 | + for (const bad of [[0xff], [0xc0, 0xaf], [0xe2, 0x28, 0xa1], | ||
| 54 | + [0xed, 0xa0, 0x80] /* encoded surrogate */, | ||
| 55 | + [0xf0, 0x9f, 0x98] /* truncated 4-byte */]) { | ||
| 56 | + const buf = Buffer.concat([valid, Buffer.from(bad), valid]); | ||
| 57 | + const expected = buf.toString('utf8'); | ||
| 58 | + assert.ok(expected.includes('�'), `size=${size} bad=${bad}`); | ||
| 59 | + for (const chunkSize of [1, 3, 64, size, size + 1, buf.length]) { | ||
| 60 | + if (buf.length > 100_000 && chunkSize < size) continue; | ||
| 61 | + assert.strictEqual(decodeInChunks(buf, chunkSize), expected, | ||
| 62 | + `invalid ${bad} in ${size}, chunkSize=${chunkSize}`); | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // A lone continuation / lead byte split across the size classes at the very | ||
| 68 | + // end is buffered by the decoder and flushed as U+FFFD by end(). | ||
| 69 | + { | ||
| 70 | + const decoder = new StringDecoder('utf8'); | ||
| 71 | + const big = Buffer.concat([Buffer.from('a'.repeat(300)), Buffer.from([0xe2, 0x82])]); | ||
| 72 | + assert.strictEqual(decoder.write(big), 'a'.repeat(300)); | ||
| 73 | + assert.strictEqual(decoder.end(), '�'); | ||
| 74 | + } | ||
| 75 | + { | ||
| 76 | + const decoder = new StringDecoder('utf8'); | ||
| 77 | + const big = Buffer.concat([Buffer.from('é'.repeat(300)), Buffer.from([0xe2, 0x82])]); | ||
| 78 | + assert.strictEqual(decoder.write(big), 'é'.repeat(300)); | ||
| 79 | + assert.strictEqual(decoder.write(Buffer.from([0xac])), '€'); | ||
| 80 | + assert.strictEqual(decoder.end(), ''); | ||
| 81 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments