aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Zilin Zhu <zhuzilinallen@gmail.com> 2022-09-10 13:48:55 +0800
committerGravatar GitHub <noreply@github.com> 2022-09-09 22:48:55 -0700
commit8b91360a33b782af423c85f9ec7277394e27beb4 (patch)
tree1c969c98a41e6265695b4f2b4ec73a0e1a71ccad
parent85d80d8fb7e6f32979b82bdf26c93c30bfea578a (diff)
downloadbun-8b91360a33b782af423c85f9ec7277394e27beb4.tar.gz
bun-8b91360a33b782af423c85f9ec7277394e27beb4.tar.zst
bun-8b91360a33b782af423c85f9ec7277394e27beb4.zip
Fix segfault due to GC and some more helper functions (#1221)
* Fix segfault due to GC and some more helper functions * fix upon reviews * add visitChildren
-rw-r--r--src/bun.js/bindings/JSBufferList.cpp11
-rw-r--r--src/bun.js/bindings/JSBufferList.h1
-rw-r--r--src/bun.js/bindings/JSReadableHelper.cpp186
-rw-r--r--src/bun.js/bindings/JSReadableHelper.h3
-rw-r--r--src/bun.js/bindings/JSReadableState.cpp171
-rw-r--r--src/bun.js/bindings/JSReadableState.h60
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp10
-rw-r--r--src/bun.js/streams.exports.js43
8 files changed, 292 insertions, 193 deletions
diff --git a/src/bun.js/bindings/JSBufferList.cpp b/src/bun.js/bindings/JSBufferList.cpp
index 948eed296..10dd3c794 100644
--- a/src/bun.js/bindings/JSBufferList.cpp
+++ b/src/bun.js/bindings/JSBufferList.cpp
@@ -183,6 +183,17 @@ JSC::GCClient::IsoSubspace* JSBufferList::subspaceForImpl(JSC::VM& vm)
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSBufferListPrototype, JSBufferListPrototype::Base);
+template<typename Visitor>
+void JSBufferList::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSBufferList* buffer = jsCast<JSBufferList*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(buffer, info());
+ Base::visitChildren(buffer, visitor);
+ for (auto& val : buffer->m_deque)
+ visitor.append(val);
+}
+DEFINE_VISIT_CHILDREN(JSBufferList);
+
static inline JSC::EncodedJSValue jsBufferListPrototypeFunction_pushBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSBufferList>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
diff --git a/src/bun.js/bindings/JSBufferList.h b/src/bun.js/bindings/JSBufferList.h
index 3c0b8d12c..a9227e981 100644
--- a/src/bun.js/bindings/JSBufferList.h
+++ b/src/bun.js/bindings/JSBufferList.h
@@ -15,6 +15,7 @@ public:
{
}
+ DECLARE_VISIT_CHILDREN;
DECLARE_INFO;
static constexpr unsigned StructureFlags = Base::StructureFlags;
diff --git a/src/bun.js/bindings/JSReadableHelper.cpp b/src/bun.js/bindings/JSReadableHelper.cpp
index ef17982d4..de1c30799 100644
--- a/src/bun.js/bindings/JSReadableHelper.cpp
+++ b/src/bun.js/bindings/JSReadableHelper.cpp
@@ -3,6 +3,7 @@
#include "JSBufferList.h"
#include "JSBuffer.h"
#include "JSEventEmitter.h"
+#include "JSStringDecoder.h"
#include "JavaScriptCore/Lookup.h"
#include "JavaScriptCore/ObjectConstructor.h"
#include "ZigGlobalObject.h"
@@ -14,24 +15,27 @@
namespace WebCore {
using namespace JSC;
+#define JSReadableHelper_EXTRACT_STREAM_STATE \
+ VM& vm = lexicalGlobalObject->vm(); \
+ auto throwScope = DECLARE_THROW_SCOPE(vm); \
+ \
+ if (callFrame->argumentCount() < 2) { \
+ throwTypeError(lexicalGlobalObject, throwScope, "Not enough arguments"_s); \
+ return JSValue::encode(jsUndefined()); \
+ } \
+ \
+ JSObject* stream = callFrame->uncheckedArgument(0).toObject(lexicalGlobalObject); \
+ RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined())); \
+ JSReadableState* state = jsCast<JSReadableState*>(callFrame->uncheckedArgument(1)); \
+ if (!state) { \
+ throwTypeError(lexicalGlobalObject, throwScope, "Second argument not ReadableState"_s); \
+ return JSValue::encode(jsUndefined()); \
+ }
+
static JSC_DECLARE_HOST_FUNCTION(jsReadable_maybeReadMore_);
JSC_DEFINE_HOST_FUNCTION(jsReadable_maybeReadMore_, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
- VM& vm = lexicalGlobalObject->vm();
- auto throwScope = DECLARE_THROW_SCOPE(vm);
-
- if (callFrame->argumentCount() < 2) {
- throwTypeError(lexicalGlobalObject, throwScope, "Not enough arguments"_s);
- return JSValue::encode(jsUndefined());
- }
-
- JSObject* stream = callFrame->uncheckedArgument(0).toObject(lexicalGlobalObject);
- RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
- JSReadableState* state = jsDynamicCast<JSReadableState*>(callFrame->uncheckedArgument(1));
- if (!state) {
- throwTypeError(lexicalGlobalObject, throwScope, "Second argument not ReadableState"_s);
- return JSValue::encode(jsUndefined());
- }
+ JSReadableHelper_EXTRACT_STREAM_STATE
auto read = stream->get(lexicalGlobalObject, Identifier::fromString(vm, "read"_s));
auto callData = JSC::getCallData(read);
@@ -43,8 +47,8 @@ JSC_DEFINE_HOST_FUNCTION(jsReadable_maybeReadMore_, (JSGlobalObject * lexicalGlo
args.append(jsNumber(0));
while (
- !state->m_reading &&
- !state->m_ended &&
+ !state->getBool(JSReadableState::reading) &&
+ !state->getBool(JSReadableState::ended) &&
(state->m_length < state->m_highWaterMark || (state->m_flowing > 0 && state->m_length == 0))) {
int64_t len = state->m_length;
@@ -58,22 +62,13 @@ JSC_DEFINE_HOST_FUNCTION(jsReadable_maybeReadMore_, (JSGlobalObject * lexicalGlo
JSC_DEFINE_HOST_FUNCTION(jsReadable_maybeReadMore, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
- VM& vm = lexicalGlobalObject->vm();
- auto throwScope = DECLARE_THROW_SCOPE(vm);
-
- if (callFrame->argumentCount() < 2) {
- throwTypeError(lexicalGlobalObject, throwScope, "Not enough arguments"_s);
- return JSValue::encode(jsUndefined());
- }
-
- JSValue streamVal = callFrame->uncheckedArgument(0);
- JSValue stateVal = callFrame->uncheckedArgument(1);
+ JSReadableHelper_EXTRACT_STREAM_STATE
// make this static?
JSFunction* maybeReadMore_ = JSC::JSFunction::create(
vm, lexicalGlobalObject, 0, "maybeReadMore_"_s, jsReadable_maybeReadMore_, ImplementationVisibility::Public);
- lexicalGlobalObject->queueMicrotask(maybeReadMore_, streamVal, stateVal, JSValue{}, JSValue{});
+ lexicalGlobalObject->queueMicrotask(maybeReadMore_, JSValue(stream), JSValue(state), JSValue{}, JSValue{});
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
}
@@ -100,23 +95,9 @@ void flow(JSGlobalObject* lexicalGlobalObject, JSObject* stream, JSReadableState
static JSC_DECLARE_HOST_FUNCTION(jsReadable_resume_);
JSC_DEFINE_HOST_FUNCTION(jsReadable_resume_, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
- VM& vm = lexicalGlobalObject->vm();
- auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSReadableHelper_EXTRACT_STREAM_STATE
- if (callFrame->argumentCount() < 2) {
- throwTypeError(lexicalGlobalObject, throwScope, "Not enough arguments"_s);
- return JSValue::encode(jsUndefined());
- }
-
- JSObject* stream = callFrame->uncheckedArgument(0).toObject(lexicalGlobalObject);
- RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
- JSReadableState* state = jsDynamicCast<JSReadableState*>(callFrame->uncheckedArgument(1));
- if (!state) {
- throwTypeError(lexicalGlobalObject, throwScope, "Second argument not ReadableState"_s);
- return JSValue::encode(jsUndefined());
- }
-
- if (!state->m_reading) {
+ if (!state->getBool(JSReadableState::reading)) {
// stream.read(0)
auto read = stream->get(lexicalGlobalObject, Identifier::fromString(vm, "read"_s));
auto callData = JSC::getCallData(read);
@@ -129,7 +110,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadable_resume_, (JSGlobalObject * lexicalGlobalObje
JSC::call(lexicalGlobalObject, read, callData, JSValue(stream), args);
}
- state->m_resumeScheduled = true;
+ state->setBool(JSReadableState::resumeScheduled, true);
// stream.emit('resume')
auto eventType = Identifier::fromString(vm, "resume"_s);
MarkedArgumentBuffer args;
@@ -142,7 +123,7 @@ JSC_DEFINE_HOST_FUNCTION(jsReadable_resume_, (JSGlobalObject * lexicalGlobalObje
flow(lexicalGlobalObject, stream, state);
- if (state->m_flowing && !state->m_reading) {
+ if (state->m_flowing > 0 && !state->getBool(JSReadableState::reading)) {
// stream.read(0)
auto read = stream->get(lexicalGlobalObject, Identifier::fromString(vm, "read"_s));
auto callData = JSC::getCallData(read);
@@ -159,54 +140,26 @@ JSC_DEFINE_HOST_FUNCTION(jsReadable_resume_, (JSGlobalObject * lexicalGlobalObje
JSC_DEFINE_HOST_FUNCTION(jsReadable_resume, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
{
- VM& vm = lexicalGlobalObject->vm();
- auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSReadableHelper_EXTRACT_STREAM_STATE
- if (callFrame->argumentCount() < 2) {
- throwTypeError(lexicalGlobalObject, throwScope, "Not enough arguments"_s);
- return JSValue::encode(jsUndefined());
- }
-
- JSValue streamVal = callFrame->uncheckedArgument(0);
- JSValue stateVal = callFrame->uncheckedArgument(1);
-
- JSReadableState* state = jsDynamicCast<JSReadableState*>(callFrame->uncheckedArgument(1));
- if (!state) {
- throwTypeError(lexicalGlobalObject, throwScope, "Second argument not ReadableState"_s);
- return JSValue::encode(jsUndefined());
- }
-
- if (!state->m_resumeScheduled) {
- state->m_resumeScheduled = true;
+ if (!state->getBool(JSReadableState::resumeScheduled)) {
+ state->setBool(JSReadableState::resumeScheduled, true);
// make this static?
JSFunction* resume_ = JSC::JSFunction::create(
vm, lexicalGlobalObject, 0, "resume_"_s, jsReadable_resume_, ImplementationVisibility::Public);
- lexicalGlobalObject->queueMicrotask(resume_, streamVal, stateVal, JSValue{}, JSValue{});
+ lexicalGlobalObject->queueMicrotask(resume_, JSValue(stream), JSValue(state), JSValue{}, JSValue{});
}
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
}
-JSC_DEFINE_HOST_FUNCTION(jsReadable_emitReadable_, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+EncodedJSValue emitReadable_(JSGlobalObject* lexicalGlobalObject, JSObject* stream, JSReadableState* state)
{
VM& vm = lexicalGlobalObject->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
- if (callFrame->argumentCount() < 2) {
- throwTypeError(lexicalGlobalObject, throwScope, "Not enough arguments"_s);
- return JSValue::encode(jsUndefined());
- }
-
- JSObject* stream = callFrame->uncheckedArgument(0).toObject(lexicalGlobalObject);
- RETURN_IF_EXCEPTION(throwScope, JSValue::encode(jsUndefined()));
- JSReadableState* state = jsDynamicCast<JSReadableState*>(callFrame->uncheckedArgument(1));
- if (!state) {
- throwTypeError(lexicalGlobalObject, throwScope, "Second argument not ReadableState"_s);
- return JSValue::encode(jsUndefined());
- }
-
- JSValue errored = state->getDirect(vm, JSC::Identifier::fromString(vm, "errored"_s));
- if (!state->m_destroyed && !errored.toBoolean(lexicalGlobalObject) && (state->m_length || state->m_ended)) {
+ JSValue errored = state->m_errored.get();
+ if (!state->getBool(JSReadableState::destroyed) && !errored.toBoolean(lexicalGlobalObject) && (state->m_length || state->getBool(JSReadableState::ended))) {
// stream.emit('readable')
auto eventType = Identifier::fromString(vm, "readable"_s);
MarkedArgumentBuffer args;
@@ -217,12 +170,79 @@ JSC_DEFINE_HOST_FUNCTION(jsReadable_emitReadable_, (JSGlobalObject * lexicalGlob
}
emitter->wrapped().emitForBindings(eventType, args);
- state->m_emittedReadable = false;
+ state->setBool(JSReadableState::emittedReadable, false);
}
- state->m_needReadable = state->m_flowing <= 0 && !state->m_ended && state->m_length <= state->m_highWaterMark;
+ state->setBool(JSReadableState::needReadable, state->m_flowing <= 0 && !state->getBool(JSReadableState::ended) && state->m_length <= state->m_highWaterMark);
flow(lexicalGlobalObject, stream, state);
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DECLARE_HOST_FUNCTION(jsReadable_emitReadable_);
+JSC_DEFINE_HOST_FUNCTION(jsReadable_emitReadable_, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ JSReadableHelper_EXTRACT_STREAM_STATE
+
+ emitReadable_(lexicalGlobalObject, stream, state);
+
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
}
+EncodedJSValue emitReadable(JSGlobalObject* lexicalGlobalObject, JSObject* stream, JSReadableState* state)
+{
+ VM& vm = lexicalGlobalObject->vm();
+
+ state->setBool(JSReadableState::needReadable, false);
+ if (!state->getBool(JSReadableState::emittedReadable)) {
+ state->setBool(JSReadableState::emittedReadable, true);
+ // make this static?
+ JSFunction* emitReadable_ = JSC::JSFunction::create(
+ vm, lexicalGlobalObject, 0, "emitReadable_"_s, jsReadable_emitReadable_, ImplementationVisibility::Public);
+
+ lexicalGlobalObject->queueMicrotask(emitReadable_, JSValue(stream), JSValue(state), JSValue{}, JSValue{});
+ }
+ return JSValue::encode(jsUndefined());
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsReadable_emitReadable, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ JSReadableHelper_EXTRACT_STREAM_STATE
+
+ RELEASE_AND_RETURN(throwScope, emitReadable(lexicalGlobalObject, stream, state));
+}
+
+JSC_DEFINE_HOST_FUNCTION(jsReadable_onEofChunk, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ JSReadableHelper_EXTRACT_STREAM_STATE
+
+ if (state->getBool(JSReadableState::ended))
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
+
+ auto decoder = jsDynamicCast<JSStringDecoder*>(state->m_decoder.get());
+ if (decoder) {
+ JSString* chunk = jsDynamicCast<JSString*>(decoder->end(vm, lexicalGlobalObject, nullptr, 0));
+ if (chunk && chunk->length()) {
+ auto buffer = jsDynamicCast<JSBufferList*>(state->m_buffer.get());
+ if (!buffer) {
+ throwTypeError(lexicalGlobalObject, throwScope, "Not buffer on stream"_s);
+ return JSValue::encode(jsUndefined());
+ }
+ buffer->push(vm, JSValue(chunk));
+ state->m_length += state->getBool(JSReadableState::objectMode) ? 1 : chunk->length();
+ }
+ }
+
+ state->setBool(JSReadableState::ended, true);
+
+ if (state->getBool(JSReadableState::sync)) {
+ RELEASE_AND_RETURN(throwScope, emitReadable(lexicalGlobalObject, stream, state));
+ } else {
+ state->setBool(JSReadableState::needReadable, false);
+ state->setBool(JSReadableState::emittedReadable, true);
+ RELEASE_AND_RETURN(throwScope, emitReadable_(lexicalGlobalObject, stream, state));
+ }
+}
+
+#undef JSReadableHelper_EXTRACT_STREAM_STATE
+
} // namespace WebCore
diff --git a/src/bun.js/bindings/JSReadableHelper.h b/src/bun.js/bindings/JSReadableHelper.h
index 9a60fc301..3a25c07cb 100644
--- a/src/bun.js/bindings/JSReadableHelper.h
+++ b/src/bun.js/bindings/JSReadableHelper.h
@@ -6,6 +6,7 @@ namespace WebCore {
JSC_DECLARE_HOST_FUNCTION(jsReadable_maybeReadMore);
JSC_DECLARE_HOST_FUNCTION(jsReadable_resume);
-JSC_DECLARE_HOST_FUNCTION(jsReadable_emitReadable_);
+JSC_DECLARE_HOST_FUNCTION(jsReadable_emitReadable);
+JSC_DECLARE_HOST_FUNCTION(jsReadable_onEofChunk);
} // namespace WebCore
diff --git a/src/bun.js/bindings/JSReadableState.cpp b/src/bun.js/bindings/JSReadableState.cpp
index bf1bc1155..cab54f1ce 100644
--- a/src/bun.js/bindings/JSReadableState.cpp
+++ b/src/bun.js/bindings/JSReadableState.cpp
@@ -38,77 +38,70 @@ void JSReadableState::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObj
{
Base::finishCreation(vm);
- bool objectMode = false;
- auto objectModeIdent = JSC::Identifier::fromString(vm, "objectMode"_s);
if (options != nullptr) {
- JSC::JSValue objectModeVal = options->getDirect(vm, objectModeIdent);
+ JSC::JSValue objectModeVal = options->getDirect(vm, JSC::Identifier::fromString(vm, "objectMode"_s));
if (isDuplex && !objectModeVal) {
objectModeVal = options->getDirect(vm, JSC::Identifier::fromString(vm, "readableObjectMode"_s));
}
- if (objectModeVal)
- objectMode = objectModeVal.toBoolean(globalObject);
+ if (objectModeVal && objectModeVal.toBoolean(globalObject))
+ setBool(JSReadableState::Mask::objectMode, true);
}
- putDirect(vm, WTFMove(objectModeIdent), JSC::jsBoolean(objectMode));
- m_highWaterMark = objectMode ? 16 : 16 * 1024; // default value
+ m_highWaterMark = getBool(JSReadableState::Mask::objectMode) ? 16 : 16 * 1024; // default value
if (options != nullptr) {
int64_t customHightWaterMark = getHighWaterMark(vm, globalObject, isDuplex, options);
if (customHightWaterMark >= 0)
m_highWaterMark = customHightWaterMark;
}
- putDirect(vm, JSC::Identifier::fromString(vm, "buffer"_s), JSBufferList::create(
+ m_buffer.set(vm, this, JSBufferList::create(
vm, globalObject, reinterpret_cast<Zig::GlobalObject*>(globalObject)->JSBufferListStructure()));
- putDirect(vm, JSC::Identifier::fromString(vm, "pipes"_s), JSC::constructEmptyArray(globalObject, nullptr, 0));
+ m_pipes.set(vm, this, JSC::constructEmptyArray(globalObject, nullptr, 0));
- if (options == nullptr) {
- m_emitClose = false;
- m_autoDestroy = false;
- } else {
+ if (options != nullptr) {
JSC::JSValue emitCloseVal = options->getDirect(vm, JSC::Identifier::fromString(vm, "emitClose"_s));
- m_emitClose = !emitCloseVal.isBoolean() || emitCloseVal.toBoolean(globalObject);
+ if (!emitCloseVal.isBoolean() || emitCloseVal.toBoolean(globalObject))
+ setBool(JSReadableState::Mask::emitClose, true);
// Has it been destroyed.
JSC::JSValue autoDestroyVal = options->getDirect(vm, JSC::Identifier::fromString(vm, "autoDestroy"_s));
- m_autoDestroy = !autoDestroyVal.isBoolean() || autoDestroyVal.toBoolean(globalObject);
+ if (!autoDestroyVal.isBoolean() || autoDestroyVal.toBoolean(globalObject))
+ setBool(JSReadableState::Mask::autoDestroy, true);
}
// Indicates whether the stream has finished destroying.
- putDirect(vm, JSC::Identifier::fromString(vm, "errored"_s), JSC::jsNull());
+ m_errored.set(vm, this, JSC::jsNull());
// Ref the piped dest which we need a drain event on it
// type: null | Writable | Set<Writable>.
- auto defaultEncodingIdent = JSC::Identifier::fromString(vm, "defaultEncoding"_s);
if (options == nullptr) {
- putDirect(vm, WTFMove(defaultEncodingIdent), JSC::jsString(vm, WTF::String("utf8"_s)));
+ m_defaultEncoding.set(vm, this, JSC::jsString(vm, WTF::String("utf8"_s)));
} else {
- JSC::JSValue defaultEncodingVal = getDirect(vm, defaultEncodingIdent);
+ JSC::JSValue defaultEncodingVal = getDirect(vm, JSC::Identifier::fromString(vm, "defaultEncoding"_s));
if (defaultEncodingVal) {
- putDirect(vm, WTFMove(defaultEncodingIdent), defaultEncodingVal);
+ m_defaultEncoding.set(vm, this, defaultEncodingVal);
} else {
- putDirect(vm, WTFMove(defaultEncodingIdent), JSC::jsString(vm, WTF::String("utf8"_s)));
+ m_defaultEncoding.set(vm, this, JSC::jsString(vm, WTF::String("utf8"_s)));
}
}
- putDirect(vm, JSC::Identifier::fromString(vm, "awaitDrainWriters"_s), JSC::jsNull());
+ m_awaitDrainWriters.set(vm, this, JSC::jsNull());
- auto decoderIdent = JSC::Identifier::fromString(vm, "decoder"_s);
- auto encodingIdent = JSC::Identifier::fromString(vm, "encoding"_s);
if (options == nullptr) {
- putDirect(vm, WTFMove(decoderIdent), JSC::jsNull());
- putDirect(vm, WTFMove(encodingIdent), JSC::jsNull());
+ m_decoder.set(vm, this, JSC::jsNull());
+ m_encoding.set(vm, this, JSC::jsNull());
} else {
- JSC::JSValue encodingVal = options->getDirect(vm, encodingIdent);
+ JSC::JSValue encodingVal = options->getDirect(vm, JSC::Identifier::fromString(vm, "encoding"_s));
if (encodingVal) {
auto constructor = reinterpret_cast<Zig::GlobalObject*>(globalObject)->JSStringDecoder();
auto constructData = JSC::getConstructData(constructor);
MarkedArgumentBuffer args;
args.append(encodingVal);
JSObject* decoder = JSC::construct(globalObject, constructor, constructData, args);
- putDirect(vm, WTFMove(decoderIdent), decoder);
- putDirect(vm, WTFMove(encodingIdent), encodingVal);
+ m_decoder.set(vm, this, decoder);
+ m_encoding.set(vm, this, encodingVal);
} else {
- putDirect(vm, WTFMove(decoderIdent), JSC::jsNull());
- putDirect(vm, WTFMove(encodingIdent), JSC::jsNull());
+ m_decoder.set(vm, this, JSC::jsNull());
+ m_encoding.set(vm, this, JSC::jsNull());
}
}
}
@@ -125,21 +118,33 @@ JSC::GCClient::IsoSubspace* JSReadableState::subspaceForImpl(JSC::VM& vm)
[](auto& spaces, auto&& space) { spaces.m_subspaceForReadableState = WTFMove(space); });
}
+template<typename Visitor>
+void JSReadableState::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSReadableState* state = jsCast<JSReadableState*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(state, info());
+ Base::visitChildren(state, visitor);
+ visitor.append(state->m_buffer);
+ visitor.append(state->m_pipes);
+ visitor.append(state->m_errored);
+ visitor.append(state->m_defaultEncoding);
+ visitor.append(state->m_awaitDrainWriters);
+ visitor.append(state->m_decoder);
+ visitor.append(state->m_encoding);
+}
+DEFINE_VISIT_CHILDREN(JSReadableState);
+
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStatePrototype, JSReadableStatePrototype::Base);
JSC_DEFINE_CUSTOM_GETTER(jsReadableState_pipesCount, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
- JSObject* thisObject = JSC::jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
- if (!thisObject) {
- RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
- }
- JSC::JSValue pipesVal = thisObject->getDirect(vm, JSC::Identifier::fromString(vm, "pipes"_s));
- if (!pipesVal) {
+ JSReadableState* state = JSC::jsDynamicCast<JSReadableState*>(JSValue::decode(thisValue));
+ if (!state) {
RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
}
- JSArray* pipes = JSC::jsDynamicCast<JSArray*>(pipesVal);
+ JSArray* pipes = JSC::jsDynamicCast<JSArray*>(state->m_pipes.get());
if (!pipes) {
RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined()));
}
@@ -179,7 +184,7 @@ JSReadableState_NULLABLE_BOOLEAN_GETTER_SETTER(flowing)
#undef JSReadableState_NULLABLE_BOOLEAN_GETTER_SETTER
-#define JSReadableState_GETTER_SETTER(NAME, TYPE) \
+#define JSReadableState_NUMBER_GETTER_SETTER(NAME) \
static JSC_DECLARE_CUSTOM_GETTER(jsReadableState_##NAME); \
JSC_DEFINE_CUSTOM_GETTER(jsReadableState_##NAME, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) \
{ \
@@ -189,7 +194,7 @@ JSReadableState_NULLABLE_BOOLEAN_GETTER_SETTER(flowing)
if (!state) { \
RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined())); \
} \
- RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::js##TYPE(state->m_##NAME))); \
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsNumber(state->m_##NAME))); \
} \
\
static JSC_DECLARE_CUSTOM_SETTER(setJSReadableState_##NAME); \
@@ -201,17 +206,43 @@ JSReadableState_NULLABLE_BOOLEAN_GETTER_SETTER(flowing)
if (!state) { \
RETURN_IF_EXCEPTION(throwScope, false); \
} \
- state->m_##NAME = JSC::JSValue::decode(encodedValue).to##TYPE(lexicalGlobalObject); \
+ state->m_##NAME = JSC::JSValue::decode(encodedValue).toNumber(lexicalGlobalObject); \
RETURN_IF_EXCEPTION(throwScope, false); \
RELEASE_AND_RETURN(throwScope, true); \
}
-#define JSReadableState_BOOLEAN_GETTER_SETTER(NAME) \
- JSReadableState_GETTER_SETTER(NAME, Boolean)
+JSReadableState_NUMBER_GETTER_SETTER(length)
+JSReadableState_NUMBER_GETTER_SETTER(highWaterMark)
+
+#undef JSReadableState_NUMBER_GETTER_SETTER
-#define JSReadableState_NUMBER_GETTER_SETTER(NAME) \
- JSReadableState_GETTER_SETTER(NAME, Number)
+#define JSReadableState_BOOLEAN_GETTER_SETTER(NAME) \
+ static JSC_DECLARE_CUSTOM_GETTER(jsReadableState_##NAME); \
+ JSC_DEFINE_CUSTOM_GETTER(jsReadableState_##NAME, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) \
+ { \
+ auto& vm = JSC::getVM(lexicalGlobalObject); \
+ auto throwScope = DECLARE_THROW_SCOPE(vm); \
+ JSReadableState* state = JSC::jsDynamicCast<JSReadableState*>(JSValue::decode(thisValue)); \
+ if (!state) { \
+ RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined())); \
+ } \
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsBoolean(state->getBool(JSReadableState::Mask::NAME)))); \
+ } \
+ \
+ static JSC_DECLARE_CUSTOM_SETTER(setJSReadableState_##NAME); \
+ JSC_DEFINE_CUSTOM_SETTER(setJSReadableState_##NAME, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) \
+ { \
+ auto& vm = JSC::getVM(lexicalGlobalObject); \
+ auto throwScope = DECLARE_THROW_SCOPE(vm); \
+ JSReadableState* state = JSC::jsDynamicCast<JSReadableState*>(JSValue::decode(thisValue)); \
+ if (!state) { \
+ RETURN_IF_EXCEPTION(throwScope, false); \
+ } \
+ state->setBool(JSReadableState::Mask::NAME, JSC::JSValue::decode(encodedValue).toBoolean(lexicalGlobalObject)); \
+ RELEASE_AND_RETURN(throwScope, true); \
+ }
+JSReadableState_BOOLEAN_GETTER_SETTER(objectMode)
JSReadableState_BOOLEAN_GETTER_SETTER(ended)
JSReadableState_BOOLEAN_GETTER_SETTER(endEmitted)
JSReadableState_BOOLEAN_GETTER_SETTER(reading)
@@ -231,12 +262,43 @@ JSReadableState_BOOLEAN_GETTER_SETTER(multiAwaitDrain)
JSReadableState_BOOLEAN_GETTER_SETTER(readingMore)
JSReadableState_BOOLEAN_GETTER_SETTER(dataEmitted)
-JSReadableState_NUMBER_GETTER_SETTER(length)
-JSReadableState_NUMBER_GETTER_SETTER(highWaterMark)
-
-#undef JSReadableState_NUMBER_GETTER_SETTER
#undef JSReadableState_BOOLEAN_GETTER_SETTER
-#undef JSReadableState_GETTER_SETTER
+
+#define JSReadableState_JSVALUE_GETTER_SETTER(NAME) \
+ static JSC_DECLARE_CUSTOM_GETTER(jsReadableState_##NAME); \
+ JSC_DEFINE_CUSTOM_GETTER(jsReadableState_##NAME, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) \
+ { \
+ auto& vm = JSC::getVM(lexicalGlobalObject); \
+ auto throwScope = DECLARE_THROW_SCOPE(vm); \
+ JSReadableState* state = JSC::jsDynamicCast<JSReadableState*>(JSValue::decode(thisValue)); \
+ if (!state) { \
+ RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined())); \
+ } \
+ RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(state->m_##NAME.get())); \
+ } \
+ static JSC_DECLARE_CUSTOM_SETTER(setJSReadableState_##NAME); \
+ JSC_DEFINE_CUSTOM_SETTER(setJSReadableState_##NAME, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, EncodedJSValue encodedValue, PropertyName attributeName)) \
+ { \
+ auto& vm = JSC::getVM(lexicalGlobalObject); \
+ auto throwScope = DECLARE_THROW_SCOPE(vm); \
+ JSReadableState* state = JSC::jsDynamicCast<JSReadableState*>(JSValue::decode(thisValue)); \
+ if (!state) { \
+ RETURN_IF_EXCEPTION(throwScope, false); \
+ } \
+ auto value = JSC::JSValue::decode(encodedValue); \
+ state->m_##NAME.set(vm, state, value); \
+ RELEASE_AND_RETURN(throwScope, true); \
+ }
+
+JSReadableState_JSVALUE_GETTER_SETTER(buffer)
+JSReadableState_JSVALUE_GETTER_SETTER(pipes)
+JSReadableState_JSVALUE_GETTER_SETTER(errored)
+JSReadableState_JSVALUE_GETTER_SETTER(defaultEncoding)
+JSReadableState_JSVALUE_GETTER_SETTER(awaitDrainWriters)
+JSReadableState_JSVALUE_GETTER_SETTER(decoder)
+JSReadableState_JSVALUE_GETTER_SETTER(encoding)
+
+#undef JSReadableState_JSVALUE_GETTER_SETTER
#define JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(NAME) \
{ #NAME ""_s, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, jsReadableState_##NAME, setJSReadableState_##NAME } }
@@ -248,6 +310,7 @@ static const HashTableValue JSReadableStatePrototypeTableValues[]
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(paused),
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(flowing),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(objectMode),
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(ended),
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(endEmitted),
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(reading),
@@ -269,6 +332,14 @@ static const HashTableValue JSReadableStatePrototypeTableValues[]
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(length),
JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(highWaterMark),
+
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(buffer),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(pipes),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(errored),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(defaultEncoding),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(awaitDrainWriters),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(decoder),
+ JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE(encoding),
};
#undef JSReadableState_GETTER_SETTER_HASH_TABLE_VALUE
diff --git a/src/bun.js/bindings/JSReadableState.h b/src/bun.js/bindings/JSReadableState.h
index d06a9dbc3..544d4b63e 100644
--- a/src/bun.js/bindings/JSReadableState.h
+++ b/src/bun.js/bindings/JSReadableState.h
@@ -15,6 +15,7 @@ public:
{
}
+ DECLARE_VISIT_CHILDREN;
DECLARE_INFO;
static constexpr unsigned StructureFlags = Base::StructureFlags;
@@ -45,31 +46,52 @@ public:
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, bool isDuplex, JSObject* options);
static void destroy(JSCell*) {}
+ enum Mask {
+ objectMode = 1 << 0,
+ emitClose = 1 << 1,
+ autoDestroy = 1 << 2,
+ ended = 1 << 3,
+ endEmitted = 1 << 4,
+ reading = 1 << 5,
+ constructed = 1 << 6,
+ sync = 1 << 7,
+ needReadable = 1 << 8,
+ emittedReadable = 1 << 9,
+ readableListening = 1<< 10,
+ resumeScheduled = 1 << 11,
+ errorEmitted = 1 << 12,
+ destroyed = 1 << 13,
+ closed = 1 << 14,
+ closeEmitted = 1 << 15,
+ multiAwaitDrain = 1 << 16,
+ readingMore = 1 << 17,
+ dataEmitted = 1 << 18,
+ };
+
+ bool getBool(Mask mask) { return m_bools & mask; }
+ void setBool(Mask mask, bool val) {
+ if (val)
+ m_bools = m_bools | mask;
+ else
+ m_bools = m_bools & mask;
+ }
+
// 0 for null, 1 for true, -1 for false
int8_t m_paused = 0;
int8_t m_flowing = 0;
- bool m_ended = false;
- bool m_endEmitted = false;
- bool m_reading = false;
- bool m_constructed = true;
- bool m_sync = true;
- bool m_needReadable = false;
- bool m_emittedReadable = false;
- bool m_readableListening = false;
- bool m_resumeScheduled = false;
- bool m_errorEmitted = false;
- // These 2 are initialized from options
- bool m_emitClose;
- bool m_autoDestroy;
- bool m_destroyed = false;
- bool m_closed = false;
- bool m_closeEmitted = false;
- bool m_multiAwaitDrain = false;
- bool m_readingMore = false;
- bool m_dataEmitted = false;
+
+ uint32_t m_bools = Mask::constructed | Mask::sync;
int64_t m_length = 0;
int64_t m_highWaterMark;
+
+ mutable WriteBarrier<Unknown> m_buffer;
+ mutable WriteBarrier<Unknown> m_pipes;
+ mutable WriteBarrier<Unknown> m_errored;
+ mutable WriteBarrier<Unknown> m_defaultEncoding;
+ mutable WriteBarrier<Unknown> m_awaitDrainWriters;
+ mutable WriteBarrier<Unknown> m_decoder;
+ mutable WriteBarrier<Unknown> m_encoding;
};
class JSReadableStatePrototype : public JSC::JSNonFinalObject {
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 012ad2d66..052fc3e7b 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -1050,8 +1050,11 @@ JSC:
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "resume"_s)),
JSC::JSFunction::create(vm, globalObject, 0, "resume"_s, jsReadable_resume, ImplementationVisibility::Public), 0);
obj->putDirect(
- vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "emitReadable_"_s)),
- JSC::JSFunction::create(vm, globalObject, 0, "emitReadable_"_s, jsReadable_emitReadable_, ImplementationVisibility::Public), 0);
+ vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "emitReadable"_s)),
+ JSC::JSFunction::create(vm, globalObject, 0, "emitReadable"_s, jsReadable_emitReadable, ImplementationVisibility::Public), 0);
+ obj->putDirect(
+ vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "onEofChunk"_s)),
+ JSC::JSFunction::create(vm, globalObject, 0, "onEofChunk"_s, jsReadable_onEofChunk, ImplementationVisibility::Public), 0);
return JSValue::encode(obj);
}
@@ -2501,6 +2504,9 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_JSFFIFunctionStructure.visit(visitor);
thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor);
thisObject->m_JSArrayBufferControllerPrototype.visit(visitor);
+ thisObject->m_JSBufferListClassStructure.visit(visitor);
+ thisObject->m_JSStringDecoderClassStructure.visit(visitor);
+ thisObject->m_JSReadableStateClassStructure.visit(visitor);
thisObject->m_lazyReadableStreamPrototypeMap.visit(visitor);
thisObject->m_requireMap.visit(visitor);
thisObject->m_processEnvObject.visit(visitor);
diff --git a/src/bun.js/streams.exports.js b/src/bun.js/streams.exports.js
index 9bb74daf4..2e031a0df 100644
--- a/src/bun.js/streams.exports.js
+++ b/src/bun.js/streams.exports.js
@@ -2488,7 +2488,8 @@ var require_readable = __commonJS({
const {
maybeReadMore,
resume,
- emitReadable_,
+ emitReadable,
+ onEofChunk,
} = globalThis[Symbol.for("Bun.lazy")]("bun:stream");
var destroyImpl = require_destroy();
var {
@@ -2622,7 +2623,7 @@ var require_readable = __commonJS({
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront) state.buffer.unshift(chunk);
else state.buffer.push(chunk);
- if (state.needReadable) emitReadable(stream);
+ if (state.needReadable) emitReadable(stream, state);
}
maybeReadMore(stream, state);
}
@@ -2695,7 +2696,7 @@ var require_readable = __commonJS({
) {
debug("read: emitReadable", state.length, state.ended);
if (state.length === 0 && state.ended) endReadable(this);
- else emitReadable(this);
+ else emitReadable(this, state);
return null;
}
n = howMuchToRead(n, state);
@@ -2755,35 +2756,6 @@ var require_readable = __commonJS({
}
return ret;
};
- function onEofChunk(stream, state) {
- debug("onEofChunk");
- if (state.ended) return;
- if (state.decoder) {
- const chunk = state.decoder.end();
- if (chunk && chunk.length) {
- state.buffer.push(chunk);
- state.length += state.objectMode ? 1 : chunk.length;
- }
- }
- state.ended = true;
- if (state.sync) {
- emitReadable(stream);
- } else {
- state.needReadable = false;
- state.emittedReadable = true;
- emitReadable_(stream, state);
- }
- }
- function emitReadable(stream) {
- const state = stream._readableState;
- debug("emitReadable", state.needReadable, state.emittedReadable);
- state.needReadable = false;
- if (!state.emittedReadable) {
- debug("emitReadable", state.flowing);
- state.emittedReadable = true;
- runOnNextTick(emitReadable_, stream, state);
- }
- }
Readable.prototype._read = function (n) {
throw new ERR_METHOD_NOT_IMPLEMENTED("_read()");
};
@@ -2963,7 +2935,7 @@ var require_readable = __commonJS({
state.emittedReadable = false;
debug("on readable", state.length, state.reading);
if (state.length) {
- emitReadable(this);
+ emitReadable(this, state);
} else if (!state.reading) {
runOnNextTick(nReadingNextTick, this);
}
@@ -3022,11 +2994,6 @@ var require_readable = __commonJS({
this._readableState.paused = true;
return this;
};
- function flow(stream) {
- const state = stream._readableState;
- debug("flow", state.flowing);
- while (state.flowing && stream.read() !== null);
- }
Readable.prototype.wrap = function (stream) {
let paused = false;
stream.on("data", (chunk) => {