From 703345ba668880e8eb111856cf97c068a8f20022 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Fri, 24 Jan 2025 17:09:54 -0800 Subject: [PATCH 1/7] js: fix Buffer constructor and Buffer.from --- src/bun.js/bindings/ErrorCode.cpp | 6 +- src/bun.js/bindings/ErrorCode.h | 2 +- src/bun.js/bindings/JSBuffer.cpp | 170 +++++++++--------- .../bindings/JSEnvironmentVariableMap.cpp | 2 + src/js/builtins/JSBufferConstructor.ts | 83 +++------ src/js/internal/buffer.ts | 40 +++++ test/js/node/buffer.test.js | 20 +-- .../test/parallel/test-buffer-arraybuffer.js | 151 ++++++++++++++++ .../js/node/test/parallel/test-buffer-from.js | 143 +++++++++++++++ .../parallel/test-buffer-sharedarraybuffer.js | 27 +++ 10 files changed, 488 insertions(+), 156 deletions(-) create mode 100644 src/js/internal/buffer.ts create mode 100644 test/js/node/test/parallel/test-buffer-arraybuffer.js create mode 100644 test/js/node/test/parallel/test-buffer-from.js create mode 100644 test/js/node/test/parallel/test-buffer-sharedarraybuffer.js diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 5a0e41286e756a..cd4ef81214d432 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 b8346a3ec386ff..1ad9ccd2a33e7c 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -2378,6 +2378,91 @@ JSC::JSObject* createBufferConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalO } // namespace WebCore +EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, JSValue arrayValue) +{ + auto* globalObject = reinterpret_cast(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 = reinterpret_cast(lexicalGlobalObject); + + auto* jsBuffer = jsCast(arrayBufferValue.asCell()); + RefPtr buffer = jsBuffer->impl(); + if (buffer->isDetached()) { + throwTypeError(globalObject, throwScope, "Buffer is detached"_s); + return {}; + } + size_t byteLength = buffer->byteLength(); + double byteLengthD = byteLength; + + // 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; + size_t maxLength = byteLength; + if (argsCount > 1) { + + if (!offsetValue.isUndefined()) { + offsetValue = jsDoubleNumber(offsetValue.toNumber(lexicalGlobalObject)); + RETURN_IF_EXCEPTION(throwScope, {}); + if (std::isnan(offsetValue.asNumber())) offsetValue = jsNumber(0); + if ((byteLengthD - offsetValue.asNumber()) < 0) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "offset"_s); + maxLength = byteLengthD - offsetValue.asNumber(); + offset = offsetValue.asNumber(); + } + + if (!lengthValue.isUndefined()) { + lengthValue = jsDoubleNumber(lengthValue.toNumber(lexicalGlobalObject)); + RETURN_IF_EXCEPTION(throwScope, {}); + if (std::isnan(lengthValue.asNumber())) lengthValue = jsNumber(0); + length = lengthValue.asNumber(); + if (length > 0) { + if (length > maxLength) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "length"_s); + } + } else { + length = 0; + } + } + } + + if (offset > byteLength) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "offset"_s); + } + + if (!length.has_value()) { + if (buffer->isResizableOrGrowableShared()) { + if (UNLIKELY(offset > byteLength)) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "byteOffset"_s); + } + } else { + length = (byteLength - offset); + } + } + if (offset + length.value() > byteLength) { + return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "length"_s); + } + + 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)); +} + static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexicalGlobalObject, JSValue newTarget, ArgList args) { VM& vm = lexicalGlobalObject->vm(); @@ -2389,7 +2474,6 @@ 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); - if (distinguishingArg.isAnyInt()) { throwScope.release(); if (args.at(1).isString()) { @@ -2409,15 +2493,14 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi return {}; } else if (distinguishingArg.isCell()) { auto type = distinguishingArg.asCell()->type(); - switch (type) { case StringType: + case StringObjectType: case DerivedStringObjectType: { throwScope.release(); return constructBufferFromStringAndEncoding(lexicalGlobalObject, distinguishingArg, encodingArg); } - case Uint16ArrayType: case Uint32ArrayType: case Int8ArrayType: @@ -2430,100 +2513,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; @@ -2531,26 +2555,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/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 2c0c09e982ba7f..ace7afd5332441 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -1,78 +1,37 @@ // 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) { + const { fromString, fromArrayBuffer, fromObject } = require("internal/buffer"); + const { isAnyArrayBuffer } = require("node:util/types"); - // 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 items === "object") { - const data = items.data; - if (items.type === "Buffer" && Array.isArray(data)) { - return new $Buffer(data); - } - } + if (typeof value === "string") return fromString(value, encodingOrOffset); - 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 (typeof value === "object" && value !== null) { + if (isAnyArrayBuffer(value)) return fromArrayBuffer(value, encodingOrOffset, length); - if (!$isJSArray(arrayLike)) { - const toPrimitive = $tryGetByIdWithWellKnownSymbol(items, "toPrimitive"); + const valueOf = value.valueOf && value.valueOf(); + if (valueOf != null && valueOf !== value && (typeof valueOf === "string" || typeof valueOf === "object")) { + return Buffer.from(valueOf, encodingOrOffset, length); + } - if (toPrimitive) { - const primitive = toPrimitive.$call(items, "string"); + const b = fromObject(value); + if (b) return b; + 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 fromString(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/src/js/internal/buffer.ts b/src/js/internal/buffer.ts new file mode 100644 index 00000000000000..ef81b1a5ea0c63 --- /dev/null +++ b/src/js/internal/buffer.ts @@ -0,0 +1,40 @@ +const { isAnyArrayBuffer } = require("node:util/types"); + +function fromString(string: string, encoding) { + return new $Buffer(string, encoding); +} + +function fromArrayBuffer(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number) { + return new $Buffer(arrayBuffer, byteOffset, length); +} + +function fromObject(obj) { + if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { + if (typeof obj.length !== "number") { + return new $Buffer(0); + } + return fromArrayLike(obj); + } + if (obj.type === "Buffer" && $isArray(obj.data)) { + return fromArrayLike(obj.data); + } +} + +function fromArrayLike(obj) { + if (obj.length <= 0) return new $Buffer(0); + if (obj.length < Buffer.poolSize >>> 1) { + // if (obj.length > poolSize - poolOffset) createPool(); + // const b = new FastBuffer(allocPool, poolOffset, obj.length); + // TypedArrayPrototypeSet(b, obj, 0); + // poolOffset += obj.length; + // alignPool(); + // return b; + } + return new $Buffer(obj); +} + +export default { + fromString, + fromObject, + fromArrayBuffer, +}; diff --git a/test/js/node/buffer.test.js b/test/js/node/buffer.test.js index 08830c4bfc8736..d3f79b0f2a3a55 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. From 77a03ae94b60995c08648348889d8fad69c892a6 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Mon, 27 Jan 2025 14:30:03 -0800 Subject: [PATCH 2/7] perf fixes --- bench/snippets/buffer-create.mjs | 12 +++++ src/bun.js/bindings/JSBuffer.cpp | 70 +++++++------------------- src/bun.js/bindings/js_classes.ts | 3 ++ src/codegen/generate-classes.ts | 2 +- src/js/builtins/JSBufferConstructor.ts | 22 +++++--- src/js/internal/buffer.ts | 40 --------------- 6 files changed, 49 insertions(+), 100 deletions(-) delete mode 100644 src/js/internal/buffer.ts diff --git a/bench/snippets/buffer-create.mjs b/bench/snippets/buffer-create.mjs index ded7f02cab7d60..7c38bfdf40cdb7 100644 --- a/bench/snippets/buffer-create.mjs +++ b/bench/snippets/buffer-create.mjs @@ -25,6 +25,10 @@ bench("Buffer.from('short string')", () => { return Buffer.from("short string"); }); +bench("new Buffer('short string')", () => { + return new Buffer("short string"); +}); + const loooong = "long string".repeat(9999).split("").join(" "); bench("Buffer.byteLength('long string'.repeat(9999))", () => { return Buffer.byteLength(loooong); @@ -45,6 +49,14 @@ 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/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 1ad9ccd2a33e7c..f4a28d1b5df97b 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -2399,65 +2399,34 @@ EncodedJSValue constructBufferFromArrayBuffer(JSC::ThrowScope& throwScope, JSGlo auto* jsBuffer = jsCast(arrayBufferValue.asCell()); RefPtr buffer = jsBuffer->impl(); if (buffer->isDetached()) { - throwTypeError(globalObject, throwScope, "Buffer is detached"_s); - return {}; + return throwVMTypeError(globalObject, throwScope, "Buffer is detached"_s); } size_t byteLength = buffer->byteLength(); - double byteLengthD = byteLength; - - // 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; - size_t maxLength = byteLength; - if (argsCount > 1) { - - if (!offsetValue.isUndefined()) { - offsetValue = jsDoubleNumber(offsetValue.toNumber(lexicalGlobalObject)); - RETURN_IF_EXCEPTION(throwScope, {}); - if (std::isnan(offsetValue.asNumber())) offsetValue = jsNumber(0); - if ((byteLengthD - offsetValue.asNumber()) < 0) return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "offset"_s); - maxLength = byteLengthD - offsetValue.asNumber(); - offset = offsetValue.asNumber(); - } + size_t length = byteLength; - if (!lengthValue.isUndefined()) { - lengthValue = jsDoubleNumber(lengthValue.toNumber(lexicalGlobalObject)); - RETURN_IF_EXCEPTION(throwScope, {}); - if (std::isnan(lengthValue.asNumber())) lengthValue = jsNumber(0); - length = lengthValue.asNumber(); - if (length > 0) { - if (length > maxLength) { - return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, lexicalGlobalObject, "length"_s); - } - } else { - length = 0; - } - } - } - - if (offset > byteLength) { - return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "offset"_s); + 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 (!length.has_value()) { - if (buffer->isResizableOrGrowableShared()) { - if (UNLIKELY(offset > byteLength)) { - return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "byteOffset"_s); - } - } else { - length = (byteLength - offset); - } - } - if (offset + length.value() > byteLength) { - return Bun::ERR::BUFFER_OUT_OF_BOUNDS(throwScope, globalObject, "length"_s); + 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 JSC::JSValue::encode({}); + return {}; } RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); @@ -2479,18 +2448,17 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi if (args.at(1).isString()) { return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "string"_s, "string"_s, distinguishingArg); } - return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt()); + return JSValue::encode(allocBuffer(lexicalGlobalObject, distinguishingArg.asAnyInt())); } 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) { 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/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 0813f293d08316..71cc7a5de44bf9 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -2281,7 +2281,7 @@ JSC_DEFINE_HOST_FUNCTION(Zig::jsFunctionInherits, (JSC::JSGlobalObject * globalO switch (id) { ${jsclasses .map(v => v[0]) - .map((v, i) => ` case ${i}: return JSValue::encode(jsBoolean(cell->inherits()));`) + .map((v, i) => ` case ${i}: return JSValue::encode(jsBoolean(jsDynamicCast(cell) != nullptr));`) .join("\n")} } return JSValue::encode(jsBoolean(false)); diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index ace7afd5332441..26eac36ffc82a8 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -2,27 +2,33 @@ // Some legacy dependencies depend on this, see #3638 $constructor; export function from(value, encodingOrOffset, length) { - const { fromString, fromArrayBuffer, fromObject } = require("internal/buffer"); - const { isAnyArrayBuffer } = require("node:util/types"); - - if (typeof value === "string") return fromString(value, encodingOrOffset); + if (typeof value === "string") return new $Buffer(value, encodingOrOffset); if (typeof value === "object" && value !== null) { - if (isAnyArrayBuffer(value)) return fromArrayBuffer(value, encodingOrOffset, length); + 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); } - const b = fromObject(value); - if (b) return b; + 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); + } const toPrimitive = $tryGetByIdWithWellKnownSymbol(value, "toPrimitive"); if (typeof toPrimitive === "function") { const primitive = toPrimitive.$call(value, "string"); if (typeof primitive === "string") { - return fromString(primitive, encodingOrOffset); + return new $Buffer(primitive, encodingOrOffset); } } } diff --git a/src/js/internal/buffer.ts b/src/js/internal/buffer.ts deleted file mode 100644 index ef81b1a5ea0c63..00000000000000 --- a/src/js/internal/buffer.ts +++ /dev/null @@ -1,40 +0,0 @@ -const { isAnyArrayBuffer } = require("node:util/types"); - -function fromString(string: string, encoding) { - return new $Buffer(string, encoding); -} - -function fromArrayBuffer(arrayBuffer: ArrayBuffer, byteOffset?: number, length?: number) { - return new $Buffer(arrayBuffer, byteOffset, length); -} - -function fromObject(obj) { - if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { - if (typeof obj.length !== "number") { - return new $Buffer(0); - } - return fromArrayLike(obj); - } - if (obj.type === "Buffer" && $isArray(obj.data)) { - return fromArrayLike(obj.data); - } -} - -function fromArrayLike(obj) { - if (obj.length <= 0) return new $Buffer(0); - if (obj.length < Buffer.poolSize >>> 1) { - // if (obj.length > poolSize - poolOffset) createPool(); - // const b = new FastBuffer(allocPool, poolOffset, obj.length); - // TypedArrayPrototypeSet(b, obj, 0); - // poolOffset += obj.length; - // alignPool(); - // return b; - } - return new $Buffer(obj); -} - -export default { - fromString, - fromObject, - fromArrayBuffer, -}; From 3257685c86bcb8d6053bc2cc5d9e31c5f6246fb1 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Mon, 27 Jan 2025 15:05:10 -0800 Subject: [PATCH 3/7] fix --- src/bun.js/bindings/JSBuffer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 67b33ff410578e..c51c6384c11293 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -2447,7 +2447,9 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi if (args.at(1).isString()) { return Bun::ERR::INVALID_ARG_TYPE(throwScope, lexicalGlobalObject, "string"_s, "string"_s, distinguishingArg); } - return JSValue::encode(allocBuffer(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)); From f1e3a2ea2fb6fa329137a20896345614bb65f225 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 5 Feb 2025 16:30:41 -0800 Subject: [PATCH 4/7] address review --- src/bun.js/bindings/JSBuffer.cpp | 17 ++++++++--------- src/js/builtins/JSBufferConstructor.ts | 11 ++++++----- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index bf04eb18eeb131..8bd4ae3185dd2c 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); @@ -319,7 +319,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)) { @@ -416,7 +416,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); @@ -435,7 +435,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)); @@ -2379,7 +2379,7 @@ JSC::JSObject* createBufferConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalO EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, JSValue arrayValue) { - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto* constructor = lexicalGlobalObject->m_typedArrayUint8.constructor(lexicalGlobalObject); MarkedArgumentBuffer argsBuffer; @@ -2393,7 +2393,7 @@ EncodedJSValue constructBufferFromArray(JSC::ThrowScope& throwScope, JSGlobalObj EncodedJSValue constructBufferFromArrayBuffer(JSC::ThrowScope& throwScope, JSGlobalObject* lexicalGlobalObject, size_t argsCount, JSValue arrayBufferValue, JSValue offsetValue, JSValue lengthValue) { - auto* globalObject = reinterpret_cast(lexicalGlobalObject); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto* jsBuffer = jsCast(arrayBufferValue.asCell()); RefPtr buffer = jsBuffer->impl(); @@ -2441,7 +2441,7 @@ 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()) { @@ -2464,7 +2464,6 @@ static inline JSC::EncodedJSValue createJSBufferFromJS(JSC::JSGlobalObject* lexi auto type = distinguishingArg.asCell()->type(); switch (type) { case StringType: - case StringObjectType: case DerivedStringObjectType: { throwScope.release(); diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 26eac36ffc82a8..31332e20a08a25 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -13,11 +13,12 @@ export function from(value, encodingOrOffset, length) { return Buffer.from(valueOf, encodingOrOffset, length); } - 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); - } + // XXX: this may be obsoleted by $isTypedArrayView check above, delete this if so once the rest of the buffer tests are confirmed passing + // 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); From a5ab050a8871ce350098aaf37b763adab808472c Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Wed, 5 Feb 2025 17:55:35 -0800 Subject: [PATCH 5/7] ah it was covering the Array case --- src/js/builtins/JSBufferConstructor.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index 31332e20a08a25..a23144c87e3e2f 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -13,12 +13,12 @@ export function from(value, encodingOrOffset, length) { return Buffer.from(valueOf, encodingOrOffset, length); } - // XXX: this may be obsoleted by $isTypedArrayView check above, delete this if so once the rest of the buffer tests are confirmed passing - // 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); - // } + // if (value.length !== undefined || $inheritsArrayBuffer(value.buffer)) { // XXX: this may be obsoleted by $isTypedArrayView check above, delete this if so once the rest of the buffer tests are confirmed passing + if (value.length !== undefined) { + 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); From c4bc27550427076851b608baecceff01c2d23eda Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Thu, 6 Feb 2025 20:52:36 -0800 Subject: [PATCH 6/7] undo this --- src/codegen/generate-classes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/codegen/generate-classes.ts b/src/codegen/generate-classes.ts index 844a64c4c57913..8d8f01be847000 100644 --- a/src/codegen/generate-classes.ts +++ b/src/codegen/generate-classes.ts @@ -2281,7 +2281,7 @@ JSC_DEFINE_HOST_FUNCTION(Zig::jsFunctionInherits, (JSC::JSGlobalObject * globalO switch (id) { ${jsclasses .map(v => v[0]) - .map((v, i) => ` case ${i}: return JSValue::encode(jsBoolean(jsDynamicCast(cell) != nullptr));`) + .map((v, i) => ` case ${i}: return JSValue::encode(jsBoolean(cell->inherits()));`) .join("\n")} } return JSValue::encode(jsBoolean(false)); From 857015a2a3a38f89faf2a06648677a81de540d04 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Thu, 6 Feb 2025 20:52:44 -0800 Subject: [PATCH 7/7] needed for 'Buffer.from({ buffer: sab })' --- src/js/builtins/JSBufferConstructor.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts index a23144c87e3e2f..26eac36ffc82a8 100644 --- a/src/js/builtins/JSBufferConstructor.ts +++ b/src/js/builtins/JSBufferConstructor.ts @@ -13,8 +13,7 @@ export function from(value, encodingOrOffset, length) { return Buffer.from(valueOf, encodingOrOffset, length); } - // if (value.length !== undefined || $inheritsArrayBuffer(value.buffer)) { // XXX: this may be obsoleted by $isTypedArrayView check above, delete this if so once the rest of the buffer tests are confirmed passing - if (value.length !== undefined) { + 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);