aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/bindings/ZigGlobalObject.cpp
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 /src/bun.js/bindings/ZigGlobalObject.cpp
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>
Diffstat (limited to 'src/bun.js/bindings/ZigGlobalObject.cpp')
-rw-r--r--src/bun.js/bindings/ZigGlobalObject.cpp267
1 files changed, 184 insertions, 83 deletions
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);