aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-01-21 03:12:59 -0800
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-01-21 03:12:59 -0800
commitd955bfe50f7aa15aca9df3bf0a40fea26a132381 (patch)
tree2433e2b9dfc4bf712903417008f9b0c5b3900d69
parentb8648adf873758a83911d3d28c94255d8c3fdd3c (diff)
downloadbun-d955bfe50f7aa15aca9df3bf0a40fea26a132381.tar.gz
bun-d955bfe50f7aa15aca9df3bf0a40fea26a132381.tar.zst
bun-d955bfe50f7aa15aca9df3bf0a40fea26a132381.zip
[buffer] Make Buffer.from pass more tests
-rw-r--r--src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp105
-rw-r--r--src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.h9
-rw-r--r--src/bun.js/builtins/js/JSBufferConstructor.js96
-rw-r--r--test/bun.js/buffer.test.js61
4 files changed, 179 insertions, 92 deletions
diff --git a/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp b/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp
index dd03c2a77..213060f58 100644
--- a/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp
+++ b/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp
@@ -48,67 +48,82 @@
namespace WebCore {
-const JSC::ConstructAbility s_jsBufferConstructorAllocCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
-const JSC::ConstructorKind s_jsBufferConstructorAllocCodeConstructorKind = JSC::ConstructorKind::None;
-const JSC::ImplementationVisibility s_jsBufferConstructorAllocCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
-const int s_jsBufferConstructorAllocCodeLength = 185;
-static const JSC::Intrinsic s_jsBufferConstructorAllocCodeIntrinsic = JSC::NoIntrinsic;
-const char* const s_jsBufferConstructorAllocCode =
- "(function (n) {\n" \
- " \"use strict\";\n" \
- " if (typeof n !== \"number\" || n < 0) {\n" \
- " @throwRangeError(\"n must be a positive integer less than 2^32\");\n" \
- " }\n" \
- " \n" \
- " return new this(n);\n" \
- "})\n" \
-;
-
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 = 1035;
+const int s_jsBufferConstructorFromCodeLength = 1832;
static const JSC::Intrinsic s_jsBufferConstructorFromCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_jsBufferConstructorFromCode =
"(function (items) {\n" \
" \"use strict\";\n" \
"\n" \
" if (!@isConstructor(this))\n" \
- " @throwTypeError(\"Buffer.from requires |this| to be a constructor\");\n" \
+ " @throwTypeError(\"Buffer.from requires |this| to be a constructor\");\n" \
"\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" \
- " //\n" \
- " if (typeof items === 'string' || (typeof items === 'object' && items && (items instanceof ArrayBuffer || items instanceof SharedArrayBuffer))) {\n" \
- " switch (@argumentCount()) {\n" \
- " case 1: {\n" \
- " return new this(items);\n" \
- " }\n" \
- " case 2: {\n" \
- " return new this(items, @argument(1));\n" \
- " }\n" \
- " default: {\n" \
- " return new this(items, @argument(1), @argument(2));\n" \
- " }\n" \
- " }\n" \
+ " //\n" \
+ " if (\n" \
+ " typeof items === \"string\" ||\n" \
+ " (typeof items === \"object\" &&\n" \
+ " (@isTypedArrayView(items) ||\n" \
+ " items instanceof ArrayBuffer ||\n" \
+ " items instanceof SharedArrayBuffer ||\n" \
+ " items instanceof @String))\n" \
+ " ) {\n" \
+ " switch (@argumentCount()) {\n" \
+ " case 1: {\n" \
+ " return new this(items);\n" \
+ " }\n" \
+ " case 2: {\n" \
+ " return new this(items, @argument(1));\n" \
+ " }\n" \
+ " default: {\n" \
+ " return new this(items, @argument(1), @argument(2));\n" \
+ " }\n" \
" }\n" \
+ " }\n" \
"\n" \
+ " var arrayLike = @toObject(\n" \
+ " items,\n" \
+ " \"The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.\",\n" \
+ " );\n" \
"\n" \
- " var arrayLike = @toObject(items, \"Buffer.from requires an array-like object - not null or undefined\");\n" \
+ " if (!@isJSArray(arrayLike)) {\n" \
+ " const toPrimitive = @tryGetByIdWithWellKnownSymbol(items, \"toPrimitive\");\n" \
"\n" \
- " //\n" \
- " //\n" \
- " //\n" \
- " if (@isTypedArrayView(arrayLike)) {\n" \
- " var length = @typedArrayLength(arrayLike);\n" \
- " var result = this.allocUnsafe(length);\n" \
- " result.set(arrayLike);\n" \
- " return result;\n" \
- " } \n" \
+ " if (toPrimitive) {\n" \
+ " const primitive = toPrimitive.@call(items, \"string\");\n" \
+ "\n" \
+ " if (typeof primitive === \"string\") {\n" \
+ " switch (@argumentCount()) {\n" \
+ " case 1: {\n" \
+ " return new this(primitive);\n" \
+ " }\n" \
+ " case 2: {\n" \
+ " return new this(primitive, @argument(1));\n" \
+ " }\n" \
+ " default: {\n" \
+ " return new this(primitive, @argument(1), @argument(2));\n" \
+ " }\n" \
+ " }\n" \
+ " }\n" \
+ " }\n" \
+ "\n" \
+ " if (!(\"length\" in arrayLike) || @isCallable(arrayLike)) {\n" \
+ " @throwTypeError(\n" \
+ " \"The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.\",\n" \
+ " );\n" \
+ " }\n" \
+ " }\n" \
"\n" \
- " //\n" \
- " //\n" \
- " //\n" \
- " return new this(@Uint8Array.from(arrayLike).buffer);\n" \
+ " //\n" \
+ " //\n" \
+ " //\n" \
+ " return new this(@Uint8Array.from(arrayLike).buffer);\n" \
"})\n" \
;
diff --git a/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.h b/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.h
index 05058bba3..f9181b402 100644
--- a/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.h
+++ b/src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.h
@@ -47,11 +47,6 @@ class FunctionExecutable;
namespace WebCore {
/* JSBufferConstructor */
-extern const char* const s_jsBufferConstructorAllocCode;
-extern const int s_jsBufferConstructorAllocCodeLength;
-extern const JSC::ConstructAbility s_jsBufferConstructorAllocCodeConstructAbility;
-extern const JSC::ConstructorKind s_jsBufferConstructorAllocCodeConstructorKind;
-extern const JSC::ImplementationVisibility s_jsBufferConstructorAllocCodeImplementationVisibility;
extern const char* const s_jsBufferConstructorFromCode;
extern const int s_jsBufferConstructorFromCodeLength;
extern const JSC::ConstructAbility s_jsBufferConstructorFromCodeConstructAbility;
@@ -59,18 +54,14 @@ extern const JSC::ConstructorKind s_jsBufferConstructorFromCodeConstructorKind;
extern const JSC::ImplementationVisibility s_jsBufferConstructorFromCodeImplementationVisibility;
#define WEBCORE_FOREACH_JSBUFFERCONSTRUCTOR_BUILTIN_DATA(macro) \
- macro(alloc, jsBufferConstructorAlloc, 1) \
macro(from, jsBufferConstructorFrom, 1) \
-#define WEBCORE_BUILTIN_JSBUFFERCONSTRUCTOR_ALLOC 1
#define WEBCORE_BUILTIN_JSBUFFERCONSTRUCTOR_FROM 1
#define WEBCORE_FOREACH_JSBUFFERCONSTRUCTOR_BUILTIN_CODE(macro) \
- macro(jsBufferConstructorAllocCode, alloc, ASCIILiteral(), s_jsBufferConstructorAllocCodeLength) \
macro(jsBufferConstructorFromCode, from, ASCIILiteral(), s_jsBufferConstructorFromCodeLength) \
#define WEBCORE_FOREACH_JSBUFFERCONSTRUCTOR_BUILTIN_FUNCTION_NAME(macro) \
- macro(alloc) \
macro(from) \
#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
diff --git a/src/bun.js/builtins/js/JSBufferConstructor.js b/src/bun.js/builtins/js/JSBufferConstructor.js
index 48342fe26..9f61220cc 100644
--- a/src/bun.js/builtins/js/JSBufferConstructor.js
+++ b/src/bun.js/builtins/js/JSBufferConstructor.js
@@ -25,52 +25,74 @@
// ^ that comment is required or the builtins generator will have a fit.
-function alloc(n) {
- "use strict";
- if (typeof n !== "number" || n < 0) {
- @throwRangeError("n must be a positive integer less than 2^32");
- }
-
- return new this(n);
-}
-
function from(items) {
"use strict";
if (!@isConstructor(this))
- @throwTypeError("Buffer.from requires |this| to be a constructor");
+ @throwTypeError("Buffer.from requires |this| to be a constructor");
+ if (@isUndefinedOrNull(items))
+ @throwTypeError(
+ "The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.",
+ );
- // TODO: figure out why private symbol not found
- if (typeof items === 'string' || (typeof items === 'object' && items && (items instanceof ArrayBuffer || items instanceof SharedArrayBuffer))) {
- switch (@argumentCount()) {
- case 1: {
- return new this(items);
- }
- case 2: {
- return new this(items, @argument(1));
- }
- default: {
- return new this(items, @argument(1), @argument(2));
- }
- }
+ // 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 this(items);
+ }
+ case 2: {
+ return new this(items, @argument(1));
+ }
+ default: {
+ return new this(items, @argument(1), @argument(2));
+ }
}
+ }
+
+ 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.",
+ );
+ if (!@isJSArray(arrayLike)) {
+ const toPrimitive = @tryGetByIdWithWellKnownSymbol(items, "toPrimitive");
- var arrayLike = @toObject(items, "Buffer.from requires an array-like object - not null or undefined");
+ if (toPrimitive) {
+ const primitive = toPrimitive.@call(items, "string");
- // Buffer-specific fast path:
- // - uninitialized memory
- // - use .set
- if (@isTypedArrayView(arrayLike)) {
- var length = @typedArrayLength(arrayLike);
- var result = this.allocUnsafe(length);
- result.set(arrayLike);
- return result;
- }
+ if (typeof primitive === "string") {
+ switch (@argumentCount()) {
+ case 1: {
+ return new this(primitive);
+ }
+ case 2: {
+ return new this(primitive, @argument(1));
+ }
+ default: {
+ return new this(primitive, @argument(1), @argument(2));
+ }
+ }
+ }
+ }
+
+ if (!("length" in arrayLike) || @isCallable(arrayLike)) {
+ @throwTypeError(
+ "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 this(@Uint8Array.from(arrayLike).buffer);
+ // 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 this(@Uint8Array.from(arrayLike).buffer);
}
diff --git a/test/bun.js/buffer.test.js b/test/bun.js/buffer.test.js
index 64c6cc41f..20fcddfa6 100644
--- a/test/bun.js/buffer.test.js
+++ b/test/bun.js/buffer.test.js
@@ -122,7 +122,8 @@ it("Buffer.from", () => {
expect(Buffer.from([254], "utf16").join(",")).toBe("254");
expect(Buffer.isBuffer(Buffer.from([254], "utf16"))).toBe(true);
- expect(Buffer.from(123).join(",")).toBe(Uint8Array.from(123).join(","));
+ expect(() => Buffer.from(123).join(",")).toThrow();
+
expect(Buffer.from({ length: 124 }).join(",")).toBe(
Uint8Array.from({ length: 124 }).join(","),
);
@@ -718,3 +719,61 @@ it("transcode", () => {
// This is a masqueradesAsUndefined function
expect(() => BufferModule.transcode()).toThrow("Not implemented");
});
+
+it("Buffer.from (Node.js test/test-buffer-from.js)", () => {
+ 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;
+ }
+ }
+
+ expect(Buffer.from(new String(checkString))).toStrictEqual(check);
+ expect(Buffer.from(new MyString())).toStrictEqual(check);
+ expect(Buffer.from(new MyPrimitive())).toStrictEqual(check);
+
+ [
+ {},
+ new Boolean(true),
+ {
+ valueOf() {
+ return null;
+ },
+ },
+ {
+ valueOf() {
+ return undefined;
+ },
+ },
+ { valueOf: null },
+ Object.create(null),
+ new Number(true),
+ new MyBadPrimitive(),
+ Symbol(),
+ 5n,
+ (one, two, three) => {},
+ undefined,
+ null,
+ ].forEach((input) => {
+ expect(() => Buffer.from(input)).toThrow();
+ expect(() => Buffer.from(input, "hex")).toThrow();
+ });
+
+ expect(() => Buffer.allocUnsafe(10)).not.toThrow(); // Should not throw.
+ expect(() => Buffer.from("deadbeaf", "hex")).not.toThrow(); // Should not throw.
+});