diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bun.js/bindings/JSBufferList.cpp | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/JSStringDecoder.cpp | 426 | ||||
-rw-r--r-- | src/bun.js/bindings/JSStringDecoder.h | 129 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 36 | ||||
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.h | 5 | ||||
-rw-r--r-- | src/bun.js/bindings/exports.zig | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/headers-handwritten.h | 1 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h | 2 | ||||
-rw-r--r-- | src/bun.js/bindings/webcore/DOMIsoSubspaces.h | 2 | ||||
-rw-r--r-- | src/bun.js/javascript.zig | 15 | ||||
-rw-r--r-- | src/bun.js/modules/StringDecoderModule.h | 15 | ||||
-rw-r--r-- | src/bun.js/webcore/encoding.zig | 15 |
12 files changed, 639 insertions, 10 deletions
diff --git a/src/bun.js/bindings/JSBufferList.cpp b/src/bun.js/bindings/JSBufferList.cpp index cccb827a9..50e0defe0 100644 --- a/src/bun.js/bindings/JSBufferList.cpp +++ b/src/bun.js/bindings/JSBufferList.cpp @@ -169,7 +169,7 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG RELEASE_AND_RETURN(throwScope, uint8Array); } -const JSC::ClassInfo JSBufferList::s_info = { "JSBufferList"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSBufferList) }; +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) { diff --git a/src/bun.js/bindings/JSStringDecoder.cpp b/src/bun.js/bindings/JSStringDecoder.cpp new file mode 100644 index 000000000..d9af6e49e --- /dev/null +++ b/src/bun.js/bindings/JSStringDecoder.cpp @@ -0,0 +1,426 @@ +#include "JSStringDecoder.h" +#include "JSBuffer.h" +#include "JavaScriptCore/Lookup.h" +#include "JavaScriptCore/ObjectConstructor.h" +#include "ZigGlobalObject.h" +#include "JSDOMOperation.h" +#include "JSDOMAttribute.h" +#include "headers.h" +#include "JSDOMConvertEnumeration.h" + +namespace WebCore { + +using namespace JSC; + +static JSC_DECLARE_HOST_FUNCTION(jsStringDecoderPrototypeFunction_write); +static JSC_DECLARE_HOST_FUNCTION(jsStringDecoderPrototypeFunction_end); +static JSC_DECLARE_HOST_FUNCTION(jsStringDecoderPrototypeFunction_text); + +static JSC_DECLARE_CUSTOM_GETTER(jsStringDecoder_lastChar); +static JSC_DECLARE_CUSTOM_GETTER(jsStringDecoder_lastNeed); +static JSC_DECLARE_CUSTOM_GETTER(jsStringDecoder_lastTotal); + +void JSStringDecoder::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); +} + +JSC::JSValue JSStringDecoder::fillLast(JSC::VM& vm, JSC::JSGlobalObject* globalObject, uint8_t* bufPtr, uint32_t length) +{ + auto throwScope = DECLARE_THROW_SCOPE(vm); + + if (m_encoding == BufferEncodingType::utf8) { + // utf8CheckExtraBytes + if ((bufPtr[0] & 0xC0) != 0x80) { + m_lastNeed = 0; + RELEASE_AND_RETURN(throwScope, JSC::jsString(vm, WTF::String(u"\uFFFD", 1))); + } + if (m_lastNeed > 1 && length > 1) { + if ((bufPtr[1] & 0xC0) != 0x80) { + m_lastNeed = 1; + RELEASE_AND_RETURN(throwScope, JSC::jsString(vm, WTF::String(u"\uFFFD", 1))); + } + if (m_lastNeed > 2 && length > 2) { + if ((bufPtr[2] & 0xC0) != 0x80) { + m_lastNeed = 2; + RELEASE_AND_RETURN(throwScope, JSC::jsString(vm, WTF::String(u"\uFFFD", 1))); + } + } + } + } + + if (m_lastNeed <= length) { + memmove(m_lastChar + m_lastTotal - m_lastNeed, bufPtr, m_lastNeed); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(m_lastChar, m_lastTotal, globalObject, static_cast<uint8_t>(m_encoding)))); + } + memmove(m_lastChar + m_lastTotal - m_lastNeed, bufPtr, length); + m_lastNeed -= length; + RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); +} + +// Checks the type of a UTF-8 byte, whether it's ASCII, a leading byte, or a +// continuation byte. If an invalid byte is detected, -2 is returned. +int8_t utf8CheckByte(uint8_t byte) { + if (byte <= 0x7F) return 0; + else if ((byte >> 5) == 0x06) return 2; + else if ((byte >> 4) == 0x0E) return 3; + else if ((byte >> 3) == 0x1E) return 4; + return (byte >> 6) == 0x02 ? -1 : -2; +} + +// Checks at most 3 bytes at the end of a Buffer in order to detect an +// incomplete multi-byte UTF-8 character. The total number of bytes (2, 3, or 4) +// needed to complete the UTF-8 character (if applicable) are returned. +uint8_t JSStringDecoder::utf8CheckIncomplete(uint8_t* bufPtr, uint32_t length, uint32_t i) +{ + uint32_t j = length - 1; + if (j < i) return 0; + int8_t nb = utf8CheckByte(bufPtr[j]); + if (nb >= 0) { + if (nb > 0) m_lastNeed = nb - 1; + return nb; + } + if (--j < i || nb == -2) return 0; + nb = utf8CheckByte(bufPtr[j]); + if (nb >= 0) { + if (nb > 0) m_lastNeed = nb - 2; + return nb; + } + if (--j < i || nb == -2) return 0; + nb = utf8CheckByte(bufPtr[j]); + if (nb >= 0) { + if (nb > 0) { + if (nb == 2) nb = 0;else m_lastNeed = nb - 3; + } + return nb; + } + return 0; +} + +// This is not the exposed text +JSC::JSValue JSStringDecoder::text(JSC::VM& vm, JSC::JSGlobalObject* globalObject, uint8_t* bufPtr, uint32_t length, uint32_t offset) +{ + auto throwScope = DECLARE_THROW_SCOPE(vm); + + switch (m_encoding) { + case BufferEncodingType::ucs2: + case BufferEncodingType::utf16le: { + if (length == offset) + RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); + if ((length - offset) % 2 == 0) { + UChar c = (static_cast<uint16_t>(bufPtr[length - 1]) << 8) + static_cast<uint16_t>(bufPtr[length - 2]); + if (c >= 0xD800 && c <= 0xDBFF) { + m_lastNeed = 2; + m_lastTotal = 4; + m_lastChar[0] = bufPtr[length - 2]; + m_lastChar[1] = bufPtr[length - 1]; + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, length - offset - 2, globalObject, static_cast<uint8_t>(m_encoding)))); + } + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, length - offset, globalObject, static_cast<uint8_t>(m_encoding)))); + } + m_lastNeed = 1; + m_lastTotal = 2; + m_lastChar[0] = bufPtr[length - 1]; + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, length - offset - 1, globalObject, static_cast<uint8_t>(m_encoding)))); + } + case BufferEncodingType::utf8: { + uint32_t total = utf8CheckIncomplete(bufPtr, length, offset); + if (!m_lastNeed) + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, length - offset, globalObject, static_cast<uint8_t>(m_encoding)))); + m_lastTotal = total; + uint32_t end = length - (total - m_lastNeed); + if (end < length) + memmove(m_lastChar, bufPtr + end, std::min(4U, length - end)); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, end - offset, globalObject, static_cast<uint8_t>(m_encoding)))); + } + case BufferEncodingType::base64: { + uint32_t n = (length - offset) % 3; + if (n == 0) + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, length - offset, globalObject, static_cast<uint8_t>(m_encoding)))); + m_lastNeed = 3 - n; + m_lastTotal = 3; + if (n == 1) { + m_lastChar[0] = bufPtr[length - 1]; + } else { + m_lastChar[0] = bufPtr[length - 2]; + m_lastChar[1] = bufPtr[length - 1]; + } + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr + offset, length - offset - n, globalObject, static_cast<uint8_t>(m_encoding)))); + } + default: { + // should never reach here. + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + } + } + +} + +JSC::JSValue JSStringDecoder::write(JSC::VM& vm, JSC::JSGlobalObject* globalObject, uint8_t* bufPtr, uint32_t length) +{ + auto throwScope = DECLARE_THROW_SCOPE(vm); + if (length == 0) + RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); + + switch (m_encoding) { + case BufferEncodingType::ucs2: + case BufferEncodingType::utf16le: + case BufferEncodingType::utf8: + case BufferEncodingType::base64: { + uint32_t offset = 0; + if (m_lastNeed) { + JSString* firstHalf = fillLast(vm, globalObject, bufPtr, length).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + if (firstHalf->length() == 0) + RELEASE_AND_RETURN(throwScope, firstHalf); + offset = m_lastNeed; + m_lastNeed = 0; + + JSString* secondHalf = text(vm, globalObject, bufPtr, length, offset).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + if (secondHalf->length() == 0) + RELEASE_AND_RETURN(throwScope, firstHalf); + RELEASE_AND_RETURN(throwScope, JSC::jsString(globalObject, firstHalf, secondHalf)); + } + JSString* str = text(vm, globalObject, bufPtr, length, offset).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + RELEASE_AND_RETURN(throwScope, str); + } + default: { + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(bufPtr, length, globalObject, static_cast<uint8_t>(m_encoding)))); + } + } +} + +JSC::JSValue JSStringDecoder::end(JSC::VM& vm, JSC::JSGlobalObject* globalObject, uint8_t* bufPtr, uint32_t length) +{ + auto throwScope = DECLARE_THROW_SCOPE(vm); + switch (m_encoding) { + case BufferEncodingType::ucs2: + case BufferEncodingType::utf16le: { + if (length == 0) { + if (m_lastNeed) { + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(m_lastChar, m_lastTotal - m_lastNeed, globalObject, static_cast<uint8_t>(m_encoding)))); + } else { + RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); + } + } + JSString* firstHalf = write(vm, globalObject, bufPtr, length).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + if (m_lastNeed) { + JSString* secondHalf = JSC::JSValue::decode(Bun__encoding__toString(m_lastChar, m_lastTotal - m_lastNeed, globalObject, static_cast<uint8_t>(m_encoding))).toString(globalObject); + RELEASE_AND_RETURN(throwScope, JSC::jsString(globalObject, firstHalf, secondHalf)); + } else { + RELEASE_AND_RETURN(throwScope, firstHalf); + } + } + case BufferEncodingType::utf8: { + if (length == 0) { + RELEASE_AND_RETURN(throwScope, m_lastNeed ? JSC::jsString(vm, WTF::String(u"\uFFFD", 1)) : JSC::jsEmptyString(vm)); + } + JSString* firstHalf = write(vm, globalObject, bufPtr, length).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + RELEASE_AND_RETURN(throwScope, m_lastNeed ? JSC::jsString(globalObject, firstHalf, WTF::String(u"\uFFFD", 1)) : firstHalf); + } + case BufferEncodingType::base64: { + if (length == 0) { + if (m_lastNeed) { + RELEASE_AND_RETURN(throwScope, JSC::JSValue::decode(Bun__encoding__toString(m_lastChar, 3 - m_lastNeed, globalObject, static_cast<uint8_t>(m_encoding)))); + } else { + RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); + } + } + JSString* firstHalf = write(vm, globalObject, bufPtr, length).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + if (m_lastNeed) { + JSString* secondHalf = JSC::JSValue::decode(Bun__encoding__toString(m_lastChar, 3 - m_lastNeed, globalObject, static_cast<uint8_t>(m_encoding))).toString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::jsUndefined()); + RELEASE_AND_RETURN(throwScope, JSC::jsString(globalObject, firstHalf, secondHalf)); + } else { + RELEASE_AND_RETURN(throwScope, firstHalf); + } + } + default: { + if (length == 0) { + RELEASE_AND_RETURN(throwScope, JSC::jsEmptyString(vm)); + } + RELEASE_AND_RETURN(throwScope, write(vm, globalObject, bufPtr, length)); + } + } +} + +const JSC::ClassInfo JSStringDecoder::s_info = { "StringDecoder"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStringDecoder) }; + +JSC::GCClient::IsoSubspace* JSStringDecoder::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSStringDecoder, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForStringDecoder.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForStringDecoder = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForStringDecoder.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForStringDecoder = WTFMove(space); }); +} + +STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSStringDecoderPrototype, JSStringDecoderPrototype::Base); + +static inline JSC::EncodedJSValue jsStringDecoderPrototypeFunction_writeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSStringDecoder>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + if (callFrame->argumentCount() < 1) { + throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + return JSValue::encode(jsUndefined()); + } + + auto buffer = callFrame->uncheckedArgument(0); + JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(buffer); + if (!view) { + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Uint8Array"_s); + return JSValue::encode(jsUndefined()); + } + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->write(vm, lexicalGlobalObject, view->typedVector(), view->length()))); +} +static inline JSC::EncodedJSValue jsStringDecoderPrototypeFunction_endBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSStringDecoder>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + if (callFrame->argumentCount() < 1) { + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->end(vm, lexicalGlobalObject, nullptr, 0))); + } + + auto buffer = callFrame->uncheckedArgument(0); + JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(buffer); + if (!view) { + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Uint8Array"_s); + return JSValue::encode(jsUndefined()); + } + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->end(vm, lexicalGlobalObject, view->typedVector(), view->length()))); +} +static inline JSC::EncodedJSValue jsStringDecoderPrototypeFunction_textBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSStringDecoder>::ClassParameter castedThis) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + if (callFrame->argumentCount() < 2) { + throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject)); + return JSValue::encode(jsUndefined()); + } + + auto buffer = callFrame->uncheckedArgument(0); + JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(buffer); + if (!view) { + throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Uint8Array"_s); + return JSValue::encode(jsUndefined()); + } + int32_t offset = callFrame->uncheckedArgument(1).toInt32(lexicalGlobalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::jsUndefined())); + if (offset > view->length()) + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(castedThis->write(vm, lexicalGlobalObject, view->typedVector() + offset, view->length() - offset))); +} + +static JSC_DEFINE_HOST_FUNCTION(jsStringDecoderPrototypeFunction_write, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSStringDecoder>::call<jsStringDecoderPrototypeFunction_writeBody>(*globalObject, *callFrame, "write"); +} +static JSC_DEFINE_HOST_FUNCTION(jsStringDecoderPrototypeFunction_end, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSStringDecoder>::call<jsStringDecoderPrototypeFunction_endBody>(*globalObject, *callFrame, "end"); +} +static JSC_DEFINE_HOST_FUNCTION(jsStringDecoderPrototypeFunction_text, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + return IDLOperation<JSStringDecoder>::call<jsStringDecoderPrototypeFunction_textBody>(*globalObject, *callFrame, "text"); +} + +static JSC_DEFINE_CUSTOM_GETTER(jsStringDecoder_lastChar, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSStringDecoder* thisObject = jsCast<JSStringDecoder*>(JSValue::decode(thisValue)); + auto buffer = ArrayBuffer::createFromBytes(thisObject->m_lastChar, 4, nullptr); + JSC::JSUint8Array* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, lexicalGlobalObject->typedArrayStructure(JSC::TypeUint8), WTFMove(buffer), 0, 4); + toBuffer(lexicalGlobalObject, uint8Array); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array)); +} +static JSC_DEFINE_CUSTOM_GETTER(jsStringDecoder_lastNeed, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSStringDecoder* thisObject = jsCast<JSStringDecoder*>(JSValue::decode(thisValue)); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsNumber(thisObject->m_lastNeed))); +} +static JSC_DEFINE_CUSTOM_GETTER(jsStringDecoder_lastTotal, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName)) +{ + auto& vm = JSC::getVM(lexicalGlobalObject); + auto throwScope = DECLARE_THROW_SCOPE(vm); + JSStringDecoder* thisObject = jsCast<JSStringDecoder*>(JSValue::decode(thisValue)); + RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(JSC::jsNumber(thisObject->m_lastTotal))); +} + +/* Hash table for prototype */ +static const HashTableValue JSStringDecoderPrototypeTableValues[] + = { + { "lastChar"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, jsStringDecoder_lastChar, 0 } }, + { "lastNeed"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, jsStringDecoder_lastNeed, 0 } }, + { "lastTotal"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, jsStringDecoder_lastTotal, 0 } }, + { "write"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsStringDecoderPrototypeFunction_write, 1 } }, + { "end"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsStringDecoderPrototypeFunction_end, 1 } }, + { "text"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsStringDecoderPrototypeFunction_text, 2 } }, + }; + +void JSStringDecoderPrototype::finishCreation(VM& vm, JSC::JSGlobalObject* globalThis) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSStringDecoder::info(), JSStringDecoderPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +const ClassInfo JSStringDecoderPrototype::s_info = { "StringDecoder"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStringDecoderPrototype) }; + +void JSStringDecoderConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype) +{ + Base::finishCreation(vm, 0, "StringDecoder"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSStringDecoderConstructor* JSStringDecoderConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSStringDecoderPrototype* prototype) { + JSStringDecoderConstructor* ptr = new (NotNull, JSC::allocateCell<JSStringDecoderConstructor>(vm)) JSStringDecoderConstructor(vm, structure, construct); + ptr->finishCreation(vm, globalObject, prototype); + return ptr; +} + +JSC::EncodedJSValue JSStringDecoderConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + JSC::VM& vm = lexicalGlobalObject->vm(); + auto encoding = BufferEncodingType::utf8; + if (callFrame->argumentCount() > 0) { + auto encoding_ = callFrame->argument(0).toString(lexicalGlobalObject); + std::optional<BufferEncodingType> opt = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, encoding_); + if (opt.has_value()) { + encoding = opt.value(); + } + } + JSStringDecoder* stringDecoder = JSStringDecoder::create( + vm, lexicalGlobalObject, reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->JSStringDecoderStructure(), encoding); + return JSC::JSValue::encode(stringDecoder); +} + +void JSStringDecoderConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype) +{ +} + +JSC::GCClient::IsoSubspace* JSStringDecoderConstructor::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl<JSStringDecoderConstructor, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForStringDecoderConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForStringDecoderConstructor = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForStringDecoderConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForStringDecoderConstructor = WTFMove(space); }); +} + +const ClassInfo JSStringDecoderConstructor::s_info = { "StringDecoder"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSStringDecoderConstructor) }; + +} // namespace Zig diff --git a/src/bun.js/bindings/JSStringDecoder.h b/src/bun.js/bindings/JSStringDecoder.h new file mode 100644 index 000000000..de2c54209 --- /dev/null +++ b/src/bun.js/bindings/JSStringDecoder.h @@ -0,0 +1,129 @@ +#pragma once + +#include "root.h" +#include "BufferEncodingType.h" + +namespace WebCore { +using namespace JSC; + +class JSStringDecoder : public JSC::JSDestructibleObject { + using Base = JSC::JSDestructibleObject; + +public: + JSStringDecoder(JSC::VM& vm, JSC::Structure* structure, BufferEncodingType encoding) + : Base(vm, structure), m_lastNeed(0), m_lastTotal(0), m_encoding(encoding) + { + } + + DECLARE_INFO; + + static constexpr unsigned StructureFlags = Base::StructureFlags; + + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, + JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, + JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + static JSStringDecoder* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, BufferEncodingType encoding) + { + JSStringDecoder* accessor = new (NotNull, JSC::allocateCell<JSStringDecoder>(vm)) JSStringDecoder(vm, structure, encoding); + accessor->finishCreation(vm, globalObject); + return accessor; + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject); + static void destroy(JSCell*) {} + + JSC::JSValue write(JSC::VM&, JSC::JSGlobalObject*, uint8_t*, uint32_t); + JSC::JSValue end(JSC::VM&, JSC::JSGlobalObject*, uint8_t*, uint32_t); + + uint8_t m_lastNeed; + uint8_t m_lastTotal; + uint8_t m_lastChar[4]; + +private: + JSC::JSValue fillLast(JSC::VM&, JSC::JSGlobalObject*, uint8_t*, uint32_t); + JSC::JSValue text(JSC::VM&, JSC::JSGlobalObject*, uint8_t*, uint32_t, uint32_t); + uint8_t utf8CheckIncomplete(uint8_t*, uint32_t, uint32_t); + + BufferEncodingType m_encoding; +}; + +class JSStringDecoderPrototype : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static JSStringDecoderPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSStringDecoderPrototype* ptr = new (NotNull, JSC::allocateCell<JSStringDecoderPrototype>(vm)) JSStringDecoderPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + +private: + JSStringDecoderPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSStringDecoderConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static JSStringDecoderConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSStringDecoderPrototype* prototype); + + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info()); + } + + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + + void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype); + + // Must be defined for each specialization class. + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + DECLARE_EXPORT_INFO; +private: + JSStringDecoderConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction) + : Base(vm, structure, nativeFunction, nativeFunction) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSStringDecoderPrototype* prototype); +}; + +} diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 05b5a3e29..855e212f9 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -87,6 +87,7 @@ #include "JSErrorEvent.h" #include "JSCloseEvent.h" #include "JSFetchHeaders.h" +#include "JSStringDecoder.h" #include "Process.h" @@ -160,6 +161,7 @@ using JSBuffer = WebCore::JSBuffer; #include "../modules/BufferModule.h" #include "../modules/EventsModule.h" #include "../modules/ProcessModule.h" +#include "../modules/StringDecoderModule.h" // #include <iostream> static bool has_loaded_jsc = false; @@ -1986,6 +1988,18 @@ void GlobalObject::finishCreation(VM& vm) init.setConstructor(constructor); }); + m_JSStringDecoderClassStructure.initLater( + [](LazyClassStructure::Initializer& init) { + auto* prototype = JSStringDecoderPrototype::create( + init.vm, init.global, JSStringDecoderPrototype::createStructure(init.vm, init.global, init.global->objectPrototype())); + auto* structure = JSStringDecoder::createStructure(init.vm, init.global, prototype); + auto* constructor = JSStringDecoderConstructor::create( + init.vm, init.global, JSStringDecoderConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), prototype); + init.setPrototype(prototype); + init.setStructure(structure); + init.setConstructor(constructor); + }); + m_JSFFIFunctionStructure.initLater( [](LazyClassStructure::Initializer& init) { init.setStructure(Zig::JSFFIFunction::createStructure(init.vm, init.global, init.global->functionPrototype())); @@ -2661,6 +2675,16 @@ static JSC_DEFINE_HOST_FUNCTION(functionFulfillModuleSync, RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); } + case SyntheticModuleType::StringDecoder: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create( + generateStringDecoderSourceCode, + JSC::SourceOrigin(WTF::URL::fileURLWithFileSystemPath("node:string_decoder"_s)), WTFMove(moduleKey))); + + globalObject->moduleLoader()->provideFetch(globalObject, key, WTFMove(source)); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); + RELEASE_AND_RETURN(scope, JSValue::encode(JSC::jsUndefined())); + } default: { auto provider = Zig::SourceProvider::create(res.result.value); globalObject->moduleLoader()->provideFetch(globalObject, key, JSC::SourceCode(provider)); @@ -2758,6 +2782,18 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderFetch(JSGlobalObject* globalOb scope.release(); return promise; } + case SyntheticModuleType::StringDecoder: { + auto source = JSC::SourceCode( + JSC::SyntheticSourceProvider::create(generateStringDecoderSourceCode, + JSC::SourceOrigin(), WTFMove(moduleKey))); + + auto sourceCode = JSSourceCode::create(vm, WTFMove(source)); + RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope)); + + promise->resolve(globalObject, sourceCode); + scope.release(); + return promise; + } default: { auto provider = Zig::SourceProvider::create(res.result.value); auto jsSourceCode = JSC::JSSourceCode::create(vm, JSC::SourceCode(provider)); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 53935a0ae..88149434c 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -186,6 +186,10 @@ public: JSC::JSValue HTTPSResponseSinkPrototype() { return m_JSHTTPSResponseSinkClassStructure.prototypeInitializedOnMainThread(this); } JSC::JSValue JSReadableHTTPSResponseSinkControllerPrototype() { return m_JSHTTPSResponseControllerPrototype.getInitializedOnMainThread(this); } + JSC::Structure* JSStringDecoderStructure() { return m_JSStringDecoderClassStructure.getInitializedOnMainThread(this); } + JSC::JSObject* JSStringDecoder() { return m_JSStringDecoderClassStructure.constructorInitializedOnMainThread(this); } + JSC::JSValue JSStringDecoderPrototype() { return m_JSStringDecoderClassStructure.prototypeInitializedOnMainThread(this); } + JSC::JSMap* readableStreamNativeMap() { return m_lazyReadableStreamPrototypeMap.getInitializedOnMainThread(this); } JSC::JSMap* requireMap() { return m_requireMap.getInitializedOnMainThread(this); } JSC::JSObject* encodeIntoObjectPrototype() { return m_encodeIntoObjectPrototype.getInitializedOnMainThread(this); } @@ -318,6 +322,7 @@ private: LazyClassStructure m_JSArrayBufferSinkClassStructure; LazyClassStructure m_JSHTTPResponseSinkClassStructure; LazyClassStructure m_JSHTTPSResponseSinkClassStructure; + LazyClassStructure m_JSStringDecoderClassStructure; LazyProperty<JSGlobalObject, JSObject> m_JSArrayBufferControllerPrototype; LazyProperty<JSGlobalObject, JSObject> m_JSHTTPSResponseControllerPrototype; diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 199259a69..2f10e75a4 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -247,6 +247,7 @@ pub const ResolvedSource = extern struct { @"node:buffer" = 1024, @"node:process" = 1025, @"node:events" = 1026, + @"node:string_decoder" = 1027, }; }; diff --git a/src/bun.js/bindings/headers-handwritten.h b/src/bun.js/bindings/headers-handwritten.h index 490242bd4..c9ac33e21 100644 --- a/src/bun.js/bindings/headers-handwritten.h +++ b/src/bun.js/bindings/headers-handwritten.h @@ -181,6 +181,7 @@ enum SyntheticModuleType : uint64_t { Buffer = 1024, Process = 1025, Events = 1026, + StringDecoder = 1027, }; extern "C" ZigErrorCode Zig_ErrorCodeParserError; diff --git a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h index 79be4bf8c..75b7995f0 100644 --- a/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h @@ -26,6 +26,8 @@ public: std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSSinkConstructor; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSSinkController; std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSSink; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForStringDecoder; + std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForStringDecoderConstructor; #include "ZigGeneratedClasses+DOMClientIsoSubspaces.h" /* --- bun --- */ diff --git a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h index 88298f9b5..42fb1d88b 100644 --- a/src/bun.js/bindings/webcore/DOMIsoSubspaces.h +++ b/src/bun.js/bindings/webcore/DOMIsoSubspaces.h @@ -26,6 +26,8 @@ public: std::unique_ptr<IsoSubspace> m_subspaceForJSSinkConstructor; std::unique_ptr<IsoSubspace> m_subspaceForJSSinkController; std::unique_ptr<IsoSubspace> m_subspaceForJSSink; + std::unique_ptr<IsoSubspace> m_subspaceForStringDecoder; + std::unique_ptr<IsoSubspace> m_subspaceForStringDecoderConstructor; #include "ZigGeneratedClasses+DOMIsoSubspaces.h" /*-- BUN --*/ diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 9b7446d9c..c8a336c5e 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -868,6 +868,16 @@ pub const VirtualMachine = struct { .hash = 0, }; }, + .@"node:string_decoder" => { + return ResolvedSource{ + .allocator = null, + .source_code = ZigString.init(""), + .specifier = ZigString.init("node:string_decoder"), + .source_url = ZigString.init("node:string_decoder"), + .hash = 0, + .tag = ResolvedSource.Tag.@"node:string_decoder", + }; + }, .@"bun:ffi" => { return ResolvedSource{ .allocator = null, @@ -2802,6 +2812,7 @@ pub const HardcodedModule = enum { @"node:module", @"node:os", @"node:stream", + @"node:string_decoder", @"node:path", @"node:perf_hooks", @"node:process", @@ -2843,6 +2854,7 @@ pub const HardcodedModule = enum { .{ "node:stream", HardcodedModule.@"node:stream" }, .{ "node:stream/consumer", HardcodedModule.@"node:stream/consumer" }, .{ "node:stream/web", HardcodedModule.@"node:stream/web" }, + .{ "node:string_decoder", HardcodedModule.@"node:string_decoder" }, .{ "node:timers", HardcodedModule.@"node:timers" }, .{ "node:timers/promises", HardcodedModule.@"node:timers/promises" }, .{ "node:url", HardcodedModule.@"node:url" }, @@ -2850,6 +2862,7 @@ pub const HardcodedModule = enum { .{ "path", HardcodedModule.@"node:path" }, .{ "process", HardcodedModule.@"node:process" }, .{ "streams", HardcodedModule.@"node:stream" }, + .{ "string_decoder", HardcodedModule.@"node:string_decoder" }, .{ "undici", HardcodedModule.@"undici" }, .{ "ws", HardcodedModule.@"ws" }, }, @@ -2887,6 +2900,7 @@ pub const HardcodedModule = enum { .{ "node:stream/consumer", "node:stream/consumer" }, .{ "node:stream/web", "node:stream/web" }, .{ "node:stream", "node:stream" }, + .{ "node:string_decoder", "node:string_decoder" }, .{ "node:timers", "node:timers" }, .{ "node:timers/promises", "node:timers/promises" }, .{ "node:url", "node:url" }, @@ -2899,6 +2913,7 @@ pub const HardcodedModule = enum { .{ "stream/consumer", "node:stream/consumer" }, .{ "stream/web", "node:stream/web" }, .{ "stream", "node:stream" }, + .{ "string_decoder", "node:string_decoder" }, .{ "timers", "node:timers" }, .{ "timers/promises", "node:timers/promises" }, .{ "undici", "undici" }, diff --git a/src/bun.js/modules/StringDecoderModule.h b/src/bun.js/modules/StringDecoderModule.h new file mode 100644 index 000000000..9beff4f3b --- /dev/null +++ b/src/bun.js/modules/StringDecoderModule.h @@ -0,0 +1,15 @@ +#include "../bindings/ZigGlobalObject.h" +#include "../bindings/JSStringDecoder.h" +#include "JavaScriptCore/JSGlobalObject.h" + +namespace Zig { + +inline void generateStringDecoderSourceCode(JSC::JSGlobalObject* lexicalGlobalObject, JSC::Identifier moduleKey, Vector<JSC::Identifier, 4>& exportNames, JSC::MarkedArgumentBuffer& exportValues) { + JSC::VM& vm = lexicalGlobalObject->vm(); + GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject); + + exportNames.append(JSC::Identifier::fromString(vm, "StringDecoder"_s)); + exportValues.append(globalObject->JSStringDecoder()); +} + +} diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index de09e4be9..00b89d576 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -819,16 +819,13 @@ pub const Encoder = struct { // For this, we rely on the GC to manage the memory to minimize potential for memory leaks return ZigString.init(input).toValueGC(global); }, - // potentially convert UTF-16 to UTF-8 - JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { - const converted = strings.toUTF16Alloc(allocator, input, false) catch return ZigString.init("Out of memory").toErrorInstance(global); - if (converted) |utf16| { - return ZigString.toExternalU16(utf16.ptr, utf16.len, global); + .ucs2, .utf16le => { + var output = allocator.alloc(u16, len / 2) catch return ZigString.init("Out of memory").toErrorInstance(global); + var i : usize = 0; + while (i < len / 2) : (i += 1) { + output[i] = (@intCast(u16, input[2 * i + 1]) << 8) + @intCast(u16, input[2 * i]); } - - var output = allocator.alloc(u8, input.len) catch return ZigString.init("Out of memory").toErrorInstance(global); - JSC.WTF.copyLCharsFromUCharSource(output.ptr, []align(1) const u16, @ptrCast([*]align(1) const u16, input.ptr)[0 .. input.len / 2]); - return ZigString.init(output).toExternalValue(global); + return ZigString.toExternalU16(output.ptr, output.len, global); }, JSC.Node.Encoding.hex => { |