diff options
author | 2023-01-21 03:12:59 -0800 | |
---|---|---|
committer | 2023-01-21 03:12:59 -0800 | |
commit | d955bfe50f7aa15aca9df3bf0a40fea26a132381 (patch) | |
tree | 2433e2b9dfc4bf712903417008f9b0c5b3900d69 | |
parent | b8648adf873758a83911d3d28c94255d8c3fdd3c (diff) | |
download | bun-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.cpp | 105 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.h | 9 | ||||
-rw-r--r-- | src/bun.js/builtins/js/JSBufferConstructor.js | 96 | ||||
-rw-r--r-- | test/bun.js/buffer.test.js | 61 |
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. +}); |