aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/bindings/JSBufferList.cpp122
-rw-r--r--src/bun.js/bindings/JSBufferList.h31
-rw-r--r--test/bun.js/bufferlist.test.ts38
3 files changed, 158 insertions, 33 deletions
diff --git a/src/bun.js/bindings/JSBufferList.cpp b/src/bun.js/bindings/JSBufferList.cpp
index e54b433e5..37b14143c 100644
--- a/src/bun.js/bindings/JSBufferList.cpp
+++ b/src/bun.js/bindings/JSBufferList.cpp
@@ -39,17 +39,26 @@ JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGloba
const size_t len = length();
if (len == 0) {
// Buffer.alloc(0)
- RELEASE_AND_RETURN(throwScope, JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, 0));
+ auto array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, 0);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, array);
}
auto iter = m_deque.begin();
+ size_t offset = m_read;
if (len == 1) {
auto array = JSC::jsDynamicCast<JSC::JSUint8Array*>(iter->get());
if (UNLIKELY(!array)) {
return throwTypeError(lexicalGlobalObject, throwScope, "concat can only be called when all buffers are Uint8Array"_s);
}
- if (UNLIKELY(array->byteLength() > n)) {
+ const size_t length = array->byteLength() - offset;
+ if (UNLIKELY(length > n)) {
return throwRangeError(lexicalGlobalObject, throwScope, "specified size too small to fit all buffers"_s);
}
+ if (offset > 0) {
+ auto buffer = array->possiblySharedBuffer();
+ array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, offset, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ }
RELEASE_AND_RETURN(throwScope, array);
}
// Buffer.allocUnsafe(n >>> 0)
@@ -58,6 +67,7 @@ JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGloba
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
}
JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(arrayBuffer), 0, n);
+ RETURN_IF_EXCEPTION(throwScope, {});
size_t i = 0;
for (const auto end = m_deque.end(); iter != end; ++iter) {
@@ -65,19 +75,26 @@ JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGloba
if (UNLIKELY(!array)) {
return throwTypeError(lexicalGlobalObject, throwScope, "concat can only be called when all buffers are Uint8Array"_s);
}
- const size_t length = array->byteLength();
+ const size_t length = array->byteLength() - offset;
if (UNLIKELY(i + length > n)) {
return throwRangeError(lexicalGlobalObject, throwScope, "specified size too small to fit all buffers"_s);
}
- if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, i, array, 0, length, JSC::CopyType::Unobservable))) {
+ if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, i, array, offset, length, JSC::CopyType::Unobservable))) {
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
}
+ offset = 0;
i += length;
}
RELEASE_AND_RETURN(throwScope, uint8Array);
}
+inline size_t slicedLength(JSString* str, size_t offset)
+{
+ const auto len = str->length();
+ return len < offset ? 0 : len - offset;
+}
+
JSC::JSValue JSBufferList::join(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSString* seq)
{
auto throwScope = DECLARE_THROW_SCOPE(vm);
@@ -86,9 +103,16 @@ JSC::JSValue JSBufferList::join(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalO
}
const bool needSeq = seq->length() != 0;
const auto end = m_deque.end();
+ size_t offset = m_read;
JSRopeString::RopeBuilder<RecordOverflow> ropeBuilder(vm);
for (auto iter = m_deque.begin();;) {
auto str = iter->get().toString(lexicalGlobalObject);
+ if (offset > 0) {
+ const size_t len = slicedLength(str, offset);
+ str = JSC::jsSubstring(lexicalGlobalObject, str, offset, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ offset = 0;
+ }
if (!ropeBuilder.append(str))
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
if (++iter == end)
@@ -120,16 +144,22 @@ JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalG
if (UNLIKELY(!str)) {
return throwTypeError(lexicalGlobalObject, throwScope, "_getString can only be called when all buffers are string"_s);
}
- const size_t len = str->length();
+ const size_t len = slicedLength(str, m_read);
size_t n = total;
if (n == len) {
m_deque.removeFirst();
+ if (m_read > 0) {
+ str = JSC::jsSubstring(lexicalGlobalObject, str, m_read, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ m_read = 0;
+ }
RELEASE_AND_RETURN(throwScope, str);
}
if (n < len) {
- JSString* firstHalf = JSC::jsSubstring(lexicalGlobalObject, str, 0, n);
- iter->set(vm, this, JSC::jsSubstring(lexicalGlobalObject, str, n, len - n));
+ JSString* firstHalf = JSC::jsSubstring(lexicalGlobalObject, str, m_read, n);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ m_read += n;
RELEASE_AND_RETURN(throwScope, firstHalf);
}
@@ -139,18 +169,25 @@ JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalG
if (UNLIKELY(!str)) {
return throwTypeError(lexicalGlobalObject, throwScope, "_getString can only be called when all buffers are string"_s);
}
- const size_t len = str->length();
+ const size_t len = slicedLength(str, m_read);
if (n < len) {
- JSString* firstHalf = JSC::jsSubstring(lexicalGlobalObject, str, 0, n);
+ JSString* firstHalf = JSC::jsSubstring(lexicalGlobalObject, str, m_read, n);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ m_read += n;
if (!ropeBuilder.append(firstHalf))
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
- iter->set(vm, this, JSC::jsSubstring(lexicalGlobalObject, str, n, len - n));
break;
}
+ if (m_read > 0) {
+ str = JSC::jsSubstring(lexicalGlobalObject, str, m_read, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ m_read = 0;
+ }
if (!ropeBuilder.append(str))
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
m_deque.removeFirst();
- if (n == len) break;
+ if (n == len)
+ break;
n -= len;
}
RELEASE_AND_RETURN(throwScope, ropeBuilder.release());
@@ -162,7 +199,9 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG
auto* subclassStructure = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->JSBufferSubclassStructure();
if (total <= 0 || length() == 0) {
// Buffer.alloc(0)
- RELEASE_AND_RETURN(throwScope, JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, 0));
+ JSC::JSUint8Array* array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, 0);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, array);
}
auto iter = m_deque.begin();
@@ -170,19 +209,25 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG
if (UNLIKELY(!array)) {
return throwTypeError(lexicalGlobalObject, throwScope, "_getBuffer can only be called when all buffers are Uint8Array"_s);
}
- const size_t len = array->byteLength();
+ const size_t len = array->byteLength() - m_read;
size_t n = total;
if (n == len) {
m_deque.removeFirst();
+ if (m_read > 0) {
+ auto buffer = array->possiblySharedBuffer();
+ array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, m_read, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ m_read = 0;
+ }
RELEASE_AND_RETURN(throwScope, array);
}
if (n < len) {
auto buffer = array->possiblySharedBuffer();
- JSC::JSUint8Array* retArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, 0, n);
- JSC::JSUint8Array* newArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, n, len - n);
- iter->set(vm, this, newArray);
- RELEASE_AND_RETURN(throwScope, retArray);
+ JSC::JSUint8Array* head = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, m_read, n);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ m_read += n;
+ RELEASE_AND_RETURN(throwScope, head);
}
// Buffer.allocUnsafe(n >>> 0)
@@ -191,33 +236,56 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
}
JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTFMove(arrayBuffer), 0, n);
+ RETURN_IF_EXCEPTION(throwScope, {});
size_t offset = 0;
for (const auto end = m_deque.end(); iter != end; ++iter) {
JSC::JSUint8Array* array = JSC::jsDynamicCast<JSC::JSUint8Array*>(iter->get());
if (UNLIKELY(!array)) {
return throwTypeError(lexicalGlobalObject, throwScope, "_getBuffer can only be called when all buffers are Uint8Array"_s);
}
- const size_t len = array->byteLength();
+ const size_t len = array->byteLength() - m_read;
if (n < len) {
- if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, 0, n, JSC::CopyType::Unobservable))) {
+ if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, m_read, n, JSC::CopyType::Unobservable))) {
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
}
- auto buffer = array->possiblySharedBuffer();
- JSC::JSUint8Array* newArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, n, len - n);
- iter->set(vm, this, newArray);
+ m_read += n;
break;
}
- if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, 0, len, JSC::CopyType::Unobservable))) {
+ if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, m_read, len, JSC::CopyType::Unobservable))) {
return throwOutOfMemoryError(lexicalGlobalObject, throwScope);
}
+ m_read = 0;
m_deque.removeFirst();
- if (n == len) break;
+ if (n == len)
+ break;
n -= len;
offset += len;
}
RELEASE_AND_RETURN(throwScope, uint8Array);
}
+// assumes `length() > 0` and `m_read > 0`
+inline JSC::JSValue JSBufferList::trim(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue value)
+{
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSC::JSString* str = JSC::jsDynamicCast<JSC::JSString*>(value);
+ if (str) {
+ const size_t len = slicedLength(str, m_read);
+ str = JSC::jsSubstring(lexicalGlobalObject, str, m_read, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, str);
+ }
+ JSC::JSUint8Array* array = JSC::jsDynamicCast<JSC::JSUint8Array*>(value);
+ if (UNLIKELY(!array))
+ return throwTypeError(lexicalGlobalObject, throwScope, "expected string or Uint8Array"_s);
+ auto* subclassStructure = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->JSBufferSubclassStructure();
+ const size_t len = array->byteLength() - m_read;
+ auto buffer = array->possiblySharedBuffer();
+ JSC::JSUint8Array* head = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, m_read, len);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, head);
+}
+
const JSC::ClassInfo JSBufferList::s_info = { "BufferList"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBufferList) };
JSC::GCClient::IsoSubspace* JSBufferList::subspaceForImpl(JSC::VM& vm)
@@ -266,14 +334,14 @@ static inline JSC::EncodedJSValue jsBufferListPrototypeFunction_unshiftBody(JSC:
}
auto v = callFrame->uncheckedArgument(0);
- castedThis->unshift(vm, v);
+ castedThis->unshift(vm, lexicalGlobalObject, v);
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
}
static inline JSC::EncodedJSValue jsBufferListPrototypeFunction_shiftBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBufferList>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
- RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->shift()));
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->shift(vm, lexicalGlobalObject)));
}
static inline JSC::EncodedJSValue jsBufferListPrototypeFunction_clearBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBufferList>::ClassParameter castedThis)
{
@@ -286,7 +354,7 @@ static inline JSC::EncodedJSValue jsBufferListPrototypeFunction_firstBody(JSC::J
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
- RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->first()));
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->first(vm, lexicalGlobalObject)));
}
static inline JSC::EncodedJSValue jsBufferListPrototypeFunction_concatBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBufferList>::ClassParameter castedThis)
{
diff --git a/src/bun.js/bindings/JSBufferList.h b/src/bun.js/bindings/JSBufferList.h
index a9227e981..c34a0ee95 100644
--- a/src/bun.js/bindings/JSBufferList.h
+++ b/src/bun.js/bindings/JSBufferList.h
@@ -52,28 +52,43 @@ public:
m_deque.append(WriteBarrier<Unknown>());
m_deque.last().set(vm, this, v);
}
- void unshift(JSC::VM& vm, JSC::JSValue v)
+ void unshift(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue v)
{
+ if (m_read > 0) {
+ m_deque.first().set(vm, this, trim(vm, lexicalGlobalObject, m_deque.first().get()));
+ m_read = 0;
+ }
m_deque.prepend(WriteBarrier<Unknown>());
m_deque.first().set(vm, this, v);
}
- JSC::JSValue shift()
+ JSC::JSValue shift(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject)
{
if (UNLIKELY(length() == 0))
return JSC::jsUndefined();
- auto v = m_deque.first().get();
+ auto value = m_deque.first().get();
+ if (m_read > 0) {
+ value = trim(vm, lexicalGlobalObject, value);
+ m_read = 0;
+ }
m_deque.removeFirst();
- return v;
+ return value;
}
void clear()
{
m_deque.clear();
+ m_read = 0;
}
- JSC::JSValue first()
+ JSC::JSValue first(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject)
{
if (UNLIKELY(length() == 0))
return JSC::jsUndefined();
- return JSC::JSValue(m_deque.first().get());
+ auto value = m_deque.first().get();
+ if (m_read > 0) {
+ value = trim(vm, lexicalGlobalObject, value);
+ m_deque.first().set(vm, this, value);
+ m_read = 0;
+ }
+ return value;
}
JSC::JSValue concat(JSC::VM&, JSC::JSGlobalObject*, int32_t);
@@ -84,6 +99,9 @@ public:
private:
Deque<WriteBarrier<Unknown>> m_deque;
+ size_t m_read = 0;
+
+ JSC::JSValue trim(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue value);
};
class JSBufferListPrototype : public JSC::JSNonFinalObject {
@@ -134,6 +152,7 @@ public:
// Must be defined for each specialization class.
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
DECLARE_EXPORT_INFO;
+
private:
JSBufferListConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction)
: Base(vm, structure, nativeFunction, nativeFunction)
diff --git a/test/bun.js/bufferlist.test.ts b/test/bun.js/bufferlist.test.ts
index e344d49b4..3aa20845b 100644
--- a/test/bun.js/bufferlist.test.ts
+++ b/test/bun.js/bufferlist.test.ts
@@ -185,3 +185,41 @@ it("should work with .unshift()", () => {
expect(list.shift()).toBe(item2);
expect(list.shift()).toBeUndefined();
});
+
+it("should work with partial .consume() followed by .first()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push("foo")).toBeUndefined();
+ expect(list.push("bar")).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(4, true)).toEqual("foob");
+ expect(list.length).toBe(1);
+ expect(list.first()).toEqual("ar");
+ expect(list.length).toBe(1);
+});
+
+it("should work with partial .consume() followed by .shift()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("foo"))).toBeUndefined();
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(4, false)).toEqual(makeUint8Array("foob"));
+ expect(list.length).toBe(1);
+ expect(list.shift()).toEqual(makeUint8Array("ar"));
+ expect(list.length).toBe(0);
+});
+
+it("should work with partial .consume() followed by .unshift()", () => {
+ const list = new Readable().readableBuffer;
+ expect(list.length).toBe(0);
+ expect(list.push(makeUint8Array("foo"))).toBeUndefined();
+ expect(list.push(makeUint8Array("bar"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(4, false)).toEqual(makeUint8Array("foob"));
+ expect(list.length).toBe(1);
+ expect(list.unshift(makeUint8Array("baz"))).toBeUndefined();
+ expect(list.length).toBe(2);
+ expect(list.consume(5, false)).toEqual(makeUint8Array("bazar"));
+ expect(list.length).toBe(0);
+});