Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion src/jsc/bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3459,7 +3459,10 @@
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);

WTF::String message = Zig::toString(*arg3);
// toStringCopy, not toString: the AggregateError (and its message string)
// outlives this call, while an untagged ZigString message would alias the
// caller's buffer without owning it. See Zig::getErrorInstance.
WTF::String message = Zig::toStringCopy(*arg3);

Check failure on line 3465 in src/jsc/bindings/bindings.cpp

View check run for this annotation

Claude / Claude Code Review

toStringCopy leaks external-tagged ZigString buffers

Switching to `toStringCopy` here drops the `isTaggedExternalPtr` (bit 62) ownership-transfer path that `toString` honored, so callers that `mark_global()` to hand the buffer to C++ now leak it. This is active for `createAggregateError`: `src/jsc/VirtualMachine.rs:2579-2591` heap-allocates the message via `bun_core::heap::release(...)`, calls `message.mark_global()`, and explicitly relies on C++ adopting it as an `ExternalStringImpl` freed via `free_global_string` — after this change the buffer i
Comment thread
claude[bot] marked this conversation as resolved.
Outdated
JSC::JSValue cause = JSC::jsUndefined();
JSC::JSArray* array = nullptr;
{
Expand Down
7 changes: 6 additions & 1 deletion src/jsc/bindings/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,12 @@ static const WTF::String toStringStatic(ZigString str)

static JSC::JSValue getErrorInstance(const ZigString* str, JSC::JSGlobalObject* globalObject)
{
WTF::String message = toString(*str);
// toStringCopy, not toString: an untagged ZigString would otherwise become
// a JSString that aliases the caller's bytes with no ownership, and several
// callers (AsyncModule's resolve/download error paths) pass stack-local
// buffers that are freed before JS can ever read error.message. The
// TypeError/SyntaxError/RangeError siblings below already copy.
WTF::String message = toStringCopy(*str);
if (message.isNull() && str->len > 0) [[unlikely]] {
// pending exception while creating an error.
return {};
Expand Down
Loading