From bf097c940189a4d8236f8bda632bf809c78cc279 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 02:25:02 -0800 Subject: [PATCH 1/9] Fix various bugs with function names and source mapping in eval, node:vm, and in-between --- src/bun.js/api/BunObject.zig | 1 - src/bun.js/api/ffi.zig | 1 - src/bun.js/api/server.zig | 1 - src/bun.js/bindings/CallSite.cpp | 88 ++++-- src/bun.js/bindings/CallSite.h | 2 + src/bun.js/bindings/ErrorStackTrace.cpp | 261 ++++++++++++++--- src/bun.js/bindings/ErrorStackTrace.h | 22 +- src/bun.js/bindings/ZigGlobalObject.cpp | 175 ++++++------ src/bun.js/bindings/bindings.cpp | 64 ++--- src/bun.js/bindings/bindings.zig | 277 +------------------ src/bun.js/bindings/exports.zig | 36 ++- src/bun.js/bindings/headers-replacements.zig | 4 - src/bun.js/bindings/headers.h | 1 - src/bun.js/bindings/headers.zig | 19 +- src/bun.js/javascript.zig | 4 +- src/bun.js/module_loader.zig | 1 - test/js/node/vm/vm.test.ts | 71 +++++ 17 files changed, 520 insertions(+), 508 deletions(-) diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 43afbb67933f7d..e688c327dec394 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -234,7 +234,6 @@ const JSPromise = bun.JSC.JSPromise; const JSInternalPromise = bun.JSC.JSInternalPromise; const JSModuleLoader = bun.JSC.JSModuleLoader; const JSPromiseRejectionOperation = bun.JSC.JSPromiseRejectionOperation; -const Exception = bun.JSC.Exception; const ErrorableZigString = bun.JSC.ErrorableZigString; const ZigGlobalObject = bun.JSC.ZigGlobalObject; const VM = bun.JSC.VM; diff --git a/src/bun.js/api/ffi.zig b/src/bun.js/api/ffi.zig index 4fad8407d1d12c..3f21d30948b228 100644 --- a/src/bun.js/api/ffi.zig +++ b/src/bun.js/api/ffi.zig @@ -63,7 +63,6 @@ const JSPromise = bun.JSC.JSPromise; const JSInternalPromise = bun.JSC.JSInternalPromise; const JSModuleLoader = bun.JSC.JSModuleLoader; const JSPromiseRejectionOperation = bun.JSC.JSPromiseRejectionOperation; -const Exception = bun.JSC.Exception; const ErrorableZigString = bun.JSC.ErrorableZigString; const ZigGlobalObject = bun.JSC.ZigGlobalObject; const VM = bun.JSC.VM; diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 46e96b7f71a566..c2ab4bcfb0378d 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -61,7 +61,6 @@ const JSPromise = bun.JSC.JSPromise; const JSInternalPromise = bun.JSC.JSInternalPromise; const JSModuleLoader = bun.JSC.JSModuleLoader; const JSPromiseRejectionOperation = bun.JSC.JSPromiseRejectionOperation; -const Exception = bun.JSC.Exception; const ErrorableZigString = bun.JSC.ErrorableZigString; const ZigGlobalObject = bun.JSC.ZigGlobalObject; const VM = bun.JSC.VM; diff --git a/src/bun.js/bindings/CallSite.cpp b/src/bun.js/bindings/CallSite.cpp index 6d2d15e09d70f7..af78f9584a8970 100644 --- a/src/bun.js/bindings/CallSite.cpp +++ b/src/bun.js/bindings/CallSite.cpp @@ -8,8 +8,10 @@ #include "JavaScriptCore/CallData.h" #include "helpers.h" +#include "wtf/text/OrdinalNumber.h" #include +#include using namespace JSC; using namespace WebCore; @@ -30,8 +32,8 @@ void CallSite::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSCStac * Thus, if we've already encountered a strict frame, we'll treat our frame as strict too. */ bool isStrictFrame = encounteredStrictFrame; + JSC::CodeBlock* codeBlock = stackFrame.codeBlock(); if (!isStrictFrame) { - JSC::CodeBlock* codeBlock = stackFrame.codeBlock(); if (codeBlock) { isStrictFrame = codeBlock->ownerExecutable()->isInStrictContext(); } @@ -65,6 +67,8 @@ void CallSite::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSCStac if (stackFrame.isEval()) { m_flags |= static_cast(Flags::IsEval); + } else if (stackFrame.isFunctionOrEval()) { + m_flags |= static_cast(Flags::IsFunction); } if (stackFrame.isConstructor()) { m_flags |= static_cast(Flags::IsConstructor); @@ -102,49 +106,79 @@ JSValue createNativeFrameForTesting(Zig::GlobalObject* globalObject) void CallSite::formatAsString(JSC::VM& vm, JSC::JSGlobalObject* globalObject, WTF::StringBuilder& sb) { - JSString* myFunctionName = functionName().toString(globalObject); - JSString* mySourceURL = sourceURL().toString(globalObject); + JSValue thisValue = jsUndefined(); + if (m_thisValue) { + thisValue = m_thisValue.get(); + } + + JSString* myFunctionName = functionName().toStringOrNull(globalObject); + JSString* mySourceURL = sourceURL().toStringOrNull(globalObject); + + String functionName; + if (myFunctionName && myFunctionName->length() > 0) { + functionName = myFunctionName->getString(globalObject); + } else if (m_flags & (static_cast(Flags::IsFunction) | static_cast(Flags::IsEval))) { + functionName = ""_s; + } - JSString* myColumnNumber = columnNumber().zeroBasedInt() >= 0 ? JSValue(columnNumber().oneBasedInt()).toString(globalObject) : jsEmptyString(vm); - JSString* myLineNumber = lineNumber().zeroBasedInt() >= 0 ? JSValue(lineNumber().oneBasedInt()).toString(globalObject) : jsEmptyString(vm); + std::optional column = columnNumber().zeroBasedInt() >= 0 ? std::optional(columnNumber()) : std::nullopt; + std::optional line = lineNumber().zeroBasedInt() >= 0 ? std::optional(lineNumber()) : std::nullopt; - bool myIsConstructor = isConstructor(); + if (functionName.length() > 0) { - if (myFunctionName->length() > 0) { - if (myIsConstructor) { + if (isConstructor()) { sb.append("new "_s); - } else { - // TODO: print type or class name if available - // sb.append(myTypeName->getString(globalObject)); - // sb.append(" "_s); } - sb.append(myFunctionName->getString(globalObject)); - } else { - sb.append(""_s); + + if (auto* object = thisValue.getObject()) { + auto catchScope = DECLARE_CATCH_SCOPE(vm); + auto className = object->calculatedClassName(object); + if (catchScope.exception()) { + catchScope.clearException(); + } + + if (className.length() > 0) { + sb.append(className); + sb.append("."_s); + } + } + + sb.append(functionName); } - sb.append(" ("_s); + if (isNative()) { + if (functionName.length() > 0) { + sb.append(" ("_s); + } sb.append("native"_s); + if (functionName.length() > 0) { + sb.append(")"_s); + } } else { - if (mySourceURL->length() == 0) { + if (functionName.length() > 0) { + sb.append(" ("_s); + } + if (!mySourceURL || mySourceURL->length() == 0) { sb.append("unknown"_s); } else { sb.append(mySourceURL->getString(globalObject)); } - if (myLineNumber->length() > 0 && myColumnNumber->length() > 0) { - sb.append(":"_s); - sb.append(myLineNumber->getString(globalObject)); - sb.append(":"_s); - sb.append(myColumnNumber->getString(globalObject)); - } else if (myLineNumber->length() > 0) { - sb.append(":"_s); - sb.append(myLineNumber->getString(globalObject)); + if (line && column) { + sb.append(':'); + sb.append(line.value().oneBasedInt()); + sb.append(':'); + sb.append(column.value().oneBasedInt()); + } else if (line) { + sb.append(':'); + sb.append(line.value().oneBasedInt()); + } + + if (functionName.length() > 0) { + sb.append(')'); } } - sb.append(")"_s); } DEFINE_VISIT_CHILDREN(CallSite); - } diff --git a/src/bun.js/bindings/CallSite.h b/src/bun.js/bindings/CallSite.h index 8dac8702b1b334..4c0d095c146322 100644 --- a/src/bun.js/bindings/CallSite.h +++ b/src/bun.js/bindings/CallSite.h @@ -25,6 +25,8 @@ class CallSite final : public JSC::JSNonFinalObject { IsEval = 2, IsConstructor = 4, IsNative = 8, + IsWasm = 16, + IsFunction = 32, }; private: diff --git a/src/bun.js/bindings/ErrorStackTrace.cpp b/src/bun.js/bindings/ErrorStackTrace.cpp index 19b5ff9ef55f27..80b8fde36e1320 100644 --- a/src/bun.js/bindings/ErrorStackTrace.cpp +++ b/src/bun.js/bindings/ErrorStackTrace.cpp @@ -5,7 +5,11 @@ #include "config.h" #include "ErrorStackTrace.h" +#include "JavaScriptCore/CallData.h" +#include "JavaScriptCore/CodeType.h" #include "JavaScriptCore/Error.h" +#include "JavaScriptCore/ExecutableBase.h" +#include "JavaScriptCore/JSType.h" #include "wtf/text/OrdinalNumber.h" #include @@ -404,6 +408,13 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, JSC::StackVisitor& visitor) m_callee = visitor->callee().asCell(); m_callFrame = visitor->callFrame(); + if (auto* codeBlock = visitor->codeBlock()) { + auto codeType = codeBlock->codeType(); + if (codeType == JSC::FunctionCode || codeType == JSC::EvalCode) { + m_isFunctionOrEval = true; + } + } + // Based on JSC's GetStackTraceFunctor (Interpreter.cpp) if (visitor->isNativeCalleeFrame()) { auto* nativeCallee = visitor->callee().asNativeCallee(); @@ -454,12 +465,21 @@ JSCStackFrame::JSCStackFrame(JSC::VM& vm, const JSC::StackFrame& frame) m_codeBlock = codeBlock; m_bytecodeIndex = frame.bytecodeIndex(); } + + auto codeType = codeBlock->codeType(); + if (codeType == JSC::FunctionCode || codeType == JSC::EvalCode) { + m_isFunctionOrEval = true; + } } if (!m_codeBlock && frame.hasLineAndColumnInfo()) { auto lineColumn = frame.computeLineAndColumn(); m_sourcePositions = { OrdinalNumber::fromOneBasedInt(lineColumn.line), OrdinalNumber::fromOneBasedInt(lineColumn.column) }; m_sourcePositionsState = SourcePositionsState::Calculated; + auto codeType = frame.codeBlock()->codeType(); + if (codeType == JSC::FunctionCode || codeType == JSC::EvalCode) { + m_isFunctionOrEval = true; + } } } @@ -531,45 +551,22 @@ ALWAYS_INLINE String JSCStackFrame::retrieveSourceURL() ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName() { - static const auto functionNameModuleCodeString = MAKE_STATIC_STRING_IMPL("module code"); - static const auto functionNameGlobalCodeString = MAKE_STATIC_STRING_IMPL("global code"); if (m_isWasmFrame) { return JSC::Wasm::makeString(m_wasmFunctionIndexOrName); } - if (m_codeBlock) { - switch (m_codeBlock->codeType()) { - case JSC::EvalCode: - // Node returns null here. - return String(); - case JSC::ModuleCode: - return String(functionNameModuleCodeString); - case JSC::FunctionCode: - break; - case JSC::GlobalCode: - return String(functionNameGlobalCodeString); - default: - ASSERT_NOT_REACHED(); + if (m_callee) { + auto* calleeObject = m_callee->getObject(); + if (calleeObject) { + return Zig::functionName(m_vm, calleeObject->globalObject(), calleeObject); } } - if (m_callee) { - if (auto* callee = m_callee->getObject()) { - // Does the code block have a user-defined name property? - JSC::JSValue name = callee->getDirect(m_vm, m_vm.propertyNames->name); - if (name && name.isString()) { - auto scope = DECLARE_CATCH_SCOPE(m_vm); - auto nameString = name.toWTFString(callee->globalObject()); - if (scope.exception()) { - scope.clearException(); - } - if (!nameString.isEmpty()) { - return nameString; - } - } - - return JSC::getCalculatedDisplayName(m_vm, callee); + if (m_codeBlock) { + auto functionName = Zig::functionName(m_vm, m_codeBlock); + if (!functionName.isEmpty()) { + return functionName; } } @@ -579,7 +576,6 @@ ALWAYS_INLINE String JSCStackFrame::retrieveFunctionName() ALWAYS_INLINE String JSCStackFrame::retrieveTypeName() { JSC::JSObject* calleeObject = JSC::jsCast(m_callee); - // return JSC::jsTypeStringForValue(m_globalObjectcalleeObject->toThis() return calleeObject->className(); } @@ -648,7 +644,7 @@ String sourceURL(JSC::CodeBlock* codeBlock) return sourceURL(source); } -String sourceURL(JSC::VM& vm, JSC::StackFrame& frame) +String sourceURL(JSC::VM& vm, const JSC::StackFrame& frame) { if (frame.isWasmFrame()) { return "[wasm code]"_s; @@ -694,4 +690,203 @@ String sourceURL(JSC::VM& vm, JSC::JSFunction* function) return Zig::sourceURL(jsExecutable->source()); } +String functionName(JSC::VM& vm, JSC::CodeBlock* codeBlock) +{ + auto codeType = codeBlock->codeType(); + + auto* executable = codeBlock->ownerExecutable(); + if (!executable) { + return String(); + } + + if (codeType == JSC::FunctionCode) { + auto* jsExecutable = jsCast(executable); + if (!jsExecutable) { + return String(); + } + + return jsExecutable->ecmaName().string(); + } + + return String(); +} + +String functionName(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* object) +{ + WTF::String functionName; + auto jstype = object->type(); + if (jstype == JSC::ProxyObjectType) return {}; + + // First try the "name" property. + { + WTF::String name; + auto catchScope = DECLARE_CATCH_SCOPE(vm); + PropertySlot slot(object, PropertySlot::InternalMethodType::VMInquiry, &vm); + if (object->getOwnNonIndexPropertySlot(vm, object->structure(), vm.propertyNames->name, slot)) { + if (!slot.isAccessor()) { + JSValue functionNameValue = slot.getValue(lexicalGlobalObject, vm.propertyNames->name); + if (functionNameValue && functionNameValue.isString()) { + name = functionNameValue.toWTFString(lexicalGlobalObject); + if (!name.isEmpty()) { + return name; + } + } + } + } + if (UNLIKELY(catchScope.exception())) { + catchScope.clearException(); + } + } + + { + // Then try the "displayName" property (what this does internally) + auto catchScope = DECLARE_CATCH_SCOPE(vm); + functionName = JSC::getCalculatedDisplayName(vm, object); + if (UNLIKELY(catchScope.exception())) { + catchScope.clearException(); + } + } + + return functionName; +} + +String functionName(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, const JSC::StackFrame& frame, bool isInFinalizer, unsigned int* flags) +{ + WTF::String functionName; + bool isConstructor = false; + if (isInFinalizer) { + + if (auto* callee = frame.callee()) { + if (auto* object = callee->getObject()) { + auto jstype = object->type(); + Structure* structure = object->structure(); + + auto setTypeFlagsIfNecessary = [&]() { + if (flags) { + if (jstype == JSC::JSFunctionType || jstype == JSC::InternalFunctionType) { + *flags |= static_cast(FunctionNameFlags::Function); + } + } + }; + + const auto getName = [&]() -> String { + // First try the "name" property. + { + unsigned attributes; + PropertyOffset offset = structure->getConcurrently(vm.propertyNames->name.impl(), attributes); + if (offset != invalidOffset && !(attributes & (PropertyAttribute::Accessor | PropertyAttribute::CustomAccessorOrValue))) { + JSValue name = object->getDirect(offset); + if (name && name.isString()) { + auto str = asString(name)->tryGetValueWithoutGC(); + if (!str->isEmpty()) { + setTypeFlagsIfNecessary(); + + return str.data; + } + } + } + } + + // Then try the "displayName" property. + { + unsigned attributes; + PropertyOffset offset = structure->getConcurrently(vm.propertyNames->displayName.impl(), attributes); + if (offset != invalidOffset && !(attributes & (PropertyAttribute::Accessor | PropertyAttribute::CustomAccessorOrValue))) { + JSValue name = object->getDirect(offset); + if (name && name.isString()) { + auto str = asString(name)->tryGetValueWithoutGC(); + if (!str->isEmpty()) { + functionName = str.data; + if (!functionName.isEmpty()) { + setTypeFlagsIfNecessary(); + return functionName; + } + } + } + } + } + + // Lastly, try type-specific properties. + if (jstype == JSC::JSFunctionType) { + auto* function = jsDynamicCast(object); + if (function) { + functionName = function->nameWithoutGC(vm); + setTypeFlagsIfNecessary(); + return functionName; + } + } else if (jstype == JSC::InternalFunctionType) { + auto* function = jsDynamicCast(object); + if (function) { + functionName = function->name(); + setTypeFlagsIfNecessary(); + return functionName; + } + } + + return functionName; + }; + + functionName = getName(); + } + } + + return functionName; + } + + if (auto codeblock = frame.codeBlock()) { + if (codeblock->isConstructor()) { + isConstructor = true; + } + + // We cannot run this in FinalizeUnconditionally, as we cannot call getters there + if (!isInFinalizer) { + auto codeType = codeblock->codeType(); + switch (codeType) { + case JSC::CodeType::FunctionCode: + case JSC::CodeType::EvalCode: { + if (flags) { + if (codeType == JSC::CodeType::EvalCode) { + *flags |= static_cast(FunctionNameFlags::Eval); + } else if (codeType == JSC::CodeType::FunctionCode) { + *flags |= static_cast(FunctionNameFlags::Function); + } + } + if (auto* callee = frame.callee()) { + if (auto* object = callee->getObject()) { + functionName = Zig::functionName(vm, lexicalGlobalObject, object); + + if (flags) { + if (auto* unlinkedCodeBlock = codeblock->unlinkedCodeBlock()) { + if (unlinkedCodeBlock->isBuiltinFunction()) { + *flags |= static_cast(FunctionNameFlags::Builtin); + } + } + } + } + } + break; + } + default: { + break; + } + } + + if (functionName.isEmpty()) { + functionName = Zig::functionName(vm, codeblock); + } + } + } else { + if (auto* callee = frame.callee()) { + if (auto* object = callee->getObject()) { + functionName = Zig::functionName(vm, lexicalGlobalObject, object); + } + } + } + + if ((flags && (*flags & static_cast(FunctionNameFlags::AddNewKeyword))) && isConstructor && !functionName.isEmpty()) { + return makeString("new "_s, functionName); + } + + return functionName; +} } diff --git a/src/bun.js/bindings/ErrorStackTrace.h b/src/bun.js/bindings/ErrorStackTrace.h index 9213ef1d889935..106b11a3db58c4 100644 --- a/src/bun.js/bindings/ErrorStackTrace.h +++ b/src/bun.js/bindings/ErrorStackTrace.h @@ -62,7 +62,9 @@ class JSCStackFrame { // m_wasmFunctionIndexOrName has meaning only when m_isWasmFrame is set JSC::Wasm::IndexOrName m_wasmFunctionIndexOrName; - bool m_isWasmFrame; + bool m_isWasmFrame = false; + + bool m_isFunctionOrEval = false; enum class SourcePositionsState { NotCalculated, @@ -86,6 +88,8 @@ class JSCStackFrame { JSC::JSString* functionName(); JSC::JSString* typeName(); + bool isFunctionOrEval() const { return m_isFunctionOrEval; } + bool hasBytecodeIndex() const { return (m_bytecodeIndex.offset() != UINT_MAX) && !m_isWasmFrame; } JSC::BytecodeIndex bytecodeIndex() const { @@ -218,8 +222,22 @@ String sourceURL(const JSC::SourceOrigin& origin); String sourceURL(JSC::SourceProvider* sourceProvider); String sourceURL(const JSC::SourceCode& sourceCode); String sourceURL(JSC::CodeBlock* codeBlock); -String sourceURL(JSC::VM& vm, JSC::StackFrame& frame); +String sourceURL(JSC::VM& vm, const JSC::StackFrame& frame); String sourceURL(JSC::StackVisitor& visitor); String sourceURL(JSC::VM& vm, JSC::JSFunction* function); +class FunctionNameFlags { +public: + static constexpr unsigned None = 0; + static constexpr unsigned Eval = 1 << 0; + static constexpr unsigned Constructor = 1 << 1; + static constexpr unsigned Builtin = 1 << 2; + static constexpr unsigned Function = 1 << 3; + static constexpr unsigned AddNewKeyword = 1 << 4; +}; + +String functionName(JSC::VM& vm, JSC::CodeBlock* codeBlock); +String functionName(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSObject* callee); +String functionName(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, const JSC::StackFrame& frame, bool isInFinalizer, unsigned int* flags); + } diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index c407e2fbe19af7..316cd3e1131428 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -1,3 +1,4 @@ + #include "root.h" #include "JavaScriptCore/PropertySlot.h" @@ -492,76 +493,38 @@ WTF::String Bun::formatStackTrace( for (size_t i = 0; i < framesCount; i++) { StackFrame& frame = stackTrace.at(i); - WTF::String functionName; - bool isBuiltinFunction = false; - - sb.append(" at "_s); - - if (auto codeblock = frame.codeBlock()) { + unsigned int flags = static_cast(FunctionNameFlags::AddNewKeyword); - if (codeblock->isConstructor()) { - sb.append("new "_s); - } - - // We cannot run this in FinalizeUnconditionally, as we cannot call getters there - // We check the errorInstance to see if we are allowed to access this memory. - if (errorInstance) { - switch (codeblock->codeType()) { - case JSC::CodeType::FunctionCode: - case JSC::CodeType::EvalCode: { - if (auto* callee = frame.callee()) { - if (auto* object = callee->getObject()) { - JSValue functionNameValue = object->getDirect(vm, vm.propertyNames->name); - if (functionNameValue && functionNameValue.isString()) { - functionName = functionNameValue.toWTFString(lexicalGlobalObject); - } - - if (functionName.isEmpty()) { - auto catchScope = DECLARE_CATCH_SCOPE(vm); - functionName = JSC::getCalculatedDisplayName(vm, object); - if (catchScope.exception()) { - catchScope.clearException(); - } - } - - if (auto* unlinkedCodeBlock = codeblock->unlinkedCodeBlock()) { - if (unlinkedCodeBlock->isBuiltinFunction()) { - isBuiltinFunction = true; - } - } - } - } - break; - } - default: { - break; - } - } + // -- get the data we need to render the text -- + JSC::JSGlobalObject* globalObjectForFrame = lexicalGlobalObject; + if (frame.hasLineAndColumnInfo()) { + auto* callee = frame.callee(); + if (auto* object = callee->getObject()) { + globalObjectForFrame = object->globalObject(); } } - if (functionName.isEmpty()) { - functionName = frame.functionName(vm); - } - - if (functionName.isEmpty()) { - sb.append(""_s); - } else { - sb.append(functionName); - } + WTF::String functionName = Zig::functionName(vm, globalObjectForFrame, frame, !errorInstance, &flags); + OrdinalNumber originalLine = {}; + OrdinalNumber originalColumn = {}; + OrdinalNumber displayLine = {}; + OrdinalNumber displayColumn = {}; + WTF::String sourceURLForFrame; if (frame.hasLineAndColumnInfo()) { ZigStackFrame remappedFrame = {}; LineColumn lineColumn = frame.computeLineAndColumn(); - OrdinalNumber originalLine = OrdinalNumber::fromOneBasedInt(lineColumn.line); - OrdinalNumber originalColumn = OrdinalNumber::fromOneBasedInt(lineColumn.column); + 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(); - String sourceURLForFrame = Zig::sourceURL(vm, frame); + sourceURLForFrame = Zig::sourceURL(vm, frame); - bool isDefinitelyNotRunninginNodeVMGlobalObject = (globalObject == lexicalGlobalObject && globalObject); + bool isDefinitelyNotRunninginNodeVMGlobalObject = globalObject == globalObjectForFrame; bool isDefaultGlobalObjectInAFinalizer = (globalObject && !lexicalGlobalObject && !errorInstance); if (isDefinitelyNotRunninginNodeVMGlobalObject || isDefaultGlobalObjectInAFinalizer) { @@ -576,11 +539,14 @@ WTF::String Bun::formatStackTrace( } } + displayLine = remappedFrame.position.line(); + displayColumn = remappedFrame.position.column(); + if (!hasSet) { hasSet = true; line = remappedFrame.position.line(); column = remappedFrame.position.column(); - sourceURL = frame.sourceURL(vm); + sourceURL = sourceURLForFrame; if (remappedFrame.remapped) { if (errorInstance) { @@ -589,24 +555,46 @@ WTF::String Bun::formatStackTrace( } } } + } + + 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()) { + sb.append(functionName); sb.append(" ("_s); - if (sourceURLForFrame.isEmpty()) { - if (isBuiltinFunction) { - sb.append("native"_s); - } else { - sb.append("unknown"_s); + } + + if (!sourceURLForFrame.isEmpty()) { + sb.append(sourceURLForFrame); + if (displayLine.zeroBasedInt() > 0) { + sb.append(":"_s); + sb.append(displayLine.oneBasedInt()); + + if (displayColumn.zeroBasedInt() > 0) { + sb.append(":"_s); + sb.append(displayColumn.oneBasedInt()); } - } else { - sb.append(sourceURLForFrame); } - sb.append(":"_s); - sb.append(remappedFrame.position.line().oneBasedInt()); - sb.append(":"_s); - sb.append(remappedFrame.position.column().oneBasedInt()); + } + + if (!functionName.isEmpty()) { sb.append(")"_s); - } else { - sb.append(" (native)"_s); } if (i != framesCount - 1) { @@ -663,12 +651,25 @@ static JSValue computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObj GlobalObject::createCallSitesFromFrames(globalObject, lexicalGlobalObject, stackTrace, callSites); // We need to sourcemap it if it's a GlobalObject. - if (globalObject == lexicalGlobalObject) { - for (int i = 0; i < stackTrace.size(); i++) { - ZigStackFrame frame = {}; - String sourceURLForFrame = Zig::sourceURL(vm, stackFrames.at(i)); + for (int i = 0; i < stackTrace.size(); i++) { + ZigStackFrame frame = {}; + String sourceURLForFrame = Zig::sourceURL(vm, stackFrames.at(i)); + + // 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 (stackFrames.at(i).hasLineAndColumnInfo()) { + auto* callee = stackFrames.at(i).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(); @@ -685,26 +686,16 @@ static JSValue computeErrorInfoWithPrepareStackTrace(JSC::VM& vm, Zig::GlobalObj sourceURLForFrame = frame.source_url.toWTFString(); } + } - auto* callsite = jsCast(callSites.at(i)); - - if (!sourceURLForFrame.isEmpty()) - callsite->setSourceURL(vm, jsString(vm, sourceURLForFrame)); + auto* callsite = jsCast(callSites.at(i)); - if (frame.remapped) { - callsite->setLineNumber(frame.position.line()); - callsite->setColumnNumber(frame.position.column()); - } - } - } else { - // if it's a different JSGlobalObject, let's still give you the sourceURL directive just to be nice. - for (int i = 0; i < stackTrace.size(); i++) { + if (!sourceURLForFrame.isEmpty()) + callsite->setSourceURL(vm, jsString(vm, sourceURLForFrame)); - String sourceURLForFrame = Zig::sourceURL(vm, stackFrames.at(i)); - if (!sourceURLForFrame.isEmpty()) { - auto* callsite = jsCast(callSites.at(i)); - callsite->setSourceURL(vm, jsString(vm, sourceURLForFrame)); - } + if (frame.remapped) { + callsite->setLineNumber(frame.position.line()); + callsite->setColumnNumber(frame.position.column()); } } diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 506b1614bc74a9..d6c1f5697e434b 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -2592,24 +2592,6 @@ JSC__JSValue JSObjectCallAsFunctionReturnValueHoldingAPILock(JSContextRef ctx, J return JSC::JSValue::encode(result); } -#pragma mark - JSC::Exception - -JSC__Exception* JSC__Exception__create(JSC__JSGlobalObject* arg0, JSC__JSObject* arg1, - unsigned char StackCaptureAction2) -{ - return JSC::Exception::create(arg0->vm(), JSC::JSValue(arg1), - StackCaptureAction2 == 0 - ? JSC::Exception::StackCaptureAction::CaptureStack - : JSC::Exception::StackCaptureAction::DoNotCaptureStack); -} - -JSC__JSValue JSC__Exception__value(JSC__Exception* arg0) -{ - return JSC::JSValue::encode(arg0->value()); -} - -// #pragma mark - JSC::PropertyNameArray - // CPP_DECL size_t JSC__PropertyNameArray__length(JSC__PropertyNameArray* arg0); // CPP_DECL const JSC__PropertyName* // JSC__PropertyNameArray__next(JSC__PropertyNameArray* arg0, size_t arg1); @@ -4166,18 +4148,18 @@ bool JSC__JSValue__stringIncludes(JSC__JSValue value, JSC__JSGlobalObject* globa return stringToSearchIn.find(searchString, 0) != WTF::notFound; } -static void populateStackFrameMetadata(JSC::VM& vm, const JSC::StackFrame* stackFrame, ZigStackFrame* frame) +static void populateStackFrameMetadata(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const JSC::StackFrame* stackFrame, ZigStackFrame* frame) { if (stackFrame->isWasmFrame()) { frame->code_type = ZigStackFrameCodeWasm; - auto name = stackFrame->functionName(vm); + auto name = Zig::functionName(vm, globalObject, *stackFrame, false, nullptr); if (!name.isEmpty()) { frame->function_name = Bun::toStringRef(name); } - auto sourceURL = stackFrame->sourceURL(vm); + auto sourceURL = Zig::sourceURL(vm, *stackFrame); if (sourceURL != "[wasm code]"_s) { // [wasm code] is a useless source URL, so we don't bother to set it. // It is the default value JSC returns. @@ -4186,7 +4168,8 @@ static void populateStackFrameMetadata(JSC::VM& vm, const JSC::StackFrame* stack return; } - frame->source_url = Bun::toStringRef(stackFrame->sourceURL(vm)); + auto sourceURL = Zig::sourceURL(vm, *stackFrame); + frame->source_url = Bun::toStringRef(sourceURL); auto m_codeBlock = stackFrame->codeBlock(); if (m_codeBlock) { switch (m_codeBlock->codeType()) { @@ -4212,17 +4195,16 @@ static void populateStackFrameMetadata(JSC::VM& vm, const JSC::StackFrame* stack } auto calleeCell = stackFrame->callee(); - if (!calleeCell || !calleeCell->isObject()) + if (!calleeCell) return; - JSC::JSObject* callee = JSC::jsCast(calleeCell); + JSC::JSObject* callee = calleeCell->getObject(); + if (!callee) + return; - // Does the code block have a user-defined name property? - JSC::JSValue name = callee->getDirect(vm, vm.propertyNames->name); - if (name && name.isString()) { - frame->function_name = Bun::toStringRef(name.toWTFString(callee->globalObject())); - } else { - frame->function_name = Bun::toStringRef(JSC::getCalculatedDisplayName(vm, callee)); + WTF::String functionName = Zig::functionName(vm, globalObject, callee); + if (!functionName.isEmpty()) { + frame->function_name = Bun::toStringRef(functionName); } } @@ -4322,9 +4304,9 @@ static void populateStackFramePosition(const JSC::StackFrame* stackFrame, BunStr } static void populateStackFrame(JSC::VM& vm, ZigStackTrace* trace, const JSC::StackFrame* stackFrame, - ZigStackFrame* frame, bool is_top, JSC::SourceProvider** referenced_source_provider) + ZigStackFrame* frame, bool is_top, JSC::SourceProvider** referenced_source_provider, JSC::JSGlobalObject* globalObject) { - populateStackFrameMetadata(vm, stackFrame, frame); + populateStackFrameMetadata(vm, globalObject, stackFrame, frame); populateStackFramePosition(stackFrame, is_top ? trace->source_lines_ptr : nullptr, is_top ? trace->source_lines_numbers : nullptr, is_top ? trace->source_lines_to_collect : 0, &frame->position, referenced_source_provider); @@ -4497,7 +4479,7 @@ class V8StackTraceIterator { } }; -static void populateStackTrace(JSC::VM& vm, const WTF::Vector& frames, ZigStackTrace* trace) +static void populateStackTrace(JSC::VM& vm, const WTF::Vector& frames, ZigStackTrace* trace, JSC::JSGlobalObject* globalObject) { uint8_t frame_i = 0; size_t stack_frame_i = 0; @@ -4513,7 +4495,7 @@ static void populateStackTrace(JSC::VM& vm, const WTF::Vector& break; ZigStackFrame* frame = &trace->frames_ptr[frame_i]; - populateStackFrame(vm, trace, &frames[stack_frame_i], frame, frame_i == 0, &trace->referenced_source_provider); + populateStackFrame(vm, trace, &frames[stack_frame_i], frame, frame_i == 0, &trace->referenced_source_provider, globalObject); stack_frame_i++; frame_i++; } @@ -4549,12 +4531,12 @@ static void fromErrorInstance(ZigException* except, JSC::JSGlobalObject* global, bool getFromSourceURL = false; if (stackTrace != nullptr && stackTrace->size() > 0) { - populateStackTrace(vm, *stackTrace, &except->stack); + populateStackTrace(vm, *stackTrace, &except->stack, global); if (UNLIKELY(scope.exception())) { scope.clearExceptionExceptTermination(); } } else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) { - populateStackTrace(vm, *err->stackTrace(), &except->stack); + populateStackTrace(vm, *err->stackTrace(), &except->stack, global); if (UNLIKELY(scope.exception())) { scope.clearExceptionExceptTermination(); } @@ -4997,11 +4979,6 @@ void JSC__JSValue__toZigException(JSC__JSValue jsException, JSC__JSGlobalObject* exceptionFromString(exception, value, global); } -void JSC__Exception__getStackTrace(JSC__Exception* arg0, ZigStackTrace* trace) -{ - populateStackTrace(arg0->vm(), arg0->stack(), trace); -} - #pragma mark - JSC::VM size_t JSC__VM__runGC(JSC__VM* vm, bool sync) @@ -5060,6 +5037,11 @@ bool JSC__JSValue__isTerminationException(JSC__JSValue JSValue0, JSC__VM* arg1) return exception != NULL && arg1->isTerminationException(exception); } +extern "C" void JSC__Exception__getStackTrace(JSC::Exception* arg0, JSC::JSGlobalObject* global, ZigStackTrace* trace) +{ + populateStackTrace(arg0->vm(), arg0->stack(), trace, global); +} + void JSC__VM__shrinkFootprint(JSC__VM* arg0) { arg0->shrinkFootprintWhenIdle(); }; void JSC__VM__whenIdle(JSC__VM* arg0, void (*ArgFn1)()) { arg0->whenIdle(ArgFn1); }; diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 6e83c569d33e67..c27c7432e8b7cd 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -1850,7 +1850,6 @@ pub const SystemError = extern struct { }; }; -pub const ReturnableException = *?*Exception; pub const Sizes = @import("../bindings/sizes.zig"); pub const JSUint8Array = opaque { @@ -2097,7 +2096,7 @@ pub fn NewGlobalObject(comptime Type: type) type { return JSValue.jsUndefined(); } - pub fn reportUncaughtException(global: *JSGlobalObject, exception: *Exception) callconv(.C) JSValue { + pub fn reportUncaughtException(global: *JSGlobalObject, exception: *JSC.Exception) callconv(.C) JSValue { if (comptime @hasDecl(Type, "reportUncaughtException")) { return @call(bun.callmod_inline, Type.reportUncaughtException, .{ global, exception }); } @@ -2605,12 +2604,6 @@ pub const JSPromise = extern struct { pub fn rejectAsHandled(this: *JSPromise, globalThis: *JSGlobalObject, value: JSValue) void { cppFn("rejectAsHandled", .{ this, globalThis, value }); } - // pub fn rejectException(this: *JSPromise, globalThis: *JSGlobalObject, value: *Exception) void { - // cppFn("rejectException", .{ this, globalThis, value }); - // } - pub fn rejectAsHandledException(this: *JSPromise, globalThis: *JSGlobalObject, value: *Exception) void { - cppFn("rejectAsHandledException", .{ this, globalThis, value }); - } pub fn create(globalThis: *JSGlobalObject) *JSPromise { return cppFn("create", .{globalThis}); @@ -2707,156 +2700,6 @@ pub const JSInternalPromise = extern struct { pub fn rejectAsHandled(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: JSValue) void { cppFn("rejectAsHandled", .{ this, globalThis, value }); } - // pub fn rejectException(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: *Exception) void { - // cppFn("rejectException", .{ this, globalThis, value }); - // } - pub fn rejectAsHandledException(this: *JSInternalPromise, globalThis: *JSGlobalObject, value: *Exception) void { - cppFn("rejectAsHandledException", .{ this, globalThis, value }); - } - // pub const PromiseCallbackPrimitive = *const fn ( - // ctx: ?*anyopaque, - // globalThis: *JSGlobalObject, - // arguments: [*]const JSValue, - // arguments_len: usize, - // ) callconv(.C) JSValue; - // pub fn then_( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // resolve_ctx: ?*anyopaque, - // onResolve: PromiseCallbackPrimitive, - // reject_ctx: ?*anyopaque, - // onReject: PromiseCallbackPrimitive, - // ) *JSInternalPromise { - // return cppFn("then_", .{ this, globalThis, resolve_ctx, onResolve, reject_ctx, onReject }); - // } - - // pub const Completion = struct { - // result: []const JSValue, - // global: *JSGlobalObject, - // resolved: bool = false, - - // pub const PromiseTask = struct { - // frame: @Frame(JSInternalPromise._wait), - // completion: Completion, - - // pub fn onResolve(this: *PromiseTask, global: *JSGlobalObject, arguments: []const JSValue) anyerror!JSValue { - // this.completion.global = global; - // this.completion.resolved = true; - // this.completion.result = arguments; - - // return resume this.frame; - // } - - // pub fn onReject(this: *PromiseTask, global: *JSGlobalObject, arguments: []const JSValue) anyerror!JSValue { - // this.completion.global = global; - // this.completion.resolved = false; - // this.completion.result = arguments; - // return resume this.frame; - // } - // }; - // }; - - // pub fn _wait( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // internal: *Completion.PromiseTask, - // ) void { - // this.then( - // globalThis, - // Completion.PromiseTask, - // internal, - // Completion.PromiseTask.onResolve, - // Completion.PromiseTask, - // internal, - // Completion.PromiseTask.onReject, - // ); - - // suspend { - // internal.frame = @frame().*; - // } - // } - - // pub fn wait( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // allocator: std.mem.Allocator, - // ) callconv(.Async) anyerror!Completion { - // var internal = try allocator.create(Completion.PromiseTask); - // defer allocator.destroy(internal); - // internal.* = Completion.Internal{ - // .frame = undefined, - // .completion = Completion{ - // .global = globalThis, - // .resolved = false, - // .result = &[_]JSValue{}, - // }, - // }; - - // this._wait(globalThis, internal); - - // return internal.completion; - // } - - // pub fn then( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // comptime Resolve: type, - // resolver: *Resolve, - // comptime onResolve: fn (*Resolve, *JSGlobalObject, []const JSValue) anyerror!JSValue, - // comptime Reject: type, - // rejecter: *Reject, - // comptime onReject: fn (*Reject, *JSGlobalObject, []const JSValue) anyerror!JSValue, - // ) *JSInternalPromise { - // return then_(this, globalThis, resolver, PromiseCallback(Resolve, onResolve), Reject, rejecter, PromiseCallback(Reject, onReject)); - // } - - // pub fn thenResolve( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // comptime Resolve: type, - // resolver: *Resolve, - // comptime onResolve: fn (*Resolve, *JSGlobalObject, []const JSValue) anyerror!JSValue, - // ) *JSInternalPromise { - // return thenResolve_(this, globalThis, resolver, PromiseCallback(Resolve, onResolve)); - // } - - // pub fn thenResolve_( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // resolve_ctx: ?*anyopaque, - // onResolve: PromiseCallbackPrimitive, - // ) *JSInternalPromise { - // return cppFn("thenResolve_", .{ - // this, - // globalThis, - // resolve_ctx, - // onResolve, - // }); - // } - - // pub fn thenReject_( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // resolve_ctx: ?*anyopaque, - // onResolve: PromiseCallbackPrimitive, - // ) *JSInternalPromise { - // return cppFn("thenReject_", .{ - // this, - // globalThis, - // resolve_ctx, - // onResolve, - // }); - // } - - // pub fn thenReject( - // this: *JSInternalPromise, - // globalThis: *JSGlobalObject, - // comptime Resolve: type, - // resolver: *Resolve, - // comptime onResolve: fn (*Resolve, *JSGlobalObject, []const JSValue) anyerror!JSValue, - // ) *JSInternalPromise { - // return thenReject_(this, globalThis, resolver, PromiseCallback(Resolve, onResolve)); - // } pub fn create(globalThis: *JSGlobalObject) *JSInternalPromise { return cppFn("create", .{globalThis}); @@ -2933,12 +2776,6 @@ pub const AnyPromise = union(enum) { } } - pub fn rejectAsHandledException(this: AnyPromise, globalThis: *JSGlobalObject, value: *Exception) void { - switch (this) { - inline else => |promise| promise.rejectAsHandledException(globalThis, value), - } - } - pub fn asValue(this: AnyPromise, globalThis: *JSGlobalObject) JSValue { return switch (this) { .normal => |promise| promise.asValue(globalThis), @@ -6263,45 +6100,6 @@ pub const JSValue = enum(i64) { extern "c" fn AsyncContextFrame__withAsyncContextIfNeeded(global: *JSGlobalObject, callback: JSValue) JSValue; -pub const Exception = extern struct { - pub const shim = Shimmer("JSC", "Exception", @This()); - bytes: shim.Bytes, - pub const Type = JSObject; - const cppFn = shim.cppFn; - - pub const include = "JavaScriptCore/Exception.h"; - pub const name = "JSC::Exception"; - pub const namespace = "JSC"; - - pub const StackCaptureAction = enum(u8) { - CaptureStack = 0, - DoNotCaptureStack = 1, - }; - - pub fn create(globalObject: *JSGlobalObject, object: *JSObject, stack_capture: StackCaptureAction) *Exception { - return cppFn( - "create", - .{ globalObject, object, @intFromEnum(stack_capture) }, - ); - } - - pub fn value(this: *Exception) JSValue { - return cppFn( - "value", - .{this}, - ); - } - - pub fn getStackTrace(this: *Exception, trace: *ZigStackTrace) void { - return cppFn( - "getStackTrace", - .{ this, trace }, - ); - } - - pub const Extern = [_][]const u8{ "create", "value", "getStackTrace" }; -}; - pub const VM = extern struct { pub const shim = Shimmer("JSC", "VM", @This()); bytes: shim.Bytes, @@ -6525,79 +6323,6 @@ pub const VM = extern struct { }; }; -pub const ThrowScope = extern struct { - pub const shim = Shimmer("JSC", "ThrowScope", @This()); - bytes: shim.Bytes, - - const cppFn = shim.cppFn; - - pub const include = "JavaScriptCore/ThrowScope.h"; - pub const name = "JSC::ThrowScope"; - pub const namespace = "JSC"; - - pub fn declare( - vm: *VM, - _: [*]u8, - file: [*]u8, - line: usize, - ) ThrowScope { - return cppFn("declare", .{ vm, file, line }); - } - - pub fn release(this: *ThrowScope) void { - return cppFn("release", .{this}); - } - - pub fn exception(this: *ThrowScope) ?*Exception { - return cppFn("exception", .{this}); - } - - pub fn clearException(this: *ThrowScope) void { - return cppFn("clearException", .{this}); - } - - pub const Extern = [_][]const u8{ - "declare", - "release", - "exception", - "clearException", - }; -}; - -pub const CatchScope = extern struct { - pub const shim = Shimmer("JSC", "CatchScope", @This()); - bytes: shim.Bytes, - - const cppFn = shim.cppFn; - - pub const include = "JavaScriptCore/CatchScope.h"; - pub const name = "JSC::CatchScope"; - pub const namespace = "JSC"; - - pub fn declare( - vm: *VM, - function_name: [*]u8, - file: [*]u8, - line: usize, - ) CatchScope { - return cppFn("declare", .{ vm, function_name, file, line }); - } - - pub fn exception(this: *CatchScope) ?*Exception { - return cppFn("exception", .{this}); - } - - pub fn clearException(this: *CatchScope) void { - return cppFn("clearException", .{this}); - } - - pub const Extern = [_][]const u8{ - "declare", - "exception", - "clearException", - }; -}; - /// Call Frame for JavaScript -> Native function calls. In Bun, it is /// preferred to use the bindings generator instead of directly decoding /// arguments. See `docs/project/bindgen.md` diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index f652c77575ecb4..4c02b558c16abe 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -21,7 +21,6 @@ const Environment = bun.Environment; const ScriptArguments = opaque {}; const JSPromise = JSC.JSPromise; const JSPromiseRejectionOperation = JSC.JSPromiseRejectionOperation; -const Exception = JSC.Exception; const JSModuleLoader = JSC.JSModuleLoader; const Microtask = JSC.Microtask; @@ -31,6 +30,17 @@ const typeBaseName = @import("../../meta.zig").typeBaseName; const String = bun.String; const JestPrettyFormat = @import("../test/pretty_format.zig").JestPrettyFormat; +pub const Exception = opaque { + extern fn JSC__Exception__getStackTrace(this: *Exception, global: *JSGlobalObject, stack: *ZigStackTrace) void; + pub fn getStackTrace(this: *Exception, global: *JSGlobalObject, stack: *ZigStackTrace) void { + JSC__Exception__getStackTrace(this, global, stack); + } + + pub fn value(this: *Exception) JSValue { + return JSValue.fromCell(this); + } +}; + pub const ZigGlobalObject = extern struct { pub const shim = Shimmer("Zig", "GlobalObject", @This()); bytes: shim.Bytes, @@ -676,9 +686,17 @@ pub const ZigStackFrame = extern struct { switch (this.code_type) { .Eval => { - try writer.writeAll("(eval)"); + if (this.enable_color) { + try std.fmt.format(writer, comptime Output.prettyFmt("", true) ++ "eval" ++ Output.prettyFmt("", true), .{}); + } else { + try writer.writeAll("eval"); + } if (!name.isEmpty()) { - try std.fmt.format(writer, "{}", .{name}); + if (this.enable_color) { + try std.fmt.format(writer, comptime Output.prettyFmt(" {}", true), .{name}); + } else { + try std.fmt.format(writer, " {}", .{name}); + } } }, .Function => { @@ -688,15 +706,15 @@ pub const ZigStackFrame = extern struct { } else { try std.fmt.format(writer, "{}", .{name}); } - } - }, - .Global => { - if (!name.isEmpty()) { - try std.fmt.format(writer, "globalThis {}", .{name}); } else { - try writer.writeAll("globalThis"); + if (this.enable_color) { + try std.fmt.format(writer, comptime Output.prettyFmt("", true) ++ "" ++ Output.prettyFmt("", true), .{}); + } else { + try writer.writeAll(""); + } } }, + .Global => {}, .Wasm => { if (!name.isEmpty()) { try std.fmt.format(writer, "{}", .{name}); diff --git a/src/bun.js/bindings/headers-replacements.zig b/src/bun.js/bindings/headers-replacements.zig index 2c9173e774b21e..cfe6142c8808e7 100644 --- a/src/bun.js/bindings/headers-replacements.zig +++ b/src/bun.js/bindings/headers-replacements.zig @@ -24,7 +24,6 @@ pub const bWTF__StringImpl = bindings.StringImpl; pub const bWTF__String = bindings.String; pub const bWTF__ExternalStringImpl = bindings.ExternalStringImpl; pub const bJSC__VM = bindings.VM; -pub const bJSC__ThrowScope = bindings.ThrowScope; pub const bJSC__SourceOrigin = bindings.SourceOrigin; pub const bJSC__SourceCode = bindings.SourceCode; pub const bJSC__PropertyName = bindings.PropertyName; @@ -40,7 +39,6 @@ pub const bJSC__JSFunction = bindings.JSFunction; pub const bJSC__JSCell = bindings.JSCell; pub const bJSC__Identifier = bindings.Identifier; pub const bJSC__Exception = bindings.Exception; -pub const bJSC__CatchScope = bindings.CatchScope; pub const bJSC__CallFrame = bindings.CallFrame; pub const bInspector__ScriptArguments = bindings.ScriptArguments; pub const JSC__JSValue = bindings.JSValue; @@ -50,12 +48,10 @@ pub const ZigString = bindings.ZigString; pub const ZigException = bindings.ZigException; pub const ResolvedSource = bindings.ResolvedSource; pub const ZigStackTrace = bindings.ZigStackTrace; -pub const ReturnableException = bindings.ReturnableException; pub const struct_Zig__JSMicrotaskCallback = bindings.Microtask; pub const bZig__JSMicrotaskCallback = struct_Zig__JSMicrotaskCallback; pub const SystemError = bindings.SystemError; const JSClassRef = bindings.C.JSClassRef; -pub const JSC__CatchScope = bindings.CatchScope; pub const Bun__Readable = bindings.NodeReadableStream; pub const Bun__Writable = bindings.NodeWritableStream; pub const Bun__ArrayBuffer = bindings.ArrayBuffer; diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index ab9f3ca4370449..7ab7f603bf8d9e 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -402,7 +402,6 @@ CPP_DECL void JSC__JSValue__toZigString(JSC__JSValue JSValue0, ZigString* arg1, #pragma mark - JSC::Exception CPP_DECL JSC__Exception* JSC__Exception__create(JSC__JSGlobalObject* arg0, JSC__JSObject* arg1, unsigned char StackCaptureAction2); -CPP_DECL void JSC__Exception__getStackTrace(JSC__Exception* arg0, ZigStackTrace* arg1); CPP_DECL JSC__JSValue JSC__Exception__value(JSC__Exception* arg0); #pragma mark - JSC::VM diff --git a/src/bun.js/bindings/headers.zig b/src/bun.js/bindings/headers.zig index 642d54cf3b91ca..acbbb7b786aeac 100644 --- a/src/bun.js/bindings/headers.zig +++ b/src/bun.js/bindings/headers.zig @@ -25,7 +25,6 @@ pub const bWTF__StringImpl = bindings.StringImpl; pub const bWTF__String = bindings.String; pub const bWTF__ExternalStringImpl = bindings.ExternalStringImpl; pub const bJSC__VM = bindings.VM; -pub const bJSC__ThrowScope = bindings.ThrowScope; pub const bJSC__SourceOrigin = bindings.SourceOrigin; pub const bJSC__SourceCode = bindings.SourceCode; pub const bJSC__PropertyName = bindings.PropertyName; @@ -40,8 +39,7 @@ pub const bJSC__JSGlobalObject = bindings.JSGlobalObject; pub const bJSC__JSFunction = bindings.JSFunction; pub const bJSC__JSCell = bindings.JSCell; pub const bJSC__Identifier = bindings.Identifier; -pub const bJSC__Exception = bindings.Exception; -pub const bJSC__CatchScope = bindings.CatchScope; + pub const bJSC__CallFrame = bindings.CallFrame; pub const bInspector__ScriptArguments = bindings.ScriptArguments; pub const JSC__JSValue = bindings.JSValue; @@ -51,12 +49,10 @@ pub const ZigString = bindings.ZigString; pub const ZigException = bindings.ZigException; pub const ResolvedSource = bindings.ResolvedSource; pub const ZigStackTrace = bindings.ZigStackTrace; -pub const ReturnableException = bindings.ReturnableException; pub const struct_Zig__JSMicrotaskCallback = bindings.Microtask; pub const bZig__JSMicrotaskCallback = struct_Zig__JSMicrotaskCallback; pub const SystemError = bindings.SystemError; const JSClassRef = bindings.C.JSClassRef; -pub const JSC__CatchScope = bindings.CatchScope; pub const Bun__Readable = bindings.NodeReadableStream; pub const Bun__Writable = bindings.NodeWritableStream; pub const Bun__ArrayBuffer = bindings.ArrayBuffer; @@ -74,8 +70,6 @@ pub const BunString = bun.String; pub const JSC__JSString = bJSC__JSString; pub const JSC__JSPromise = bJSC__JSPromise; pub const JSC__VM = bJSC__VM; -pub const JSC__ThrowScope = bJSC__ThrowScope; -pub const JSC__Exception = bJSC__Exception; pub const JSC__JSObject = bJSC__JSObject; pub const JSC__JSCell = bJSC__JSCell; pub const JSC__JSGlobalObject = bJSC__JSGlobalObject; @@ -295,9 +289,6 @@ pub extern fn JSC__JSValue__toStringOrNull(JSValue0: JSC__JSValue, arg1: *bindin pub extern fn JSC__JSValue__toUInt64NoTruncate(JSValue0: JSC__JSValue) u64; pub extern fn JSC__JSValue__toZigException(JSValue0: JSC__JSValue, arg1: *bindings.JSGlobalObject, arg2: [*c]ZigException) void; pub extern fn JSC__JSValue__toZigString(JSValue0: JSC__JSValue, arg1: [*c]ZigString, arg2: *bindings.JSGlobalObject) void; -pub extern fn JSC__Exception__create(arg0: *bindings.JSGlobalObject, arg1: [*c]bindings.JSObject, StackCaptureAction2: u8) [*c]bindings.Exception; -pub extern fn JSC__Exception__getStackTrace(arg0: [*c]bindings.Exception, arg1: [*c]ZigStackTrace) void; -pub extern fn JSC__Exception__value(arg0: [*c]bindings.Exception) JSC__JSValue; pub extern fn JSC__VM__blockBytesAllocated(arg0: *bindings.VM) usize; pub extern fn JSC__VM__clearExecutionTimeLimit(arg0: *bindings.VM) void; pub extern fn JSC__VM__collectAsync(arg0: *bindings.VM) void; @@ -324,13 +315,7 @@ pub extern fn JSC__VM__setExecutionTimeLimit(arg0: *bindings.VM, arg1: f64) void pub extern fn JSC__VM__shrinkFootprint(arg0: *bindings.VM) void; pub extern fn JSC__VM__throwError(arg0: *bindings.VM, arg1: *bindings.JSGlobalObject, JSValue2: JSC__JSValue) void; pub extern fn JSC__VM__whenIdle(arg0: *bindings.VM, ArgFn1: ?*const fn (...) callconv(.C) void) void; -pub extern fn JSC__ThrowScope__clearException(arg0: [*c]bindings.ThrowScope) void; -pub extern fn JSC__ThrowScope__declare(arg0: *bindings.VM, arg1: [*c]u8, arg2: [*c]u8, arg3: usize) bJSC__ThrowScope; -pub extern fn JSC__ThrowScope__exception(arg0: [*c]bindings.ThrowScope) [*c]bindings.Exception; -pub extern fn JSC__ThrowScope__release(arg0: [*c]bindings.ThrowScope) void; -pub extern fn JSC__CatchScope__clearException(arg0: [*c]bindings.CatchScope) void; -pub extern fn JSC__CatchScope__declare(arg0: *bindings.VM, arg1: [*c]u8, arg2: [*c]u8, arg3: usize) bJSC__CatchScope; -pub extern fn JSC__CatchScope__exception(arg0: [*c]bindings.CatchScope) [*c]bindings.Exception; + pub extern fn FFI__ptr__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue) void; pub extern fn Reader__u8__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue) void; pub extern fn Reader__u16__put(arg0: *bindings.JSGlobalObject, JSValue1: JSC__JSValue) void; diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index 4029ab0fbb7409..fb6073528e535a 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -15,6 +15,7 @@ const ErrorableString = bun.JSC.ErrorableString; const Arena = @import("../mimalloc_arena.zig").Arena; const C = bun.C; +const Exception = bun.JSC.Exception; const Allocator = std.mem.Allocator; const IdentityContext = @import("../identity_context.zig").IdentityContext; const Fs = @import("../fs.zig"); @@ -70,7 +71,6 @@ const JSPromise = bun.JSC.JSPromise; const JSInternalPromise = bun.JSC.JSInternalPromise; const JSModuleLoader = bun.JSC.JSModuleLoader; const JSPromiseRejectionOperation = bun.JSC.JSPromiseRejectionOperation; -const Exception = bun.JSC.Exception; const ErrorableZigString = bun.JSC.ErrorableZigString; const ZigGlobalObject = bun.JSC.ZigGlobalObject; const VM = bun.JSC.VM; @@ -3327,7 +3327,7 @@ pub const VirtualMachine = struct { var holder = ZigException.Holder.init(); var zig_exception: *ZigException = holder.zigException(); holder.deinit(this); - exception_.getStackTrace(&zig_exception.stack); + exception_.getStackTrace(this.global, &zig_exception.stack); if (zig_exception.stack.frames_len > 0) { if (allow_ansi_color) { printStackTrace(Writer, writer, zig_exception.stack, true) catch {}; diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 009c0142a4691a..8b7103acf1cfe1 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -68,7 +68,6 @@ const JSPromise = bun.JSC.JSPromise; const JSInternalPromise = bun.JSC.JSInternalPromise; const JSModuleLoader = bun.JSC.JSModuleLoader; const JSPromiseRejectionOperation = bun.JSC.JSPromiseRejectionOperation; -const Exception = bun.JSC.Exception; const ErrorableZigString = bun.JSC.ErrorableZigString; const ZigGlobalObject = bun.JSC.ZigGlobalObject; const VM = bun.JSC.VM; diff --git a/test/js/node/vm/vm.test.ts b/test/js/node/vm/vm.test.ts index f0a66ec2e905ee..38a626895e6b77 100644 --- a/test/js/node/vm/vm.test.ts +++ b/test/js/node/vm/vm.test.ts @@ -453,3 +453,74 @@ resp.text().then((a) => { delete URL.prototype.ok; } }); + +test("can get sourceURL from eval inside node:vm", () => { + try { + runInNewContext( + ` +throw new Error("hello"); +//# sourceURL=hellohello.js +`, + {}, + ); + } catch (e: any) { + var err: Error = e; + } + + expect(err!.stack!.replaceAll("\r\n", "\n").replaceAll(import.meta.path, "")).toMatchInlineSnapshot(` +"Error: hello + at hellohello.js:2:16 + at runInNewContext (unknown) + at (:459:5)" +`); +}); + +test("can get sourceURL inside node:vm", () => { + const err = runInNewContext( + ` + +function hello() { + return Bun.inspect(new Error("hello")); +} + +hello(); + +//# sourceURL=hellohello.js +`, + { Bun }, + ); + + expect(err.replaceAll("\r\n", "\n").replaceAll(import.meta.path, "")).toMatchInlineSnapshot(` +"4 | return Bun.inspect(new Error("hello")); + ^ +error: hello + at hello (hellohello.js:4:24) + at hellohello.js:7:6 + at (:479:15) +" +`); +}); + +test("eval sourceURL is correct", () => { + const err = eval( + ` + +function hello() { + return Bun.inspect(new Error("hello")); +} + +hello(); + +//# sourceURL=hellohello.js +`, + ); + expect(err.replaceAll("\r\n", "\n").replaceAll(import.meta.path, "")).toMatchInlineSnapshot(` +"4 | return Bun.inspect(new Error("hello")); + ^ +error: hello + at hello (hellohello.js:4:24) + at eval (hellohello.js:7:6) + at (:505:15) +" +`); +}); From 80cf218b4617d9738c9108f14cbb7194c6242b45 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 02:55:09 -0800 Subject: [PATCH 2/9] Update inspect-error.test.js --- test/js/bun/util/inspect-error.test.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/test/js/bun/util/inspect-error.test.js b/test/js/bun/util/inspect-error.test.js index 8e438f9cc1d6e3..ef35ad4542fe08 100644 --- a/test/js/bun/util/inspect-error.test.js +++ b/test/js/bun/util/inspect-error.test.js @@ -15,7 +15,7 @@ test("error.cause", () => { 5 | const err2 = new Error("error 2", { cause: err }); ^ error: error 2 - at [dir]/inspect-error.test.js:5:16 + at ([dir]/inspect-error.test.js:5:16) 1 | import { expect, test, describe, jest } from "bun:test"; 2 | @@ -23,7 +23,7 @@ error: error 2 4 | const err = new Error("error 1"); ^ error: error 1 - at [dir]/inspect-error.test.js:4:15 + at ([dir]/inspect-error.test.js:4:15) " `); }); @@ -43,7 +43,7 @@ test("Error", () => { 32 | const err = new Error("my message"); ^ error: my message - at [dir]/inspect-error.test.js:32:15 + at ([dir]/inspect-error.test.js:32:15) " `); }); @@ -118,9 +118,10 @@ test("Error inside minified file (no color) ", () => { 26 | exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};expo error: error inside long minified file! - at [dir]/inspect-error-fixture.min.js:26:2846 - at [dir]/inspect-error-fixture.min.js:26:2890 - at [dir]/inspect-error.test.js:102:5" + at ([dir]/inspect-error-fixture.min.js:26:2846) + at ([dir]/inspect-error-fixture.min.js:26:2890) + at require (9:26) + at ([dir]/inspect-error.test.js:102:5)" `); } }); @@ -149,9 +150,10 @@ test("Error inside minified file (color) ", () => { 26 | exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};exports.forwardRef=function(a){return{$$typeof:v,render:a}};expo | ... truncated error: error inside long minified file! - at [dir]/inspect-error-fixture.min.js:26:2846 - at [dir]/inspect-error-fixture.min.js:26:2890 - at [dir]/inspect-error.test.js:130:5" + at ([dir]/inspect-error-fixture.min.js:26:2846) + at ([dir]/inspect-error-fixture.min.js:26:2890) + at require (9:26) + at ([dir]/inspect-error.test.js:131:5)" `); } }); @@ -165,7 +167,7 @@ test("Inserted originalLine and originalColumn do not appear in node:util.inspec .replaceAll(import.meta.path.replaceAll("\\", "/"), "[file]"), ).toMatchInlineSnapshot(` "Error: my message - at ([file]:160:19)" + at ([file]:162:19)" `); }); From 38ed9caea7eb72318b84ae586ee762de5f07af23 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 02:56:17 -0800 Subject: [PATCH 3/9] Update test-test.test.ts.snap --- test/js/bun/test/__snapshots__/test-test.test.ts.snap | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/js/bun/test/__snapshots__/test-test.test.ts.snap b/test/js/bun/test/__snapshots__/test-test.test.ts.snap index aad0206a46e6b4..d07cc41e5add0e 100644 --- a/test/js/bun/test/__snapshots__/test-test.test.ts.snap +++ b/test/js/bun/test/__snapshots__/test-test.test.ts.snap @@ -13,7 +13,7 @@ my-test.test.js: 5 | throw new Error('## stage beforeAll ##'); ^ error: ## stage beforeAll ## - at /my-test.test.js:5:11 + at (/my-test.test.js:5:11) ------------------------------- (pass) my-test @@ -35,7 +35,7 @@ my-test.test.js: 5 | throw new Error('## stage beforeEach ##'); ^ error: ## stage beforeEach ## - at /my-test.test.js:5:11 + at (/my-test.test.js:5:11) (fail) my-test 0 pass @@ -58,7 +58,7 @@ my-test.test.js: 5 | throw new Error('## stage afterEach ##'); ^ error: ## stage afterEach ## - at /my-test.test.js:5:11 + at (/my-test.test.js:5:11) ------------------------------- @@ -83,7 +83,7 @@ my-test.test.js: 5 | throw new Error('## stage afterAll ##'); ^ error: ## stage afterAll ## - at /my-test.test.js:5:11 + at (/my-test.test.js:5:11) ------------------------------- @@ -107,7 +107,7 @@ my-test.test.js: 5 | throw new Error('## stage describe ##'); ^ error: ## stage describe ## - at /my-test.test.js:5:11 + at (/my-test.test.js:5:11) at /my-test.test.js:3:1 ------------------------------- From 9cb5a62c12d1cdadce6c3c7e959235930ea889fb Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 03:01:51 -0800 Subject: [PATCH 4/9] Fix the tests --- test/bake/dev-server-harness.ts | 7 ++++++- test/bundler/expectBundled.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/bake/dev-server-harness.ts b/test/bake/dev-server-harness.ts index d937938bf68b8b..571e4faa18fd87 100644 --- a/test/bake/dev-server-harness.ts +++ b/test/bake/dev-server-harness.ts @@ -204,7 +204,12 @@ function snapshotCallerLocation(): string { } function stackTraceFileName(line: string): string { - return / \(((?:[A-Za-z]:)?.*?)[:)]/.exec(line)![1].replaceAll("\\", "/"); + const match = /(?:at (?:<[^>]+> )?\(|at )?((?:[A-Za-z]:)?[^:)]+)/.exec(line); + let result = match![1].trim(); + if (result.startsWith("at ")) { + result = result.slice(3); + } + return result.replaceAll("\\", "/").trim(); } async function withAnnotatedStack(stackLine: string, cb: () => Promise): Promise { diff --git a/test/bundler/expectBundled.ts b/test/bundler/expectBundled.ts index a96a3aed1cad5c..36c52021f3397a 100644 --- a/test/bundler/expectBundled.ts +++ b/test/bundler/expectBundled.ts @@ -1558,7 +1558,7 @@ for (const [key, blob] of build.outputs) { if (run.errorLineMatch) { const stackTraceLine = stack.pop()!; - const match = /at (.*):(\d+):(\d+)$/.exec(stackTraceLine); + const match = /at (?:<[^>]+> \()?([^)]+):(\d+):(\d+)\)?$/.exec(stackTraceLine); if (match) { const line = readFileSync(match[1], "utf-8").split("\n")[+match[2] - 1]; if (!run.errorLineMatch.test(line)) { From 0b836d1c7bb5206f5bcf2d700c391902627a3e18 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 03:18:58 -0800 Subject: [PATCH 5/9] More fixes --- src/bun.js/bindings/bindings.cpp | 23 +++++++++++++++++++++++ src/bun.js/bindings/exports.zig | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index d6c1f5697e434b..5271d2fc756226 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -4808,6 +4808,23 @@ void exceptionFromString(ZigException* except, JSC::JSValue value, JSC::JSGlobal return; } + if (value.isCell()) { + // This code is mostly here for debugging purposes if this spot is reached. + JSCell* cell = value.asCell(); + auto type = cell->type(); + + switch (type) { + case JSC::SymbolType: { + except->message = Bun::toStringRef(jsCast(cell)->descriptiveString()); + return; + } + + default: { + break; + } + } + } + auto str = value.toWTFString(global); if (UNLIKELY(scope.exception())) { scope.clearExceptionExceptTermination(); @@ -4817,6 +4834,12 @@ void exceptionFromString(ZigException* except, JSC::JSValue value, JSC::JSGlobal except->message = Bun::toStringRef(str); } +extern "C" JSC::EncodedJSValue JSC__Exception__asJSValue(JSC__Exception* exception) +{ + JSC::Exception* jscException = jsCast(exception); + return JSC::JSValue::encode(jscException->value()); +} + void JSC__VM__releaseWeakRefs(JSC__VM* arg0) { arg0->finalizeSynchronousJSExecution(); diff --git a/src/bun.js/bindings/exports.zig b/src/bun.js/bindings/exports.zig index 4c02b558c16abe..2fead91daa16e3 100644 --- a/src/bun.js/bindings/exports.zig +++ b/src/bun.js/bindings/exports.zig @@ -32,12 +32,13 @@ const JestPrettyFormat = @import("../test/pretty_format.zig").JestPrettyFormat; pub const Exception = opaque { extern fn JSC__Exception__getStackTrace(this: *Exception, global: *JSGlobalObject, stack: *ZigStackTrace) void; + extern fn JSC__Exception__asJSValue(this: *Exception) JSValue; pub fn getStackTrace(this: *Exception, global: *JSGlobalObject, stack: *ZigStackTrace) void { JSC__Exception__getStackTrace(this, global, stack); } pub fn value(this: *Exception) JSValue { - return JSValue.fromCell(this); + return JSC__Exception__asJSValue(this); } }; From fb73302e4ac6b14958b786076ab7a4005a7e001c Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 03:42:38 -0800 Subject: [PATCH 6/9] Attempt to keep it as an exception for better stacktraces --- src/bun.js/bindings/bindings.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index 5271d2fc756226..7638c302efaa08 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -4837,7 +4837,7 @@ void exceptionFromString(ZigException* except, JSC::JSValue value, JSC::JSGlobal extern "C" JSC::EncodedJSValue JSC__Exception__asJSValue(JSC__Exception* exception) { JSC::Exception* jscException = jsCast(exception); - return JSC::JSValue::encode(jscException->value()); + return JSC::JSValue::encode(jscException); } void JSC__VM__releaseWeakRefs(JSC__VM* arg0) @@ -4987,11 +4987,21 @@ void JSC__JSValue__toZigException(JSC__JSValue jsException, JSC__JSGlobalObject* return; } - if (JSC::Exception* jscException = JSC::jsDynamicCast(value)) { - if (JSC::ErrorInstance* error = JSC::jsDynamicCast(jscException->value())) { - fromErrorInstance(exception, global, error, &jscException->stack(), jscException->value()); + if (value.classInfoOrNull() == JSC::Exception::info()) { + auto* jscException = jsCast(value); + JSValue unwrapped = jscException->value(); + + if (JSC::ErrorInstance* error = JSC::jsDynamicCast(unwrapped)) { + fromErrorInstance(exception, global, error, &jscException->stack(), unwrapped); return; } + + if (jscException->stack().size() > 0) { + populateStackTrace(global->vm(), jscException->stack(), &exception->stack, global); + } + + exceptionFromString(exception, unwrapped, global); + return; } if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { From fe4f1e6bb8035c3b438ddd83af950e9aa5c6ac66 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 03:44:42 -0800 Subject: [PATCH 7/9] Update inspect-error.test.js --- test/js/bun/util/inspect-error.test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/js/bun/util/inspect-error.test.js b/test/js/bun/util/inspect-error.test.js index ef35ad4542fe08..a439b5c5ad2dc8 100644 --- a/test/js/bun/util/inspect-error.test.js +++ b/test/js/bun/util/inspect-error.test.js @@ -120,7 +120,6 @@ test("Error inside minified file (no color) ", () => { error: error inside long minified file! at ([dir]/inspect-error-fixture.min.js:26:2846) at ([dir]/inspect-error-fixture.min.js:26:2890) - at require (9:26) at ([dir]/inspect-error.test.js:102:5)" `); } @@ -152,8 +151,7 @@ test("Error inside minified file (color) ", () => { error: error inside long minified file! at ([dir]/inspect-error-fixture.min.js:26:2846) at ([dir]/inspect-error-fixture.min.js:26:2890) - at require (9:26) - at ([dir]/inspect-error.test.js:131:5)" + at ([dir]/inspect-error.test.js:130:5)" `); } }); @@ -167,7 +165,7 @@ test("Inserted originalLine and originalColumn do not appear in node:util.inspec .replaceAll(import.meta.path.replaceAll("\\", "/"), "[file]"), ).toMatchInlineSnapshot(` "Error: my message - at ([file]:162:19)" + at ([file]:160:19)" `); }); From 6059531f5027b64dd9f7268e05e608af3dfbf1ca Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 04:32:41 -0800 Subject: [PATCH 8/9] fix regexes --- test/bake/dev-server-harness.ts | 28 ++++++++++++++++++++++++---- test/cli/test/bun-test.test.ts | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/test/bake/dev-server-harness.ts b/test/bake/dev-server-harness.ts index 571e4faa18fd87..a9621c9d5595d1 100644 --- a/test/bake/dev-server-harness.ts +++ b/test/bake/dev-server-harness.ts @@ -202,14 +202,34 @@ function snapshotCallerLocation(): string { } throw new Error("Couldn't find caller location in stack trace"); } - function stackTraceFileName(line: string): string { - const match = /(?:at (?:<[^>]+> )?\(|at )?((?:[A-Za-z]:)?[^:)]+)/.exec(line); - let result = match![1].trim(); + let result = line.trim(); + + // Remove leading "at " and any parentheses if (result.startsWith("at ")) { result = result.slice(3); } - return result.replaceAll("\\", "/").trim(); + + // Handle case with angle brackets like "" + const angleStart = result.indexOf("<"); + const angleEnd = result.indexOf(">"); + if (angleStart >= 0 && angleEnd > angleStart) { + result = result.slice(angleEnd + 1); + } + + // Remove parentheses and everything after colon + const openParen = result.indexOf("("); + if (openParen >= 0) { + result = result.slice(openParen + 1); + } + + const colon = result.indexOf(":"); + if (colon >= 0) { + result = result.slice(0, colon); + } + + result = result.trim(); + return result.replaceAll("\\", "/"); } async function withAnnotatedStack(stackLine: string, cb: () => Promise): Promise { diff --git a/test/cli/test/bun-test.test.ts b/test/cli/test/bun-test.test.ts index d4d2e83a194ebf..cc9f72a03c8efc 100644 --- a/test/cli/test/bun-test.test.ts +++ b/test/cli/test/bun-test.test.ts @@ -577,7 +577,7 @@ describe("bun test", () => { GITHUB_ACTIONS: "true", }, }); - expect(stderr).toMatch(/::error title=error: Oops!::/); + expect(stderr).toMatch(/::error file=.*,line=\d+,col=\d+,title=error: Oops!::/m); }); test("should annotate a test timeout", () => { const stderr = runTest({ From 2b6a6b74ef894c17efdd01fd65d57a942ea9bc29 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Tue, 7 Jan 2025 06:03:07 -0800 Subject: [PATCH 9/9] Update dev-server-harness.ts --- test/bake/dev-server-harness.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/bake/dev-server-harness.ts b/test/bake/dev-server-harness.ts index a9621c9d5595d1..8714d208dc516d 100644 --- a/test/bake/dev-server-harness.ts +++ b/test/bake/dev-server-harness.ts @@ -193,11 +193,12 @@ class DevFetchPromise extends Promise { function snapshotCallerLocation(): string { const stack = new Error().stack!; - const lines = stack.split("\n"); + const lines = stack.replaceAll("\r\n", "\n").split("\n"); let i = 1; for (; i < lines.length; i++) { - if (!lines[i].includes(import.meta.filename)) { - return lines[i]; + const line = lines[i].replaceAll("\\", "/"); + if (line.includes(import.meta.path.replaceAll("\\", "/"))) { + return line; } } throw new Error("Couldn't find caller location in stack trace"); @@ -207,23 +208,31 @@ function stackTraceFileName(line: string): string { // Remove leading "at " and any parentheses if (result.startsWith("at ")) { - result = result.slice(3); + result = result.slice(3).trim(); } // Handle case with angle brackets like "" const angleStart = result.indexOf("<"); const angleEnd = result.indexOf(">"); if (angleStart >= 0 && angleEnd > angleStart) { - result = result.slice(angleEnd + 1); + result = result.slice(angleEnd + 1).trim(); } // Remove parentheses and everything after colon const openParen = result.indexOf("("); if (openParen >= 0) { - result = result.slice(openParen + 1); + result = result.slice(openParen + 1).trim(); + } + + // Handle drive letters (e.g. C:) and line numbers + let colon = result.indexOf(":"); + + // Check for drive letter (e.g. C:) by looking for single letter before colon + if (colon > 0 && /[a-zA-Z]/.test(result[colon - 1])) { + // On Windows, skip past drive letter colon to find line number colon + colon = result.indexOf(":", colon + 1); } - const colon = result.indexOf(":"); if (colon >= 0) { result = result.slice(0, colon); }