From d862966631e4a183cb3b481ac352638d596e7677 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Sat, 22 Feb 2025 00:24:44 -0800 Subject: [PATCH] node: test-buffer-write.js (#17450) --- src/bun.js/bindings/ErrorCode.cpp | 10 +- src/bun.js/bindings/JSBuffer.cpp | 199 +++++++++--------- src/bun.js/node/buffer.zig | 2 + src/bun.js/webcore/encoding.zig | 4 +- test/cli/create/create-jsx.test.ts | 2 +- test/js/bun/globals.test.js | 2 +- test/js/node/buffer.test.js | 14 +- test/js/node/dns/node-dns.test.js | 2 +- test/js/node/process/process.test.js | 2 +- test/js/node/test/common/index.js | 1 + .../js/node/test/parallel/test-buffer-fill.js | 4 +- .../node/test/parallel/test-buffer-write.js | 150 +++++++++++++ .../test-process-exit-code-validation.js | 6 +- .../test-zlib-deflate-constructors.js | 23 +- .../test/parallel/test-zlib-flush-flags.js | 8 +- 15 files changed, 290 insertions(+), 139 deletions(-) create mode 100644 test/js/node/test/parallel/test-buffer-write.js diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index db322a0582..76fca95eae 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -398,7 +398,7 @@ void determineSpecificType(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WTF:: StringView view = str; const bool needsEllipsis = jsString->length() > 28; - const bool needsEscape = str->contains('\''); + const bool needsEscape = str->contains('"'); if (needsEllipsis) { view = str->substring(0, 25); } @@ -425,17 +425,13 @@ void determineSpecificType(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WTF:: } } } else { - builder.append('\''); + builder.append('"'); builder.append(view); } if (needsEllipsis) { builder.append("..."_s); } - if (UNLIKELY(needsEscape)) { - builder.append('"'); - } else { - builder.append('\''); - } + builder.append('"'); builder.append(')'); return; } diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 971172393c..d9563967c4 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -278,7 +278,7 @@ static int normalizeCompareVal(int val, size_t a_length, size_t b_length) return val; } -static WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::ThrowScope& scope, JSValue arg) +static WebCore::BufferEncodingType parseEncoding(JSC::ThrowScope& scope, JSC::JSGlobalObject* lexicalGlobalObject, JSValue arg, bool validateUnknown) { auto arg_ = arg.toStringOrNull(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); @@ -286,6 +286,10 @@ static WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lexicalGlo std::optional encoded = parseEnumeration2(*lexicalGlobalObject, view); if (UNLIKELY(!encoded)) { + if (validateUnknown) { + Bun::V::validateString(scope, lexicalGlobalObject, arg, "encoding"_s); + RETURN_IF_EXCEPTION(scope, WebCore::BufferEncodingType::utf8); + } Bun::ERR::UNKNOWN_ENCODING(scope, lexicalGlobalObject, view); return WebCore::BufferEncodingType::utf8; } @@ -293,6 +297,25 @@ static WebCore::BufferEncodingType parseEncoding(JSC::JSGlobalObject* lexicalGlo return encoded.value(); } +uint32_t validateOffset(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, JSC::JSValue name, uint32_t min, uint32_t max) +{ + if (UNLIKELY(!value.isNumber())) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + auto value_num = value.asNumber(); + if (UNLIKELY(std::fmod(value_num, 1.0) != 0)) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + if (UNLIKELY(value_num < min || value_num > max)) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min, max, value); + uint32_t result = JSC::toInt32(value_num); + return result; +} +uint32_t validateOffset(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSC::JSValue value, WTF::ASCIILiteral name, uint32_t min, uint32_t max) +{ + if (UNLIKELY(!value.isNumber())) return Bun::ERR::INVALID_ARG_TYPE(scope, globalObject, name, "number"_s, value); + auto value_num = value.asNumber(); + if (UNLIKELY(std::fmod(value_num, 1.0) != 0)) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, "an integer"_s, value); + if (UNLIKELY(value_num < min || value_num > max)) return Bun::ERR::OUT_OF_RANGE(scope, globalObject, name, min, max, value); + uint32_t result = JSC::toInt32(value_num); + return result; +} + namespace WebCore { using namespace JSC; @@ -590,7 +613,11 @@ static JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSGlobalOb Bun::V::validateNumber(scope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(scope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + if (length == 0) { + return JSValue::encode(createEmptyBuffer(lexicalGlobalObject)); + } // fill argument if (UNLIKELY(callFrame->argumentCount() > 1)) { auto* uint8Array = createUninitializedBuffer(lexicalGlobalObject, length); @@ -606,7 +633,7 @@ static JSC::EncodedJSValue jsBufferConstructorFunction_allocBody(JSC::JSGlobalOb if (callFrame->argumentCount() > 2) { EnsureStillAliveScope arg2 = callFrame->uncheckedArgument(2); if (!arg2.value().isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, arg2.value()); + encoding = parseEncoding(scope, lexicalGlobalObject, arg2.value(), true); RETURN_IF_EXCEPTION(scope, {}); } } @@ -1246,8 +1273,7 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlobalObjec } if (!encodingValue.isUndefined() && value.isString()) { - if (!encodingValue.isString()) return Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "encoding"_s, "string"_s, encodingValue); - encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); + encoding = parseEncoding(scope, lexicalGlobalObject, encodingValue, true); RETURN_IF_EXCEPTION(scope, {}); } @@ -1290,7 +1316,7 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlobalObjec size_t length = view->byteLength(); if (UNLIKELY(length == 0)) { - throwTypeError(lexicalGlobalObject, scope, "Buffer cannot be empty"_s); + scope.throwException(lexicalGlobalObject, createError(lexicalGlobalObject, Bun::ErrorCode::ERR_INVALID_ARG_VALUE, "Buffer cannot be empty"_s)); return {}; } @@ -1805,7 +1831,7 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalO return jsBufferToString(vm, lexicalGlobalObject, castedThis, start, end, encoding); if (!arg1.isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, arg1); + encoding = parseEncoding(scope, lexicalGlobalObject, arg1, false); RETURN_IF_EXCEPTION(scope, {}); } @@ -1906,27 +1932,39 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_SliceWithEncoding(JSC::JSGl template static JSC::EncodedJSValue jsBufferPrototypeFunction_writeEncodingBody(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSArrayBufferView* castedThis, JSString* str, JSValue offsetValue, JSValue lengthValue) { - size_t offset = 0; - size_t length = castedThis->byteLength(); - size_t max = length; + size_t byteLength = castedThis->byteLength(); auto scope = DECLARE_THROW_SCOPE(vm); - if (UNLIKELY(!parseArrayIndex(scope, lexicalGlobalObject, offsetValue, offset, "offset must be > 0"_s))) { - return {}; + double offset; + double length; + + if (offsetValue.isUndefined()) { + offset = 0; + } else if (offsetValue.isNumber()) { + offset = offsetValue.asNumber(); + } else { + offset = offsetValue.toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); + } + if (lengthValue.isUndefined()) { + length = byteLength - offset; + } else if (lengthValue.isNumber()) { + length = lengthValue.asNumber(); + } else { + length = lengthValue.toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(scope, {}); } - if (UNLIKELY(offset > max)) { - throwNodeRangeError(lexicalGlobalObject, scope, "offset is out of bounds"_s); - return {}; + if (offset < 0 || offset > byteLength) { + Bun::ERR::BUFFER_OUT_OF_BOUNDS(scope, lexicalGlobalObject, "offset"); + RETURN_IF_EXCEPTION(scope, {}); + } + if (length < 0 || length > byteLength - offset) { + Bun::ERR::BUFFER_OUT_OF_BOUNDS(scope, lexicalGlobalObject, "length"); + RETURN_IF_EXCEPTION(scope, {}); } - if (UNLIKELY(!parseArrayIndex(scope, lexicalGlobalObject, lengthValue, max, "length must be > 0"_s))) { - return {}; - } - - size_t max_length = std::min(length - offset, max); - - RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, max_length, encoding)); + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); } template @@ -1937,7 +1975,8 @@ static JSC::EncodedJSValue jsBufferPrototypeFunctionWriteWithEncoding(JSC::JSGlo auto* castedThis = JSC::jsDynamicCast(callFrame->thisValue()); - JSString* text = callFrame->argument(0).toStringOrNull(lexicalGlobalObject); + auto arg0 = callFrame->argument(0); + JSString* text = arg0.toStringOrNull(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); JSValue offsetValue = callFrame->argument(1); @@ -1961,90 +2000,62 @@ static JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlobalObje auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); - uint32_t offset = 0; - uint32_t length = castedThis->byteLength(); - uint32_t max = length; - WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8; + auto stringValue = callFrame->argument(0); + auto offsetValue = callFrame->argument(1); + auto lengthValue = callFrame->argument(2); + auto encodingValue = callFrame->argument(3); - if (UNLIKELY(callFrame->argumentCount() == 0)) { - throwTypeError(lexicalGlobalObject, scope, "Not enough arguments"_s); - return {}; - } - - EnsureStillAliveScope arg0 = callFrame->argument(0); - auto* str = arg0.value().toStringOrNull(lexicalGlobalObject); - if (!str) { - throwTypeError(lexicalGlobalObject, scope, "write() expects a string"_s); - return {}; - } - - JSValue offsetValue = jsUndefined(); - JSValue lengthValue = jsUndefined(); - JSValue encodingValue = jsUndefined(); - - switch (callFrame->argumentCount()) { - case 4: - encodingValue = callFrame->uncheckedArgument(3); - FALLTHROUGH; - case 3: - lengthValue = callFrame->uncheckedArgument(2); - FALLTHROUGH; - case 2: - offsetValue = callFrame->uncheckedArgument(1); - break; - default: - break; - } - - auto setEncoding = [&]() { - if (!encodingValue.isUndefined()) { - encoding = parseEncoding(lexicalGlobalObject, scope, encodingValue); - } - }; + uint32_t offset; + uint32_t length; if (offsetValue.isUndefined()) { - // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1053 - RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); + Bun::V::validateString(scope, lexicalGlobalObject, stringValue, "string"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* str = stringValue.toString(lexicalGlobalObject); + offset = 0; + length = castedThis->byteLength(); + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, WebCore::BufferEncodingType::utf8)); } - if (lengthValue.isUndefined() && offsetValue.isString()) { - // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1056 encodingValue = offsetValue; - setEncoding(); + offset = 0; + length = castedThis->byteLength(); + + auto* str = stringValue.toString(lexicalGlobalObject); + auto encoding = parseEncoding(scope, lexicalGlobalObject, encodingValue, false); RETURN_IF_EXCEPTION(scope, {}); RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); - } - - if (UNLIKELY(!offsetValue.isNumber())) { - throwTypeError(lexicalGlobalObject, scope, "Invalid offset"_s); - return {}; - } - - int32_t userOffset = offsetValue.toInt32(lexicalGlobalObject); - RETURN_IF_EXCEPTION(scope, {}); - if (userOffset < 0 || userOffset > max) { - throwNodeRangeError(lexicalGlobalObject, scope, "Offset is out of bounds"_s); - return {}; - } - offset = static_cast(userOffset); - uint32_t remaining = max - static_cast(userOffset); - - // https://github.com/nodejs/node/blob/e676942f814915b2d24fc899bb42dc71ae6c8226/lib/buffer.js#L1062-L1077 - if (lengthValue.isUndefined()) { - length = remaining; - } else if (lengthValue.isString()) { - encodingValue = lengthValue; - setEncoding(); - RETURN_IF_EXCEPTION(scope, {}); - length = remaining; } else { - setEncoding(); - - int32_t userLength = lengthValue.toInt32(lexicalGlobalObject); + length = castedThis->byteLength(); + offset = validateOffset(scope, lexicalGlobalObject, offsetValue, "offset"_s, 0, length); RETURN_IF_EXCEPTION(scope, {}); - length = std::min(static_cast(userLength), remaining); + uint32_t remaining = castedThis->byteLength() - offset; + + if (lengthValue.isUndefined()) { + length = remaining; + } else if (lengthValue.isString()) { + encodingValue = lengthValue; + length = remaining; + } else { + length = validateOffset(scope, lexicalGlobalObject, lengthValue, "length"_s, 0, length); + RETURN_IF_EXCEPTION(scope, {}); + if (length > remaining) { + length = remaining; + } + } } + Bun::V::validateString(scope, lexicalGlobalObject, stringValue, "string"_s); + RETURN_IF_EXCEPTION(scope, {}); + auto* str = stringValue.toString(lexicalGlobalObject); + + if (!encodingValue.toBoolean(lexicalGlobalObject)) { + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, WebCore::BufferEncodingType::utf8)); + } + + auto encoding = parseEncoding(scope, lexicalGlobalObject, encodingValue, false); + RETURN_IF_EXCEPTION(scope, {}); + RELEASE_AND_RETURN(scope, writeToBuffer(lexicalGlobalObject, castedThis, str, offset, length, encoding)); } diff --git a/src/bun.js/node/buffer.zig b/src/bun.js/node/buffer.zig index 175fa9a6b6..5ee8ecb092 100644 --- a/src/bun.js/node/buffer.zig +++ b/src/bun.js/node/buffer.zig @@ -50,6 +50,8 @@ pub const BufferVectorized = struct { Encoder.writeU8(str.slice().ptr, str.slice().len, buf.ptr, buf.len, .hex), } catch return false; + if (written == 0 and str.length() > 0) return false; + switch (written) { 0 => return true, 1 => { diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index 95846b6e32..2823320ce7 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -1253,7 +1253,7 @@ pub const Encoder = struct { }, .hex => { - return strings.decodeHexToBytes(to_ptr[0..to_len], u8, input[0..len]); + return strings.decodeHexToBytesTruncate(to_ptr[0..to_len], u8, input[0..len]); }, .base64, .base64url => { @@ -1332,7 +1332,7 @@ pub const Encoder = struct { }, .hex => { - return strings.decodeHexToBytes(to[0..to_len], u16, input[0..len]); + return strings.decodeHexToBytesTruncate(to[0..to_len], u16, input[0..len]); }, .base64, .base64url => { diff --git a/test/cli/create/create-jsx.test.ts b/test/cli/create/create-jsx.test.ts index 7e4f8f5b88..46c414981d 100644 --- a/test/cli/create/create-jsx.test.ts +++ b/test/cli/create/create-jsx.test.ts @@ -80,7 +80,7 @@ async function fetchAndInjectHTML(url: string) { constructor(url) { } }; - + location.href = url; document.write(initial); window.happyDOM.waitUntilComplete().then(() => { diff --git a/test/js/bun/globals.test.js b/test/js/bun/globals.test.js index 14d6449bda..97b71e2268 100644 --- a/test/js/bun/globals.test.js +++ b/test/js/bun/globals.test.js @@ -36,7 +36,7 @@ it("ERR_INVALID_THIS", () => { } catch (e) { expect(e.code).toBe("ERR_INVALID_THIS"); expect(e.name).toBe("TypeError"); - expect(e.message).toBe("Expected this to be instanceof Request, but received type string ('hellooo')"); + expect(e.message).toBe(`Expected this to be instanceof Request, but received type string ("hellooo")`); } }); diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 064e497f41..0b58d1c52e 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -21,8 +21,8 @@ afterEach(() => gc()); */ const NumberIsInteger = Number.isInteger; class ERR_INVALID_ARG_TYPE extends TypeError { - constructor() { - super("Invalid arg type" + Array.prototype.join.call(arguments, " ")); + constructor(name, type, value) { + super(`The "${name}" argument must be of type ${type}. Received type ${typeof value} (${Bun.inspect(value)})`); this.code = "ERR_INVALID_ARG_TYPE"; } } @@ -101,7 +101,7 @@ const validateInteger = (value, name, min = Number.MIN_SAFE_INTEGER, max = Numbe if (value < min || value > max) throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); }; const validateOffset = (value, name, min = 0, max = kMaxLength) => validateInteger(value, name, min, max); -function nodeJSBufferWriteFn(string, offset, length, encoding = "utf8") { +function nodeJSBufferWriteFn(string, offset, length, encoding) { // Buffer#write(string); if (offset === undefined) { return this.utf8Write(string, 0, this.length); @@ -129,7 +129,8 @@ function nodeJSBufferWriteFn(string, offset, length, encoding = "utf8") { } } - if (!encoding) return this.utf8Write(string, offset, length); + if (!encoding || encoding === "utf8") return this.utf8Write(string, offset, length); + if (encoding === "ascii") return this.asciiWrite(string, offset, length); const ops = getEncodingOps(encoding); if (ops === undefined) throw new ERR_UNKNOWN_ENCODING(encoding); @@ -270,7 +271,9 @@ for (let withOverridenBufferWrite of [false, true]) { // Invalid encoding for Buffer.write expect(() => b.write("test string", 0, 5, "invalid")).toThrow(/encoding/); // Unsupported arguments for Buffer.write - expect(() => b.write("test", "utf8", 0)).toThrow(/invalid/i); + expect(() => b.write("test", "utf8", 0)).toThrow( + `The "offset" argument must be of type number. Received type string ("utf8")`, + ); }); it("create 0-length buffers", () => { @@ -2761,7 +2764,6 @@ for (let withOverridenBufferWrite of [false, true]) { expect(latin1Write.call(buf, "í", 28)).toBe(1); expect(latin1Write.call(buf, "é", 30)).toBe(1); expect(latin1Write.call(buf, "ò", 32)).toBe(1); - expect(latin1Write.call(buf, "ò", 32, 999999)).toBe(1); expect(buf).toStrictEqual( new Uint8Array(Buffer.from("6f6c64206d63646f6e616c6420686164206120666172e920ed20e920ed20e920f2", "hex")), diff --git a/test/js/node/dns/node-dns.test.js b/test/js/node/dns/node-dns.test.js index 0fedecf1f0..aad547803a 100644 --- a/test/js/node/dns/node-dns.test.js +++ b/test/js/node/dns/node-dns.test.js @@ -423,7 +423,7 @@ describe("test invalid arguments", () => { }).toThrow("Expected address to be a non-empty string for 'lookupService'."); expect(() => { dns.lookupService("google.com", 443, (err, hostname, service) => {}); - }).toThrow("The \"address\" argument is invalid. Received type string ('google.com')"); + }).toThrow(`The "address" argument is invalid. Received type string ("google.com")`); }); }); diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 340dcdd82b..f390bafdd3 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -374,7 +374,7 @@ it("process.argv in testing", () => { describe("process.exitCode", () => { it("validates int", () => { expect(() => (process.exitCode = "potato")).toThrow( - `The "code" argument must be of type number. Received type string ('potato')`, + `The "code" argument must be of type number. Received type string ("potato")`, ); expect(() => (process.exitCode = 1.2)).toThrow( `The value of \"code\" is out of range. It must be an integer. Received 1.2`, diff --git a/test/js/node/test/common/index.js b/test/js/node/test/common/index.js index ff9255b51c..ef04b184e3 100644 --- a/test/js/node/test/common/index.js +++ b/test/js/node/test/common/index.js @@ -914,6 +914,7 @@ function invalidArgTypeHelper(input) { let inspected = inspect(input, { colors: false }); if (inspected.length > 28) { inspected = `${inspected.slice(inspected, 0, 25)}...`; } + if (inspected.startsWith("'") && inspected.endsWith("'")) inspected = `"${inspected.slice(1, inspected.length - 1)}"`; // BUN: util.inspect uses ' but bun uses " for strings return ` Received type ${typeof input} (${inspected})`; } diff --git a/test/js/node/test/parallel/test-buffer-fill.js b/test/js/node/test/parallel/test-buffer-fill.js index 55e5d3399f..f5ef0fa17a 100644 --- a/test/js/node/test/parallel/test-buffer-fill.js +++ b/test/js/node/test/parallel/test-buffer-fill.js @@ -2,8 +2,6 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -// const { codes: { ERR_OUT_OF_RANGE } } = require('internal/errors'); -// const { internalBinding } = require('internal/test/binding'); const SIZE = 28; const buf1 = Buffer.allocUnsafe(SIZE); @@ -366,7 +364,7 @@ Buffer.alloc(8, ''); // Testing process.binding. Make sure "end" is properly checked for range errors. // assert.throws( -// () => { internalBinding('buffer').fill(Buffer.alloc(1), 1, 1, -2, 1); }, +// () => { process.binding('buffer').fill(Buffer.alloc(1), 1, 1, -2, 1); }, // { code: 'ERR_OUT_OF_RANGE' } // ); diff --git a/test/js/node/test/parallel/test-buffer-write.js b/test/js/node/test/parallel/test-buffer-write.js new file mode 100644 index 0000000000..4eeaab03f6 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-write.js @@ -0,0 +1,150 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +[-1, 10].forEach((offset) => { + assert.throws( + () => Buffer.alloc(9).write('foo', offset), + { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + message: 'The value of "offset" is out of range. ' + + `It must be >= 0 and <= 9. Received ${offset}` + } + ); +}); + +const resultMap = new Map([ + ['utf8', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['ucs2', Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ['ascii', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['latin1', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['binary', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['utf16le', Buffer.from([102, 0, 111, 0, 111, 0, 0, 0, 0])], + ['base64', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['base64url', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], + ['hex', Buffer.from([102, 111, 111, 0, 0, 0, 0, 0, 0])], +]); + +// utf8, ucs2, ascii, latin1, utf16le +const encodings = ['utf8', 'utf-8', 'ucs2', 'ucs-2', 'ascii', 'latin1', + 'binary', 'utf16le', 'utf-16le']; + +encodings + .reduce((es, e) => es.concat(e, e.toUpperCase()), []) + .forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('foo', encoding); + assert.strictEqual(buf.write('foo', 0, len, encoding), len); + + if (encoding.includes('-')) + encoding = encoding.replace('-', ''); + + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); + }); + +// base64 +['base64', 'BASE64', 'base64url', 'BASE64URL'].forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('Zm9v', encoding); + + assert.strictEqual(buf.write('Zm9v', 0, len, encoding), len); + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); +}); + +// hex +['hex', 'HEX'].forEach((encoding) => { + const buf = Buffer.alloc(9); + const len = Buffer.byteLength('666f6f', encoding); + + assert.strictEqual(buf.write('666f6f', 0, len, encoding), len); + assert.deepStrictEqual(buf, resultMap.get(encoding.toLowerCase())); +}); + +// Invalid encodings +for (let i = 1; i < 10; i++) { + const encoding = String(i).repeat(i); + const error = common.expectsError({ + code: 'ERR_UNKNOWN_ENCODING', + name: 'TypeError', + message: `Unknown encoding: ${encoding}` + }); + + assert.ok(!Buffer.isEncoding(encoding)); + assert.throws(() => Buffer.alloc(9).write('foo', encoding), error); +} + +// UCS-2 overflow CVE-2018-12115 +for (let i = 1; i < 4; i++) { + // Allocate two Buffers sequentially off the pool. Run more than once in case + // we hit the end of the pool and don't get sequential allocations + const x = Buffer.allocUnsafe(4).fill(0); + const y = Buffer.allocUnsafe(4).fill(1); + // Should not write anything, pos 3 doesn't have enough room for a 16-bit char + assert.strictEqual(x.write('ыыыыыы', 3, 'ucs2'), 0); + // CVE-2018-12115 experienced via buffer overrun to next block in the pool + assert.strictEqual(Buffer.compare(y, Buffer.alloc(4, 1)), 0); +} + +// Should not write any data when there is no space for 16-bit chars +const z = Buffer.alloc(4, 0); +assert.strictEqual(z.write('\u0001', 3, 'ucs2'), 0); +assert.strictEqual(Buffer.compare(z, Buffer.alloc(4, 0)), 0); +// Make sure longer strings are written up to the buffer end. +assert.strictEqual(z.write('abcd', 2), 2); +assert.deepStrictEqual([...z], [0, 0, 0x61, 0x62]); + +// Large overrun could corrupt the process +assert.strictEqual(Buffer.alloc(4) + .write('ыыыыыы'.repeat(100), 3, 'utf16le'), 0); + +{ + // .write() does not affect the byte after the written-to slice of the Buffer. + // Refs: https://github.com/nodejs/node/issues/26422 + const buf = Buffer.alloc(8); + assert.strictEqual(buf.write('ыы', 1, 'utf16le'), 4); + assert.deepStrictEqual([...buf], [0, 0x4b, 0x04, 0x4b, 0x04, 0, 0, 0]); +} + +{ + const buf = Buffer.alloc(1); + assert.strictEqual(buf.write('ww'), 1); + assert.strictEqual(buf.toString(), 'w'); +} + +assert.throws(() => { + const buf = Buffer.alloc(1); + assert.strictEqual(buf.asciiWrite('ww', 0, -1)); +}, common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + message: '"length" is outside of buffer bounds', +})); +assert.throws(() => { + const buf = Buffer.alloc(1); + assert.strictEqual(buf.latin1Write('ww', 0, -1)); +}, common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + message: '"length" is outside of buffer bounds', +})); +assert.throws(() => { + const buf = Buffer.alloc(1); + assert.strictEqual(buf.utf8Write('ww', 0, -1)); +}, common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + message: '"length" is outside of buffer bounds', +})); + +assert.throws(() => { + Buffer.alloc(1).asciiWrite('ww', 0, 2); +}, common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + message: '"length" is outside of buffer bounds', +})); + +assert.throws(() => { + Buffer.alloc(1).asciiWrite('ww', 1, 1); +}, common.expectsError({ + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + message: '"length" is outside of buffer bounds', +})); diff --git a/test/js/node/test/parallel/test-process-exit-code-validation.js b/test/js/node/test/parallel/test-process-exit-code-validation.js index 7548dcdc38..dc9bb4c979 100644 --- a/test/js/node/test/parallel/test-process-exit-code-validation.js +++ b/test/js/node/test/parallel/test-process-exit-code-validation.js @@ -6,17 +6,17 @@ const invalids = [ { code: '', expected: 1, - pattern: "Received type string \\(''\\)$", + pattern: `Received type string \\(""\\)$`, }, { code: '1 one', expected: 1, - pattern: "Received type string \\('1 one'\\)$", + pattern: `Received type string \\("1 one"\\)$`, }, { code: 'two', expected: 1, - pattern: "Received type string \\('two'\\)$", + pattern: `Received type string \\("two"\\)$`, }, { code: {}, diff --git a/test/js/node/test/parallel/test-zlib-deflate-constructors.js b/test/js/node/test/parallel/test-zlib-deflate-constructors.js index 9f918e13c9..4c735c78d0 100644 --- a/test/js/node/test/parallel/test-zlib-deflate-constructors.js +++ b/test/js/node/test/parallel/test-zlib-deflate-constructors.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const zlib = require('zlib'); const assert = require('assert'); @@ -18,8 +18,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.chunkSize" property must be of type number. ' + - "Received type string ('test')" + message: 'The "options.chunkSize" property must be of type number.' + common.invalidArgTypeHelper('test') } ); @@ -52,8 +51,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.windowBits" property must be of type number. ' + - "Received type string ('test')" + message: 'The "options.windowBits" property must be of type number.' + common.invalidArgTypeHelper('test') } ); @@ -93,8 +91,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.level" property must be of type number. ' + - "Received type string ('test')" + message: 'The "options.level" property must be of type number.' + common.invalidArgTypeHelper('test') } ); @@ -134,8 +131,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "level" argument must be of type number. ' + - "Received type string ('test')" + message: 'The "level" argument must be of type number.' + common.invalidArgTypeHelper('test') } ); @@ -175,8 +171,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.memLevel" property must be of type number. ' + - "Received type string ('test')" + message: 'The "options.memLevel" property must be of type number.' + common.invalidArgTypeHelper('test') } ); @@ -223,8 +218,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.strategy" property must be of type number. ' + - "Received type string ('test')" + message: 'The "options.strategy" property must be of type number.' + common.invalidArgTypeHelper('test') } ); @@ -264,8 +258,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "strategy" argument must be of type number. ' + - "Received type string ('test')" + message: 'The "strategy" argument must be of type number.' + common.invalidArgTypeHelper('test') } ); diff --git a/test/js/node/test/parallel/test-zlib-flush-flags.js b/test/js/node/test/parallel/test-zlib-flush-flags.js index 3d8e609adb..f156c81847 100644 --- a/test/js/node/test/parallel/test-zlib-flush-flags.js +++ b/test/js/node/test/parallel/test-zlib-flush-flags.js @@ -1,5 +1,5 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const zlib = require('zlib'); @@ -10,8 +10,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.flush" property must be of type number. ' + - "Received type string ('foobar')" + message: 'The "options.flush" property must be of type number.' + common.invalidArgTypeHelper('foobar') } ); @@ -32,8 +31,7 @@ assert.throws( { code: 'ERR_INVALID_ARG_TYPE', name: 'TypeError', - message: 'The "options.finishFlush" property must be of type number. ' + - "Received type string ('foobar')" + message: 'The "options.finishFlush" property must be of type number.' + common.invalidArgTypeHelper('foobar') } );