diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b7fb61eb8b4c..c2319dfe338fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_policy(SET CMP0091 NEW) cmake_policy(SET CMP0067 NEW) set(Bun_VERSION "1.1.6") -set(WEBKIT_TAG 5fe78270f2efca3c0ee3013259776923053d5011) +set(WEBKIT_TAG f0fbde91399d504266064d8845628bf29a9e834d) set(BUN_WORKDIR "${CMAKE_CURRENT_BINARY_DIR}") message(STATUS "Configuring Bun ${Bun_VERSION} in ${BUN_WORKDIR}") diff --git a/src/bun.js/bindings/BunObject.cpp b/src/bun.js/bindings/BunObject.cpp index afb215e92c042..87f3c0d784c13 100644 --- a/src/bun.js/bindings/BunObject.cpp +++ b/src/bun.js/bindings/BunObject.cpp @@ -355,25 +355,6 @@ JSC_DEFINE_HOST_FUNCTION(functionBunSleep, extern "C" JSC::EncodedJSValue Bun__escapeHTML8(JSGlobalObject* globalObject, JSC::EncodedJSValue input, const LChar* ptr, size_t length); extern "C" JSC::EncodedJSValue Bun__escapeHTML16(JSGlobalObject* globalObject, JSC::EncodedJSValue input, const UChar* ptr, size_t length); -// JSC_DEFINE_JIT_OPERATION(functionBunEscapeHTMLWithoutTypeCheck, JSC::EncodedJSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSObject* castedglobalObject, JSString* string)) -// { -// JSC::VM& vm = JSC::getVM(lexicalGlobalObject); -// IGNORE_WARNINGS_BEGIN("frame-address") -// CallFrame* callFrame = DECLARE_CALL_FRAME(vm); -// IGNORE_WARNINGS_END -// JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); -// size_t length = string->length(); -// if (!length) -// return JSValue::encode(string); - -// auto resolvedString = string->value(lexicalGlobalObject); -// if (!resolvedString.is8Bit()) { -// return Bun__escapeHTML16(lexicalGlobalObject, JSValue::encode(string), resolvedString.characters16(), length); -// } else { -// return Bun__escapeHTML8(lexicalGlobalObject, JSValue::encode(string), resolvedString.characters8(), length); -// } -// } - JSC_DEFINE_HOST_FUNCTION(functionBunEscapeHTML, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = JSC::getVM(lexicalGlobalObject); @@ -393,9 +374,11 @@ JSC_DEFINE_HOST_FUNCTION(functionBunEscapeHTML, (JSC::JSGlobalObject * lexicalGl auto resolvedString = string->value(lexicalGlobalObject); JSC::EncodedJSValue encodedInput = JSValue::encode(string); if (!resolvedString.is8Bit()) { - RELEASE_AND_RETURN(scope, Bun__escapeHTML16(lexicalGlobalObject, encodedInput, resolvedString.characters16(), length)); + const auto span = resolvedString.span16(); + RELEASE_AND_RETURN(scope, Bun__escapeHTML16(lexicalGlobalObject, encodedInput, span.data(), span.size())); } else { - RELEASE_AND_RETURN(scope, Bun__escapeHTML8(lexicalGlobalObject, encodedInput, resolvedString.characters8(), length)); + const auto span = resolvedString.span8(); + RELEASE_AND_RETURN(scope, Bun__escapeHTML8(lexicalGlobalObject, encodedInput, span.data(), span.size())); } } diff --git a/src/bun.js/bindings/BunString.cpp b/src/bun.js/bindings/BunString.cpp index 7e10ff1ef6c9e..202fbf937d133 100644 --- a/src/bun.js/bindings/BunString.cpp +++ b/src/bun.js/bindings/BunString.cpp @@ -491,9 +491,11 @@ size_t BunString::utf8ByteLength(const WTF::String& str) return 0; if (str.is8Bit()) { - return simdutf::utf8_length_from_latin1(reinterpret_cast(str.characters8()), static_cast(str.length())); + const auto s = str.span8(); + return simdutf::utf8_length_from_latin1(reinterpret_cast(s.data()), static_cast(s.size())); } else { - return simdutf::utf8_length_from_utf16(reinterpret_cast(str.characters16()), static_cast(str.length())); + const auto s = str.span16(); + return simdutf::utf8_length_from_utf16(reinterpret_cast(s.data()), static_cast(s.size())); } } @@ -561,10 +563,6 @@ extern "C" bool WTFStringImpl__isThreadSafe( return false; return !(wtf->isSymbol() || wtf->isAtom()); - // if (wtf->is8Bit()) - // return wtf->characters8() == reinterpret_cast_ptr(reinterpret_cast(wtf) + tailOffset()); - - // return wtf->characters16() == reinterpret_cast_ptr(reinterpret_cast(wtf) + tailOffset()); } extern "C" void Bun__WTFStringImpl__ensureHash(WTF::StringImpl* str) diff --git a/src/bun.js/bindings/CallSite.cpp b/src/bun.js/bindings/CallSite.cpp index d79f7914dd009..018f05a479ed1 100644 --- a/src/bun.js/bindings/CallSite.cpp +++ b/src/bun.js/bindings/CallSite.cpp @@ -83,6 +83,22 @@ void CallSite::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.append(thisCallSite->m_functionName); visitor.append(thisCallSite->m_sourceURL); } +JSC_DEFINE_HOST_FUNCTION(nativeFrameForTesting, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSC::JSFunction* function = jsCast(callFrame->argument(0)); + + return JSValue::encode( + JSC::call(globalObject, function, JSC::ArgList(), "nativeFrameForTesting"_s)); +} + +JSValue createNativeFrameForTesting(Zig::GlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + + return JSC::JSFunction::create(vm, globalObject, 1, "nativeFrameForTesting"_s, nativeFrameForTesting, ImplementationVisibility::Public); +} void CallSite::formatAsString(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WTF::StringBuilder& sb) { diff --git a/src/bun.js/bindings/CallSite.h b/src/bun.js/bindings/CallSite.h index 574df948aeee9..765f961131bac 100644 --- a/src/bun.js/bindings/CallSite.h +++ b/src/bun.js/bindings/CallSite.h @@ -96,4 +96,5 @@ class CallSite final : public JSC::JSNonFinalObject { DECLARE_VISIT_CHILDREN; }; +JSValue createNativeFrameForTesting(Zig::GlobalObject* globalObject); } diff --git a/src/bun.js/bindings/ErrorStackTrace.cpp b/src/bun.js/bindings/ErrorStackTrace.cpp index 2dcddc4ec8bac..8af50c11260e5 100644 --- a/src/bun.js/bindings/ErrorStackTrace.cpp +++ b/src/bun.js/bindings/ErrorStackTrace.cpp @@ -37,6 +37,37 @@ JSCStackTrace JSCStackTrace::fromExisting(JSC::VM& vm, const WTF::Vector ImplementationVisibility { + if (auto* codeBlock = visitor->codeBlock()) { + if (auto* executable = codeBlock->ownerExecutable()) { + return executable->implementationVisibility(); + } + return ImplementationVisibility::Public; + } + +#if ENABLE(WEBASSEMBLY) + if (visitor->isNativeCalleeFrame()) + return visitor->callee().asNativeCallee()->implementationVisibility(); +#endif + + if (visitor->callee().isCell()) { + if (auto* callee = visitor->callee().asCell()) { + if (auto* jsFunction = jsDynamicCast(callee)) { + if (auto* executable = jsFunction->executable()) + return executable->implementationVisibility(); + return ImplementationVisibility::Public; + } + } + } + + return ImplementationVisibility::Public; + }(); + + return implementationVisibility != ImplementationVisibility::Public; +} + JSCStackTrace JSCStackTrace::captureCurrentJSStackTrace(Zig::GlobalObject* globalObject, JSC::CallFrame* callFrame, size_t frameLimit, JSC::JSValue caller) { JSC::VM& vm = globalObject->vm(); @@ -63,23 +94,64 @@ JSCStackTrace JSCStackTrace::captureCurrentJSStackTrace(Zig::GlobalObject* globa callerName = callerFunctionInternal->name(); } - JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus { - // skip caller frame and all frames above it - if (!callerName.isEmpty()) { + if (!callerName.isEmpty()) { + JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus { + if (isImplementationVisibilityPrivate(visitor)) { + return WTF::IterationStatus::Continue; + } + + framesCount += 1; + + // skip caller frame and all frames above it if (!belowCaller) { + skipFrames += 1; + if (visitor->functionName() == callerName) { belowCaller = true; return WTF::IterationStatus::Continue; } + } + + return WTF::IterationStatus::Continue; + }); + } else if (caller && caller.isCell()) { + JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus { + if (isImplementationVisibilityPrivate(visitor)) { + return WTF::IterationStatus::Continue; + } + + framesCount += 1; + + // skip caller frame and all frames above it + if (!belowCaller) { + auto callee = visitor->callee(); skipFrames += 1; + if (callee.isCell() && callee.asCell() == caller) { + belowCaller = true; + return WTF::IterationStatus::Continue; + } } - } - if (!visitor->isNativeFrame()) { - framesCount++; - } - return WTF::IterationStatus::Continue; - }); + return WTF::IterationStatus::Continue; + }); + } else if (caller.isEmpty() || caller.isUndefined()) { + // Skip the first frame. + JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus { + if (isImplementationVisibilityPrivate(visitor)) { + return WTF::IterationStatus::Continue; + } + + framesCount += 1; + + if (!belowCaller) { + skipFrames += 1; + belowCaller = true; + } + + return WTF::IterationStatus::Continue; + }); + } + framesCount = std::min(frameLimit, framesCount); // Create the actual stack frames @@ -87,7 +159,7 @@ JSCStackTrace JSCStackTrace::captureCurrentJSStackTrace(Zig::GlobalObject* globa stackFrames.reserveInitialCapacity(framesCount); JSC::StackVisitor::visit(callFrame, vm, [&](JSC::StackVisitor& visitor) -> WTF::IterationStatus { // Skip native frames - if (visitor->isNativeFrame()) { + if (isImplementationVisibilityPrivate(visitor)) { return WTF::IterationStatus::Continue; } diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 58e449eaa85a6..d334ac5868773 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -257,9 +257,11 @@ static inline JSC::EncodedJSValue writeToBuffer(JSC::JSGlobalObject* lexicalGlob case WebCore::BufferEncodingType::hex: { if (view.is8Bit()) { - written = Bun__encoding__writeLatin1(view.characters8(), view.length(), reinterpret_cast(castedThis->vector()) + offset, length, static_cast(encoding)); + const auto span = view.span8(); + written = Bun__encoding__writeLatin1(span.data(), span.size(), reinterpret_cast(castedThis->vector()) + offset, length, static_cast(encoding)); } else { - written = Bun__encoding__writeUTF16(view.characters16(), view.length(), reinterpret_cast(castedThis->vector()) + offset, length, static_cast(encoding)); + const auto span = view.span16(); + written = Bun__encoding__writeUTF16(span.data(), span.size(), reinterpret_cast(castedThis->vector()) + offset, length, static_cast(encoding)); } break; } @@ -373,6 +375,8 @@ static JSC::EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalOb JSC::EncodedJSValue result; if (view.is8Bit()) { + const auto span = view.span8(); + switch (encoding) { case WebCore::BufferEncodingType::utf8: case WebCore::BufferEncodingType::ucs2: @@ -380,12 +384,13 @@ static JSC::EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalOb case WebCore::BufferEncodingType::base64: case WebCore::BufferEncodingType::base64url: case WebCore::BufferEncodingType::hex: { - result = Bun__encoding__constructFromLatin1(lexicalGlobalObject, view.characters8(), view.length(), static_cast(encoding)); + + result = Bun__encoding__constructFromLatin1(lexicalGlobalObject, span.data(), span.size(), static_cast(encoding)); break; } case WebCore::BufferEncodingType::ascii: // ascii is a noop for latin1 case WebCore::BufferEncodingType::latin1: { // The native encoding is latin1, so we don't need to do any conversion. - result = JSValue::encode(createBuffer(lexicalGlobalObject, view.characters8(), view.length())); + result = JSValue::encode(createBuffer(lexicalGlobalObject, span.data(), span.size())); break; } default: { @@ -394,6 +399,7 @@ static JSC::EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalOb } } } else { + const auto span = view.span16(); switch (encoding) { case WebCore::BufferEncodingType::utf8: case WebCore::BufferEncodingType::base64: @@ -401,14 +407,14 @@ static JSC::EncodedJSValue constructFromEncoding(JSGlobalObject* lexicalGlobalOb case WebCore::BufferEncodingType::hex: case WebCore::BufferEncodingType::ascii: case WebCore::BufferEncodingType::latin1: { - result = Bun__encoding__constructFromUTF16(lexicalGlobalObject, view.characters16(), view.length(), static_cast(encoding)); + result = Bun__encoding__constructFromUTF16(lexicalGlobalObject, span.data(), span.size(), static_cast(encoding)); break; } case WebCore::BufferEncodingType::ucs2: case WebCore::BufferEncodingType::utf16le: { // The native encoding is UTF-16 // so we don't need to do any conversion. - result = JSValue::encode(createBuffer(lexicalGlobalObject, reinterpret_cast(view.characters16()), view.length() * 2)); + result = JSValue::encode(createBuffer(lexicalGlobalObject, reinterpret_cast(span.data()), span.size() * 2)); break; } default: { @@ -600,17 +606,19 @@ static inline JSC::EncodedJSValue jsBufferByteLengthFromStringAndEncoding(JSC::J auto view = str->tryGetValue(lexicalGlobalObject); if (view.is8Bit()) { - if (view.characters8()[length - 1] == 0x3D) { + const auto span = view.span8(); + if (span.data()[length - 1] == 0x3D) { length--; - if (length > 1 && view.characters8()[length - 1] == '=') + if (length > 1 && span.data()[length - 1] == '=') length--; } } else { - if (view.characters16()[length - 1] == 0x3D) { + const auto span = view.span16(); + if (span.data()[length - 1] == 0x3D) { length--; - if (length > 1 && view.characters16()[length - 1] == '=') + if (length > 1 && span.data()[length - 1] == '=') length--; } } @@ -626,9 +634,11 @@ static inline JSC::EncodedJSValue jsBufferByteLengthFromStringAndEncoding(JSC::J case WebCore::BufferEncodingType::utf8: { auto view = str->tryGetValue(lexicalGlobalObject); if (view.is8Bit()) { - written = Bun__encoding__byteLengthLatin1(view.characters8(), view.length(), static_cast(encoding)); + const auto span = view.span8(); + written = Bun__encoding__byteLengthLatin1(span.data(), span.size(), static_cast(encoding)); } else { - written = Bun__encoding__byteLengthUTF16(view.characters16(), view.length(), static_cast(encoding)); + const auto span = view.span16(); + written = Bun__encoding__byteLengthUTF16(span.data(), span.size(), static_cast(encoding)); } break; } diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index a39efe445fed1..a49e17adcd144 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -222,7 +222,7 @@ static EncodedJSValue assignHeadersFromUWebSockets(uWS::HttpRequest* request, JS nameString = WTF::httpHeaderNameStringImpl(name); lowercasedNameString = nameString; } else { - nameString = String({ nameView.characters8(), nameView.length() }); + nameString = nameView.toString(); lowercasedNameString = nameString.convertToASCIILowercase(); } diff --git a/src/bun.js/bindings/NodeURL.cpp b/src/bun.js/bindings/NodeURL.cpp index 0977ea8a6ddac..a5fcdbc33ed5e 100644 --- a/src/bun.js/bindings/NodeURL.cpp +++ b/src/bun.js/bindings/NodeURL.cpp @@ -62,7 +62,8 @@ JSC_DEFINE_HOST_FUNCTION(jsDomainToASCII, (JSC::JSGlobalObject * globalObject, J UChar hostnameBuffer[hostnameBufferLength]; UErrorCode error = U_ZERO_ERROR; UIDNAInfo processingDetails = UIDNA_INFO_INITIALIZER; - int32_t numCharactersConverted = uidna_nameToASCII(encoder, StringView(domain).characters16(), domain.length(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error); + const auto span = domain.span16(); + int32_t numCharactersConverted = uidna_nameToASCII(encoder, span.data(), span.size(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error); if (U_SUCCESS(error) && !(processingDetails.errors & ~allowedNameToASCIIErrors) && numCharactersConverted) { return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(std::span { hostnameBuffer, static_cast(numCharactersConverted) }))); @@ -131,7 +132,9 @@ JSC_DEFINE_HOST_FUNCTION(jsDomainToUnicode, (JSC::JSGlobalObject * globalObject, UErrorCode error = U_ZERO_ERROR; UIDNAInfo processingDetails = UIDNA_INFO_INITIALIZER; - int32_t numCharactersConverted = uidna_nameToUnicode(encoder, StringView(domain).characters16(), domain.length(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error); + const auto span = domain.span16(); + + int32_t numCharactersConverted = uidna_nameToUnicode(encoder, span.data(), span.size(), hostnameBuffer, hostnameBufferLength, &processingDetails, &error); if (U_SUCCESS(error) && !(processingDetails.errors & ~allowedNameToUnicodeErrors) && numCharactersConverted) { return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(std::span { hostnameBuffer, static_cast(numCharactersConverted) }))); diff --git a/src/bun.js/bindings/PathInlines.h b/src/bun.js/bindings/PathInlines.h index 26c0314f9df43..1bedaa091b758 100644 --- a/src/bun.js/bindings/PathInlines.h +++ b/src/bun.js/bindings/PathInlines.h @@ -20,7 +20,7 @@ ALWAYS_INLINE bool isAbsolutePath(WTF::String input) auto len = input.length(); if (len < 1) return false; - auto bytes = input.characters8(); + const auto bytes = input.span8().data(); if (bytes[0] == '/' || bytes[0] == '\\') return true; if (len < 2) @@ -32,7 +32,7 @@ ALWAYS_INLINE bool isAbsolutePath(WTF::String input) auto len = input.length(); if (len < 1) return false; - auto bytes = input.characters16(); + const auto bytes = input.span16().data(); if (bytes[0] == '/' || bytes[0] == '\\') return true; if (len < 2) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 8b32547f8f8b4..b539478ceeeb2 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1493,7 +1493,7 @@ JSC_DEFINE_HOST_FUNCTION(functionBTOA, LChar* ptr; unsigned length = encodedString.length(); auto dest = WTF::String::createUninitialized(length, ptr); - WTF::StringImpl::copyCharacters(ptr, { encodedString.characters16(), length }); + WTF::StringImpl::copyCharacters(ptr, encodedString.span16()); encodedString = WTFMove(dest); } @@ -1501,7 +1501,7 @@ JSC_DEFINE_HOST_FUNCTION(functionBTOA, RELEASE_AND_RETURN( throwScope, Bun__encoding__toString( - encodedString.characters8(), + encodedString.span8().data(), length, globalObject, static_cast(WebCore::BufferEncodingType::base64))); diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index ae92acc6ab941..7a8444305e791 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -116,30 +116,65 @@ static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsi return sv.substring(start, end - start); } +template +static void writeResponseHeader(UWSResponse* res, const WTF::StringView& name, const WTF::StringView& value) +{ + WTF::CString nameStr; + WTF::CString valueStr; + + std::string_view nameView; + std::string_view valueView; + + if (name.is8Bit()) { + const auto nameSpan = name.span8(); + nameView = std::string_view(reinterpret_cast(nameSpan.data()), nameSpan.size()); + } else { + nameStr = name.utf8(); + nameView = std::string_view(nameStr.data(), nameStr.length()); + } + + if (value.is8Bit()) { + const auto valueSpan = value.span8(); + valueView = std::string_view(reinterpret_cast(valueSpan.data()), valueSpan.size()); + } else { + valueStr = value.utf8(); + valueView = std::string_view(valueStr.data(), valueStr.length()); + } + + res->writeHeader(nameView, valueView); +} + template static void copyToUWS(WebCore::FetchHeaders* headers, UWSResponse* res) { auto& internalHeaders = headers->internalHeaders(); for (auto& value : internalHeaders.getSetCookieHeaders()) { - res->writeHeader(std::string_view("set-cookie", 10), std::string_view(value.is8Bit() ? reinterpret_cast(value.characters8()) : value.utf8().data(), value.length())); + + if (value.is8Bit()) { + const auto valueSpan = value.span8(); + res->writeHeader(std::string_view("set-cookie", 10), std::string_view(reinterpret_cast(valueSpan.data()), valueSpan.size())); + } else { + WTF::CString valueStr = value.utf8(); + res->writeHeader(std::string_view("set-cookie", 10), std::string_view(valueStr.data(), valueStr.length())); + } } for (const auto& header : internalHeaders.commonHeaders()) { const auto& name = WebCore::httpHeaderNameString(header.key); const auto& value = header.value; - res->writeHeader( - std::string_view(name.is8Bit() ? reinterpret_cast(name.characters8()) : name.utf8().data(), name.length()), - std::string_view(value.is8Bit() ? reinterpret_cast(value.characters8()) : value.utf8().data(), value.length())); + writeResponseHeader(res, name, value); } for (auto& header : internalHeaders.uncommonHeaders()) { const auto& name = header.key; const auto& value = header.value; - res->writeHeader( - std::string_view(name.is8Bit() ? reinterpret_cast(name.characters8()) : name.utf8().data(), name.length()), - std::string_view(value.is8Bit() ? reinterpret_cast(value.characters8()) : value.utf8().data(), value.length())); + + WTF::CString nameStr; + WTF::CString valueStr; + + writeResponseHeader(res, name, value); } } @@ -1437,18 +1472,21 @@ void WebCore__FetchHeaders__copyTo(WebCore__FetchHeaders* headers, StringPointer auto value = pair->value; names[count] = { i, name.length() }; - if (name.is8Bit()) - memcpy(&buf[i], name.characters8(), name.length()); - else { - StringImpl::copyCharacters(&buf[i], { name.characters16(), name.length() }); + if (name.is8Bit()) { + const auto nameSpan = name.span8(); + memcpy(&buf[i], nameSpan.data(), nameSpan.size()); + } else { + StringImpl::copyCharacters(&buf[i], name.span16()); } i += name.length(); values[count++] = { i, value.length() }; - if (value.is8Bit()) - memcpy(&buf[i], value.characters8(), value.length()); - else - StringImpl::copyCharacters(&buf[i], { value.characters16(), value.length() }); + if (value.is8Bit()) { + const auto nameSpan = value.span8(); + memcpy(&buf[i], nameSpan.data(), nameSpan.size()); + } else { + StringImpl::copyCharacters(&buf[i], value.span16()); + } i += value.length(); } @@ -2781,9 +2819,9 @@ void JSC__JSValue__toZigString(JSC__JSValue JSValue0, ZigString* arg1, JSC__JSGl auto str = strValue->value(arg2); if (str.is8Bit()) { - arg1->ptr = str.characters8(); + arg1->ptr = str.span8().data(); } else { - arg1->ptr = Zig::taggedUTF16Ptr(str.characters16()); + arg1->ptr = Zig::taggedUTF16Ptr(str.span16().data()); } arg1->len = str.length(); @@ -2923,9 +2961,14 @@ void JSC__JSPromise__rejectWithCaughtException(JSC__JSPromise* arg0, JSC__JSGlob void JSC__JSPromise__resolve(JSC__JSPromise* arg0, JSC__JSGlobalObject* arg1, JSC__JSValue JSValue2) { + JSValue target = JSValue::decode(JSValue2); + ASSERT_WITH_MESSAGE(arg0->inherits(), "Argument is not a promise"); ASSERT_WITH_MESSAGE(arg0->status(arg0->vm()) == JSC::JSPromise::Status::Pending, "Promise is already resolved or rejected"); - ASSERT_WITH_MESSAGE(!JSValue::decode(JSValue2).inherits(), "Called .resolve() with another Promise. Did you mean to do that?"); + ASSERT(!target.isEmpty()); + ASSERT_WITH_MESSAGE(arg0 != target, "Promise cannot be resoled to itself"); + + // Note: the Promise can be another promise. Since we go through the generic promise resolve codepath. arg0->resolve(arg1, JSC::JSValue::decode(JSValue2)); } @@ -3941,8 +3984,8 @@ static void populateStackFramePosition(const JSC::StackFrame* stackFrame, BunStr while ((lineStop < sourceLength) && ('\n' != sourceString[lineStop])) { lineStop++; } - if (source_lines_count > 1 && source_lines != nullptr) { - auto chars = sourceString.characters8(); + if (source_lines_count > 1 && source_lines != nullptr && sourceString.is8Bit()) { + auto chars = sourceString.span8().data(); // Most of the time, when you look at a stack trace, you want a couple lines above diff --git a/src/bun.js/bindings/helpers.h b/src/bun.js/bindings/helpers.h index b6f4b34ec9e69..34d2be0430806 100644 --- a/src/bun.js/bindings/helpers.h +++ b/src/bun.js/bindings/helpers.h @@ -261,7 +261,7 @@ static ZigString toZigString(WTF::String* str) { return str->isEmpty() ? ZigStringEmpty - : ZigString { str->is8Bit() ? str->characters8() : taggedUTF16Ptr(str->characters16()), + : ZigString { str->is8Bit() ? str->span8().data() : taggedUTF16Ptr(str->span16().data()), str->length() }; } @@ -269,7 +269,7 @@ static ZigString toZigString(WTF::StringImpl& str) { return str.isEmpty() ? ZigStringEmpty - : ZigString { str.is8Bit() ? str.characters8() : taggedUTF16Ptr(str.characters16()), + : ZigString { str.is8Bit() ? str.span8().data() : taggedUTF16Ptr(str.span16().data()), str.length() }; } @@ -277,7 +277,7 @@ static ZigString toZigString(WTF::StringView& str) { return str.isEmpty() ? ZigStringEmpty - : ZigString { str.is8Bit() ? str.characters8() : taggedUTF16Ptr(str.characters16()), + : ZigString { str.is8Bit() ? str.span8().data() : taggedUTF16Ptr(str.span16().data()), str.length() }; } @@ -285,7 +285,7 @@ static ZigString toZigString(const WTF::StringView& str) { return str.isEmpty() ? ZigStringEmpty - : ZigString { str.is8Bit() ? str.characters8() : taggedUTF16Ptr(str.characters16()), + : ZigString { str.is8Bit() ? str.span8().data() : taggedUTF16Ptr(str.span16().data()), str.length() }; } diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index ec1007c86d843..d240fe7535454 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -2019,9 +2019,10 @@ extern "C" napi_status napi_get_value_string_utf8(napi_env env, if (buf == nullptr) { if (writtenPtr != nullptr) { if (view.is8Bit()) { - *writtenPtr = Bun__encoding__byteLengthLatin1(view.characters8(), length, static_cast(WebCore::BufferEncodingType::utf8)); + + *writtenPtr = Bun__encoding__byteLengthLatin1(view.span8().data(), length, static_cast(WebCore::BufferEncodingType::utf8)); } else { - *writtenPtr = Bun__encoding__byteLengthUTF16(view.characters16(), length, static_cast(WebCore::BufferEncodingType::utf8)); + *writtenPtr = Bun__encoding__byteLengthUTF16(view.span16().data(), length, static_cast(WebCore::BufferEncodingType::utf8)); } } @@ -2041,9 +2042,9 @@ extern "C" napi_status napi_get_value_string_utf8(napi_env env, size_t written; if (view.is8Bit()) { - written = Bun__encoding__writeLatin1(view.characters8(), view.length(), reinterpret_cast(buf), bufsize - 1, static_cast(WebCore::BufferEncodingType::utf8)); + written = Bun__encoding__writeLatin1(view.span8().data(), view.length(), reinterpret_cast(buf), bufsize - 1, static_cast(WebCore::BufferEncodingType::utf8)); } else { - written = Bun__encoding__writeUTF16(view.characters16(), view.length(), reinterpret_cast(buf), bufsize - 1, static_cast(WebCore::BufferEncodingType::utf8)); + written = Bun__encoding__writeUTF16(view.span16().data(), view.length(), reinterpret_cast(buf), bufsize - 1, static_cast(WebCore::BufferEncodingType::utf8)); } if (writtenPtr != nullptr) { diff --git a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp index 6c8912fdc7c2c..25230b21978b7 100644 --- a/src/bun.js/bindings/sqlite/JSSQLStatement.cpp +++ b/src/bun.js/bindings/sqlite/JSSQLStatement.cpp @@ -509,8 +509,7 @@ static void initializeColumnNames(JSC::JSGlobalObject* lexicalGlobalObject, JSSQ // We can't have two properties with the same name, so we use validColumns to track this. auto preCount = columnNames->size(); columnNames->add( - Identifier::fromString(vm, WTF::String::fromUTF8({name, len})) - ); + Identifier::fromString(vm, WTF::String::fromUTF8({ name, len }))); auto curCount = columnNames->size(); if (preCount != curCount) { @@ -651,9 +650,9 @@ static inline bool rebindValue(JSC::JSGlobalObject* lexicalGlobalObject, sqlite3 } if (roped.is8Bit() && roped.containsOnlyASCII()) { - CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast(roped.characters8()), roped.length(), transientOrStatic)); + CHECK_BIND(sqlite3_bind_text(stmt, i, reinterpret_cast(roped.span8().data()), roped.length(), transientOrStatic)); } else if (!roped.is8Bit()) { - CHECK_BIND(sqlite3_bind_text16(stmt, i, roped.characters16(), roped.length() * 2, transientOrStatic)); + CHECK_BIND(sqlite3_bind_text16(stmt, i, roped.span16().data(), roped.length() * 2, transientOrStatic)); } else { auto utf8 = roped.utf8(); CHECK_BIND(sqlite3_bind_text(stmt, i, utf8.data(), utf8.length(), SQLITE_TRANSIENT)); @@ -1046,9 +1045,9 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementExecuteFunction, (JSC::JSGlobalObject * l if ( // fast path: ascii latin1 string is utf8 - sqlString.is8Bit() && simdutf::validate_ascii(reinterpret_cast(sqlString.characters8()), sqlString.length())) { + sqlString.is8Bit() && simdutf::validate_ascii(reinterpret_cast(sqlString.span8().data()), sqlString.length())) { - sqlStringHead = reinterpret_cast(sqlString.characters8()); + sqlStringHead = reinterpret_cast(sqlString.span8().data()); end = sqlStringHead + sqlString.length(); } else { // slow path: utf16 or latin1 string with supplemental characters @@ -1223,8 +1222,8 @@ JSC_DEFINE_HOST_FUNCTION(jsSQLStatementPrepareStatementFunction, (JSC::JSGlobalO int rc = SQLITE_OK; if ( // fast path: ascii latin1 string is utf8 - sqlString.is8Bit() && simdutf::validate_ascii(reinterpret_cast(sqlString.characters8()), sqlString.length())) { - rc = sqlite3_prepare_v3(db, reinterpret_cast(sqlString.characters8()), sqlString.length(), flags, &statement, nullptr); + sqlString.is8Bit() && simdutf::validate_ascii(reinterpret_cast(sqlString.span8().data()), sqlString.length())) { + rc = sqlite3_prepare_v3(db, reinterpret_cast(sqlString.span8().data()), sqlString.length(), flags, &statement, nullptr); } else { // slow path: utf16 or latin1 string with supplemental characters CString utf8 = sqlString.utf8(); diff --git a/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp b/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp index 5021c699d83f6..c1a44ecb23674 100644 --- a/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp +++ b/src/bun.js/bindings/webcore/HTTPHeaderMap.cpp @@ -155,7 +155,7 @@ void HTTPHeaderMap::setUncommonHeaderCloneName(const StringView name, const Stri if (index == notFound) { LChar* ptr = nullptr; auto nameCopy = WTF::String::createUninitialized(name.length(), ptr); - memcpy(ptr, name.characters8(), name.length()); + memcpy(ptr, name.span8().data(), name.length()); m_uncommonHeaders.append(UncommonHeader { nameCopy, value }); } else m_uncommonHeaders[index].value = value; diff --git a/src/bun.js/bindings/webcore/HTTPHeaderNames.cpp b/src/bun.js/bindings/webcore/HTTPHeaderNames.cpp index 207973a73d968..2c548b6a019ba 100644 --- a/src/bun.js/bindings/webcore/HTTPHeaderNames.cpp +++ b/src/bun.js/bindings/webcore/HTTPHeaderNames.cpp @@ -662,14 +662,15 @@ bool findHTTPHeaderName(StringView stringView, HTTPHeaderName& headerName) return false; if (stringView.is8Bit()) { - if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast(stringView.characters8()), length)) { + if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast(stringView.span8().data()), length)) { headerName = nameAndString->headerName; return true; } } else { LChar characters[maxHTTPHeaderNameLength]; + const auto span = stringView.span16(); for (unsigned i = 0; i < length; ++i) { - UChar character = stringView.characters16()[i]; + UChar character = span.data()[i]; if (!isASCII(character)) return false; diff --git a/src/bun.js/bindings/webcore/HTTPHeaderNames.gperf b/src/bun.js/bindings/webcore/HTTPHeaderNames.gperf index 2b7bcee4f1301..2ef3d133086d2 100644 --- a/src/bun.js/bindings/webcore/HTTPHeaderNames.gperf +++ b/src/bun.js/bindings/webcore/HTTPHeaderNames.gperf @@ -258,14 +258,15 @@ bool findHTTPHeaderName(StringView stringView, HTTPHeaderName& headerName) return false; if (stringView.is8Bit()) { - if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast(stringView.characters8()), length)) { + if (auto nameAndString = HTTPHeaderNamesHash::findHeaderNameImpl(reinterpret_cast(stringView.span8().data()), length)) { headerName = nameAndString->headerName; return true; } } else { LChar characters[maxHTTPHeaderNameLength]; + const auto span = stringView.span16(); for (unsigned i = 0; i < length; ++i) { - UChar character = stringView.characters16()[i]; + UChar character = span.data()[i]; if (!isASCII(character)) return false; diff --git a/src/bun.js/bindings/webcore/HTTPParsers.cpp b/src/bun.js/bindings/webcore/HTTPParsers.cpp index 27e56bdc30a2a..6dbae5a983721 100644 --- a/src/bun.js/bindings/webcore/HTTPParsers.cpp +++ b/src/bun.js/bindings/webcore/HTTPParsers.cpp @@ -126,8 +126,9 @@ bool isValidHTTPHeaderValue(const String& value) if (isTabOrSpace(c)) return false; if (value.is8Bit()) { - const LChar* end = value.characters8() + value.length(); - for (const LChar* p = value.characters8(); p != end; ++p) { + const LChar* begin = value.span8().data(); + const LChar* end = begin + value.length(); + for (const LChar* p = begin; p != end; ++p) { if (UNLIKELY(*p <= 13)) { LChar c = *p; if (c == 0x00 || c == 0x0A || c == 0x0D) @@ -198,7 +199,7 @@ bool isValidHTTPToken(StringView value) return false; if (value.is8Bit()) { - const LChar* characters = value.characters8(); + const LChar* characters = value.span8().data(); const LChar* end = characters + value.length(); while (characters < end) { if (!RFC7230::isTokenCharacter(*characters++)) diff --git a/src/bun.js/bindings/webcore/JSTextEncoder.cpp b/src/bun.js/bindings/webcore/JSTextEncoder.cpp index 41c4bafd29eb7..a2daec9f2c17a 100644 --- a/src/bun.js/bindings/webcore/JSTextEncoder.cpp +++ b/src/bun.js/bindings/webcore/JSTextEncoder.cpp @@ -262,10 +262,10 @@ JSC_DEFINE_JIT_OPERATION(jsTextEncoderEncodeWithoutTypeCheck, JSC::EncodedJSValu } str = input->value(lexicalGlobalObject); - res = TextEncoder__encode8(lexicalGlobalObject, str.characters8(), str.length()); + res = TextEncoder__encode8(lexicalGlobalObject, str.span8().data(), str.length()); } else { str = input->value(lexicalGlobalObject); - res = TextEncoder__encode16(lexicalGlobalObject, str.characters16(), str.length()); + res = TextEncoder__encode16(lexicalGlobalObject, str.span16().data(), str.length()); } if (UNLIKELY(JSC::JSValue::decode(res).isObject() && JSC::JSValue::decode(res).getObject()->isErrorInstance())) { @@ -286,9 +286,9 @@ JSC_DEFINE_JIT_OPERATION(jsTextEncoderPrototypeFunction_encodeIntoWithoutTypeChe auto source = sourceStr->value(lexicalGlobalObject); size_t res = 0; if (!source.is8Bit()) { - res = TextEncoder__encodeInto16(source.characters16(), source.length(), destination->vector(), destination->byteLength()); + res = TextEncoder__encodeInto16(source.span16().data(), source.length(), destination->vector(), destination->byteLength()); } else { - res = TextEncoder__encodeInto8(source.characters8(), source.length(), destination->vector(), destination->byteLength()); + res = TextEncoder__encodeInto8(source.span8().data(), source.length(), destination->vector(), destination->byteLength()); } Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); @@ -389,10 +389,10 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeBody(JSC: } str = input->value(lexicalGlobalObject); - res = TextEncoder__encode8(lexicalGlobalObject, str.characters8(), str.length()); + res = TextEncoder__encode8(lexicalGlobalObject, str.span8().data(), str.length()); } else { str = input->value(lexicalGlobalObject); - res = TextEncoder__encode16(lexicalGlobalObject, str.characters16(), str.length()); + res = TextEncoder__encode16(lexicalGlobalObject, str.span16().data(), str.length()); } if (UNLIKELY(JSC::JSValue::decode(res).isObject() && JSC::JSValue::decode(res).getObject()->isErrorInstance())) { @@ -426,9 +426,11 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeIntoBody( size_t res = 0; if (!source.is8Bit()) { - res = TextEncoder__encodeInto16(source.characters16(), source.length(), destination->vector(), destination->byteLength()); + const auto span = source.span16(); + res = TextEncoder__encodeInto16(span.data(), span.size(), destination->vector(), destination->byteLength()); } else { - res = TextEncoder__encodeInto8(source.characters8(), source.length(), destination->vector(), destination->byteLength()); + const auto span = source.span8(); + res = TextEncoder__encodeInto8(span.data(), span.size(), destination->vector(), destination->byteLength()); } Zig::GlobalObject* globalObject = reinterpret_cast(lexicalGlobalObject); diff --git a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp index 85b23f38459cb..6d77a4cabc368 100644 --- a/src/bun.js/bindings/webcore/SerializedScriptValue.cpp +++ b/src/bun.js/bindings/webcore/SerializedScriptValue.cpp @@ -903,12 +903,15 @@ class CloneSerializer : CloneBase { return true; } writeLittleEndian(out, StringTag); + const auto length = string.length(); if (string.is8Bit()) { - writeLittleEndian(out, string.length() | StringDataIs8BitFlag); - return writeLittleEndian(out, string.characters8(), string.length()); + const auto span = string.span8(); + writeLittleEndian(out, length | StringDataIs8BitFlag); + return writeLittleEndian(out, span.data(), length); } - writeLittleEndian(out, string.length()); - return writeLittleEndian(out, string.characters16(), string.length()); + const auto span = string.span16(); + writeLittleEndian(out, length); + return writeLittleEndian(out, span.data(), length); } private: @@ -2063,11 +2066,11 @@ class CloneSerializer : CloneBase { if (!length) return; if (str.is8Bit()) { - if (!writeLittleEndian(m_buffer, str.characters8(), length)) + if (!writeLittleEndian(m_buffer, str.span8().data(), length)) fail(); return; } - if (!writeLittleEndian(m_buffer, str.characters16(), length)) + if (!writeLittleEndian(m_buffer, str.span16().data(), length)) fail(); } @@ -3241,7 +3244,7 @@ class CloneDeserializer : CloneBase { if (is8Bit) { if ((end - ptr) < static_cast(length)) return false; - str = Identifier::fromString(vm, {reinterpret_cast(ptr), length}); + str = Identifier::fromString(vm, { reinterpret_cast(ptr), length }); ptr += length; return true; } @@ -3251,7 +3254,7 @@ class CloneDeserializer : CloneBase { return false; #if ASSUME_LITTLE_ENDIAN - str = Identifier::fromString(vm, {reinterpret_cast(ptr), length}); + str = Identifier::fromString(vm, { reinterpret_cast(ptr), length }); ptr += length * sizeof(UChar); #else UChar* characters; diff --git a/src/bun.js/bindings/webcore/SharedBuffer.cpp b/src/bun.js/bindings/webcore/SharedBuffer.cpp index 989b9d6e161e3..7c372d4a9cd5f 100644 --- a/src/bun.js/bindings/webcore/SharedBuffer.cpp +++ b/src/bun.js/bindings/webcore/SharedBuffer.cpp @@ -672,12 +672,12 @@ RefPtr utf8Buffer(const String& string) char* p = reinterpret_cast(buffer.data()); if (length) { if (string.is8Bit()) { - const LChar* d = string.characters8(); - if (!WTF::Unicode::convertLatin1ToUTF8(&d, d + length, &p, p + buffer.size())) + const auto d = string.span8(); + if (!WTF::Unicode::convertLatin1ToUTF8(d, &p, p + buffer.size())) return nullptr; } else { - const UChar* d = string.characters16(); - if (WTF::Unicode::convertUTF16ToUTF8(&d, d + length, &p, p + buffer.size()) != WTF::Unicode::ConversionResult::Success) + auto d = string.span16(); + if (WTF::Unicode::convertUTF16ToUTF8(d, &p, p + buffer.size()) != WTF::Unicode::ConversionResult::Success) return nullptr; } } diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index 7bb86e3714904..16930ddd6b252 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -1917,7 +1917,7 @@ pub const MiniEventLoop = struct { } store.* = JSC.WebCore.Blob.Store{ - .ref_count = 2, + .ref_count = std.atomic.Value(u32).init(2), .allocator = bun.default_allocator, .data = .{ .file = JSC.WebCore.Blob.FileStore{ @@ -1949,7 +1949,7 @@ pub const MiniEventLoop = struct { } store.* = JSC.WebCore.Blob.Store{ - .ref_count = 2, + .ref_count = std.atomic.Value(u32).init(2), .allocator = bun.default_allocator, .data = .{ .file = JSC.WebCore.Blob.FileStore{ diff --git a/src/bun.js/rare_data.zig b/src/bun.js/rare_data.zig index 1f6b6f8ede82a..41ae7fdea396f 100644 --- a/src/bun.js/rare_data.zig +++ b/src/bun.js/rare_data.zig @@ -283,7 +283,7 @@ pub fn stderr(rare: *RareData) *Blob.Store { } store.* = Blob.Store{ - .ref_count = 2, + .ref_count = std.atomic.Value(u32).init(2), .allocator = default_allocator, .data = .{ .file = Blob.FileStore{ @@ -315,7 +315,7 @@ pub fn stdout(rare: *RareData) *Blob.Store { .err => {}, } store.* = Blob.Store{ - .ref_count = 2, + .ref_count = std.atomic.Value(u32).init(2), .allocator = default_allocator, .data = .{ .file = Blob.FileStore{ @@ -347,7 +347,7 @@ pub fn stdin(rare: *RareData) *Blob.Store { } store.* = Blob.Store{ .allocator = default_allocator, - .ref_count = 2, + .ref_count = std.atomic.Value(u32).init(2), .data = .{ .file = Blob.FileStore{ .pathlike = .{ diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index db20eb2ba79ad..8adc5a87ad63e 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -1146,6 +1146,7 @@ pub const Blob = struct { return .zero; }; }; + defer source_blob.detach(); const destination_store = destination_blob.store; if (destination_store) |store| { @@ -1583,7 +1584,7 @@ pub const Blob = struct { data: Data, mime_type: MimeType = MimeType.none, - ref_count: u32 = 0, + ref_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(1), is_all_ascii: ?bool = null, allocator: std.mem.Allocator, @@ -1602,8 +1603,8 @@ pub const Blob = struct { }; pub fn ref(this: *Store) void { - assert(this.ref_count > 0); - this.ref_count += 1; + const old = this.ref_count.fetchAdd(1, .Monotonic); + assert(old > 0); } pub fn external(ptr: ?*anyopaque, _: ?*anyopaque, _: usize) callconv(.C) void { @@ -1634,7 +1635,7 @@ pub const Blob = struct { ), }, .allocator = allocator, - .ref_count = 1, + .ref_count = std.atomic.Value(u32).init(1), }); return store; } @@ -1645,7 +1646,7 @@ pub const Blob = struct { .bytes = ByteStore.init(bytes, allocator), }, .allocator = allocator, - .ref_count = 1, + .ref_count = std.atomic.Value(u32).init(1), }); return store; } @@ -1658,9 +1659,9 @@ pub const Blob = struct { } pub fn deref(this: *Blob.Store) void { - assert(this.ref_count >= 1); - this.ref_count -= 1; - if (this.ref_count == 0) { + const old = this.ref_count.fetchSub(1, .Monotonic); + assert(old >= 1); + if (old == 1) { this.deinit(); } } @@ -3507,7 +3508,7 @@ pub const Blob = struct { .bytes = result, }, .allocator = bun.default_allocator, - .ref_count = 1, + .ref_count = std.atomic.Value(u32).init(1), }, ); var blob = initWithStore(store, globalThis); @@ -3962,14 +3963,10 @@ pub const Blob = struct { JSC.JSValue.JSType.StringObject, JSC.JSValue.JSType.DerivedStringObject, => { - var sliced = top_value.toSlice(global, bun.default_allocator); - const is_all_ascii = !sliced.isAllocated(); - if (!sliced.isAllocated() and sliced.len > 0) { - sliced.ptr = @as([*]const u8, @ptrCast((try bun.default_allocator.dupe(u8, sliced.slice())).ptr)); - sliced.allocator = NullableAllocator.init(bun.default_allocator); - } - - return Blob.initWithAllASCII(@constCast(sliced.slice()), bun.default_allocator, global, is_all_ascii); + var str = top_value.toBunString(global); + defer str.deref(); + const bytes, const ascii = try str.toOwnedSliceReturningAllASCII(bun.default_allocator); + return Blob.initWithAllASCII(bytes, bun.default_allocator, global, ascii); }, JSC.JSValue.JSType.ArrayBuffer, diff --git a/src/bun.js/webcore/blob/ReadFile.zig b/src/bun.js/webcore/blob/ReadFile.zig index 803d3cd1e48b4..c3ea88c97827f 100644 --- a/src/bun.js/webcore/blob/ReadFile.zig +++ b/src/bun.js/webcore/blob/ReadFile.zig @@ -287,6 +287,7 @@ pub const ReadFile = struct { defer store.deref(); const system_error = this.system_error; + const total_size = this.total_size; bun.destroy(this); if (system_error) |err| { @@ -294,7 +295,7 @@ pub const ReadFile = struct { return; } - cb(cb_ctx, .{ .result = .{ .buf = buf, .total_size = this.total_size, .is_temporary = true } }); + cb(cb_ctx, .{ .result = .{ .buf = buf, .total_size = total_size, .is_temporary = true } }); } pub fn run(this: *ReadFile, task: *ReadFileTask) void { diff --git a/src/bun.js/webcore/blob/WriteFile.zig b/src/bun.js/webcore/blob/WriteFile.zig index 25a724a05a714..055dcebc15e13 100644 --- a/src/bun.js/webcore/blob/WriteFile.zig +++ b/src/bun.js/webcore/blob/WriteFile.zig @@ -711,6 +711,7 @@ pub const WriteFileWaitFromLockedValueTask = struct { if (new_promise.asAnyPromise()) |_promise| { switch (_promise.status(globalThis.vm())) { .Pending => { + // Fulfill the new promise using the old promise promise.resolve( globalThis, new_promise, diff --git a/src/js/internal-for-testing.ts b/src/js/internal-for-testing.ts index fe9c089698d62..69cd88e897d3f 100644 --- a/src/js/internal-for-testing.ts +++ b/src/js/internal-for-testing.ts @@ -34,3 +34,8 @@ export const upgrade_test_helpers = $zig("upgrade_command.zig", "upgrade_js_bind openTempDirWithoutSharingDelete: () => void; closeTempDirHandle: () => void; }; + +export const nativeFrameForTesting: (callback: () => void) => void = $cpp( + "CallSite.cpp", + "createNativeFrameForTesting", +); diff --git a/src/string.zig b/src/string.zig index d36e7139f0d14..c3cd95d4287e1 100644 --- a/src/string.zig +++ b/src/string.zig @@ -318,22 +318,25 @@ pub const String = extern struct { } pub fn toOwnedSlice(this: String, allocator: std.mem.Allocator) ![]u8 { + const bytes, _ = try this.toOwnedSliceReturningAllASCII(allocator); + return bytes; + } + + pub fn toOwnedSliceReturningAllASCII(this: String, allocator: std.mem.Allocator) !struct { []u8, bool } { switch (this.tag) { - .ZigString => return try this.value.ZigString.toOwnedSlice(allocator), + .ZigString => return .{ try this.value.ZigString.toOwnedSlice(allocator), true }, .WTFStringImpl => { var utf8_slice = this.value.WTFStringImpl.toUTF8WithoutRef(allocator); - if (utf8_slice.allocator.get()) |alloc| { if (!isWTFAllocator(alloc)) { - return @constCast(utf8_slice.slice()); + return .{ @constCast(utf8_slice.slice()), false }; } } - return @constCast((try utf8_slice.clone(allocator)).slice()); + return .{ @constCast((try utf8_slice.clone(allocator)).slice()), true }; }, - .StaticZigString => return try this.value.StaticZigString.toOwnedSlice(allocator), - .Empty => return &[_]u8{}, - else => unreachable, + .StaticZigString => return .{ try this.value.StaticZigString.toOwnedSlice(allocator), false }, + else => return .{ &[_]u8{}, false }, } } diff --git a/test/js/bun/io/bun-write-leak-fixture.js b/test/js/bun/io/bun-write-leak-fixture.js new file mode 100644 index 0000000000000..9ef463ce91ae1 --- /dev/null +++ b/test/js/bun/io/bun-write-leak-fixture.js @@ -0,0 +1,26 @@ +// Avoid using String.prototype.repeat in this file because it's very slow in +// debug builds of JavaScriptCore +const MAX_ALLOWED_MEMORY_USAGE = 256; +const dest = process.argv.at(-1); + +async function run(inputType) { + for (let i = 0; i < 100; i++) { + const largeFile = inputType; + await Bun.write(dest, largeFile); + Bun.gc(true); + const rss = (process.memoryUsage.rss() / 1024 / 1024) | 0; + console.log("Memory usage:", rss, "MB"); + if (rss > MAX_ALLOWED_MEMORY_USAGE) { + throw new Error("Memory usage is too high"); + } + } +} + +// 30 MB, plain-text ascii +await run(new Buffer(1024 * 1024 * 1).fill("A".charCodeAt(0)).toString("utf-8")); + +// ~15 MB, UTF-16 emoji +await run(new Buffer(1024 * 1024 * 1).fill("😃").toString("utf-8")); + +// 30 MB, ArrayBufferView +await run(new Uint8Array(1024 * 1024 * 1).fill("B".charCodeAt(0))); diff --git a/test/js/bun/io/bun-write-leak.test.ts b/test/js/bun/io/bun-write-leak.test.ts new file mode 100644 index 0000000000000..f12c25c1584e6 --- /dev/null +++ b/test/js/bun/io/bun-write-leak.test.ts @@ -0,0 +1,20 @@ +import { test, expect, describe } from "bun:test"; +import path from "node:path"; + +import "harness"; +import { tempDirWithFiles } from "harness"; + +// https://github.com/oven-sh/bun/issues/10588 +test( + "Bun.write should not leak the output data", + async () => { + const dir = tempDirWithFiles("bun-write-leak-fixture", { + "bun-write-leak-fixture.js": await Bun.file(path.join(import.meta.dir, "bun-write-leak-fixture.js")).text(), + "out.bin": "here", + }); + + const dest = path.join(dir, "out.bin"); + expect([path.join(dir, "bun-write-leak-fixture.js"), dest]).toRun(); + }, + 30 * 1000, +); diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js index e4f932c127ded..892594a888aed 100644 --- a/test/js/node/v8/capture-stack-trace.test.js +++ b/test/js/node/v8/capture-stack-trace.test.js @@ -1,3 +1,4 @@ +import { nativeFrameForTesting } from "bun:internal-for-testing"; import { test, expect, afterEach } from "bun:test"; const origPrepareStackTrace = Error.prepareStackTrace; @@ -131,13 +132,16 @@ test("capture stack trace limit", () => { captureStackTrace(); } + var originalLimit = Error.stackTraceLimit; function captureStackTrace() { let e1 = {}; Error.captureStackTrace(e1); + expect(e1.stack.split("\n").length).toBe(11); let e2 = new Error(); Error.captureStackTrace(e2); + expect(e2.stack.split("\n").length).toBe(11); let e3 = {}; @@ -158,8 +162,11 @@ test("capture stack trace limit", () => { Error.captureStackTrace(e6); expect(e6.stack.split("\n").length).toBe(13); } - - f1(); + try { + f1(); + } finally { + Error.stackTraceLimit = originalLimit; + } }); test("prepare stack trace", () => { @@ -443,9 +450,12 @@ test("CallFrame.p.isNative", () => { Error.prepareStackTrace = (e, s) => { expect(s[0].isNative()).toBe(false); expect(s[1].isNative()).toBe(true); + expect(s[2].isNative()).toBe(false); }; - [1, 2].sort(() => { - Error.captureStackTrace(new Error("")); + + nativeFrameForTesting(() => { + const err = new Error(""); + Error.captureStackTrace(err); return 0; }); Error.prepareStackTrace = prevPrepareStackTrace; @@ -469,6 +479,7 @@ test("CallFrame.p.toString", () => { expect(e.stack[0].toString().includes("")).toBe(true); }); +// TODO: line numbers are wrong in a release build test.todo("err.stack should invoke prepareStackTrace", () => { var lineNumber = -1; var functionName = ""; @@ -492,9 +503,9 @@ test.todo("err.stack should invoke prepareStackTrace", () => { functionWithAName(); expect(functionName).toBe("functionWithAName"); - expect(lineNumber).toBe(491); + expect(lineNumber).toBe(391); // TODO: this is wrong - expect(parentLineNumber).toBe(499); + expect(parentLineNumber).toBe(394); }); test("Error.prepareStackTrace inside a node:vm works", () => {