diff options
author | 2023-09-20 05:52:59 -0700 | |
---|---|---|
committer | 2023-09-20 05:52:59 -0700 | |
commit | ff7f642099b2ce777347b125e802083d324d3cbd (patch) | |
tree | fd10a5207a64dd13ee6b02855a5c47c05ab597f5 /src/bun.js/bindings/ZigGlobalObject.cpp | |
parent | 1456c72648927e54ab78b00205917258672a4ecc (diff) | |
download | bun-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.cpp | 267 |
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); |