js: fix Buffer constructor and Buffer.from (#16731)

This commit is contained in:
Meghan Denny
2025-02-07 15:13:21 -08:00
committed by GitHub
parent c970922456
commit 180500181f
11 changed files with 444 additions and 174 deletions

View File

@@ -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);
});

View File

@@ -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 {};
}

View File

@@ -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);

View File

@@ -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<Zig::GlobalObject*>(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<Zig::GlobalObject*>(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<Zig::GlobalObject*>(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<Zig::GlobalObject*>(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<Zig::GlobalObject*>(lexicalGlobalObject);
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, static_cast<size_t>(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<JSC::JSArrayBuffer*>(arrayBufferValue.asCell());
RefPtr<ArrayBuffer> 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<Zig::GlobalObject*>(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<JSC::JSArrayBufferView*>(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<JSC::JSArrayBufferView*>(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<uint8_t*>(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<size_t> 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<JSC::JSArrayBuffer*>(distinguishingArg.asCell());
RefPtr<ArrayBuffer> 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));

View File

@@ -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)
{

View File

@@ -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"],
];

View File

@@ -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);
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);
}
// 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);
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);
}
case 2: {
return new $Buffer(items, $argument(1));
}
default: {
return new $Buffer(items, $argument(1), $argument(2));
}
}
}
if (typeof items === "object") {
const data = items.data;
if (items.type === "Buffer" && Array.isArray(data)) {
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<any>;
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.",
throw $ERR_INVALID_ARG_TYPE(
"first argument",
["string", "Buffer", "ArrayBuffer", "Array", "Array-like Object"],
value,
);
}
}
// 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);
}
export function isBuffer(bufferlike) {

View File

@@ -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++) {

View File

@@ -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));

View File

@@ -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);

View File

@@ -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.