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
1 change: 1 addition & 0 deletions src/js/builtins/BunBuiltinNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ using namespace JSC;
macro(byobRequest) \
macro(bytes) \
macro(cancel) \
macro(capturedStackTrace) \
macro(checkBufferRead) \
macro(checks) \
macro(cloneArrayBuffer) \
Expand Down
4 changes: 4 additions & 0 deletions src/jsc/bindings/CallSite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ void CallSite::formatAsString(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WT
std::optional<OrdinalNumber> column = columnNumber().zeroBasedInt() >= 0 ? std::optional(columnNumber()) : std::nullopt;
std::optional<OrdinalNumber> line = lineNumber().zeroBasedInt() >= 0 ? std::optional(lineNumber()) : std::nullopt;

if (isAsync()) {
sb.append("async "_s);
}

if (functionName.length() > 0) {

if (isConstructor()) {
Expand Down
134 changes: 118 additions & 16 deletions src/jsc/bindings/FormatStackTraceForJS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,36 @@

WTF::StringBuilder sb;

WTF::String name = "Error"_s;
auto errorName = errorObject->getIfPropertyExists(lexicalGlobalObject, vm.propertyNames->name);
RETURN_IF_EXCEPTION(scope, {});
if (errorName && !errorName.isUndefined()) {
auto* str = errorName.toString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
auto value = str->value(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
name = value.data;
}

WTF::String message;
auto errorMessage = errorObject->getIfPropertyExists(lexicalGlobalObject, vm.propertyNames->message);
RETURN_IF_EXCEPTION(scope, {});
if (errorMessage) {
if (errorMessage && !errorMessage.isUndefined()) {
auto* str = errorMessage.toString(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
if (str->length() > 0) {
auto value = str->view(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
sb.append("Error: "_s);
sb.append(value.data);
} else {
sb.append("Error"_s);
}
auto value = str->value(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
message = value.data;
}

if (name.isEmpty()) {
sb.append(message);
} else if (message.isEmpty()) {
sb.append(name);
} else {
sb.append("Error"_s);
sb.append(name);
sb.append(": "_s);
sb.append(message);
}

for (size_t i = 0; i < framesCount; i++) {
Expand Down Expand Up @@ -428,7 +443,7 @@
return Bun::formatStackTrace(vm, globalObject, lexicalGlobalObject, name, message, line, column, sourceURL, stackTrace, errorInstance);
}

static JSValue computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector<StackFrame>& stackFrames, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorObject, JSObject* prepareStackTrace)
static JSArray* buildSourceMappedCallSitesArray(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector<StackFrame>& stackFrames)
{
auto scope = DECLARE_THROW_SCOPE(vm);

Expand Down Expand Up @@ -513,6 +528,19 @@
JSArray* callSitesArray = JSC::constructArray(globalObject, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), callSites);
RETURN_IF_EXCEPTION(scope, {});

return callSitesArray;
}

static JSValue computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector<StackFrame>& stackFrames, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorObject, JSObject* prepareStackTrace)
{
UNUSED_PARAM(line);
UNUSED_PARAM(column);
UNUSED_PARAM(sourceURL);
auto scope = DECLARE_THROW_SCOPE(vm);

JSArray* callSitesArray = buildSourceMappedCallSitesArray(vm, globalObject, lexicalGlobalObject, stackFrames);
RETURN_IF_EXCEPTION(scope, {});

RELEASE_AND_RETURN(scope, formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSitesArray, prepareStackTrace));
}

Expand Down Expand Up @@ -756,6 +784,73 @@
return true;
}

JSC_DEFINE_CUSTOM_GETTER(nonErrorInstanceLazyStackCustomGetter, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto scope = DECLARE_THROW_SCOPE(vm);

JSObject* receiver = JSValue::decode(thisValue).getObject();
if (!receiver) [[unlikely]]
return JSValue::encode(jsUndefined());

const auto& privateName = WebCore::builtinNames(vm).capturedStackTracePrivateName();
JSObject* errorObject = nullptr;
JSC::JSArray* callSites = nullptr;
for (JSObject* o = receiver; o;) {
JSValue v = o->getDirect(vm, privateName);
if (v && v.isNull())
return JSValue::encode(jsUndefined());
if (auto* arr = v ? dynamicDowncast<JSC::JSArray>(v) : nullptr) {
callSites = arr;
errorObject = o;
break;
}
JSValue proto = o->getPrototypeDirect();
o = proto.isObject() ? asObject(proto) : nullptr;

Check warning on line 809 in src/jsc/bindings/FormatStackTraceForJS.cpp

View check run for this annotation

Claude / Claude Code Review

getPrototypeDirect() proto-walk misses holder when receiver is a Proxy

The proto-chain walk advances via `o->getPrototypeDirect()`, which reads the structure's stored prototype and bypasses `[[GetPrototypeOf]]` — for a `ProxyObject` that's `jsNull()`, so `const t = {}; Error.captureStackTrace(t); new Proxy(t, {}).stack` now returns `undefined` instead of the stack string (a regression from both pre-PR Bun and Node, on the same receiver-vs-holder axis the walk was added to fix). Unwrapping `ProxyObject` to its `target()` before/inside the loop (iteratively, for nest
Comment thread
robobun marked this conversation as resolved.
}
Comment thread
claude[bot] marked this conversation as resolved.
if (!callSites) [[unlikely]]
return JSValue::encode(jsUndefined());
Comment thread
claude[bot] marked this conversation as resolved.

JSC::EnsureStillAliveScope keepCallSites(callSites);

// Sentinel so re-entry through a name/message getter hits the isNull check above; restored if the user code throws.
errorObject->putDirect(vm, privateName, jsNull(), 0);

auto* globalObject = defaultGlobalObject(lexicalGlobalObject);

JSValue result;
if (globalObject->isInsideErrorPrepareStackTraceCallback) {
result = formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSites);
} else {
globalObject->isInsideErrorPrepareStackTraceCallback = true;
result = formatStackTraceToJSValueWithoutPrepareStackTrace(vm, globalObject, lexicalGlobalObject, errorObject, callSites);
globalObject->isInsideErrorPrepareStackTraceCallback = false;
}
Comment thread
claude[bot] marked this conversation as resolved.
if (scope.exception()) [[unlikely]] {
unsigned attrs = 0;
JSValue currentStack = errorObject->getDirect(vm, vm.propertyNames->stack, attrs);
if (currentStack && (attrs & JSC::PropertyAttribute::CustomAccessor))
errorObject->putDirect(vm, privateName, callSites, 0);
else
errorObject->putDirect(vm, privateName, jsUndefined(), 0);
return {};
}
Comment thread
claude[bot] marked this conversation as resolved.

errorObject->putDirect(vm, vm.propertyNames->stack, result, JSC::PropertyAttribute::DontEnum | 0);
errorObject->putDirect(vm, privateName, jsUndefined(), 0);
return JSValue::encode(result);
}

JSC_DEFINE_CUSTOM_SETTER(nonErrorInstanceLazyStackCustomSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName))
{
auto& vm = JSC::getVM(globalObject);
if (auto* object = JSValue::decode(thisValue).getObject()) {
object->putDirect(vm, vm.propertyNames->stack, JSValue::decode(value), JSC::PropertyAttribute::DontEnum | 0);
object->putDirect(vm, WebCore::builtinNames(vm).capturedStackTracePrivateName(), jsUndefined(), 0);
}
return true;
}

JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
{
Zig::GlobalObject* globalObject = static_cast<Zig::GlobalObject*>(lexicalGlobalObject);
Expand Down Expand Up @@ -806,12 +901,19 @@
instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, globalObject->m_lazyStackCustomGetterSetter.get(globalObject), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::CustomAccessor | 0);
}
} else {
OrdinalNumber line;
OrdinalNumber column;
String sourceURL;
JSValue result = computeErrorInfoToJSValue(vm, stackTrace, line, column, sourceURL, errorObject, nullptr);
JSArray* callSitesArray = buildSourceMappedCallSitesArray(vm, globalObject, lexicalGlobalObject, stackTrace);
RETURN_IF_EXCEPTION(scope, {});
errorObject->putDirect(vm, vm.propertyNames->stack, result, JSC::PropertyAttribute::DontEnum | 0);

{
const auto& propertyName = vm.propertyNames->stack;
VM::DeletePropertyModeScope deleteScope(vm, VM::DeletePropertyMode::IgnoreConfigurable);
DeletePropertySlot slot;
JSObject::deleteProperty(errorObject, globalObject, propertyName, slot);
}
RETURN_IF_EXCEPTION(scope, {});

errorObject->putDirect(vm, WebCore::builtinNames(vm).capturedStackTracePrivateName(), callSitesArray, 0);
errorObject->putDirectCustomAccessor(vm, vm.propertyNames->stack, globalObject->m_nonErrorLazyStackCustomGetterSetter.get(globalObject), JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::CustomAccessor | 0);
Comment thread
claude[bot] marked this conversation as resolved.
}

return JSC::JSValue::encode(JSC::jsUndefined());
Expand Down
2 changes: 2 additions & 0 deletions src/jsc/bindings/FormatStackTraceForJS.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ JSC_DECLARE_HOST_FUNCTION(jsFunctionDefaultErrorPrepareStackTrace);
// JSC Custom Accessors - error.stack getter/setter
JSC_DECLARE_CUSTOM_GETTER(errorInstanceLazyStackCustomGetter);
JSC_DECLARE_CUSTOM_SETTER(errorInstanceLazyStackCustomSetter);
JSC_DECLARE_CUSTOM_GETTER(nonErrorInstanceLazyStackCustomGetter);
JSC_DECLARE_CUSTOM_SETTER(nonErrorInstanceLazyStackCustomSetter);

// Internal wrapper functions for JSC error info callbacks
WTF::String computeErrorInfoWrapperToString(JSC::VM& vm, WTF::Vector<JSC::StackFrame>& stackTrace, unsigned int& line_in, unsigned int& column_in, WTF::String& sourceURL, void* bunErrorData);
Expand Down
5 changes: 5 additions & 0 deletions src/jsc/bindings/ZigGlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2112,6 +2112,11 @@ void GlobalObject::finishCreation(VM& vm)
init.set(CustomGetterSetter::create(init.vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter));
});

m_nonErrorLazyStackCustomGetterSetter.initLater(
[](const Initializer<CustomGetterSetter>& init) {
init.set(CustomGetterSetter::create(init.vm, nonErrorInstanceLazyStackCustomGetter, nonErrorInstanceLazyStackCustomSetter));
});

m_JSDOMFileConstructor.initLater(
[](const Initializer<JSObject>& init) {
JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
Expand Down
1 change: 1 addition & 0 deletions src/jsc/bindings/ZigGlobalObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@ class GlobalObject : public Bun::GlobalScope {
V(public, LazyPropertyOfGlobalObject<JSObject>, m_performanceObject) \
V(public, LazyPropertyOfGlobalObject<Bun::Process>, m_processObject) \
V(public, LazyPropertyOfGlobalObject<CustomGetterSetter>, m_lazyStackCustomGetterSetter) \
V(public, LazyPropertyOfGlobalObject<CustomGetterSetter>, m_nonErrorLazyStackCustomGetterSetter) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_ServerRouteListStructure) \
V(public, LazyPropertyOfGlobalObject<Structure>, m_JSBunRequestStructure) \
V(public, LazyPropertyOfGlobalObject<JSObject>, m_JSBunRequestParamsPrototype) \
Expand Down
Loading
Loading