aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-05-01 04:36:17 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-05-01 04:36:17 -0700
commitde23f2f8aa505af99ebf0cdf42d7fa96185b7bdd (patch)
tree2b13290d10dbeb186095c8e9ce807e9603b41cda
parentb6aa9887169f3cb7b7e866d8ea5544bc168dec86 (diff)
downloadbun-de23f2f8aa505af99ebf0cdf42d7fa96185b7bdd.tar.gz
bun-de23f2f8aa505af99ebf0cdf42d7fa96185b7bdd.tar.zst
bun-de23f2f8aa505af99ebf0cdf42d7fa96185b7bdd.zip
Buffer.compare & Buffer.equal
-rw-r--r--integration/bunjs-only-snippets/buffer.test.js25
-rw-r--r--src/javascript/jsc/bindings/JSBuffer.cpp224
2 files changed, 244 insertions, 5 deletions
diff --git a/integration/bunjs-only-snippets/buffer.test.js b/integration/bunjs-only-snippets/buffer.test.js
index c934f872d..ba325adc9 100644
--- a/integration/bunjs-only-snippets/buffer.test.js
+++ b/integration/bunjs-only-snippets/buffer.test.js
@@ -147,6 +147,31 @@ it("Buffer.from", () => {
gc();
});
+it("Buffer.equals", () => {
+ var a = new Uint8Array(10);
+ a[2] = 1;
+ var b = new Uint8Array(10);
+ b[2] = 1;
+ Buffer.toBuffer(a);
+ Buffer.toBuffer(b);
+ expect(a.equals(b)).toBe(true);
+ b[2] = 0;
+ expect(a.equals(b)).toBe(false);
+});
+
+it("Buffer.compare", () => {
+ var a = new Uint8Array(10);
+ a[2] = 1;
+ var b = new Uint8Array(10);
+ b[2] = 1;
+ Buffer.toBuffer(a);
+ Buffer.toBuffer(b);
+ expect(a.compare(b)).toBe(0);
+ b[2] = 0;
+ expect(a.compare(b)).toBe(1);
+ expect(b.compare(a)).toBe(-1);
+});
+
it("Buffer.copy", () => {
var array1 = new Uint8Array(128);
array1.fill(100);
diff --git a/src/javascript/jsc/bindings/JSBuffer.cpp b/src/javascript/jsc/bindings/JSBuffer.cpp
index eab0a7734..ab5e0341d 100644
--- a/src/javascript/jsc/bindings/JSBuffer.cpp
+++ b/src/javascript/jsc/bindings/JSBuffer.cpp
@@ -80,6 +80,24 @@ bool JSBuffer__isBuffer(JSC::JSGlobalObject* lexicalGlobalObject, JSC::EncodedJS
return !!jsBuffer->getIfPropertyExists(lexicalGlobalObject, clientData->builtinNames().dataViewPrivateName());
}
+// Normalize val to be an integer in the range of [1, -1] since
+// implementations of memcmp() can vary by platform.
+static int normalizeCompareVal(int val, size_t a_length, size_t b_length)
+{
+ if (val == 0) {
+ if (a_length > b_length)
+ return 1;
+ else if (a_length < b_length)
+ return -1;
+ } else {
+ if (val > 0)
+ return 1;
+ else
+ return -1;
+ }
+ return val;
+}
+
namespace WebCore {
using namespace JSC;
@@ -348,7 +366,89 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_byteLengthBody(JSC
static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
- return JSValue::encode(jsUndefined());
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ if (callFrame->argumentCount() < 1) {
+ throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto buffer = callFrame->uncheckedArgument(0);
+ JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(vm, buffer);
+ if (UNLIKELY(!view)) {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ if (UNLIKELY(view->isDetached())) {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array is detached"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ size_t targetStart = 0;
+ size_t targetEndInit = view->byteLength();
+ size_t targetEnd = targetEndInit;
+
+ size_t sourceStart = 0;
+ size_t sourceEndInit = castedThis->byteLength();
+ size_t sourceEnd = sourceEndInit;
+
+ if (callFrame->argumentCount() > 1) {
+ if (auto targetEnd_ = callFrame->uncheckedArgument(1).tryGetAsUint32Index()) {
+ targetStart = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ if (callFrame->argumentCount() > 2) {
+ auto targetEndArgument = callFrame->uncheckedArgument(2);
+ if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
+ targetEnd = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+
+ if (callFrame->argumentCount() > 3) {
+ auto targetEndArgument = callFrame->uncheckedArgument(3);
+ if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
+ sourceStart = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+
+ if (callFrame->argumentCount() > 4) {
+ auto targetEndArgument = callFrame->uncheckedArgument(4);
+ if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
+ sourceEnd = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+ }
+
+ if (targetStart > std::min(targetEnd, targetEndInit) || targetEnd > targetEndInit) {
+ return throwVMError(lexicalGlobalObject, throwScope, createRangeError(lexicalGlobalObject, "targetStart and targetEnd out of range"_s));
+ }
+
+ if (sourceStart > std::min(sourceEnd, sourceEndInit) || sourceEnd > sourceEndInit) {
+ return throwVMError(lexicalGlobalObject, throwScope, createRangeError(lexicalGlobalObject, "sourceStart and sourceEnd out of range"_s));
+ }
+
+ auto sourceLength = sourceEnd - sourceStart;
+ auto targetLength = targetEnd - targetStart;
+ auto actualLength = std::min(sourceLength, targetLength);
+
+ auto sourceStartPtr = castedThis->typedVector() + sourceStart;
+ auto targetStartPtr = view->typedVector() + targetStart;
+
+ auto result = actualLength > 0 ? memcmp(sourceStartPtr, targetStartPtr, actualLength) : 0;
+
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsNumber(normalizeCompareVal(result, sourceLength, targetLength))));
}
static inline JSC::EncodedJSValue jsBufferConstructorFunction_concatBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
@@ -485,7 +585,89 @@ STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBufferPrototype, JSBufferPrototype::Base);
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_compareBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
- return JSC::JSValue::encode(jsUndefined());
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ if (callFrame->argumentCount() < 1) {
+ throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
+ return JSValue::encode(jsUndefined());
+ }
+
+ JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(vm, callFrame->uncheckedArgument(0));
+
+ if (UNLIKELY(!view)) {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Uint8Array"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ if (UNLIKELY(view->isDetached())) {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array is detached"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ size_t targetStart = 0;
+ size_t targetEndInit = view->byteLength();
+ size_t targetEnd = targetEndInit;
+
+ size_t sourceStart = 0;
+ size_t sourceEndInit = castedThis->byteLength();
+ size_t sourceEnd = sourceEndInit;
+
+ if (callFrame->argumentCount() > 1) {
+ if (auto targetEnd_ = callFrame->uncheckedArgument(1).tryGetAsUint32Index()) {
+ targetStart = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ if (callFrame->argumentCount() > 2) {
+ auto targetEndArgument = callFrame->uncheckedArgument(2);
+ if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
+ targetEnd = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+
+ if (callFrame->argumentCount() > 3) {
+ auto targetEndArgument = callFrame->uncheckedArgument(3);
+ if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
+ sourceStart = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+
+ if (callFrame->argumentCount() > 4) {
+ auto targetEndArgument = callFrame->uncheckedArgument(4);
+ if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
+ sourceEnd = targetEnd_.value();
+ } else {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected number"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ }
+ }
+
+ if (targetStart > std::min(targetEnd, targetEndInit) || targetEnd > targetEndInit) {
+ return throwVMError(lexicalGlobalObject, throwScope, createRangeError(lexicalGlobalObject, "targetStart and targetEnd out of range"_s));
+ }
+
+ if (sourceStart > std::min(sourceEnd, sourceEndInit) || sourceEnd > sourceEndInit) {
+ return throwVMError(lexicalGlobalObject, throwScope, createRangeError(lexicalGlobalObject, "sourceStart and sourceEnd out of range"_s));
+ }
+
+ auto sourceLength = sourceEnd - sourceStart;
+ auto targetLength = targetEnd - targetStart;
+ auto actualLength = std::min(sourceLength, targetLength);
+
+ auto sourceStartPtr = castedThis->typedVector() + sourceStart;
+ auto targetStartPtr = view->typedVector() + targetStart;
+
+ auto result = actualLength > 0 ? memcmp(sourceStartPtr, targetStartPtr, actualLength) : 0;
+
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsNumber(normalizeCompareVal(result, sourceLength, targetLength))));
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
@@ -537,7 +719,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlob
}
if (callFrame->argumentCount() > 3) {
- auto targetEndArgument = callFrame->uncheckedArgument(2);
+ auto targetEndArgument = callFrame->uncheckedArgument(3);
if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
sourceStart = targetEnd_.value();
} else {
@@ -547,7 +729,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlob
}
if (callFrame->argumentCount() > 4) {
- auto targetEndArgument = callFrame->uncheckedArgument(2);
+ auto targetEndArgument = callFrame->uncheckedArgument(4);
if (auto targetEnd_ = targetEndArgument.tryGetAsUint32Index()) {
sourceEnd = targetEnd_.value();
} else {
@@ -576,14 +758,46 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_copyBody(JSC::JSGlob
return JSValue::encode(jsNumber(actualLength));
}
+
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_equalsBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
- return JSC::JSValue::encode(jsUndefined());
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ if (callFrame->argumentCount() < 1) {
+ throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto buffer = callFrame->uncheckedArgument(0);
+ JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(vm, buffer);
+ if (UNLIKELY(!view)) {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ if (UNLIKELY(view->isDetached())) {
+ throwVMTypeError(lexicalGlobalObject, throwScope, "Uint8Array is detached"_s);
+ return JSValue::encode(jsUndefined());
+ }
+
+ size_t a_length = castedThis->byteLength();
+ size_t b_length = view->byteLength();
+ auto sourceStartPtr = castedThis->typedVector();
+ auto targetStartPtr = view->typedVector();
+
+ // same pointer, same length, same contents
+ if (sourceStartPtr == targetStartPtr && a_length == b_length)
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsBoolean(true)));
+
+ size_t compare_length = std::min(a_length, b_length);
+ auto result = compare_length > 0 ? memcmp(sourceStartPtr, targetStartPtr, compare_length) : 0;
+
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsBoolean(normalizeCompareVal(result, a_length, b_length) == 0)));
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
+
return JSC::JSValue::encode(jsUndefined());
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_includesBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)