diff options
author | 2022-09-10 13:48:55 +0800 | |
---|---|---|
committer | 2022-09-09 22:48:55 -0700 | |
commit | 8b91360a33b782af423c85f9ec7277394e27beb4 (patch) | |
tree | 1c969c98a41e6265695b4f2b4ec73a0e1a71ccad | |
parent | 85d80d8fb7e6f32979b82bdf26c93c30bfea578a (diff) | |
download | bun-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.cpp | 11 | ||||
-rw-r--r-- | src/bun.js/bindings/JSBufferList.h | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/JSReadableHelper.cpp | 186 | ||||
-rw-r--r-- | src/bun.js/bindings/JSReadableHelper.h | 3 | ||||
-rw-r--r-- | src/bun.js/bindings/JSReadableState.cpp | 171 | ||||
-rw-r--r-- | src/bun.js/bindings/JSReadableState.h | 60 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 10 | ||||
-rw-r--r-- | src/bun.js/streams.exports.js | 43 |
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) => { |