aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-01-21 23:03:43 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-01-21 23:03:43 -0800
commita2cfdf0e1c0353428f0682969e3fcfa86cc2d6a7 (patch)
treefa2ca92b403e50b2d464e2584296b2831818cf7c
parent29a576a167f45eb5080bf79afae7a02fe682d75d (diff)
downloadbun-a2cfdf0e1c0353428f0682969e3fcfa86cc2d6a7.tar.gz
bun-a2cfdf0e1c0353428f0682969e3fcfa86cc2d6a7.tar.zst
bun-a2cfdf0e1c0353428f0682969e3fcfa86cc2d6a7.zip
Refactor `new Buffer` to pass Node.js tests
-rw-r--r--src/bun.js/bindings/JSBuffer.cpp117
-rw-r--r--src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp10
-rw-r--r--src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h9
-rw-r--r--src/bun.js/builtins/js/JSBufferConstructor.js8
-rw-r--r--test/bun.js/buffer.test.js115
5 files changed, 234 insertions, 25 deletions
diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp
index 8a958580d..5b038f3ae 100644
--- a/src/bun.js/bindings/JSBuffer.cpp
+++ b/src/bun.js/bindings/JSBuffer.cpp
@@ -1773,15 +1773,16 @@ JSC_DEFINE_HOST_FUNCTION(constructJSBuffer, (JSC::JSGlobalObject * lexicalGlobal
{
VM& vm = lexicalGlobalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
- UNUSED_PARAM(throwScope);
- size_t argsCount = std::min<size_t>(3, callFrame->argumentCount());
+ size_t argsCount = callFrame->argumentCount();
if (argsCount == 0) {
- RELEASE_AND_RETURN(throwScope, (constructBufferEmpty(lexicalGlobalObject, callFrame)));
+ RELEASE_AND_RETURN(throwScope, constructBufferEmpty(lexicalGlobalObject, callFrame));
}
JSValue distinguishingArg = callFrame->uncheckedArgument(0);
+ auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
if (distinguishingArg.isAnyInt()) {
- RELEASE_AND_RETURN(throwScope, JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt()));
+ throwScope.release();
+ return JSBuffer__bufferFromLength(lexicalGlobalObject, distinguishingArg.asAnyInt());
} else if (distinguishingArg.isCell()) {
auto type = distinguishingArg.asCell()->type();
@@ -1789,7 +1790,102 @@ JSC_DEFINE_HOST_FUNCTION(constructJSBuffer, (JSC::JSGlobalObject * lexicalGlobal
case StringType:
case StringObjectType:
case DerivedStringObjectType: {
- RELEASE_AND_RETURN(throwScope, (constructBufferFromStringAndEncoding(lexicalGlobalObject, callFrame)));
+ throwScope.release();
+ return constructBufferFromStringAndEncoding(lexicalGlobalObject, callFrame);
+ }
+
+ case Uint8ArrayType:
+ case Uint8ClampedArrayType:
+ case Uint16ArrayType:
+ case Uint32ArrayType:
+ case Int8ArrayType:
+ case Int16ArrayType:
+ case Int32ArrayType:
+ case Float32ArrayType:
+ case Float64ArrayType:
+ case BigInt64ArrayType:
+ case BigUint64ArrayType:
+ case DataViewType: {
+ // 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 JSValue::encode({});
+ }
+
+ auto* subclassStructure = globalObject->JSBufferSubclassStructure();
+ auto* uint8Array = JSC::JSUint8Array::createUninitialized(lexicalGlobalObject, subclassStructure, byteLength);
+ if (UNLIKELY(!uint8Array)) {
+ throwOutOfMemoryError(globalObject, throwScope);
+ return JSValue::encode({});
+ }
+
+ if (byteLength) {
+ memcpy(uint8Array->vector(), 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 = callFrame->uncheckedArgument(1).toTypedArrayIndex(globalObject, "byteOffset"_s);
+
+ // TOOD: return Node.js error
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+
+ if (argsCount > 2) {
+ // If the length value is present but undefined, treat it as missing.
+ JSValue lengthValue = callFrame->uncheckedArgument(2);
+ if (!lengthValue.isUndefined()) {
+ length = lengthValue.toTypedArrayIndex(globalObject, "length"_s);
+
+ // TOOD: return Node.js error
+ RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
+ }
+ }
+ }
+
+ 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 JSValue::encode({});
+ }
+
+ if (!length) {
+ size_t byteLength = buffer->byteLength();
+ if (buffer->isResizableOrGrowableShared()) {
+ if (UNLIKELY(offset > byteLength)) {
+ // TOOD: return Node.js error
+ throwRangeError(globalObject, throwScope, "byteOffset exceeds source ArrayBuffer byteLength"_s);
+ return JSValue::encode({});
+ }
+ } 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));
+ }
+ default: {
+ break;
}
}
}
@@ -1801,14 +1897,11 @@ JSC_DEFINE_HOST_FUNCTION(constructJSBuffer, (JSC::JSGlobalObject * lexicalGlobal
for (size_t i = 1; i < argsCount; ++i)
args.append(callFrame->uncheckedArgument(i));
- JSC::JSObject* object = JSC::construct(lexicalGlobalObject, constructor, callFrame->newTarget(), args, "Failed to construct 'Buffer' object"_s);
+ JSC::CallData callData = JSC::getCallData(constructor);
+ JSC::JSObject* object = JSC::construct(lexicalGlobalObject, constructor, callData, args, globalObject->JSBufferConstructor());
if (!object) {
- return JSC::JSValue::encode(JSC::jsUndefined());
+ return JSC::JSValue::encode({});
}
- auto value = JSC::JSValue(object);
-
- toBuffer(lexicalGlobalObject, JSC::jsCast<JSC::JSUint8Array*>(value));
-
- RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(value));
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(object));
}
diff --git a/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp b/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp
index 7025ea8b8..a38be91a7 100644
--- a/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp
+++ b/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp
@@ -51,7 +51,7 @@ namespace WebCore {
const JSC::ConstructAbility s_jsBufferConstructorFromCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
const JSC::ConstructorKind s_jsBufferConstructorFromCodeConstructorKind = JSC::ConstructorKind::None;
const JSC::ImplementationVisibility s_jsBufferConstructorFromCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
-const int s_jsBufferConstructorFromCodeLength = 1984;
+const int s_jsBufferConstructorFromCodeLength = 1843;
static const JSC::Intrinsic s_jsBufferConstructorFromCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_jsBufferConstructorFromCode =
"(function (items) {\n" \
@@ -60,16 +60,12 @@ const char* const s_jsBufferConstructorFromCode =
" if (!@isConstructor(this))\n" \
" @throwTypeError(\"Buffer.from requires |this| to be a constructor\");\n" \
"\n" \
- " if (@isUndefinedOrNull(items))\n" \
+ " if (@isUndefinedOrNull(items)) {\n" \
" @throwTypeError(\n" \
" \"The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.\",\n" \
" );\n" \
- "\n" \
- " if (@argumentCount() === 1 && @isTypedArrayView(items)) {\n" \
- " var out = this.allocUnsafe(items.byteLength);\n" \
- " out.copy(items);\n" \
- " return out;\n" \
" }\n" \
+ " \n" \
"\n" \
" //\n" \
" if (\n" \
diff --git a/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h b/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h
index 75de0fce2..a3b17f7cc 100644
--- a/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h
+++ b/src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h
@@ -322,6 +322,11 @@ extern const int s_jsBufferPrototypeSliceCodeLength;
extern const JSC::ConstructAbility s_jsBufferPrototypeSliceCodeConstructAbility;
extern const JSC::ConstructorKind s_jsBufferPrototypeSliceCodeConstructorKind;
extern const JSC::ImplementationVisibility s_jsBufferPrototypeSliceCodeImplementationVisibility;
+extern const char* const s_jsBufferPrototypeParentCode;
+extern const int s_jsBufferPrototypeParentCodeLength;
+extern const JSC::ConstructAbility s_jsBufferPrototypeParentCodeConstructAbility;
+extern const JSC::ConstructorKind s_jsBufferPrototypeParentCodeConstructorKind;
+extern const JSC::ImplementationVisibility s_jsBufferPrototypeParentCodeImplementationVisibility;
extern const char* const s_jsBufferPrototypeInitializeBunBufferCode;
extern const int s_jsBufferPrototypeInitializeBunBufferCodeLength;
extern const JSC::ConstructAbility s_jsBufferPrototypeInitializeBunBufferCodeConstructAbility;
@@ -384,6 +389,7 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe
macro(hexSlice, jsBufferPrototypeHexSlice, 2) \
macro(toJSON, jsBufferPrototypeToJSON, 0) \
macro(slice, jsBufferPrototypeSlice, 2) \
+ macro(parent, jsBufferPrototypeParent, 0) \
macro(initializeBunBuffer, jsBufferPrototypeInitializeBunBuffer, 1) \
#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_SETBIGUINT64 1
@@ -441,6 +447,7 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe
#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_HEXSLICE 1
#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_TOJSON 1
#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_SLICE 1
+#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_PARENT 1
#define WEBCORE_BUILTIN_JSBUFFERPROTOTYPE_INITIALIZEBUNBUFFER 1
#define WEBCORE_FOREACH_JSBUFFERPROTOTYPE_BUILTIN_CODE(macro) \
@@ -499,6 +506,7 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe
macro(jsBufferPrototypeHexSliceCode, hexSlice, ASCIILiteral(), s_jsBufferPrototypeHexSliceCodeLength) \
macro(jsBufferPrototypeToJSONCode, toJSON, ASCIILiteral(), s_jsBufferPrototypeToJSONCodeLength) \
macro(jsBufferPrototypeSliceCode, slice, ASCIILiteral(), s_jsBufferPrototypeSliceCodeLength) \
+ macro(jsBufferPrototypeParentCode, parent, "get parent"_s, s_jsBufferPrototypeParentCodeLength) \
macro(jsBufferPrototypeInitializeBunBufferCode, initializeBunBuffer, ASCIILiteral(), s_jsBufferPrototypeInitializeBunBufferCodeLength) \
#define WEBCORE_FOREACH_JSBUFFERPROTOTYPE_BUILTIN_FUNCTION_NAME(macro) \
@@ -513,6 +521,7 @@ extern const JSC::ImplementationVisibility s_jsBufferPrototypeInitializeBunBuffe
macro(initializeBunBuffer) \
macro(latin1Slice) \
macro(latin1Write) \
+ macro(parent) \
macro(readBigInt64BE) \
macro(readBigInt64LE) \
macro(readBigUInt64BE) \
diff --git a/src/bun.js/builtins/js/JSBufferConstructor.js b/src/bun.js/builtins/js/JSBufferConstructor.js
index 4c3650360..10994394e 100644
--- a/src/bun.js/builtins/js/JSBufferConstructor.js
+++ b/src/bun.js/builtins/js/JSBufferConstructor.js
@@ -31,16 +31,12 @@ function from(items) {
if (!@isConstructor(this))
@throwTypeError("Buffer.from requires |this| to be a constructor");
- if (@isUndefinedOrNull(items))
+ if (@isUndefinedOrNull(items)) {
@throwTypeError(
"The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.",
);
-
- if (@argumentCount() === 1 && @isTypedArrayView(items)) {
- var out = this.allocUnsafe(items.byteLength);
- out.copy(items);
- return out;
}
+
// TODO: figure out why private symbol not found
if (
diff --git a/test/bun.js/buffer.test.js b/test/bun.js/buffer.test.js
index 20fcddfa6..193124b00 100644
--- a/test/bun.js/buffer.test.js
+++ b/test/bun.js/buffer.test.js
@@ -777,3 +777,118 @@ it("Buffer.from (Node.js test/test-buffer-from.js)", () => {
expect(() => Buffer.allocUnsafe(10)).not.toThrow(); // Should not throw.
expect(() => Buffer.from("deadbeaf", "hex")).not.toThrow(); // Should not throw.
});
+
+it("new Buffer() (Node.js test/test-buffer-new.js)", () => {
+ const LENGTH = 16;
+
+ const ab = new ArrayBuffer(LENGTH);
+ const dv = new DataView(ab);
+ const ui = new Uint8Array(ab);
+ const buf = Buffer.from(ab);
+
+ expect(buf instanceof Buffer).toBe(true);
+ // expect(buf.parent, buf.buffer);
+ expect(buf.buffer).toBe(ab);
+ expect(buf.length).toBe(ab.byteLength);
+
+ buf.fill(0xc);
+ for (let i = 0; i < LENGTH; i++) {
+ expect(ui[i]).toBe(0xc);
+ ui[i] = 0xf;
+ expect(buf[i]).toBe(0xf);
+ }
+
+ buf.writeUInt32LE(0xf00, 0);
+ buf.writeUInt32BE(0xb47, 4);
+ buf.writeDoubleLE(3.1415, 8);
+ expect(dv.getUint32(0, true)).toBe(0xf00);
+ expect(dv.getUint32(4)).toBe(0xb47);
+ expect(dv.getFloat64(8, true)).toBe(3.1415);
+
+ // 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);
+
+ // 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);
+ expect(buf.length).toBe(3);
+ expect(buf[0]).toBe(2);
+ expect(buf[1]).toBe(3);
+ expect(buf[2]).toBe(4);
+ buf[0] = 9;
+ expect(ab[1]).toBe(9);
+
+ expect(() => Buffer.from(ab.buffer, 6)).toThrow();
+ expect(() => Buffer.from(ab.buffer, 3, 6)).toThrow();
+ }
+
+ // 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);
+ expect(buf.length).toBe(3);
+ expect(buf[0]).toBe(2);
+ expect(buf[1]).toBe(3);
+ expect(buf[2]).toBe(4);
+ buf[0] = 9;
+ expect(ab[1]).toBe(9);
+
+ expect(() => Buffer(ab.buffer, 6)).toThrow();
+ expect(() => Buffer(ab.buffer, 3, 6)).toThrow();
+ }
+
+ {
+ // If byteOffset is not numeric, it defaults to 0.
+ const ab = new ArrayBuffer(10);
+ const expected = Buffer.from(ab, 0);
+ expect(Buffer.from(ab, "fhqwhgads")).toStrictEqual(expected);
+ expect(Buffer.from(ab, NaN)).toStrictEqual(expected);
+ expect(Buffer.from(ab, {})).toStrictEqual(expected);
+ expect(Buffer.from(ab, [])).toStrictEqual(expected);
+
+ // If byteOffset can be converted to a number, it will be.
+ expect(Buffer.from(ab, [1])).toStrictEqual(Buffer.from(ab, 1));
+
+ // If byteOffset is Infinity, throw.
+ expect(() => {
+ Buffer.from(ab, Infinity);
+ }).toThrow();
+ }
+
+ {
+ // If length is not numeric, it defaults to 0.
+ const ab = new ArrayBuffer(10);
+ const expected = Buffer.from(ab, 0, 0);
+ expect(Buffer.from(ab, 0, "fhqwhgads")).toStrictEqual(expected);
+ expect(Buffer.from(ab, 0, NaN)).toStrictEqual(expected);
+ expect(Buffer.from(ab, 0, {})).toStrictEqual(expected);
+ expect(Buffer.from(ab, 0, [])).toStrictEqual(expected);
+
+ // If length can be converted to a number, it will be.
+ expect(Buffer.from(ab, 0, [1])).toStrictEqual(Buffer.from(ab, 0, 1));
+
+ // If length is Infinity, throw.
+ expect(() => Buffer.from(ab, 0, Infinity)).toThrow();
+ }
+
+ // Test an array like entry with the length set to NaN.
+ expect(Buffer.from({ length: NaN })).toStrictEqual(Buffer.alloc(0));
+});