diff options
author | 2023-01-21 23:03:43 -0800 | |
---|---|---|
committer | 2023-01-21 23:03:43 -0800 | |
commit | a2cfdf0e1c0353428f0682969e3fcfa86cc2d6a7 (patch) | |
tree | fa2ca92b403e50b2d464e2584296b2831818cf7c | |
parent | 29a576a167f45eb5080bf79afae7a02fe682d75d (diff) | |
download | bun-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.cpp | 117 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/JSBufferConstructorBuiltins.cpp | 10 | ||||
-rw-r--r-- | src/bun.js/builtins/cpp/JSBufferPrototypeBuiltins.h | 9 | ||||
-rw-r--r-- | src/bun.js/builtins/js/JSBufferConstructor.js | 8 | ||||
-rw-r--r-- | test/bun.js/buffer.test.js | 115 |
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)); +}); |