aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/bindings/JSBuffer.cpp191
-rw-r--r--test/bun.js/buffer.test.js78
2 files changed, 237 insertions, 32 deletions
diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp
index a6f321b21..87ce722f2 100644
--- a/src/bun.js/bindings/JSBuffer.cpp
+++ b/src/bun.js/bindings/JSBuffer.cpp
@@ -231,33 +231,11 @@ static inline EncodedJSValue constructBufferFromLength(JSGlobalObject* lexicalGl
return jsBufferConstructorFunction_allocUnsafeBody(lexicalGlobalObject, callFrame);
}
-static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
+static EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalObject, JSString* str, WebCore::BufferEncodingType encoding)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
- uint32_t offset = 0;
- uint32_t length = castedThis->length();
- WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8;
-
auto scope = DECLARE_THROW_SCOPE(vm);
- EnsureStillAliveScope arg0 = callFrame->argument(0);
- auto* str = arg0.value().toString(lexicalGlobalObject);
-
- EnsureStillAliveScope arg1 = callFrame->argument(1);
-
- if (str->length() == 0)
- return constructBufferEmpty(lexicalGlobalObject, callFrame);
-
- if (callFrame->argumentCount() > 1) {
- std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, callFrame->argument(1));
- if (!encoded) {
- throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s);
- return JSC::JSValue::encode(jsUndefined());
- }
-
- encoding = encoded.value();
- }
-
auto view = str->tryGetValue(lexicalGlobalObject);
JSC::EncodedJSValue result;
@@ -339,6 +317,37 @@ static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGl
scope.throwException(lexicalGlobalObject, decoded);
return JSC::JSValue::encode(jsUndefined());
}
+ return result;
+}
+
+static inline JSC::EncodedJSValue constructBufferFromStringAndEncoding(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ uint32_t offset = 0;
+ uint32_t length = castedThis->length();
+ WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8;
+
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ EnsureStillAliveScope arg0 = callFrame->argument(0);
+ auto* str = arg0.value().toString(lexicalGlobalObject);
+
+ EnsureStillAliveScope arg1 = callFrame->argument(1);
+
+ if (str->length() == 0)
+ return constructBufferEmpty(lexicalGlobalObject, callFrame);
+
+ if (callFrame->argumentCount() > 1) {
+ std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, callFrame->argument(1));
+ if (!encoded) {
+ throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s);
+ return JSC::JSValue::encode(jsUndefined());
+ }
+
+ encoding = encoded.value();
+ }
+
+ JSC::EncodedJSValue result = constructFromEncoding(lexicalGlobalObject, str, encoding);
RELEASE_AND_RETURN(scope, result);
}
@@ -982,20 +991,138 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_fillBody(JSC::JSGlob
}
}
-static inline JSC::EncodedJSValue jsBufferPrototypeFunction_includesBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
+static int64_t indexOf(const uint8_t* thisPtr, int64_t thisLength, const uint8_t* valuePtr, int64_t valueLength, int64_t byteOffset)
+{
+ if (thisLength < valueLength + byteOffset)
+ return -1;
+ auto start = thisPtr + byteOffset;
+ auto it = static_cast<uint8_t*>(memmem(start, static_cast<size_t>(thisLength - byteOffset), valuePtr, static_cast<size_t>(valueLength)));
+ if (it != NULL) {
+ return it - thisPtr;
+ }
+ return -1;
+}
+
+static int64_t lastIndexOf(const uint8_t* thisPtr, int64_t thisLength, const uint8_t* valuePtr, int64_t valueLength, int64_t byteOffset)
+{
+ auto start = thisPtr;
+ auto end = thisPtr + std::min(thisLength, byteOffset + valueLength);
+ auto it = std::find_end(start, end, valuePtr, valuePtr + valueLength);
+ if (it != end) {
+ return it - thisPtr;
+ }
+ return -1;
+}
+
+static int64_t indexOf(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis, bool last)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
- return JSC::JSValue::encode(jsUndefined());
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ if (callFrame->argumentCount() < 1) {
+ throwVMError(lexicalGlobalObject, scope, createNotEnoughArgumentsError(lexicalGlobalObject));
+ return JSValue::encode(jsUndefined());
+ }
+
+ auto value = callFrame->uncheckedArgument(0);
+ WebCore::BufferEncodingType encoding = WebCore::BufferEncodingType::utf8;
+
+ int64_t length = static_cast<int64_t>(castedThis->byteLength());
+ const uint8_t* typedVector = castedThis->typedVector();
+
+ int64_t byteOffset = last ? length - 1 : 0;
+
+ if (callFrame->argumentCount() > 1) {
+ auto byteOffset_ = callFrame->uncheckedArgument(1).toNumber(lexicalGlobalObject);
+ if (std::isnan(byteOffset_) || std::isinf(byteOffset_)) {
+ byteOffset = last ? length - 1 : 0;
+ } else if (byteOffset_ < 0) {
+ byteOffset = length + static_cast<int64_t>(byteOffset_);
+ } else {
+ byteOffset = static_cast<int64_t>(byteOffset_);
+ }
+
+ if (last) {
+ if (byteOffset < 0) {
+ return -1;
+ } else if (byteOffset > length - 1) {
+ byteOffset = length - 1;
+ }
+ } else {
+ if (byteOffset <= 0) {
+ byteOffset = 0;
+ } else if (byteOffset > length - 1) {
+ return -1;
+ }
+ }
+
+ if (callFrame->argumentCount() > 2) {
+ std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, callFrame->uncheckedArgument(2));
+ if (!encoded) {
+ throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s);
+ return JSC::JSValue::encode(jsUndefined());
+ }
+
+ encoding = encoded.value();
+ }
+ }
+
+ if (value.isString()) {
+ auto* str = value.toString(lexicalGlobalObject);
+ JSC::EncodedJSValue encodedBuffer = constructFromEncoding(lexicalGlobalObject, str, encoding);
+ auto* arrayValue = JSC::jsDynamicCast<JSC::JSUint8Array*>(JSC::JSValue::decode(encodedBuffer));
+ int64_t lengthValue = static_cast<int64_t>(arrayValue->byteLength());
+ const uint8_t* typedVectorValue = arrayValue->typedVector();
+ if (last) {
+ return lastIndexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset);
+ } else {
+ return indexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset);
+ }
+ } else if (value.isNumber()) {
+ uint8_t byteValue = static_cast<uint8_t>(value.toNumber(lexicalGlobalObject));
+ if (last) {
+ for (int64_t i = byteOffset; i >= 0; --i) {
+ if (byteValue == typedVector[i]) {
+ return i;
+ }
+ }
+ } else {
+ for (int64_t i = byteOffset; i < length; ++i) {
+ if (byteValue == typedVector[i]) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ } else if (auto* arrayValue = JSC::jsDynamicCast<JSC::JSUint8Array*>(value)) {
+ size_t lengthValue = arrayValue->byteLength();
+ const uint8_t* typedVectorValue = arrayValue->typedVector();
+ if (last) {
+ return lastIndexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset);
+ } else {
+ return indexOf(typedVector, length, typedVectorValue, lengthValue, byteOffset);
+ }
+ } else {
+ throwTypeError(lexicalGlobalObject, scope, "Invalid value type"_s);
+ return -1;
+ }
+
+ return -1;
+}
+
+static inline JSC::EncodedJSValue jsBufferPrototypeFunction_includesBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
+{
+ auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, false);
+ return JSC::JSValue::encode(jsBoolean(index != -1));
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_indexOfBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
- auto& vm = JSC::getVM(lexicalGlobalObject);
- return JSC::JSValue::encode(jsUndefined());
+ auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, false);
+ return JSC::JSValue::encode(jsNumber(index));
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_lastIndexOfBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
- auto& vm = JSC::getVM(lexicalGlobalObject);
- return JSC::JSValue::encode(jsUndefined());
+ auto index = indexOf(lexicalGlobalObject, callFrame, castedThis, true);
+ return JSC::JSValue::encode(jsNumber(index));
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap16Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBuffer>::ClassParameter castedThis)
{
@@ -1434,9 +1561,9 @@ static const HashTableValue JSBufferPrototypeTableValues[]
{ "fill"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_fill), (intptr_t)(4) } },
{ "hexSlice"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { (intptr_t) static_cast<BuiltinGenerator>(jsBufferPrototypeHexSliceCodeGenerator), (intptr_t)(2) } },
{ "hexWrite"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { (intptr_t) static_cast<BuiltinGenerator>(jsBufferPrototypeHexWriteCodeGenerator), (intptr_t)(1) } },
- // { "includes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_includes), (intptr_t)(3) } },
- // { "indexOf"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_indexOf), (intptr_t)(3) } },
- // { "lastIndexOf"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_lastIndexOf), (intptr_t)(3) } },
+ { "includes"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_includes), (intptr_t)(3) } },
+ { "indexOf"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_indexOf), (intptr_t)(3) } },
+ { "lastIndexOf"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { (intptr_t) static_cast<RawNativeFunction>(jsBufferPrototypeFunction_lastIndexOf), (intptr_t)(3) } },
{ "latin1Slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { (intptr_t) static_cast<BuiltinGenerator>(jsBufferPrototypeLatin1SliceCodeGenerator), (intptr_t)(2) } },
{ "latin1Write"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { (intptr_t) static_cast<BuiltinGenerator>(jsBufferPrototypeLatin1WriteCodeGenerator), (intptr_t)(1) } },
{ "readBigInt64"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { (intptr_t) static_cast<BuiltinGenerator>(jsBufferPrototypeReadBigInt64LECodeGenerator), (intptr_t)(1) } },
diff --git a/test/bun.js/buffer.test.js b/test/bun.js/buffer.test.js
index fa086d744..0b3abd270 100644
--- a/test/bun.js/buffer.test.js
+++ b/test/bun.js/buffer.test.js
@@ -386,3 +386,81 @@ it("read", () => {
expect(buf.readUInt8(0)).toBe(255);
reset();
});
+
+it("includes", () => {
+ const buf = Buffer.from('this is a buffer');
+
+ expect(buf.includes('this')).toBe(true);
+ expect(buf.includes('is')).toBe(true);
+ expect(buf.includes(Buffer.from('a buffer'))).toBe(true);
+ expect(buf.includes(97)).toBe(true);
+ expect(buf.includes(Buffer.from('a buffer example'))).toBe(false);
+ expect(buf.includes(Buffer.from('a buffer example').slice(0, 8))).toBe(true);
+ expect(buf.includes('this', 4)).toBe(false);
+});
+
+it("indexOf", () => {
+ const buf = Buffer.from('this is a buffer');
+
+ expect(buf.indexOf('this')).toBe(0);
+ expect(buf.indexOf('is')).toBe(2);
+ expect(buf.indexOf(Buffer.from('a buffer'))).toBe(8);
+ expect(buf.indexOf(97)).toBe(8);
+ expect(buf.indexOf(Buffer.from('a buffer example'))).toBe(-1);
+ expect(buf.indexOf(Buffer.from('a buffer example').slice(0, 8))).toBe(8);
+
+ const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le');
+
+ expect(utf16Buffer.indexOf('\u03a3', 0, 'utf16le')).toBe(4);
+ expect(utf16Buffer.indexOf('\u03a3', -4, 'utf16le')).toBe(6);
+
+ const b = Buffer.from('abcdef');
+
+ // Passing a value that's a number, but not a valid byte.
+ // Prints: 2, equivalent to searching for 99 or 'c'.
+ expect(b.indexOf(99.9)).toBe(2);
+ expect(b.indexOf(256 + 99)).toBe(2);
+
+ // Passing a byteOffset that coerces to NaN or 0.
+ // Prints: 1, searching the whole buffer.
+ expect(b.indexOf('b', undefined)).toBe(1);
+ expect(b.indexOf('b', {})).toBe(1);
+ expect(b.indexOf('b', null)).toBe(1);
+ expect(b.indexOf('b', [])).toBe(1);
+});
+
+it("lastIndexOf", () => {
+ const buf = Buffer.from('this buffer is a buffer');
+
+ expect(buf.lastIndexOf('this')).toBe(0);
+ expect(buf.lastIndexOf('this', 0)).toBe(0);
+ expect(buf.lastIndexOf('this', -1000)).toBe(-1);
+ expect(buf.lastIndexOf('buffer')).toBe(17);
+ expect(buf.lastIndexOf(Buffer.from('buffer'))).toBe(17);
+ expect(buf.lastIndexOf(97)).toBe(15);
+ expect(buf.lastIndexOf(Buffer.from('yolo'))).toBe(-1);
+ expect(buf.lastIndexOf('buffer', 5)).toBe(5);
+ expect(buf.lastIndexOf('buffer', 4)).toBe(-1);
+
+ const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le');
+
+ expect(utf16Buffer.lastIndexOf('\u03a3', undefined, 'utf16le')).toBe(6);
+ expect(utf16Buffer.lastIndexOf('\u03a3', -5, 'utf16le')).toBe(4);
+
+ const b = Buffer.from('abcdef');
+
+ // Passing a value that's a number, but not a valid byte.
+ // Prints: 2, equivalent to searching for 99 or 'c'.
+ expect(b.lastIndexOf(99.9)).toBe(2);
+ expect(b.lastIndexOf(256 + 99)).toBe(2);
+
+ // Passing a byteOffset that coerces to NaN or 0.
+ // Prints: 1, searching the whole buffer.
+ expect(b.lastIndexOf('b', undefined)).toBe(1);
+ expect(b.lastIndexOf('b', {})).toBe(1);
+
+ // Passing a byteOffset that coerces to 0.
+ // Prints: -1, equivalent to passing 0.
+ expect(b.lastIndexOf('b', null)).toBe(-1);
+ expect(b.lastIndexOf('b', [])).toBe(-1);
+});