diff options
-rw-r--r-- | src/bun.js/bindings/JSBuffer.cpp | 191 | ||||
-rw-r--r-- | test/bun.js/buffer.test.js | 78 |
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); +}); |