Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/bun_core/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,8 @@ pub fn format_utf16_type(slice_: &[u16], writer: &mut impl fmt::Write) -> fmt::R
if result.read == 0 || result.written == 0 {
break;
}
write_bytes(writer, &chunk[..result.written as usize])?;
slice = &slice[result.read as usize..];
write_bytes(writer, &chunk[..result.written])?;
slice = &slice[result.read..];
}
Ok(())
}
Expand All @@ -562,7 +562,7 @@ pub(crate) fn format_utf16_type_with_path_options(
break;
}

let to_write = &chunk[..result.written as usize];
let to_write = &chunk[..result.written];
Comment thread
robobun marked this conversation as resolved.
if !opts.escape_backslashes && opts.path_sep == PathSep::Any {
write_bytes(writer, to_write)?;
} else {
Expand All @@ -584,7 +584,7 @@ pub(crate) fn format_utf16_type_with_path_options(
}
write_bytes(writer, ptr)?;
}
slice = &slice[result.read as usize..];
slice = &slice[result.read..];
}
Ok(())
}
Expand Down Expand Up @@ -1095,8 +1095,8 @@ pub fn format_latin1(slice_: &[u8], writer: &mut impl fmt::Write) -> fmt::Result
if result.read == 0 || result.written == 0 {
break;
}
write_bytes(writer, &chunk[..result.written as usize])?;
slice = &slice[result.read as usize..];
write_bytes(writer, &chunk[..result.written])?;
slice = &slice[result.read..];
}

if !slice.is_empty() {
Expand Down
27 changes: 13 additions & 14 deletions src/bun_core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1682,10 +1682,15 @@ pub(crate) mod strings_impl {
}

/// Result of an encode-into-fixed-buffer operation. Port of `EncodeIntoResult`.
/// Counts are `usize` because `written` can be exactly 2^32 (JSC's max
/// ArrayBuffer size), which a `u32` would wrap to 0. `repr(C)` to return by
/// value from `TextEncoder__encodeInto8/16` (`TextEncoderEncodeIntoResult`
/// in `headers-handwritten.h`).
Comment thread
robobun marked this conversation as resolved.
#[repr(C)]
#[derive(Clone, Copy, Default, Debug)]
pub struct EncodeIntoResult {
pub read: u32,
pub written: u32,
pub read: usize,
pub written: usize,
}

/// Port of `elementLengthUTF16IntoUTF8`: the exact UTF-8 byte length of a
Expand Down Expand Up @@ -1741,8 +1746,8 @@ pub(crate) mod strings_impl {
};
if r.status == simdutf::Status::SUCCESS {
return EncodeIntoResult {
read: utf16.len() as u32,
written: r.count as u32,
read: utf16.len(),
written: r.count,
};
}
}
Expand All @@ -1760,10 +1765,7 @@ pub(crate) mod strings_impl {
written += n;
read += adv as usize;
}
EncodeIntoResult {
read: read as u32,
written: written as u32,
}
EncodeIntoResult { read, written }
}

/// Port of `copyLatin1IntoUTF8` — encode Latin-1 into a fixed-size UTF-8 buffer.
Expand Down Expand Up @@ -1827,8 +1829,8 @@ pub(crate) mod strings_impl {
debug_assert!(latin1_[read] >= 0x80);
if STOP {
return EncodeIntoResult {
written: u32::MAX,
read: u32::MAX,
written: usize::MAX,
read: usize::MAX,
};
}
if buf_.len() - written < 2 {
Expand All @@ -1840,10 +1842,7 @@ pub(crate) mod strings_impl {
read += 1;
}

EncodeIntoResult {
written: written as u32,
read: read as u32,
}
EncodeIntoResult { written, read }
}

/// Null-terminated variant of `to_utf8_from_latin1`. Returns `ZBox` so
Expand Down
13 changes: 6 additions & 7 deletions src/bun_core/string/immutable/unicode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,8 @@ pub fn copy_cp1252_into_utf16(buf_: &mut [u16], latin1_: &[u8]) -> EncodeIntoRes
}

EncodeIntoResult {
read: (buf_total - buf.len()) as u32,
written: (latin1_total - latin1.len()) as u32,
read: buf_total - buf.len(),
written: latin1_total - latin1.len(),
}
}

Expand All @@ -459,7 +459,6 @@ pub fn copy_latin1_into_utf16(buf_: &mut [u16], latin1_: &[u8]) -> EncodeIntoRes
for (out, &inp) in buf_[..len].iter_mut().zip(latin1_[..len].iter()) {
*out = u16::from(inp);
}
let len = len as u32;
EncodeIntoResult {
read: len,
written: len,
Expand Down Expand Up @@ -1039,8 +1038,8 @@ fn copy_utf16_into_utf8_with_buffer_impl<const ALLOW_TRUNCATED_UTF8_SEQUENCE: bo
}

return EncodeIntoResult {
read: utf16.len() as u32,
written: result.count as u32,
read: utf16.len(),
written: result.count,
};
}
}
Expand Down Expand Up @@ -1140,8 +1139,8 @@ fn copy_utf16_into_utf8_with_buffer_impl<const ALLOW_TRUNCATED_UTF8_SEQUENCE: bo
}

EncodeIntoResult {
read: (utf16.len() - utf16_remaining.len()) as u32,
written: (buf_total - remaining.len()) as u32,
read: utf16.len() - utf16_remaining.len(),
written: buf_total - remaining.len(),
}
}

Expand Down
18 changes: 9 additions & 9 deletions src/http_jsc/websocket_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ impl<const SSL: bool> WebSocket<SSL> {
let content_byte_len: usize = strings::element_length_utf16_into_utf8(utf16);
let mut buf = vec![0u8; content_byte_len];
let encode_result = strings::copy_utf16_into_utf8(&mut buf, utf16);
buf.truncate(encode_result.written as usize);
buf.truncate(encode_result.written);
utf8_storage = buf;
&utf8_storage
}
Expand All @@ -976,7 +976,7 @@ impl<const SSL: bool> WebSocket<SSL> {
} else {
let mut buf = vec![0u8; content_byte_len];
let encode_result = strings::copy_latin1_into_utf8(&mut buf, latin1);
buf.truncate(encode_result.written as usize);
buf.truncate(encode_result.written);
utf8_storage = buf;
&utf8_storage
}
Expand Down Expand Up @@ -1829,7 +1829,7 @@ fn encode_close_reason(reason: &ZigString, buf: &mut [u8; MAX_CONTROL_PAYLOAD])
} else {
// Latin-1 → UTF-8: raw Latin-1 bytes would fail `send_close_with_body`'s UTF-8 check.
let result = strings::copy_latin1_into_utf8(cursor.get_mut(), reason.slice());
if (result.read as usize) < reason.slice().len() {
if result.read < reason.slice().len() {
return None;
}
cursor.set_position(result.written as u64);
Expand Down Expand Up @@ -2341,20 +2341,20 @@ impl Copy<'_> {
match self {
Copy::Utf16(utf16) => {
let encoded = strings::copy_utf16_into_utf8_impl::<true>(parts.payload, utf16);
debug_assert_eq!(encoded.written as usize, content_byte_len);
debug_assert_eq!(encoded.read as usize, utf16.len());
debug_assert_eq!(encoded.written, content_byte_len);
debug_assert_eq!(encoded.read, utf16.len());
header
.write_header(&mut parts.header, encoded.written as usize)
.write_header(&mut parts.header, encoded.written)
.expect("unreachable");
Mask::fill_in_place(global_this, parts.mask, parts.payload);
}
Copy::Latin1(latin1) => {
let encoded = strings::copy_latin1_into_utf8(parts.payload, latin1);
debug_assert_eq!(encoded.written as usize, content_byte_len);
debug_assert_eq!(encoded.written, content_byte_len);
// latin1 can contain non-ascii
debug_assert_eq!(encoded.read as usize, latin1.len());
debug_assert_eq!(encoded.read, latin1.len());
header
.write_header(&mut parts.header, encoded.written as usize)
.write_header(&mut parts.header, encoded.written)
.expect("unreachable");
Mask::fill_in_place(global_this, parts.mask, parts.payload);
}
Expand Down
2 changes: 1 addition & 1 deletion src/install/PackageInstall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2200,7 +2200,7 @@ impl<'a> PackageInstall<'a> {
}

let res = strings::copy_utf16_into_utf8(&mut dest_buf[..], &wbuf[..i]);
let mut offset: usize = res.written as usize;
let mut offset: usize = res.written;
if dest_buf[offset - 1] != bun_paths::SEP_WINDOWS {
dest_buf[offset] = bun_paths::SEP_WINDOWS;
offset += 1;
Expand Down
9 changes: 9 additions & 0 deletions src/jsc/bindings/headers-handwritten.h
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,15 @@ extern "C" void ZigString__freeGlobal(const unsigned char* ptr, size_t len);
extern "C" size_t Bun__encoding__writeLatin1(const unsigned char* ptr, size_t len, unsigned char* to, size_t other_len, Encoding encoding);
extern "C" size_t Bun__encoding__writeUTF16(const char16_t* ptr, size_t len, unsigned char* to, size_t other_len, Encoding encoding);

// Mirrors `EncodeIntoResult` in bun_core (repr(C)). size_t counts: `written`
// can be exactly 2^32 (a full max-size Uint8Array), which would wrap a u32.
Comment thread
robobun marked this conversation as resolved.
typedef struct TextEncoderEncodeIntoResult {
size_t read;
size_t written;
} TextEncoderEncodeIntoResult;
extern "C" TextEncoderEncodeIntoResult TextEncoder__encodeInto8(const unsigned char* stringPtr, size_t stringLen, void* ptr, size_t len);
extern "C" TextEncoderEncodeIntoResult TextEncoder__encodeInto16(const char16_t* stringPtr, size_t stringLen, void* ptr, size_t len);

extern "C" size_t Bun__encoding__byteLengthLatin1AsUTF8(const unsigned char* ptr, size_t len);
extern "C" size_t Bun__encoding__byteLengthUTF16AsUTF8(const char16_t* ptr, size_t len);

Expand Down
47 changes: 12 additions & 35 deletions src/jsc/bindings/v8/V8String.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "V8HandleScope.h"
#include "wtf/SIMDUTF.h"
#include "v8_compatibility_assertions.h"
#include "headers-handwritten.h"

ASSERT_V8_TYPE_LAYOUT_MATCHES(v8::String)

Expand Down Expand Up @@ -149,9 +150,6 @@ bool String::IsExternalOneByte() const
return !impl->isNull() && impl->impl()->isExternal() && impl->is8Bit();
}

extern "C" size_t TextEncoder__encodeInto8(const Latin1Character* stringPtr, size_t stringLen, void* ptr, size_t len);
extern "C" size_t TextEncoder__encodeInto16(const char16_t* stringPtr, size_t stringLen, void* ptr, size_t len);

int String::WriteUtf8(Isolate* isolate, char* buffer, int length, int* nchars_ref, int options) const
{
RELEASE_ASSERT(options == 0);
Expand All @@ -160,10 +158,11 @@ int String::WriteUtf8(Isolate* isolate, char* buffer, int length, int* nchars_re

size_t unsigned_length = length < 0 ? static_cast<size_t>(std::numeric_limits<int>::max()) : static_cast<size_t>(length);

uint64_t result = string.is8Bit() ? TextEncoder__encodeInto8(string.span8().data(), string.span8().size(), buffer, unsigned_length)
: TextEncoder__encodeInto16(string.span16().data(), string.span16().size(), buffer, unsigned_length);
uint32_t read = static_cast<uint32_t>(result);
uint32_t written = static_cast<uint32_t>(result >> 32);
TextEncoderEncodeIntoResult result = string.is8Bit() ? TextEncoder__encodeInto8(string.span8().data(), string.span8().size(), buffer, unsigned_length)
: TextEncoder__encodeInto16(string.span16().data(), string.span16().size(), buffer, unsigned_length);
// unsigned_length <= INT_MAX, so both counts fit in 32 bits here.
uint32_t read = static_cast<uint32_t>(result.read);
uint32_t written = static_cast<uint32_t>(result.written);

if (written < length && read == string.length()) {
buffer[written] = 0;
Expand Down Expand Up @@ -239,37 +238,15 @@ size_t String::WriteUtf8V2(Isolate* isolate, char* buffer, size_t capacity, int
// uses when kReplaceInvalidUtf8 is not set, so the result size matches either
// way).
if (str->is8Bit()) {
// Latin-1 expands at most 2x: 2 * (2^31 - 1) < 2^32, so the packed
// 32-bit counts cannot wrap.
const auto span = str->span8();
uint64_t result = TextEncoder__encodeInto8(span.data(), span.size(), buffer, writableCapacity);
read = static_cast<uint32_t>(result);
written = static_cast<uint32_t>(result >> 32);
TextEncoderEncodeIntoResult result = TextEncoder__encodeInto8(span.data(), span.size(), buffer, writableCapacity);
read = result.read;
written = result.written;
} else {
// UTF-16 expands up to 3x, which can exceed the 32-bit counts
// TextEncoder__encodeInto packs its result into (3 * (2^31 - 1) >
// 2^32). Encode in chunks small enough that each chunk's counts
// fit, accumulating in size_t.
const auto span = str->span16();
const size_t total = span.size();
constexpr size_t maxChunk = static_cast<size_t>(1) << 30; // <= 3 GiB UTF-8 per chunk
while (read < total) {
size_t chunkLength = std::min(maxChunk, total - read);
// Never split a surrogate pair across chunks: the encoder
// would see two unpaired halves and write U+FFFD twice.
if (read + chunkLength < total && U16_IS_LEAD(span[read + chunkLength - 1])) {
chunkLength--;
}
uint64_t result = TextEncoder__encodeInto16(span.data() + read, chunkLength, buffer + written, writableCapacity - written);
const uint32_t chunkRead = static_cast<uint32_t>(result);
const uint32_t chunkWritten = static_cast<uint32_t>(result >> 32);
read += chunkRead;
written += chunkWritten;
if (chunkRead < chunkLength) {
// Ran out of output capacity.
break;
}
}
TextEncoderEncodeIntoResult result = TextEncoder__encodeInto16(span.data(), span.size(), buffer, writableCapacity);
read = result.read;
written = result.written;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Expand Down
9 changes: 4 additions & 5 deletions src/jsc/bindings/webcore/JSTextEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,13 @@
#include "JSDOMOperation.h"
#include "JSDOMWrapperCache.h"
#include "BunClientData.h"
#include "headers-handwritten.h"

namespace WebCore {
using namespace JSC;

extern "C" JSC::EncodedJSValue TextEncoder__encode8(JSC::JSGlobalObject* global, const Latin1Character* stringPtr, size_t stringLen);
extern "C" JSC::EncodedJSValue TextEncoder__encode16(JSC::JSGlobalObject* global, const char16_t* stringPtr, size_t stringLen);
extern "C" size_t TextEncoder__encodeInto8(const Latin1Character* stringPtr, size_t stringLen, void* ptr, size_t len);
extern "C" size_t TextEncoder__encodeInto16(const char16_t* stringPtr, size_t stringLen, void* ptr, size_t len);
extern "C" JSC::EncodedJSValue TextEncoder__encodeRopeString(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSString* str);

template<> TextEncoder::EncodeIntoResult convertDictionary<TextEncoder::EncodeIntoResult>(JSGlobalObject& lexicalGlobalObject, JSValue value)
Expand Down Expand Up @@ -345,7 +344,7 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeIntoBody(
return {};
}

size_t res = 0;
TextEncoderEncodeIntoResult res = {};
if (!source->is8Bit()) {
const auto span = source->span16();
res = TextEncoder__encodeInto16(span.data(), span.size(), destination->vector(), destination->byteLength());
Expand All @@ -356,8 +355,8 @@ static inline JSC::EncodedJSValue jsTextEncoderPrototypeFunction_encodeIntoBody(

Bun::GlobalScope* globalScope = reinterpret_cast<Bun::GlobalScope*>(lexicalGlobalObject);
auto* result = JSC::constructEmptyObject(vm, globalScope->encodeIntoObjectStructure());
result->putDirectOffset(vm, 0, JSC::jsNumber(static_cast<uint32_t>(res)));
result->putDirectOffset(vm, 1, JSC::jsNumber(static_cast<uint32_t>(res >> 32)));
result->putDirectOffset(vm, 0, JSC::jsNumber(res.read));
result->putDirectOffset(vm, 1, JSC::jsNumber(res.written));

return JSValue::encode(result);
}
Expand Down
2 changes: 1 addition & 1 deletion src/paths/string_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn from_w_path<'a>(buf: &'a mut [u8], utf16: &[u16]) -> &'a ZStr {
let to_copy = strings::trim_prefix_comptime::<u16>(utf16, &windows::LONG_PATH_PREFIX);
let last = buf.len() - 1;
let encode_into_result = strings::copy_utf16_into_utf8(&mut buf[..last], to_copy);
let written = encode_into_result.written as usize;
let written = encode_into_result.written;
debug_assert!(written < buf.len());
buf[written] = 0;
ZStr::from_buf(buf, written)
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/node/node_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ mod _impl {
let mut buf1: Vec<u16> = vec![0u16; k.utf16_byte_length() + 1];
let mut buf2: Vec<u16> = vec![0u16; v.utf16_byte_length() + 1];
let len1: usize = if k.is_8bit() {
strings::copy_latin1_into_utf16(&mut buf1, k.latin1()).written as usize
strings::copy_latin1_into_utf16(&mut buf1, k.latin1()).written
} else {
buf1[0..k.length()].copy_from_slice(k.utf16());
k.length()
Expand All @@ -600,7 +600,7 @@ mod _impl {
break 'str_ EMPTY_W.as_ptr();
}
let len2: usize = if v.is_8bit() {
strings::copy_latin1_into_utf16(&mut buf2, v.latin1()).written as usize
strings::copy_latin1_into_utf16(&mut buf2, v.latin1()).written
} else {
buf2[0..v.length()].copy_from_slice(v.utf16());
v.length()
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/webcore/TextDecoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ impl TextDecoder {
Ok(unsafe {
jsc::zig_string::to_external_u16(
bun_core::heap::into_raw(bytes).cast::<u16>(),
out.written as usize,
out.written,
global_this,
)
})
Expand Down
Loading