#include "root.h" #include "FormatStackTraceForJS.h" #include "ZigGlobalObject.h" #include "helpers.h" #include "JavaScriptCore/ArgList.h" #include "JavaScriptCore/CallData.h" #include "JavaScriptCore/TopExceptionScope.h" #include "JavaScriptCore/Error.h" #include "JavaScriptCore/ErrorInstance.h" #include "JavaScriptCore/ExceptionScope.h" #include "JavaScriptCore/Identifier.h" #include "JavaScriptCore/JSArray.h" #include "JavaScriptCore/JSCast.h" #include "JavaScriptCore/JSCJSValue.h" #include "JavaScriptCore/JSObject.h" #include "JavaScriptCore/JSString.h" #include "JavaScriptCore/StackFrame.h" #include "JavaScriptCore/VM.h" #include "BunClientData.h" #include "CallSite.h" #include "ErrorStackTrace.h" #include "headers-handwritten.h" using namespace JSC; using namespace WebCore; namespace Bun { static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites) { auto scope = DECLARE_THROW_SCOPE(vm); // default formatting size_t framesCount = callSites->length(); WTF::StringBuilder sb; auto errorMessage = errorObject->getIfPropertyExists(lexicalGlobalObject, vm.propertyNames->message); RETURN_IF_EXCEPTION(scope, {}); if (errorMessage) { 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); } } else { sb.append("Error"_s); } for (size_t i = 0; i < framesCount; i++) { sb.append("\n at "_s); JSC::JSValue callSiteValue = callSites->getIndex(lexicalGlobalObject, i); RETURN_IF_EXCEPTION(scope, {}); if (CallSite* callSite = JSC::jsDynamicCast(callSiteValue)) { callSite->formatAsString(vm, lexicalGlobalObject, sb); RETURN_IF_EXCEPTION(scope, {}); } else { // This matches Node.js / V8's behavior // It can become "at [object Object]" if the object is not a CallSite auto* str = callSiteValue.toString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); auto value = str->value(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); sb.append(value.data); } } return jsString(vm, sb.toString()); } static JSValue formatStackTraceToJSValue(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites, JSValue prepareStackTrace) { auto scope = DECLARE_THROW_SCOPE(vm); auto stackStringValue = formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSites); RETURN_IF_EXCEPTION(scope, {}); if (prepareStackTrace && prepareStackTrace.isObject()) { JSC::CallData prepareStackTraceCallData = JSC::getCallData(prepareStackTrace); if (prepareStackTraceCallData.type != JSC::CallData::Type::None) { // 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, stackStringValue, 0); JSC::MarkedArgumentBuffer arguments; arguments.append(errorObject); arguments.append(callSites); JSC::JSValue result = profiledCall( lexicalGlobalObject, JSC::ProfilingReason::Other, prepareStackTrace, prepareStackTraceCallData, lexicalGlobalObject->m_errorStructure.constructor(globalObject), arguments); RETURN_IF_EXCEPTION(scope, stackStringValue); if (result.isUndefinedOrNull()) { result = jsUndefined(); } return result; } } return stackStringValue; } static JSValue formatStackTraceToJSValueWithoutPrepareStackTrace(JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* errorObject, JSC::JSArray* callSites) { JSValue prepareStackTrace = {}; if (lexicalGlobalObject->inherits()) { if (auto prepare = globalObject->m_errorConstructorPrepareStackTraceValue.get()) { prepareStackTrace = prepare; } } else { auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm); auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(globalObject); prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, JSC::Identifier::fromString(vm, "prepareStackTrace"_s)); CLEAR_IF_EXCEPTION(scope); } return formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSites, prepareStackTrace); } WTF::String formatStackTrace( JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, const WTF::String& name, const WTF::String& message, OrdinalNumber& line, OrdinalNumber& column, WTF::String& sourceURL, Vector& stackTrace, JSC::JSObject* errorInstance) { WTF::StringBuilder sb; if (!name.isEmpty()) { sb.append(name); if (!message.isEmpty()) { sb.append(": "_s); sb.append(message); } } else if (!message.isEmpty()) { sb.append(message); } // FIXME: why can size == 6 and capacity == 0? // https://discord.com/channels/876711213126520882/1174901590457585765/1174907969419350036 size_t framesCount = stackTrace.size(); bool hasSet = false; void* bunVM = nullptr; const auto getBunVM = [&]() -> void* { if (!bunVM) { bunVM = clientData(vm)->bunVM; } return bunVM; }; if (errorInstance) { if (JSC::ErrorInstance* err = jsDynamicCast(errorInstance)) { if (err->errorType() == ErrorType::SyntaxError && (stackTrace.isEmpty() || stackTrace.at(0).sourceURL(vm) != err->sourceURL())) { // There appears to be an off-by-one error. // The following reproduces the issue: // /* empty comment */ // "".test(/[a-0]/); auto originalLine = WTF::OrdinalNumber::fromOneBasedInt(err->line()); ZigStackFrame remappedFrame = {}; memset(&remappedFrame, 0, sizeof(ZigStackFrame)); remappedFrame.position.line_zero_based = originalLine.zeroBasedInt(); remappedFrame.position.column_zero_based = 0; String sourceURLForFrame = err->sourceURL(); // If it's not a Zig::GlobalObject, don't bother source-mapping it. if (globalObject && !sourceURLForFrame.isEmpty()) { // https://github.com/oven-sh/bun/issues/3595 if (!sourceURLForFrame.isEmpty()) { remappedFrame.source_url = Bun::toStringRef(sourceURLForFrame); // This ensures the lifetime of the sourceURL is accounted for correctly Bun__remapStackFramePositions(getBunVM(), &remappedFrame, 1); sourceURLForFrame = remappedFrame.source_url.toWTFString(); } } // there is always a newline before each stack frame line, ensuring that the name + message // exist on the first line, even if both are empty sb.append("\n"_s); sb.append(" at ("_s); sb.append(remappedFrame.source_url.toWTFString()); if (remappedFrame.remapped) { errorInstance->putDirect(vm, builtinNames(vm).originalLinePublicName(), jsNumber(originalLine.oneBasedInt()), PropertyAttribute::DontEnum | 0); hasSet = true; line = remappedFrame.position.line(); } if (remappedFrame.remapped) { sb.append(':'); sb.append(remappedFrame.position.line().oneBasedInt()); } else { sb.append(':'); sb.append(originalLine.oneBasedInt()); } sb.append(')'); } } } if (framesCount == 0) { ASSERT(stackTrace.isEmpty()); return sb.toString(); } sb.append("\n"_s); for (size_t i = 0; i < framesCount; i++) { StackFrame& frame = stackTrace.at(i); unsigned int flags = static_cast(FunctionNameFlags::AddNewKeyword); // -- get the data we need to render the text -- JSC::JSGlobalObject* globalObjectForFrame = lexicalGlobalObject; if (frame.hasLineAndColumnInfo()) { auto* callee = frame.callee(); if (callee) { if (auto* object = callee->getObject()) { globalObjectForFrame = object->globalObject(); } } } WTF::String functionName = Zig::functionName(vm, globalObjectForFrame, frame, errorInstance ? Zig::FinalizerSafety::NotInFinalizer : Zig::FinalizerSafety::MustNotTriggerGC, &flags); OrdinalNumber originalLine = {}; OrdinalNumber originalColumn = {}; OrdinalNumber displayLine = {}; OrdinalNumber displayColumn = {}; WTF::String sourceURLForFrame; if (frame.hasLineAndColumnInfo()) { ZigStackFrame remappedFrame = {}; LineColumn lineColumn = frame.computeLineAndColumn(); originalLine = OrdinalNumber::fromOneBasedInt(lineColumn.line); originalColumn = OrdinalNumber::fromOneBasedInt(lineColumn.column); displayLine = originalLine; displayColumn = originalColumn; remappedFrame.position.line_zero_based = originalLine.zeroBasedInt(); remappedFrame.position.column_zero_based = originalColumn.zeroBasedInt(); sourceURLForFrame = Zig::sourceURL(vm, frame); bool isDefinitelyNotRunninginNodeVMGlobalObject = globalObject == globalObjectForFrame; bool isDefaultGlobalObjectInAFinalizer = (globalObject && !lexicalGlobalObject && !errorInstance); if (isDefinitelyNotRunninginNodeVMGlobalObject || isDefaultGlobalObjectInAFinalizer) { // https://github.com/oven-sh/bun/issues/3595 if (!sourceURLForFrame.isEmpty()) { remappedFrame.source_url = Bun::toStringRef(sourceURLForFrame); // This ensures the lifetime of the sourceURL is accounted for correctly Bun__remapStackFramePositions(getBunVM(), &remappedFrame, 1); sourceURLForFrame = remappedFrame.source_url.toWTFString(); } } displayLine = remappedFrame.position.line(); displayColumn = remappedFrame.position.column(); if (!hasSet) { hasSet = true; line = remappedFrame.position.line(); column = remappedFrame.position.column(); sourceURL = sourceURLForFrame; if (remappedFrame.remapped) { if (errorInstance) { errorInstance->putDirect(vm, builtinNames(vm).originalLinePublicName(), jsNumber(originalLine.oneBasedInt()), PropertyAttribute::DontEnum | 0); errorInstance->putDirect(vm, builtinNames(vm).originalColumnPublicName(), jsNumber(originalColumn.oneBasedInt()), PropertyAttribute::DontEnum | 0); } } } } if (functionName.isEmpty()) { if (flags & (static_cast(FunctionNameFlags::Eval) | static_cast(FunctionNameFlags::Function))) { functionName = ""_s; } } if (sourceURLForFrame.isEmpty()) { if (flags & static_cast(FunctionNameFlags::Builtin)) { sourceURLForFrame = "native"_s; } else { sourceURLForFrame = "unknown"_s; } } // --- actually render the text --- sb.append(" at "_s); if (!functionName.isEmpty()) { if (frame.isAsyncFrame()) { sb.append("async "_s); } sb.append(functionName); sb.append(" ("_s); } if (!sourceURLForFrame.isEmpty()) { sb.append(sourceURLForFrame); if (displayLine.zeroBasedInt() > 0 || displayColumn.zeroBasedInt() > 0) { sb.append(':'); sb.append(displayLine.oneBasedInt()); if (displayColumn.zeroBasedInt() > 0) { sb.append(':'); sb.append(displayColumn.oneBasedInt()); } } } if (!functionName.isEmpty()) { sb.append(')'); } if (i != framesCount - 1) { sb.append("\n"_s); } } return sb.toString(); } // error.stack calls this function static String computeErrorInfoWithoutPrepareStackTrace( JSC::VM& vm, Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, Vector& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorInstance) { auto scope = DECLARE_THROW_SCOPE(vm); 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)) { if (!lexicalGlobalObject) { lexicalGlobalObject = errorInstance->globalObject(); } name = instance->sanitizedNameString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); message = instance->sanitizedMessageString(lexicalGlobalObject); RETURN_IF_EXCEPTION(scope, {}); } } if (!globalObject) [[unlikely]] { globalObject = defaultGlobalObject(); } 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& stackFrames, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorObject, JSObject* prepareStackTrace) { auto scope = DECLARE_THROW_SCOPE(vm); JSCStackTrace stackTrace = JSCStackTrace::fromExisting(vm, stackFrames); // Note: we cannot use tryCreateUninitializedRestricted here because we cannot allocate memory inside initializeIndex() MarkedArgumentBuffer callSites; // Create the call sites (one per frame) Zig::createCallSitesFromFrames(globalObject, lexicalGlobalObject, stackTrace, callSites); // We need to sourcemap it if it's a GlobalObject. for (int i = 0; i < stackTrace.size(); i++) { ZigStackFrame frame = {}; auto& stackFrame = stackFrames.at(i); String sourceURLForFrame = Zig::sourceURL(vm, stackFrame); // When you use node:vm, the global object can be different on a // per-frame basis. We should sourcemap the frames which are in Bun's // global object, and not sourcemap the frames which are in a different // global object. JSGlobalObject* globalObjectForFrame = lexicalGlobalObject; if (stackFrame.hasLineAndColumnInfo()) { auto* callee = stackFrame.callee(); // https://github.com/oven-sh/bun/issues/17698 if (callee) { if (auto* object = callee->getObject()) { globalObjectForFrame = object->globalObject(); } } } if (globalObjectForFrame == globalObject) { if (JSCStackFrame::SourcePositions* sourcePositions = stackTrace.at(i).getSourcePositions()) { frame.position.line_zero_based = sourcePositions->line.zeroBasedInt(); frame.position.column_zero_based = sourcePositions->column.zeroBasedInt(); } else { frame.position.line_zero_based = -1; frame.position.column_zero_based = -1; } if (!sourceURLForFrame.isEmpty()) { frame.source_url = Bun::toStringRef(sourceURLForFrame); // This ensures the lifetime of the sourceURL is accounted for correctly Bun__remapStackFramePositions(globalObject->bunVM(), &frame, 1); sourceURLForFrame = frame.source_url.toWTFString(); } } auto* callsite = jsCast(callSites.at(i)); if (!sourceURLForFrame.isEmpty()) callsite->setSourceURL(vm, jsString(vm, sourceURLForFrame)); if (frame.remapped) { callsite->setLineNumber(frame.position.line()); callsite->setColumnNumber(frame.position.column()); } } JSArray* callSitesArray = JSC::constructArray(globalObject, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), callSites); RETURN_IF_EXCEPTION(scope, {}); RELEASE_AND_RETURN(scope, formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSitesArray, prepareStackTrace)); } static String computeErrorInfoToString(JSC::VM& vm, Vector& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL) { Zig::GlobalObject* globalObject = nullptr; JSC::JSGlobalObject* lexicalGlobalObject = nullptr; return computeErrorInfoWithoutPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, nullptr); } static JSValue computeErrorInfoToJSValueWithoutSkipping(JSC::VM& vm, Vector& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorInstance, void* bunErrorData) { UNUSED_PARAM(bunErrorData); Zig::GlobalObject* globalObject = nullptr; JSC::JSGlobalObject* lexicalGlobalObject = nullptr; lexicalGlobalObject = errorInstance->globalObject(); globalObject = jsDynamicCast(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); // Error.prepareStackTrace - https://v8.dev/docs/stack-trace-api#customizing-stack-traces if (!globalObject) { // node:vm will use a different JSGlobalObject globalObject = defaultGlobalObject(); if (!globalObject->isInsideErrorPrepareStackTraceCallback) { auto* errorConstructor = lexicalGlobalObject->m_errorStructure.constructor(lexicalGlobalObject); auto prepareStackTrace = errorConstructor->getIfPropertyExists(lexicalGlobalObject, Identifier::fromString(vm, "prepareStackTrace"_s)); RETURN_IF_EXCEPTION(scope, {}); if (prepareStackTrace) { if (prepareStackTrace.isCell() && prepareStackTrace.isObject() && prepareStackTrace.isCallable()) { globalObject->isInsideErrorPrepareStackTraceCallback = true; auto result = computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject()); globalObject->isInsideErrorPrepareStackTraceCallback = false; RELEASE_AND_RETURN(scope, result); } } } } else if (!globalObject->isInsideErrorPrepareStackTraceCallback) { if (JSValue prepareStackTrace = globalObject->m_errorConstructorPrepareStackTraceValue.get()) { if (prepareStackTrace) { if (prepareStackTrace.isCallable()) { globalObject->isInsideErrorPrepareStackTraceCallback = true; auto result = computeErrorInfoWithPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance, prepareStackTrace.getObject()); globalObject->isInsideErrorPrepareStackTraceCallback = false; RELEASE_AND_RETURN(scope, result); } } } } String result = computeErrorInfoWithoutPrepareStackTrace(vm, globalObject, lexicalGlobalObject, stackTrace, line, column, sourceURL, errorInstance); RETURN_IF_EXCEPTION(scope, {}); return jsString(vm, result); } static JSValue computeErrorInfoToJSValue(JSC::VM& vm, Vector& stackTrace, OrdinalNumber& line, OrdinalNumber& column, String& sourceURL, JSObject* errorInstance, void* bunErrorData) { return computeErrorInfoToJSValueWithoutSkipping(vm, stackTrace, line, column, sourceURL, errorInstance, bunErrorData); } WTF::String computeErrorInfoWrapperToString(JSC::VM& vm, Vector& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL, void* bunErrorData) { UNUSED_PARAM(bunErrorData); OrdinalNumber line = OrdinalNumber::fromOneBasedInt(line_in); OrdinalNumber column = OrdinalNumber::fromOneBasedInt(column_in); auto scope = DECLARE_TOP_EXCEPTION_SCOPE(vm); WTF::String result = computeErrorInfoToString(vm, stackTrace, line, column, sourceURL); if (scope.exception()) { // TODO: is this correct? vm.setOnComputeErrorInfo doesnt appear to properly handle a function that can throw // test/js/node/test/parallel/test-stream-writable-write-writev-finish.js is the one that trips the exception checker (void)scope.tryClearException(); result = WTF::emptyString(); } line_in = line.oneBasedInt(); column_in = column.oneBasedInt(); return result; } void computeLineColumnWithSourcemap(JSC::VM& vm, JSC::SourceProvider* _Nonnull sourceProvider, JSC::LineColumn& lineColumn) { auto sourceURL = sourceProvider->sourceURL(); if (sourceURL.isEmpty()) { return; } OrdinalNumber line = OrdinalNumber::fromOneBasedInt(lineColumn.line); OrdinalNumber column = OrdinalNumber::fromOneBasedInt(lineColumn.column); ZigStackFrame frame = {}; frame.position.line_zero_based = line.zeroBasedInt(); frame.position.column_zero_based = column.zeroBasedInt(); frame.source_url = Bun::toStringRef(sourceURL); Bun__remapStackFramePositions(Bun::vm(vm), &frame, 1); if (frame.remapped) { lineColumn.line = frame.position.line().oneBasedInt(); lineColumn.column = frame.position.column().oneBasedInt(); } } JSC::JSValue computeErrorInfoWrapperToJSValue(JSC::VM& vm, Vector& stackTrace, unsigned int& line_in, unsigned int& column_in, String& sourceURL, JSObject* errorInstance, void* bunErrorData) { OrdinalNumber line = OrdinalNumber::fromOneBasedInt(line_in); OrdinalNumber column = OrdinalNumber::fromOneBasedInt(column_in); JSValue result = computeErrorInfoToJSValue(vm, stackTrace, line, column, sourceURL, errorInstance, bunErrorData); line_in = line.oneBasedInt(); column_in = column.oneBasedInt(); return result; } JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncAppendStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { Zig::GlobalObject* globalObject = static_cast(lexicalGlobalObject); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::ErrorInstance* source = jsDynamicCast(callFrame->argument(0)); JSC::ErrorInstance* destination = jsDynamicCast(callFrame->argument(1)); if (!source || !destination) { throwTypeError(lexicalGlobalObject, scope, "First & second argument must be an Error object"_s); return {}; } if (!destination->stackTrace()) { destination->captureStackTrace(vm, globalObject, 1); } if (source->stackTrace()) { destination->stackTrace()->appendVector(*source->stackTrace()); source->stackTrace()->clear(); } return JSC::JSValue::encode(jsUndefined()); } JSC_DEFINE_HOST_FUNCTION(jsFunctionDefaultErrorPrepareStackTrace, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto& vm = JSC::getVM(lexicalGlobalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* globalObject = defaultGlobalObject(lexicalGlobalObject); auto errorObject = jsDynamicCast(callFrame->argument(0)); auto callSites = jsDynamicCast(callFrame->argument(1)); if (!errorObject) { throwTypeError(lexicalGlobalObject, scope, "First argument must be an Error object"_s); return {}; } if (!callSites) { callSites = JSArray::create(vm, globalObject->arrayStructureForIndexingTypeDuringAllocation(JSC::ArrayWithContiguous), 0); } JSValue result = formatStackTraceToJSValue(vm, globalObject, lexicalGlobalObject, errorObject, callSites, jsUndefined()); RETURN_IF_EXCEPTION(scope, {}); return JSC::JSValue::encode(result); } JSC_DEFINE_CUSTOM_GETTER(errorInstanceLazyStackCustomGetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, PropertyName)) { auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); auto* errorObject = jsDynamicCast(JSValue::decode(thisValue)); // This shouldn't be possible. if (!errorObject) { return JSValue::encode(jsUndefined()); } OrdinalNumber line; OrdinalNumber column; String sourceURL; auto stackTrace = errorObject->stackTrace(); JSValue result; if (stackTrace == nullptr) { WTF::Vector emptyTrace; result = computeErrorInfoToJSValue(vm, emptyTrace, line, column, sourceURL, errorObject, nullptr); } else { result = computeErrorInfoToJSValue(vm, *stackTrace, line, column, sourceURL, errorObject, nullptr); stackTrace->clear(); errorObject->setStackFrames(vm, {}); } RETURN_IF_EXCEPTION(scope, {}); errorObject->putDirect(vm, vm.propertyNames->stack, result, 0); return JSValue::encode(result); } JSC_DEFINE_CUSTOM_SETTER(errorInstanceLazyStackCustomSetter, (JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue value, PropertyName)) { auto& vm = JSC::getVM(globalObject); JSValue decodedValue = JSValue::decode(thisValue); if (auto* object = decodedValue.getObject()) { object->putDirect(vm, vm.propertyNames->stack, JSValue::decode(value), 0); } return true; } JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { Zig::GlobalObject* globalObject = static_cast(lexicalGlobalObject); auto& vm = JSC::getVM(globalObject); auto scope = DECLARE_THROW_SCOPE(vm); JSC::JSValue objectArg = callFrame->argument(0); if (!objectArg.isObject()) { return JSC::JSValue::encode(throwTypeError(lexicalGlobalObject, scope, "invalid_argument"_s)); } JSC::JSObject* errorObject = objectArg.asCell()->getObject(); JSC::JSValue caller = callFrame->argument(1); size_t stackTraceLimit = globalObject->stackTraceLimit().value(); if (stackTraceLimit == 0) { stackTraceLimit = DEFAULT_ERROR_STACK_TRACE_LIMIT; } WTF::Vector stackTrace; JSCStackTrace::getFramesForCaller(vm, callFrame, errorObject, caller, stackTrace, stackTraceLimit); if (auto* instance = jsDynamicCast(errorObject)) { // Force materialization before replacing the stack frames, so that JSC's // internal lazy error info mechanism doesn't later see the replaced (possibly empty) // stack trace and fail to create the stack property. if (!instance->hasMaterializedErrorInfo()) instance->materializeErrorInfoIfNeeded(vm); RETURN_IF_EXCEPTION(scope, {}); instance->setStackFrames(vm, WTF::move(stackTrace)); { const auto& propertyName = vm.propertyNames->stack; VM::DeletePropertyModeScope deleteScope(vm, VM::DeletePropertyMode::IgnoreConfigurable); DeletePropertySlot slot; JSObject::deleteProperty(instance, globalObject, propertyName, slot); } RETURN_IF_EXCEPTION(scope, {}); if (auto* zigGlobalObject = jsDynamicCast(globalObject)) { instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, zigGlobalObject->m_lazyStackCustomGetterSetter.get(zigGlobalObject), JSC::PropertyAttribute::CustomAccessor | 0); } else { instance->putDirectCustomAccessor(vm, vm.propertyNames->stack, CustomGetterSetter::create(vm, errorInstanceLazyStackCustomGetter, errorInstanceLazyStackCustomSetter), JSC::PropertyAttribute::CustomAccessor | 0); } } else { OrdinalNumber line; OrdinalNumber column; String sourceURL; JSValue result = computeErrorInfoToJSValue(vm, stackTrace, line, column, sourceURL, errorObject, nullptr); RETURN_IF_EXCEPTION(scope, {}); errorObject->putDirect(vm, vm.propertyNames->stack, result, 0); } return JSC::JSValue::encode(JSC::jsUndefined()); } } // namespace Bun namespace Zig { void createCallSitesFromFrames(Zig::GlobalObject* globalObject, JSC::JSGlobalObject* lexicalGlobalObject, JSCStackTrace& stackTrace, MarkedArgumentBuffer& 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 * strict mode function and all frames below (its caller etc.) are not allow to access * their receiver and function objects. For those frames, getFunction() and getThis() * will return undefined."." */ bool encounteredStrictFrame = false; // 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); if (!encounteredStrictFrame) { encounteredStrictFrame = callSite->isStrict(); } callSites.append(callSite); } } } // namespace Zig