diff --git a/bench/snippets/buffer-create.mjs b/bench/snippets/buffer-create.mjs index c9740b7bc20c7b..7c38bfdf40cdb7 100644 --- a/bench/snippets/buffer-create.mjs +++ b/bench/snippets/buffer-create.mjs @@ -39,24 +39,24 @@ bench("Buffer.from(ArrayBuffer(100))", () => { return Buffer.from(hundred); }); -bench("new Buffer(ArrayBuffer(100))", () => { - return new Buffer(hundred); -}); - var hundredArray = new Uint8Array(100); bench("Buffer.from(Uint8Array(100))", () => { return Buffer.from(hundredArray); }); -bench("new Buffer(Uint8Array(100))", () => { - return new Buffer(hundredArray); -}); - var empty = new Uint8Array(0); bench("Buffer.from(Uint8Array(0))", () => { return Buffer.from(empty); }); +bench("new Buffer(ArrayBuffer(100))", () => { + return new Buffer(hundred); +}); + +bench("new Buffer(Uint8Array(100))", () => { + return new Buffer(hundredArray); +}); + bench("new Buffer(Uint8Array(0))", () => { return new Buffer(empty); }); diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 0d8df8f13e0b5a..32d9e1b3b14fbd 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -618,8 +618,12 @@ JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalOb return {}; } -JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name) { + if (!name.isEmpty()) { + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, makeString("\""_s, name, "\" is outside of buffer bounds"_s))); + return {}; + } throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); return {}; } diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index 226201199b2fcb..533aa01bdee0c0 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -78,7 +78,7 @@ JSC::EncodedJSValue INVALID_ARG_VALUE(JSC::ThrowScope& throwScope, JSC::JSGlobal JSC::EncodedJSValue UNKNOWN_ENCODING(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::StringView encoding); JSC::EncodedJSValue INVALID_STATE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& statemsg); JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); -JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ASCIILiteral name); JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal, bool triedUppercase = false); JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue port, bool allowZero); JSC::EncodedJSValue UNCAUGHT_EXCEPTION_CAPTURE_ALREADY_SET(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 662bad2b86a2ea..ecd0e243edd2fc 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -116,7 +116,7 @@ static JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_write); extern "C" EncodedJSValue WebCore_BufferEncodingType_toJS(JSC::JSGlobalObject* lexicalGlobalObject, WebCore::BufferEncodingType encoding) { // clang-format off - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); switch (encoding) { case WebCore::BufferEncodingType::utf8: return JSC::JSValue::encode(globalObject->commonStrings().utf8String(globalObject)); case WebCore::BufferEncodingType::ucs2: return JSC::JSValue::encode(globalObject->commonStrings().ucs2String(globalObject)); @@ -218,7 +218,7 @@ static JSUint8Array* allocBuffer(JSC::JSGlobalObject* lexicalGlobalObject, size_ auto throwScope = DECLARE_THROW_SCOPE(vm); #endif - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto* subclassStructure = globalObject->JSBufferSubclassStructure(); auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, byteLength); @@ -320,7 +320,7 @@ JSC::EncodedJSValue JSBuffer__bufferFromPointerAndLengthAndDeinit(JSC::JSGlobalO JSC::JSUint8Array* uint8Array = nullptr; - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto* subclassStructure = globalObject->JSBufferSubclassStructure(); if (LIKELY(length > 0)) { @@ -417,7 +417,7 @@ JSC::JSUint8Array* createEmptyBuffer(JSC::JSGlobalObject* lexicalGlobalObject) JSC::JSUint8Array* createUninitializedBuffer(JSC::JSGlobalObject* lexicalGlobalObject, size_t length) { - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto* subclassStructure = globalObject->JSBufferSubclassStructure(); return JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, subclassStructure, length); @@ -436,7 +436,7 @@ static inline JSC::JSUint8Array* JSBuffer__bufferFromLengthAsArray(JSC::JSGlobal return nullptr; } - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto* subclassStructure = globalObject->JSBufferSubclassStructure(); JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, static_cast(length)); @@ -2412,6 +2412,60 @@ JSC::JSObject* createBufferConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalO } // namespace WebCore +EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, JSValue arrayValue) +{ + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto* constructor = lexicalGlobalObject->m_typedArrayUint8.constructor(lexicalGlobalObject); + MarkedArgumentBuffer argsBuffer; + argsBuffer.append(arrayValue); + JSValue target = globalObject->JSBufferConstructor(); + // TODO: I wish we could avoid this - it adds ~30ns of overhead just using JSC::construct. + auto* object = JSC::construct(lexicalGlobalObject, constructor, target, argsBuffer, "Buffer failed to construct"_s); + RETURN_IF_EXCEPTION(throwScope, {}); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(object)); +} + +EncodedJSValue constructBufferFromArrayBuffer(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, size_t argsCount, JSValue arrayBufferValue, JSValue offsetValue, JSValue lengthValue) +{ + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + auto* jsBuffer = jsCast(arrayBufferValue.asCell()); + RefPtr buffer = jsBuffer->impl(); + if (buffer->isDetached()) { + return throwVMTypeError(globalObject, throwScope, "Buffer is detached"_s); + } + size_t byteLength = buffer->byteLength(); + size_t offset = 0; + size_t length = byteLength; + + if (!offsetValue.isUndefined()) { + double offsetD = offsetValue.toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + if (std::isnan(offsetD)) offsetD = 0; + offset = offsetD; + if (offset > byteLength) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "offset"_s); + length -= offset; + } + + if (!lengthValue.isUndefined()) { + double lengthD = lengthValue.toNumber(lexicalGlobalObject); + RETURN_IF_EXCEPTION(throwScope, {}); + if (std::isnan(lengthD)) lengthD = 0; + length = lengthD; + if (length > byteLength - offset) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "length"_s); + } + + auto* subclassStructure = globalObject->JSBufferSubclassStructure(); + auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(buffer), offset, length); + if (UNLIKELY(!uint8Array)) { + throwOutOfMemoryError(globalObject, throwScope); + return {}; + } + + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); +} + static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSValue newTarget, ArgList args) { auto& vm = JSC::getVM(lexicalGlobalObject); @@ -2423,28 +2477,27 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi } JSValue distinguishingArg = args.at(0); JSValue encodingArg = argsCount > 1 ? args.at(1) : JSValue(); - auto* globalObject = reinterpret_cast(lexicalGlobalObject); - + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); if (distinguishingArg.isAnyInt()) { throwScope.release(); if (args.at(1).isString()) { return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "string"_s, "string"_s, distinguishingArg); } - return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt()); + auto anyint = distinguishingArg.asAnyInt(); + if (anyint < 0 or anyint > Bun::Buffer::kMaxLength) return Bun::ERR::OUT_OF_RANGE(throwScope, lexicalGlobalObject, "size"_s, 0, Bun::Buffer::kMaxLength, distinguishingArg); + return JSValue::encode(allocBuffer(lexicalGlobalObject, anyint)); } else if (distinguishingArg.isNumber()) { JSValue lengthValue = distinguishingArg; Bun::V::validateNumber(throwScope, lexicalGlobalObject, lengthValue, "size"_s, jsNumber(0), jsNumber(Bun::Buffer::kMaxLength)); RETURN_IF_EXCEPTION(throwScope, {}); size_t length = lengthValue.toLength(lexicalGlobalObject); - return JSBuffer__bufferFromLength(lexicalGlobalObject, length); + return JSValue::encode(allocBuffer(lexicalGlobalObject, length)); } else if (distinguishingArg.isUndefinedOrNull() || distinguishingArg.isBoolean()) { auto arg_string = distinguishingArg.toWTFString(globalObject); auto message = makeString("The first argument must be of type string or an instance of Buffer, ArrayBuffer, Array or an Array-like object. Received "_s, arg_string); - throwTypeError(lexicalGlobalObject, throwScope, message); - return {}; + return throwVMTypeError(globalObject, throwScope, message); } else if (distinguishingArg.isCell()) { auto type = distinguishingArg.asCell()->type(); - switch (type) { case StringType: case StringObjectType: @@ -2452,7 +2505,6 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi throwScope.release(); return constructBufferFromStringAndEncoding(lexicalGlobalObject, distinguishingArg, encodingArg); } - case Uint16ArrayType: case Uint32ArrayType: case Int8ArrayType: @@ -2465,100 +2517,41 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi case BigUint64ArrayType: { // byteOffset and byteLength are ignored in this case, which is consitent with Node.js and new Uint8Array() JSC::JSArrayBufferView* view = jsCast(distinguishingArg.asCell()); - void* data = view->vector(); size_t byteLength = view->length(); - if (UNLIKELY(!data)) { throwException(globalObject, throwScope, createRangeError(globalObject, "Buffer is detached"_s)); return {}; } - auto* uint8Array = createUninitializedBuffer(lexicalGlobalObject, byteLength); if (UNLIKELY(!uint8Array)) { ASSERT(throwScope.exception()); return {}; } - if (byteLength) { uint8Array->setFromTypedArray(lexicalGlobalObject, 0, view, 0, byteLength, CopyType::LeftToRight); } - RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); break; } - case DataViewType: case Uint8ArrayType: case Uint8ClampedArrayType: { // byteOffset and byteLength are ignored in this case, which is consitent with Node.js and new Uint8Array() JSC::JSArrayBufferView* view = jsCast(distinguishingArg.asCell()); - void* data = view->vector(); size_t byteLength = view->byteLength(); - if (UNLIKELY(!data)) { throwException(globalObject, throwScope, createRangeError(globalObject, "Buffer is detached"_s)); return {}; } - auto* uint8Array = createBuffer(lexicalGlobalObject, static_cast(data), byteLength); - RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); } case ArrayBufferType: { // This closely matches `new Uint8Array(buffer, byteOffset, length)` in JavaScriptCore's implementation. // See Source/JavaScriptCore/runtime/JSGenericTypedArrayViewConstructorInlines.h - size_t offset = 0; - std::optional length; - if (argsCount > 1) { - - offset = args.at(1).toTypedArrayIndex(globalObject, "byteOffset"_s); - - // TOOD: return Node.js error - RETURN_IF_EXCEPTION(throwScope, {}); - - if (argsCount > 2) { - // If the length value is present but undefined, treat it as missing. - JSValue lengthValue = args.at(2); - if (!lengthValue.isUndefined()) { - length = lengthValue.toTypedArrayIndex(globalObject, "length"_s); - - // TOOD: return Node.js error - RETURN_IF_EXCEPTION(throwScope, {}); - } - } - } - - auto* jsBuffer = jsCast(distinguishingArg.asCell()); - RefPtr buffer = jsBuffer->impl(); - if (buffer->isDetached()) { - // TOOD: return Node.js error - throwTypeError(globalObject, throwScope, "Buffer is detached"_s); - return {}; - } - - if (!length) { - size_t byteLength = buffer->byteLength(); - if (buffer->isResizableOrGrowableShared()) { - if (UNLIKELY(offset > byteLength)) { - // TOOD: return Node.js error - throwNodeRangeError(globalObject, throwScope, "byteOffset exceeds source ArrayBuffer byteLength"_s); - return {}; - } - } else { - length = (byteLength - offset); - } - } - - auto* subclassStructure = globalObject->JSBufferSubclassStructure(); - auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(buffer), offset, length); - if (UNLIKELY(!uint8Array)) { - throwOutOfMemoryError(globalObject, throwScope); - return JSC::JSValue::encode({}); - } - - RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); + return constructBufferFromArrayBuffer(throwScope, lexicalGlobalObject, args.size(), distinguishingArg, args.at(1), args.at(2)); } default: { break; @@ -2566,26 +2559,8 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi } } - JSC::JSObject* constructor = lexicalGlobalObject->m_typedArrayUint8.constructor(lexicalGlobalObject); - - MarkedArgumentBuffer argsBuffer; - argsBuffer.append(distinguishingArg); - for (size_t i = 1; i < argsCount; ++i) - argsBuffer.append(args.at(i)); - - JSValue target = newTarget; - if (!target || !target.isCell()) { - target = globalObject->JSBufferConstructor(); - } - - JSC::JSObject* object = JSC::construct(lexicalGlobalObject, constructor, target, args, "Buffer failed to construct"_s); - if (!object) { - return JSC::JSValue::encode({}); - } - - RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(object)); + return constructBufferFromArray(throwScope, lexicalGlobalObject, distinguishingArg); } - JSC_DEFINE_HOST_FUNCTION(callJSBuffer, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { return createJSBufferFromJS(lexicalGlobalObject, callFrame->thisValue(), ArgList(callFrame)); diff --git a/src/bun.js/bindings/JSEnvironmentVariableMap.cpp b/src/bun.js/bindings/JSEnvironmentVariableMap.cpp index 53463a05611bfb..efff692442cf29 100644 --- a/src/bun.js/bindings/JSEnvironmentVariableMap.cpp +++ b/src/bun.js/bindings/JSEnvironmentVariableMap.cpp @@ -254,6 +254,7 @@ JSC_DEFINE_CUSTOM_SETTER(jsBunConfigVerboseFetchSetter, (JSGlobalObject * global return true; } +#if OS(WINDOWS) extern "C" void Bun__Process__editWindowsEnvVar(BunString, BunString); JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::CallFrame* callFrame)) @@ -274,6 +275,7 @@ JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::Cal } RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined())); } +#endif JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject) { diff --git a/src/bun.js/bindings/js_classes.ts b/src/bun.js/bindings/js_classes.ts index 31386e35804c87..564fbb39bcfb6a 100644 --- a/src/bun.js/bindings/js_classes.ts +++ b/src/bun.js/bindings/js_classes.ts @@ -1,8 +1,11 @@ export default [ // class list for $inherits*() builtins, eg. $inheritsBlob() // tests if a value is an instanceof a native class in a robust cross-realm manner + // source-of-truth impl in src/codegen/generate-classes.ts + // result in build/debug/codegen/ZigGeneratedClasses.cpp ["Blob"], ["ReadableStream", "JSReadableStream.h"], ["WritableStream", "JSWritableStream.h"], ["TransformStream", "JSTransformStream.h"], + ["ArrayBuffer"], ]; diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 2c0c09e982ba7f..26eac36ffc82a8 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -1,78 +1,43 @@ // This is marked as a constructor because Node.js allows `new Buffer.from`, // Some legacy dependencies depend on this, see #3638 $constructor; -export function from(items) { - if ($isUndefinedOrNull(items)) { - throw new TypeError( - "The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.", - ); - } +export function from(value, encodingOrOffset, length) { + if (typeof value === "string") return new $Buffer(value, encodingOrOffset); - // TODO: figure out why private symbol not found - if ( - typeof items === "string" || - (typeof items === "object" && - ($isTypedArrayView(items) || - items instanceof ArrayBuffer || - items instanceof SharedArrayBuffer || - items instanceof String)) - ) { - switch ($argumentCount()) { - case 1: { - return new $Buffer(items); - } - case 2: { - return new $Buffer(items, $argument(1)); - } - default: { - return new $Buffer(items, $argument(1), $argument(2)); - } + if (typeof value === "object" && value !== null) { + if ($inheritsArrayBuffer(value)) return new $Buffer(value, encodingOrOffset, length); + if ($isTypedArrayView(value)) return new $Buffer(value, encodingOrOffset, length); + + const valueOf = value.valueOf && value.valueOf(); + if (valueOf != null && valueOf !== value && (typeof valueOf === "string" || typeof valueOf === "object")) { + return Buffer.from(valueOf, encodingOrOffset, length); } - } - if (typeof items === "object") { - const data = items.data; - if (items.type === "Buffer" && Array.isArray(data)) { + + if (value.length !== undefined || $inheritsArrayBuffer(value.buffer)) { + if (typeof value.length !== "number") return new $Buffer(0); + if (value.length <= 0) return new $Buffer(0); + return new $Buffer(value); + } + const { type, data } = value; + if (type === "Buffer" && $isArray(data)) { + if (data.length <= 0) return new $Buffer(0); return new $Buffer(data); } - } - - var arrayLike = $toObject( - items, - "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.", - ) as ArrayLike; - - if (!$isJSArray(arrayLike)) { - const toPrimitive = $tryGetByIdWithWellKnownSymbol(items, "toPrimitive"); - - if (toPrimitive) { - const primitive = toPrimitive.$call(items, "string"); + const toPrimitive = $tryGetByIdWithWellKnownSymbol(value, "toPrimitive"); + if (typeof toPrimitive === "function") { + const primitive = toPrimitive.$call(value, "string"); if (typeof primitive === "string") { - switch ($argumentCount()) { - case 1: { - return new $Buffer(primitive); - } - case 2: { - return new $Buffer(primitive, $argument(1)); - } - default: { - return new $Buffer(primitive, $argument(1), $argument(2)); - } - } + return new $Buffer(primitive, encodingOrOffset); } } - - if (!("length" in arrayLike) || $isCallable(arrayLike)) { - throw new TypeError( - "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.", - ); - } } - // Don't pass the second argument because Node's Buffer.from doesn't accept - // a function and Uint8Array.from requires it if it exists - // That means we cannot use $tailCallFowrardArguments here, sadly - return new $Buffer(Uint8Array.from(arrayLike).buffer); + throw $ERR_INVALID_ARG_TYPE( + "first argument", + ["string", "Buffer", "ArrayBuffer", "Array", "Array-like Object"], + value, + ); } export function isBuffer(bufferlike) { diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index dbb059883fc3c6..b19ae42cf2071a 100644 --- a/test/js/node/buffer.test.js +++ b/test/js/node/buffer.test.js @@ -1,6 +1,7 @@ import { Buffer, SlowBuffer, isAscii, isUtf8, kMaxLength } from "buffer"; import { afterEach, beforeEach, describe, expect, it } from "bun:test"; import { gc } from "harness"; +import vm from "node:vm"; const BufferModule = await import("buffer"); @@ -2116,7 +2117,7 @@ for (let withOverridenBufferWrite of [false, true]) { const buf = Buffer.from(ab); expect(buf instanceof Buffer).toBe(true); - // expect(buf.parent, buf.buffer); + expect(buf.parent, buf.buffer); expect(buf.buffer).toBe(ab); expect(buf.length).toBe(ab.byteLength); @@ -2136,13 +2137,12 @@ for (let withOverridenBufferWrite of [false, true]) { // Now test protecting users from doing stupid things - // expect(function () { - // function AB() {} - // Object.setPrototypeOf(AB, ArrayBuffer); - // Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); - // // Buffer.from(new AB()); - // }).toThrow(); - // console.log(origAB !== ab); + expect(function () { + function AB() {} + Object.setPrototypeOf(AB, ArrayBuffer); + Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); + Buffer.from(new AB()); + }).toThrow(); // Test the byteOffset and length arguments { @@ -2670,8 +2670,8 @@ for (let withOverridenBufferWrite of [false, true]) { }); // Test that ArrayBuffer from a different context is detected correctly - // const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); - // expect(Buffer.byteLength(arrayBuf)).toBe(0); + const arrayBuf = vm.runInNewContext("new ArrayBuffer()"); + expect(Buffer.byteLength(arrayBuf)).toBe(0); // Verify that invalid encodings are treated as utf8 for (let i = 1; i < 10; i++) { diff --git a/test/js/node/test/parallel/test-buffer-arraybuffer.js b/test/js/node/test/parallel/test-buffer-arraybuffer.js new file mode 100644 index 00000000000000..d721888d15f76d --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-arraybuffer.js @@ -0,0 +1,151 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const LENGTH = 16; + +const ab = new ArrayBuffer(LENGTH); +const dv = new DataView(ab); +const ui = new Uint8Array(ab); +const buf = Buffer.from(ab); + + +assert.ok(buf instanceof Buffer); +assert.strictEqual(buf.parent, buf.buffer); +assert.strictEqual(buf.buffer, ab); +assert.strictEqual(buf.length, ab.byteLength); + + +buf.fill(0xC); +for (let i = 0; i < LENGTH; i++) { + assert.strictEqual(ui[i], 0xC); + ui[i] = 0xF; + assert.strictEqual(buf[i], 0xF); +} + +buf.writeUInt32LE(0xF00, 0); +buf.writeUInt32BE(0xB47, 4); +buf.writeDoubleLE(3.1415, 8); + +assert.strictEqual(dv.getUint32(0, true), 0xF00); +assert.strictEqual(dv.getUint32(4), 0xB47); +assert.strictEqual(dv.getFloat64(8, true), 3.1415); + + +// Now test protecting users from doing stupid things + +assert.throws(function() { + function AB() { } + Object.setPrototypeOf(AB, ArrayBuffer); + Object.setPrototypeOf(AB.prototype, ArrayBuffer.prototype); + Buffer.from(new AB()); +}, { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The first argument must be of type string, Buffer, ArrayBuffer, Array, or Array-like Object.' + + ' Received an instance of AB' +}); + +// Test the byteOffset and length arguments +{ + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer.from(ab.buffer, 1, 3); + assert.strictEqual(buf.length, 3); + assert.strictEqual(buf[0], 2); + assert.strictEqual(buf[1], 3); + assert.strictEqual(buf[2], 4); + buf[0] = 9; + assert.strictEqual(ab[1], 9); + + assert.throws(() => Buffer.from(ab.buffer, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer.from(ab.buffer, 3, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +// Test the deprecated Buffer() version also +{ + const ab = new Uint8Array(5); + ab[0] = 1; + ab[1] = 2; + ab[2] = 3; + ab[3] = 4; + ab[4] = 5; + const buf = Buffer(ab.buffer, 1, 3); + assert.strictEqual(buf.length, 3); + assert.strictEqual(buf[0], 2); + assert.strictEqual(buf[1], 3); + assert.strictEqual(buf[2], 4); + buf[0] = 9; + assert.strictEqual(ab[1], 9); + + assert.throws(() => Buffer(ab.buffer, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); + assert.throws(() => Buffer(ab.buffer, 3, 6), { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +{ + // If byteOffset is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0); + assert.deepStrictEqual(Buffer.from(ab, 'fhqwhgads'), expected); + assert.deepStrictEqual(Buffer.from(ab, NaN), expected); + assert.deepStrictEqual(Buffer.from(ab, {}), expected); + assert.deepStrictEqual(Buffer.from(ab, []), expected); + + // If byteOffset can be converted to a number, it will be. + assert.deepStrictEqual(Buffer.from(ab, [1]), Buffer.from(ab, 1)); + + // If byteOffset is Infinity, throw. + assert.throws(() => { + Buffer.from(ab, Infinity); + }, { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"offset" is outside of buffer bounds' + }); +} + +{ + // If length is not numeric, it defaults to 0. + const ab = new ArrayBuffer(10); + const expected = Buffer.from(ab, 0, 0); + assert.deepStrictEqual(Buffer.from(ab, 0, 'fhqwhgads'), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, NaN), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, {}), expected); + assert.deepStrictEqual(Buffer.from(ab, 0, []), expected); + + // If length can be converted to a number, it will be. + assert.deepStrictEqual(Buffer.from(ab, 0, [1]), Buffer.from(ab, 0, 1)); + + // If length is Infinity, throw. + assert.throws(() => { + Buffer.from(ab, 0, Infinity); + }, { + code: 'ERR_BUFFER_OUT_OF_BOUNDS', + name: 'RangeError', + message: '"length" is outside of buffer bounds' + }); +} + +// Test an array like entry with the length set to NaN. +assert.deepStrictEqual(Buffer.from({ length: NaN }), Buffer.alloc(0)); diff --git a/test/js/node/test/parallel/test-buffer-from.js b/test/js/node/test/parallel/test-buffer-from.js new file mode 100644 index 00000000000000..7457f8d66a4433 --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-from.js @@ -0,0 +1,143 @@ +'use strict'; + +const common = require('../common'); +const { deepStrictEqual, strictEqual, throws } = require('assert'); +const { runInNewContext } = require('vm'); + +const checkString = 'test'; + +const check = Buffer.from(checkString); + +class MyString extends String { + constructor() { + super(checkString); + } +} + +class MyPrimitive { + [Symbol.toPrimitive]() { + return checkString; + } +} + +class MyBadPrimitive { + [Symbol.toPrimitive]() { + return 1; + } +} + +deepStrictEqual(Buffer.from(new String(checkString)), check); +deepStrictEqual(Buffer.from(new MyString()), check); +deepStrictEqual(Buffer.from(new MyPrimitive()), check); +deepStrictEqual( + Buffer.from(runInNewContext('new String(checkString)', { checkString })), + check +); + +[ + {}, + new Boolean(true), + { valueOf() { return null; } }, + { valueOf() { return undefined; } }, + { valueOf: null }, + { __proto__: null }, + new Number(true), + new MyBadPrimitive(), + Symbol(), + 5n, + (one, two, three) => {}, + undefined, + null, +].forEach((input) => { + const errObj = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + message: 'The first argument must be of type string, Buffer, ArrayBuffer, Array, or Array-like Object.' + + common.invalidArgTypeHelper(input) + }; + throws(() => Buffer.from(input), errObj); + throws(() => Buffer.from(input, 'hex'), errObj); +}); + +Buffer.allocUnsafe(10); // Should not throw. +Buffer.from('deadbeaf', 'hex'); // Should not throw. + + +{ + const u16 = new Uint16Array([0xffff]); + const b16 = Buffer.copyBytesFrom(u16); + u16[0] = 0; + strictEqual(b16.length, 2); + strictEqual(b16[0], 255); + strictEqual(b16[1], 255); +} + +{ + const u16 = new Uint16Array([0, 0xffff]); + const b16 = Buffer.copyBytesFrom(u16, 1, 5); + u16[0] = 0xffff; + u16[1] = 0; + strictEqual(b16.length, 2); + strictEqual(b16[0], 255); + strictEqual(b16[1], 255); +} + +{ + const u32 = new Uint32Array([0xffffffff]); + const b32 = Buffer.copyBytesFrom(u32); + u32[0] = 0; + strictEqual(b32.length, 4); + strictEqual(b32[0], 255); + strictEqual(b32[1], 255); + strictEqual(b32[2], 255); + strictEqual(b32[3], 255); +} + +throws(() => { + Buffer.copyBytesFrom(); +}, { + code: 'ERR_INVALID_ARG_TYPE', +}); + +['', Symbol(), true, false, {}, [], () => {}, 1, 1n, null, undefined].forEach( + (notTypedArray) => throws(() => { + Buffer.copyBytesFrom('nope'); + }, { + code: 'ERR_INVALID_ARG_TYPE', + }) +); + +['', Symbol(), true, false, {}, [], () => {}, 1n].forEach((notANumber) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), notANumber); + }, { + code: 'ERR_INVALID_ARG_TYPE', + }) +); + +[-1, NaN, 1.1, -Infinity].forEach((outOfRange) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), outOfRange); + }, { + code: 'ERR_OUT_OF_RANGE', + }) +); + +['', Symbol(), true, false, {}, [], () => {}, 1n].forEach((notANumber) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), 0, notANumber); + }, { + code: 'ERR_INVALID_ARG_TYPE', + }) +); + +[-1, NaN, 1.1, -Infinity].forEach((outOfRange) => + throws(() => { + Buffer.copyBytesFrom(new Uint8Array(1), 0, outOfRange); + }, { + code: 'ERR_OUT_OF_RANGE', + }) +); + +// Invalid encoding is allowed +Buffer.from('asd', 1); diff --git a/test/js/node/test/parallel/test-buffer-sharedarraybuffer.js b/test/js/node/test/parallel/test-buffer-sharedarraybuffer.js new file mode 100644 index 00000000000000..52cec6e9c5cd1a --- /dev/null +++ b/test/js/node/test/parallel/test-buffer-sharedarraybuffer.js @@ -0,0 +1,27 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +const sab = new SharedArrayBuffer(24); +const arr1 = new Uint16Array(sab); +const arr2 = new Uint16Array(12); +arr2[0] = 5000; +arr1[0] = 5000; +arr1[1] = 4000; +arr2[1] = 4000; + +const arr_buf = Buffer.from(arr1.buffer); +const ar_buf = Buffer.from(arr2.buffer); + +assert.deepStrictEqual(arr_buf, ar_buf); + +arr1[1] = 6000; +arr2[1] = 6000; + +assert.deepStrictEqual(arr_buf, ar_buf); + +// Checks for calling Buffer.byteLength on a SharedArrayBuffer. +assert.strictEqual(Buffer.byteLength(sab), sab.byteLength); + +Buffer.from({ buffer: sab }); // Should not throw.