aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-09-20 05:52:59 -0700
committerGravatar GitHub <noreply@github.com> 2023-09-20 05:52:59 -0700
commitff7f642099b2ce777347b125e802083d324d3cbd (patch)
treefd10a5207a64dd13ee6b02855a5c47c05ab597f5
parent1456c72648927e54ab78b00205917258672a4ecc (diff)
downloadbun-ff7f642099b2ce777347b125e802083d324d3cbd.tar.gz
bun-ff7f642099b2ce777347b125e802083d324d3cbd.tar.zst
bun-ff7f642099b2ce777347b125e802083d324d3cbd.zip
Call `Error.prepareStackTrace` on `new Error().stack` (#5802)
* Always call `Error.prepareStackTrace` * Support node:vm * Remove this * Handle more cases --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r--src/bun.js/bindings/ErrorStackTrace.cpp76
-rw-r--r--src/bun.js/bindings/ErrorStackTrace.h12
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp267
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.h6
-rw-r--r--test/js/node/v8/capture-stack-trace.test.js69
-rw-r--r--test/js/node/vm/vm.test.ts2
6 files changed, 277 insertions, 155 deletions
diff --git a/src/bun.js/bindings/ErrorStackTrace.cpp b/src/bun.js/bindings/ErrorStackTrace.cpp
index be20f7737..f7013c9c4 100644
--- a/src/bun.js/bindings/ErrorStackTrace.cpp
+++ b/src/bun.js/bindings/ErrorStackTrace.cpp
@@ -131,8 +131,8 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor)
: m_vm(vm)
, m_codeBlock(nullptr)
, m_bytecodeIndex(JSC::BytecodeIndex())
- , m_sourceURL(nullptr)
- , m_functionName(nullptr)
+ , m_sourceURL()
+ , m_functionName()
, m_isWasmFrame(false)
, m_sourcePositionsState(SourcePositionsState::NotCalculated)
{
@@ -163,8 +163,8 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame)
, m_callFrame(nullptr)
, m_codeBlock(nullptr)
, m_bytecodeIndex(JSC::BytecodeIndex())
- , m_sourceURL(nullptr)
- , m_functionName(nullptr)
+ , m_sourceURL()
+ , m_functionName()
, m_isWasmFrame(false)
, m_sourcePositionsState(SourcePositionsState::NotCalculated)
{
@@ -193,7 +193,7 @@ JSC::JSString* JSCStackFrame::sourceURL()
m_sourceURL = retrieveSourceURL();
}
- return m_sourceURL;
+ return jsString(this->m_vm, m_sourceURL);
}
JSC::JSString* JSCStackFrame::functionName()
@@ -202,7 +202,7 @@ JSC::JSString* JSCStackFrame::functionName()
m_functionName = retrieveFunctionName();
}
- return m_functionName;
+ return jsString(this->m_vm, m_functionName);
}
JSC::JSString* JSCStackFrame::typeName()
@@ -211,7 +211,7 @@ JSC::JSString* JSCStackFrame::typeName()
m_typeName = retrieveTypeName();
}
- return m_typeName;
+ return jsString(this->m_vm, m_typeName);
}
JSCStackFrame::SourcePositions* JSCStackFrame::getSourcePositions()
@@ -223,91 +223,61 @@ JSCStackFrame::SourcePositions* JSCStackFrame::getSourcePositions()
return (SourcePositionsState::Calculated == m_sourcePositionsState) ? &m_sourcePositions : nullptr;
}
-ALWAYS_INLINE JSC::JSString* JSCStackFrame::retrieveSourceURL()
+ALWAYS_INLINE String JSCStackFrame::retrieveSourceURL()
{
static auto sourceURLWasmString = MAKE_STATIC_STRING_IMPL("[wasm code]");
static auto sourceURLNativeString = MAKE_STATIC_STRING_IMPL("[native code]");
if (m_isWasmFrame) {
- return jsOwnedString(m_vm, sourceURLWasmString);
+ return String(sourceURLWasmString);
}
if (!m_codeBlock) {
- return jsOwnedString(m_vm, sourceURLNativeString);
+ return String(sourceURLNativeString);
}
- String sourceURL = m_codeBlock->ownerExecutable()->sourceURL();
- return sourceURL.isNull() ? m_vm.smallStrings.emptyString() : JSC::jsString(m_vm, sourceURL);
+ return m_codeBlock->ownerExecutable()->sourceURL();
}
-ALWAYS_INLINE JSC::JSString* JSCStackFrame::retrieveFunctionName()
+ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName()
{
static auto functionNameEvalCodeString = MAKE_STATIC_STRING_IMPL("eval code");
static auto functionNameModuleCodeString = MAKE_STATIC_STRING_IMPL("module code");
static auto functionNameGlobalCodeString = MAKE_STATIC_STRING_IMPL("global code");
if (m_isWasmFrame) {
- return jsString(m_vm, JSC::Wasm::makeString(m_wasmFunctionIndexOrName));
+ return JSC::Wasm::makeString(m_wasmFunctionIndexOrName);
}
if (m_codeBlock) {
switch (m_codeBlock->codeType()) {
case JSC::EvalCode:
- return JSC::jsOwnedString(m_vm, functionNameEvalCodeString);
+ return String(functionNameEvalCodeString);
case JSC::ModuleCode:
- return JSC::jsOwnedString(m_vm, functionNameModuleCodeString);
+ return String(functionNameModuleCodeString);
case JSC::FunctionCode:
break;
case JSC::GlobalCode:
- return JSC::jsOwnedString(m_vm, functionNameGlobalCodeString);
+ return String(functionNameGlobalCodeString);
default:
ASSERT_NOT_REACHED();
}
}
- if (!m_callee || !m_callee->isObject()) {
- return m_vm.smallStrings.emptyString();
+ String name;
+ if (m_callee) {
+ if (m_callee->isObject())
+ name = getCalculatedDisplayName(m_vm, jsCast<JSObject*>(m_callee)).impl();
}
- JSC::JSObject* calleeAsObject = JSC::jsCast<JSC::JSObject*>(m_callee);
-
- // First, try the "displayName" property
- JSC::JSValue displayName = calleeAsObject->getDirect(m_vm, m_vm.propertyNames->displayName);
- if (displayName && isJSString(displayName)) {
- return JSC::asString(displayName);
- }
-
- // Our addition - if there's no "dispalyName" property, try the "name" property
- JSC::JSValue name = calleeAsObject->getDirect(m_vm, m_vm.propertyNames->name);
- if (name && isJSString(name)) {
- return JSC::asString(name);
- }
-
- /* For functions (either JSFunction or InternalFunction), fallback to their "native" name property.
- * Based on JSC::getCalculatedDisplayName, "inlining" the
- * JSFunction::calculatedDisplayName\InternalFunction::calculatedDisplayName calls */
- if (JSC::JSFunction* function = JSC::jsDynamicCast<JSC::JSFunction*>(calleeAsObject)) {
- // Based on JSC::JSFunction::calculatedDisplayName, skipping the "displayName" property check
- WTF::String actualName = function->name(m_vm);
- if (!actualName.isEmpty() || function->isHostOrBuiltinFunction()) {
- return JSC::jsString(m_vm, actualName);
- }
-
- return JSC::jsString(m_vm, function->jsExecutable()->name().string());
- }
- if (JSC::InternalFunction* function = JSC::jsDynamicCast<JSC::InternalFunction*>(calleeAsObject)) {
- // Based on JSC::InternalFunction::calculatedDisplayName, skipping the "displayName" property check
- return JSC::jsString(m_vm, function->name());
- }
-
- return m_vm.smallStrings.emptyString();
+ return name.isNull() ? emptyString() : name;
}
-ALWAYS_INLINE JSC::JSString* JSCStackFrame::retrieveTypeName()
+ALWAYS_INLINE String JSCStackFrame::retrieveTypeName()
{
JSC::JSObject* calleeObject = JSC::jsCast<JSC::JSObject*>(m_callee);
// return JSC::jsTypeStringForValue(m_globalObjectcalleeObject->toThis()
- return jsString(m_vm, makeString(calleeObject->className()));
+ return calleeObject->className();
}
// General flow here is based on JSC's appendSourceToError (ErrorInstance.cpp)
diff --git a/src/bun.js/bindings/ErrorStackTrace.h b/src/bun.js/bindings/ErrorStackTrace.h
index 1284376a4..ac40eaf4b 100644
--- a/src/bun.js/bindings/ErrorStackTrace.h
+++ b/src/bun.js/bindings/ErrorStackTrace.h
@@ -61,9 +61,9 @@ private:
JSC::BytecodeIndex m_bytecodeIndex;
// Lazy-initialized
- JSC::JSString* m_sourceURL;
- JSC::JSString* m_functionName;
- JSC::JSString* m_typeName;
+ WTF::String m_sourceURL;
+ WTF::String m_functionName;
+ WTF::String m_typeName;
// m_wasmFunctionIndexOrName has meaning only when m_isWasmFrame is set
JSC::Wasm::IndexOrName m_wasmFunctionIndexOrName;
@@ -105,7 +105,7 @@ public:
bool isConstructor() const { return m_codeBlock && (JSC::CodeForConstruct == m_codeBlock->specializationKind()); }
private:
- ALWAYS_INLINE JSC::JSString* retrieveSourceURL();
+ ALWAYS_INLINE String retrieveSourceURL();
/* Regarding real functions (not eval\module\global code), both v8 and JSC seem to follow
* the same logic, which is to first try the function's "display name", and if it's not defined,
@@ -119,9 +119,9 @@ private:
* and just try to use the "name" property when needed, so our lookup will be:
* "display name" property -> "name" property -> JSFunction\InternalFunction "name" methods.
*/
- ALWAYS_INLINE JSC::JSString* retrieveFunctionName();
+ ALWAYS_INLINE String retrieveFunctionName();
- ALWAYS_INLINE JSC::JSString* retrieveTypeName();
+ ALWAYS_INLINE String retrieveTypeName();
bool calculateSourcePositions();
};
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp
index 9b36157fe..2f3aa2209 100644
--- a/src/bun.js/bindings/ZigGlobalObject.cpp
+++ b/src/bun.js/bindings/ZigGlobalObject.cpp
@@ -315,29 +315,16 @@ static bool skipNextComputeErrorInfo = false;
// error.stack calls this function
static String computeErrorInfoWithoutPrepareStackTrace(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned& line, unsigned& column, String& sourceURL, JSObject* errorInstance)
{
- if (!errorInstance) {
- return String();
- }
-
- if (skipNextComputeErrorInfo) {
- return String();
- }
-
- Zig::GlobalObject* globalObject = jsDynamicCast<Zig::GlobalObject*>(errorInstance->globalObject());
- if (!globalObject) {
- // Happens in node:vm
- globalObject = jsDynamicCast<Zig::GlobalObject*>(Bun__getDefaultGlobal());
- }
+ auto* lexicalGlobalObject = errorInstance->globalObject();
+ Zig::GlobalObject* globalObject = jsDynamicCast<Zig::GlobalObject*>(lexicalGlobalObject);
WTF::String name = "Error"_s;
WTF::String message;
- if (errorInstance) {
- // Note that we are not allowed to allocate memory in here. It's called inside a finalizer.
- if (auto* instance = jsDynamicCast<ErrorInstance*>(errorInstance)) {
- name = instance->sanitizedNameString(globalObject);
- message = instance->sanitizedMessageString(globalObject);
- }
+ // Note that we are not allowed to allocate memory in here. It's called inside a finalizer.
+ if (auto* instance = jsDynamicCast<ErrorInstance*>(errorInstance)) {
+ name = instance->sanitizedNameString(lexicalGlobalObject);
+ message = instance->sanitizedMessageString(lexicalGlobalObject);
}
WTF::StringBuilder sb;
@@ -391,20 +378,23 @@ static String computeErrorInfoWithoutPrepareStackTrace(JSC::VM& vm, Vector<Stack
unsigned int thisColumn = 0;
frame.computeLineAndColumn(thisLine, thisColumn);
memset(remappedFrames + i, 0, sizeof(ZigStackFrame));
-
remappedFrames[i].position.line = thisLine;
remappedFrames[i].position.column_start = thisColumn;
+
String sourceURLForFrame = frame.sourceURL(vm);
- if (!sourceURLForFrame.isEmpty()) {
- remappedFrames[i].source_url = Bun::toString(sourceURLForFrame);
- } else {
- // https://github.com/oven-sh/bun/issues/3595
- remappedFrames[i].source_url = BunStringEmpty;
- }
+ // If it's not a Zig::GlobalObject, don't bother source-mapping it.
+ if (globalObject) {
+ if (!sourceURLForFrame.isEmpty()) {
+ remappedFrames[i].source_url = Bun::toString(sourceURLForFrame);
+ } else {
+ // https://github.com/oven-sh/bun/issues/3595
+ remappedFrames[i].source_url = BunStringEmpty;
+ }
- // This ensures the lifetime of the sourceURL is accounted for correctly
- Bun__remapStackFramePositions(globalObject, remappedFrames + i, 1);
+ // This ensures the lifetime of the sourceURL is accounted for correctly
+ Bun__remapStackFramePositions(globalObject, remappedFrames + i, 1);
+ }
if (!hasSet) {
hasSet = true;
@@ -412,11 +402,9 @@ static String computeErrorInfoWithoutPrepareStackTrace(JSC::VM& vm, Vector<Stack
column = thisColumn;
sourceURL = frame.sourceURL(vm);
- if (errorInstance) {
- if (remappedFrames[i].remapped) {
- errorInstance->putDirect(vm, Identifier::fromString(vm, "originalLine"_s), jsNumber(thisLine), 0);
- errorInstance->putDirect(vm, Identifier::fromString(vm, "originalColumn"_s), jsNumber(thisColumn), 0);
- }
+ if (remappedFrames[i].remapped) {
+ errorInstance->putDirect(vm, Identifier::fromString(vm, "originalLine"_s), jsNumber(thisLine), 0);
+ errorInstance->putDirect(vm, Identifier::fromString(vm, "originalColumn"_s), jsNumber(thisColumn), 0);
}
}
@@ -438,8 +426,95 @@ static String computeErrorInfoWithoutPrepareStackTrace(JSC::VM& vm, Vector<Stack
return sb.toString();
}
+static String computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector<StackFrame>& stackFrames, unsigned& line, unsigned& column, String& sourceURL, JSObject* errorObject, JSObject* prepareStackTrace)
+{
+ auto scope = DECLARE_THROW_SCOPE(vm);
+ size_t stackTraceLimit = globalObject->stackTraceLimit().value();
+ if (stackTraceLimit == 0) {
+ stackTraceLimit = DEFAULT_ERROR_STACK_TRACE_LIMIT;
+ }
+
+ JSCStackTrace stackTrace = JSCStackTrace::fromExisting(vm, stackFrames);
+
+ // Note: we cannot use tryCreateUninitializedRestricted here because we cannot allocate memory inside initializeIndex()
+ JSC::JSArray* callSites = JSC::JSArray::create(vm,
+ globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous),
+ stackTrace.size());
+
+ // Create the call sites (one per frame)
+ GlobalObject::createCallSitesFromFrames(globalObject, lexicalGlobalObject, stackTrace, callSites);
+
+ // We need to sourcemap it if it's a GlobalObject.
+ if (globalObject == lexicalGlobalObject) {
+ size_t framesCount = stackTrace.size();
+ ZigStackFrame remappedFrames[framesCount];
+ for (int i = 0; i < framesCount; i++) {
+ memset(remappedFrames + i, 0, sizeof(ZigStackFrame));
+ remappedFrames[i].source_url = Bun::toString(lexicalGlobalObject, stackTrace.at(i).sourceURL());
+ if (JSCStackFrame::SourcePositions* sourcePositions = stackTrace.at(i).getSourcePositions()) {
+ remappedFrames[i].position.line = sourcePositions->line.zeroBasedInt();
+ remappedFrames[i].position.column_start = sourcePositions->startColumn.zeroBasedInt() + 1;
+ } else {
+ remappedFrames[i].position.line = -1;
+ remappedFrames[i].position.column_start = -1;
+ }
+ }
+
+ Bun__remapStackFramePositions(globalObject, remappedFrames, framesCount);
+
+ for (size_t i = 0; i < framesCount; i++) {
+ JSC::JSValue callSiteValue = callSites->getIndex(lexicalGlobalObject, i);
+ CallSite* callSite = JSC::jsDynamicCast<CallSite*>(callSiteValue);
+ if (remappedFrames[i].remapped) {
+ int32_t remappedColumnStart = remappedFrames[i].position.column_start;
+ JSC::JSValue columnNumber = JSC::jsNumber(remappedColumnStart);
+ callSite->setColumnNumber(columnNumber);
+
+ int32_t remappedLine = remappedFrames[i].position.line;
+ JSC::JSValue lineNumber = JSC::jsNumber(remappedLine);
+ callSite->setLineNumber(lineNumber);
+ }
+ }
+ }
+
+ globalObject->formatStackTrace(vm, lexicalGlobalObject, errorObject, callSites, prepareStackTrace);
+
+ RETURN_IF_EXCEPTION(scope, String());
+ return String();
+}
+
static String computeErrorInfo(JSC::VM& vm, Vector<StackFrame>& stackTrace, unsigned& line, unsigned& column, String& sourceURL, JSObject* errorInstance)
{
+ if (skipNextComputeErrorInfo) {
+ return String();
+ }
+
+ if (!errorInstance) {
+ return String();
+ }
+
+ auto* lexicalGlobalObject = errorInstance->globalObject();
+ Zig::GlobalObject* globalObject = jsDynamicCast<Zig::GlobalObject*>(lexicalGlobalObject);
+
+ // Error.prepareStackTrace - https://v8.dev/docs/stack-trace-api#customizing-stack-traces
+ if (!globalObject) {
+ // node:vm will use a different JSGlobalObject
+ globalObject = Bun__getDefaultGlobal();
+
+ auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(lexicalGlobalObject);
+ if (JSValue prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "prepareStackTrace"_s))) {
+ if (prepareStackTrace.isCell() && prepareStackTrace.isObject() && prepareStackTrace.isCallable()) {
+ return computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject());
+ }
+ }
+ } else {
+ if (JSValue prepareStackTrace = globalObject->m_errorConstructorPrepareStackTraceValue.get()) {
+ if (prepareStackTrace.isCell() && prepareStackTrace.isObject() && prepareStackTrace.isCallable()) {
+ return computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject());
+ }
+ }
+ }
+
return computeErrorInfoWithoutPrepareStackTrace(vm, stackTrace, line, column, sourceURL, errorInstance);
}
@@ -806,6 +881,28 @@ void GlobalObject::setConsole(void* console)
this->setConsoleClient(new Zig::ConsoleClient(console));
}
+JSC_DEFINE_CUSTOM_GETTER(errorConstructorPrepareStackTraceGetter,
+ (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue,
+ JSC::PropertyName))
+{
+ Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSValue value = jsUndefined();
+ if (thisObject->m_errorConstructorPrepareStackTraceValue) {
+ value = thisObject->m_errorConstructorPrepareStackTraceValue.get();
+ }
+ return JSValue::encode(value);
+}
+
+JSC_DEFINE_CUSTOM_SETTER(errorConstructorPrepareStackTraceSetter,
+ (JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue,
+ JSC::EncodedJSValue encodedValue, JSC::PropertyName property))
+{
+ auto& vm = JSC::getVM(lexicalGlobalObject);
+ Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(lexicalGlobalObject);
+ thisObject->m_errorConstructorPrepareStackTraceValue.set(vm, thisObject, JSValue::decode(encodedValue));
+ return true;
+}
+
#pragma mark - Globals
JSC_DEFINE_CUSTOM_GETTER(globalOnMessage,
@@ -2441,7 +2538,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionPerformMicrotaskVariadic, (JSGlobalObject * g
return JSValue::encode(jsUndefined());
}
-void GlobalObject::createCallSitesFromFrames(JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, JSC::JSArray* callSites)
+void GlobalObject::createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, JSC::JSArray* callSites)
{
/* From v8's "Stack Trace API" (https://github.com/v8/v8/wiki/Stack-Trace-API):
* "To maintain restrictions imposed on strict mode functions, frames that have a
@@ -2449,10 +2546,11 @@ void GlobalObject::createCallSitesFromFrames(JSC::JSGlobalObject* lexicalGlobalO
* their receiver and function objects. For those frames, getFunction() and getThis()
* will return undefined."." */
bool encounteredStrictFrame = false;
- GlobalObject* globalObject = reinterpret_cast<GlobalObject*>(lexicalGlobalObject);
+ // TODO: is it safe to use CallSite structure from a different JSGlobalObject? This case would happen within a node:vm
JSC::Structure* callSiteStructure = globalObject->callSiteStructure();
size_t framesCount = stackTrace.size();
+
for (size_t i = 0; i < framesCount; i++) {
CallSite* callSite = CallSite::create(lexicalGlobalObject, callSiteStructure, stackTrace.at(i), encounteredStrictFrame);
callSites->putDirectIndex(lexicalGlobalObject, i, callSite);
@@ -2463,40 +2561,19 @@ void GlobalObject::createCallSitesFromFrames(JSC::JSGlobalObject* lexicalGlobalO
}
}
-JSC::JSValue GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites)
+void GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStackTrace)
{
auto scope = DECLARE_THROW_SCOPE(vm);
- JSValue errorValue = this->get(this, JSC::Identifier::fromString(vm, "Error"_s));
- if (UNLIKELY(scope.exception())) {
- return JSValue();
- }
-
- if (!errorValue || errorValue.isUndefined() || !errorValue.isObject()) {
- return JSValue(jsEmptyString(vm));
- }
-
- auto* errorConstructor = jsDynamicCast<JSC::JSObject*>(errorValue);
-
- /* If the user has set a callable Error.prepareStackTrace - use it to format the stack trace. */
- JSC::JSValue prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "prepareStackTrace"_s));
- if (prepareStackTrace && prepareStackTrace.isCallable()) {
- JSC::CallData prepareStackTraceCallData = JSC::getCallData(prepareStackTrace);
- if (prepareStackTraceCallData.type != JSC::CallData::Type::None) {
- JSC::MarkedArgumentBuffer arguments;
- arguments.append(errorObject);
- arguments.append(callSites);
- ASSERT(!arguments.hasOverflowed());
+ auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(this);
- JSC::JSValue result = profiledCall(
- lexicalGlobalObject,
- JSC::ProfilingReason::Other,
- prepareStackTrace,
- prepareStackTraceCallData,
- errorConstructor,
- arguments);
- RETURN_IF_EXCEPTION(scope, JSC::jsUndefined());
- return result;
+ if (!prepareStackTrace) {
+ if (lexicalGlobalObject->inherits<Zig::GlobalObject>()) {
+ if (auto prepare = this->m_errorConstructorPrepareStackTraceValue.get()) {
+ prepareStackTrace = prepare;
+ }
+ } else {
+ prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "prepareStackTrace"_s));
}
}
@@ -2525,7 +2602,43 @@ JSC::JSValue GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* le
}
}
- return JSC::JSValue(jsString(vm, sb.toString()));
+ bool orignialSkipNextComputeErrorInfo = skipNextComputeErrorInfo;
+ skipNextComputeErrorInfo = true;
+ if (errorObject->hasProperty(lexicalGlobalObject, vm.propertyNames->stack)) {
+ skipNextComputeErrorInfo = true;
+ errorObject->deleteProperty(lexicalGlobalObject, vm.propertyNames->stack);
+ }
+ skipNextComputeErrorInfo = orignialSkipNextComputeErrorInfo;
+
+ // In Node, if you console.log(error.stack) inside Error.prepareStackTrace
+ // it will display the stack as a formatted string, so we have to do the same.
+ errorObject->putDirect(vm, vm.propertyNames->stack, JSC::JSValue(jsString(vm, sb.toString())), 0);
+
+ if (prepareStackTrace && prepareStackTrace.isCallable()) {
+ JSC::CallData prepareStackTraceCallData = JSC::getCallData(prepareStackTrace);
+
+ if (prepareStackTraceCallData.type != JSC::CallData::Type::None) {
+ JSC::MarkedArgumentBuffer arguments;
+ arguments.append(errorObject);
+ arguments.append(callSites);
+
+ JSC::JSValue result = profiledCall(
+ lexicalGlobalObject,
+ JSC::ProfilingReason::Other,
+ prepareStackTrace,
+ prepareStackTraceCallData,
+ errorConstructor,
+ arguments);
+
+ RETURN_IF_EXCEPTION(scope, void());
+
+ if (result.isUndefinedOrNull()) {
+ result = jsUndefined();
+ }
+
+ errorObject->putDirect(vm, vm.propertyNames->stack, result, 0);
+ }
+ }
}
JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
@@ -2581,7 +2694,7 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalOb
stackTrace.size());
// Create the call sites (one per frame)
- GlobalObject::createCallSitesFromFrames(lexicalGlobalObject, stackTrace, callSites);
+ GlobalObject::createCallSitesFromFrames(globalObject, lexicalGlobalObject, stackTrace, callSites);
/* Format the stack trace.
* Note that v8 won't actually format the stack trace here, but will create a "stack" accessor
@@ -2623,23 +2736,9 @@ JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalOb
}
}
- JSC::JSValue formattedStackTrace = globalObject->formatStackTrace(vm, lexicalGlobalObject, errorObject, callSites);
+ globalObject->formatStackTrace(vm, lexicalGlobalObject, errorObject, callSites, JSC::JSValue());
RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode({}));
- bool orignialSkipNextComputeErrorInfo = skipNextComputeErrorInfo;
- skipNextComputeErrorInfo = true;
- if (errorObject->hasProperty(lexicalGlobalObject, vm.propertyNames->stack)) {
- skipNextComputeErrorInfo = true;
- errorObject->deleteProperty(lexicalGlobalObject, vm.propertyNames->stack);
- }
- skipNextComputeErrorInfo = orignialSkipNextComputeErrorInfo;
-
- if (formattedStackTrace.isUndefinedOrNull()) {
- formattedStackTrace = JSC::jsUndefined();
- }
-
- errorObject->putDirect(vm, vm.propertyNames->stack, formattedStackTrace, 0);
-
if (auto* instance = jsDynamicCast<JSC::ErrorInstance*>(errorObject)) {
// we make a separate copy of the StackTrace unfortunately so that we
// can later console.log it without losing the info
@@ -3572,11 +3671,12 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
JSC::JSObject* errorConstructor = this->errorConstructor();
errorConstructor->putDirectNativeFunction(vm, this, JSC::Identifier::fromString(vm, "captureStackTrace"_s), 2, errorConstructorFuncCaptureStackTrace, ImplementationVisibility::Public, JSC::NoIntrinsic, PropertyAttribute::DontEnum | 0);
errorConstructor->putDirectNativeFunction(vm, this, JSC::Identifier::fromString(vm, "appendStackTrace"_s), 2, errorConstructorFuncAppendStackTrace, ImplementationVisibility::Private, JSC::NoIntrinsic, PropertyAttribute::DontEnum | 0);
+ errorConstructor->putDirectCustomAccessor(vm, JSC::Identifier::fromString(vm, "prepareStackTrace"_s), JSC::CustomGetterSetter::create(vm, errorConstructorPrepareStackTraceGetter, errorConstructorPrepareStackTraceSetter), PropertyAttribute::DontEnum | 0);
JSC::JSObject* consoleObject = this->get(this, JSC::Identifier::fromString(vm, "console"_s)).getObject();
consoleObject->putDirectBuiltinFunction(vm, this, vm.propertyNames->asyncIteratorSymbol, consoleObjectAsyncIteratorCodeGenerator(vm), PropertyAttribute::Builtin | 0);
consoleObject->putDirectBuiltinFunction(vm, this, clientData->builtinNames().writePublicName(), consoleObjectWriteCodeGenerator(vm), PropertyAttribute::Builtin | 0);
- consoleObject->putDirectCustomAccessor(vm, Identifier::fromString(vm, "Console"_s), CustomGetterSetter::create(vm, getConsoleConstructor, nullptr), 0);
+ consoleObject->putDirectCustomAccessor(vm, Identifier::fromString(vm, "Console"_s), CustomGetterSetter::create(vm, getConsoleConstructor, noop_setter), 0);
}
extern "C" bool JSC__JSGlobalObject__startRemoteInspector(JSC__JSGlobalObject* globalObject, unsigned char* host, uint16_t arg1)
@@ -3640,6 +3740,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
visitor.append(thisObject->m_nodeModuleOverriddenResolveFilename);
visitor.append(thisObject->m_nextTickQueue);
+ visitor.append(thisObject->m_errorConstructorPrepareStackTraceValue);
thisObject->m_JSArrayBufferSinkClassStructure.visit(visitor);
thisObject->m_JSBufferListClassStructure.visit(visitor);
diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h
index ad5138527..031dc4a13 100644
--- a/src/bun.js/bindings/ZigGlobalObject.h
+++ b/src/bun.js/bindings/ZigGlobalObject.h
@@ -152,8 +152,8 @@ public:
void clearDOMGuardedObjects();
- static void createCallSitesFromFrames(JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, JSC::JSArray* callSites);
- JSC::JSValue formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites);
+ static void createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, JSC::JSArray* callSites);
+ void formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStack = JSC::jsUndefined());
static void reportUncaughtExceptionAtEventLoop(JSGlobalObject*, JSC::Exception*);
static JSGlobalObject* deriveShadowRealmGlobalObject(JSGlobalObject* globalObject);
@@ -351,6 +351,8 @@ public:
// mutable WriteBarrier<Unknown> m_JSBunDebuggerValue;
mutable WriteBarrier<JSFunction> m_thenables[promiseFunctionsSize + 1];
+ mutable WriteBarrier<JSC::Unknown> m_errorConstructorPrepareStackTraceValue;
+
Structure* memoryFootprintStructure()
{
return m_memoryFootprintStructure.getInitializedOnMainThread(this);
diff --git a/test/js/node/v8/capture-stack-trace.test.js b/test/js/node/v8/capture-stack-trace.test.js
index cb2624681..c03491b33 100644
--- a/test/js/node/v8/capture-stack-trace.test.js
+++ b/test/js/node/v8/capture-stack-trace.test.js
@@ -17,6 +17,17 @@ test("Regular .stack", () => {
expect(err.stack).toMatch(/at new Foo/);
});
+test("throw inside Error.prepareStackTrace doesnt crash", () => {
+
+ Error.prepareStackTrace = function (err, stack) {
+ Error.prepareStackTrace = null;
+ throw new Error('wat');
+ };
+
+expect(() => new Error().stack).toThrow("wat");
+
+});
+
test("capture stack trace", () => {
function f1() {
f2();
@@ -460,14 +471,52 @@ test("CallFrame.p.toString", () => {
expect(e.stack[0].toString().includes("<anonymous>")).toBe(true);
});
-test.todo("err.stack should invoke prepareStackTrace", () => {
- // This is V8's behavior.
- let prevPrepareStackTrace = Error.prepareStackTrace;
- let wasCalled = false;
- Error.prepareStackTrace = (e, s) => {
- wasCalled = true;
- };
- const e = new Error();
- e.stack;
- expect(wasCalled).toBe(true);
+test("err.stack should invoke prepareStackTrace", () => {
+ var lineNumber = -1;
+ var functionName = "";
+ var parentLineNumber = -1;
+ function functionWithAName() {
+ // This is V8's behavior.
+ let prevPrepareStackTrace = Error.prepareStackTrace;
+
+ Error.prepareStackTrace = (e, s) => {
+ lineNumber = s[0].getLineNumber();
+ functionName = s[0].getFunctionName();
+ parentLineNumber = s[1].getLineNumber();
+ expect(s[0].getFileName().includes("capture-stack-trace.test.js")).toBe(true);
+ expect(s[1].getFileName().includes("capture-stack-trace.test.js")).toBe(true);
+ };
+ const e = new Error();
+ e.stack;
+ Error.prepareStackTrace = prevPrepareStackTrace;
+ }
+
+ functionWithAName();
+
+ expect(functionName).toBe("functionWithAName");
+ expect(lineNumber).toBe(490);
+ // TODO: this is wrong
+ expect(parentLineNumber).toBe(499);
+});
+
+
+test("Error.prepareStackTrace inside a node:vm works", () => {
+ const {runInNewContext} = require("node:vm");
+ Error.prepareStackTrace = null;
+ const result = runInNewContext(
+ `
+ Error.prepareStackTrace = (err, stack) => {
+ if (typeof err.stack !== "string") {
+ throw new Error("err.stack is not a string");
+ }
+
+ return "custom stack trace";
+ };
+
+ const err = new Error();
+ err.stack;
+ `
+ )
+ expect(result).toBe("custom stack trace");
+ expect(Error.prepareStackTrace).toBeNull();
});
diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts
index 510448c5e..79becfed0 100644
--- a/test/js/node/vm/vm.test.ts
+++ b/test/js/node/vm/vm.test.ts
@@ -101,7 +101,7 @@ function testRunInContext(
expect(typeof result).toBe("function");
expect(result()).toBe("bar");
});
- test.skip("can throw a syntax error", () => {
+ test("can throw a syntax error", () => {
const context = createContext({});
const result = () => fn("!?", context);
expect(result).toThrow({