From db7bcd79ff26423e5f72627f203a67b36802a2c1 Mon Sep 17 00:00:00 2001 From: robobun Date: Sun, 12 Oct 2025 16:29:56 -0700 Subject: [PATCH] Refactor: move ZigException functions to dedicated file (#23560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR moves error-related functions from `bindings.cpp` into a new dedicated file `ZigException.cpp` for better code organization. ## Changes Moved the following functions to `ZigException.cpp`: - `populateStackFrameMetadata` - `populateStackFramePosition` - `populateStackFrame` - `populateStackTrace` - `fromErrorInstance` - `exceptionFromString` - `JSC__JSValue__toZigException` - `ZigException__collectSourceLines` - `JSC__Exception__getStackTrace` Also moved helper functions and types: - `V8StackTraceIterator` class - `getNonObservable` - `PopulateStackTraceFlags` enum - `StringView_slice` helper - `SYNTAX_ERROR_CODE` macro ## Test plan - Built successfully with `bun bd` - All exception handling functions are properly exported - No functional changes, pure refactoring 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot Co-authored-by: Claude --- src/bun.js/bindings/ZigException.cpp | 893 +++++++++++++++++++++++++++ src/bun.js/bindings/bindings.cpp | 841 ------------------------- 2 files changed, 893 insertions(+), 841 deletions(-) create mode 100644 src/bun.js/bindings/ZigException.cpp diff --git a/src/bun.js/bindings/ZigException.cpp b/src/bun.js/bindings/ZigException.cpp new file mode 100644 index 0000000000..77197e5707 --- /dev/null +++ b/src/bun.js/bindings/ZigException.cpp @@ -0,0 +1,893 @@ +/** + * ZigException handling and error processing utilities. + * + * This file contains functions for converting JavaScript exceptions to Zig exceptions, + * processing stack traces, and collecting source lines. + */ +#include "root.h" + +#include "JavaScriptCore/ErrorType.h" +#include "JavaScriptCore/CatchScope.h" +#include "JavaScriptCore/Exception.h" +#include "ErrorCode+List.h" +#include "ErrorCode.h" +#include "JavaScriptCore/ThrowScope.h" + +#include "JavaScriptCore/JSCast.h" +#include "JavaScriptCore/JSType.h" +#include "JavaScriptCore/JSCJSValue.h" +#include "JavaScriptCore/JSGlobalObject.h" +#include "headers.h" + +#include "BunClientData.h" +#include "WebCoreJSBuiltins.h" + +#include "JavaScriptCore/BytecodeIndex.h" +#include "JavaScriptCore/CodeBlock.h" +#include "JavaScriptCore/ErrorInstance.h" +#include "JavaScriptCore/ErrorInstanceInlines.h" +#include "JavaScriptCore/ExceptionScope.h" +#include "JavaScriptCore/JSObject.h" +#include "JavaScriptCore/JSString.h" +#include "JavaScriptCore/StackFrame.h" +#include "JavaScriptCore/VM.h" +#include "ZigGlobalObject.h" +#include "helpers.h" +#include "JavaScriptCore/JSObjectInlines.h" + +#include "wtf/Assertions.h" +#include "wtf/text/OrdinalNumber.h" +#include "wtf/text/StringView.h" +#include "wtf/text/WTFString.h" +#include "wtf/text/StringToIntegerConversion.h" + +#include "ErrorStackFrame.h" +#include "ErrorStackTrace.h" +#include "ObjectBindings.h" + +#include +#include "wtf-bindings.h" + +static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsigned end) +{ + return sv.substring(start, end - start); +} + +using namespace JSC; +using namespace WebCore; + +enum PopulateStackTraceFlags { + OnlyPosition, + OnlySourceLines, +}; + +#define SYNTAX_ERROR_CODE 4 + +static void populateStackFrameMetadata(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const JSC::StackFrame& stackFrame, ZigStackFrame& frame) +{ + + if (stackFrame.isWasmFrame()) { + frame.code_type = ZigStackFrameCodeWasm; + + auto name = Zig::functionName(vm, globalObject, stackFrame, false, nullptr); + if (!name.isEmpty()) { + frame.function_name = Bun::toStringRef(name); + } + + 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. + frame.source_url = Bun::toStringRef(sourceURL); + } + return; + } + + auto sourceURL = Zig::sourceURL(vm, stackFrame); + frame.source_url = Bun::toStringRef(sourceURL); + auto m_codeBlock = stackFrame.codeBlock(); + if (m_codeBlock) { + switch (m_codeBlock->codeType()) { + case JSC::EvalCode: { + frame.code_type = ZigStackFrameCodeEval; + return; + } + case JSC::ModuleCode: { + frame.code_type = ZigStackFrameCodeModule; + return; + } + case JSC::GlobalCode: { + frame.code_type = ZigStackFrameCodeGlobal; + return; + } + case JSC::FunctionCode: { + frame.code_type = !m_codeBlock->isConstructor() ? ZigStackFrameCodeFunction : ZigStackFrameCodeConstructor; + break; + } + default: + ASSERT_NOT_REACHED(); + } + } + + auto calleeCell = stackFrame.callee(); + if (!calleeCell) + return; + + JSC::JSObject* callee = calleeCell->getObject(); + if (!callee) + return; + + WTF::String functionName = Zig::functionName(vm, globalObject, callee); + if (!functionName.isEmpty()) { + frame.function_name = Bun::toStringRef(functionName); + } + + frame.is_async = stackFrame.isAsyncFrame(); +} + +static void populateStackFramePosition(const JSC::StackFrame& stackFrame, BunString* source_lines, + OrdinalNumber* source_line_numbers, uint8_t source_lines_count, + ZigStackFramePosition& position, JSC::SourceProvider** referenced_source_provider, PopulateStackTraceFlags flags) +{ + auto code = stackFrame.codeBlock(); + if (!code) + return; + + auto* provider = code->source().provider(); + if (!provider) [[unlikely]] + return; + // Make sure the range is valid: + // https://github.com/oven-sh/bun/issues/6951 + WTF::StringView sourceString = provider->source(); + if (sourceString.isNull()) [[unlikely]] + return; + + if (!stackFrame.hasBytecodeIndex()) { + if (stackFrame.hasLineAndColumnInfo()) { + auto lineColumn = stackFrame.computeLineAndColumn(); + position.line_zero_based = OrdinalNumber::fromOneBasedInt(lineColumn.line).zeroBasedInt(); + position.column_zero_based = OrdinalNumber::fromOneBasedInt(lineColumn.column).zeroBasedInt(); + } + + position.byte_position = -1; + return; + } + + auto location = Bun::getAdjustedPositionForBytecode(code, stackFrame.bytecodeIndex()); + memcpy(&position, &location, sizeof(ZigStackFramePosition)); + if (flags == PopulateStackTraceFlags::OnlyPosition) + return; + + if (source_lines_count > 1 && source_lines != nullptr && sourceString.is8Bit()) { + // Search for the beginning of the line + unsigned int lineStart = location.byte_position; + while (lineStart > 0 && sourceString[lineStart] != '\n') { + lineStart--; + } + + // Search for the end of the line + unsigned int lineEnd = location.byte_position; + unsigned int maxSearch = sourceString.length(); + while (lineEnd < maxSearch && sourceString[lineEnd] != '\n') { + lineEnd++; + } + + const unsigned char* bytes = sourceString.span8().data(); + + // Most of the time, when you look at a stack trace, you want a couple lines above. + + // It is key to not clone this data because source code strings are large. + // Usage of toStringView (non-owning) is safe as we ref the provider. + provider->ref(); + if (*referenced_source_provider != nullptr) { + (*referenced_source_provider)->deref(); + } + *referenced_source_provider = provider; + source_lines[0] = Bun::toStringView(sourceString.substring(lineStart, lineEnd - lineStart)); + source_line_numbers[0] = location.line(); + + if (lineStart > 0) { + auto byte_offset_in_source_string = lineStart - 1; + uint8_t source_line_i = 1; + auto remaining_lines_to_grab = source_lines_count - 1; + + { + // This should probably be code points instead of newlines + while (byte_offset_in_source_string > 0 && bytes[byte_offset_in_source_string] != '\n') { + byte_offset_in_source_string--; + } + + byte_offset_in_source_string -= byte_offset_in_source_string > 0; + } + + while (byte_offset_in_source_string > 0 && remaining_lines_to_grab > 0) { + unsigned int end_of_line_offset = byte_offset_in_source_string; + + // This should probably be code points instead of newlines + while (byte_offset_in_source_string > 0 && bytes[byte_offset_in_source_string] != '\n') { + byte_offset_in_source_string--; + } + + // We are at the beginning of the line + source_lines[source_line_i] = Bun::toStringView(sourceString.substring(byte_offset_in_source_string, end_of_line_offset - byte_offset_in_source_string + 1)); + + source_line_numbers[source_line_i] = location.line().fromZeroBasedInt(location.line().zeroBasedInt() - source_line_i); + source_line_i++; + + remaining_lines_to_grab--; + + byte_offset_in_source_string -= byte_offset_in_source_string > 0; + } + } + } +} + +static void populateStackFrame(JSC::VM& vm, ZigStackTrace& trace, const JSC::StackFrame& stackFrame, + ZigStackFrame& frame, bool is_top, JSC::SourceProvider** referenced_source_provider, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags) +{ + if (flags == PopulateStackTraceFlags::OnlyPosition) { + populateStackFrameMetadata(vm, globalObject, stackFrame, frame); + populateStackFramePosition(stackFrame, nullptr, + nullptr, + 0, frame.position, referenced_source_provider, flags); + } else if (flags == PopulateStackTraceFlags::OnlySourceLines) { + 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, flags); + } +} + +class V8StackTraceIterator { +public: + class StackFrame { + public: + StringView functionName {}; + StringView sourceURL {}; + WTF::OrdinalNumber lineNumber = WTF::OrdinalNumber::fromZeroBasedInt(0); + WTF::OrdinalNumber columnNumber = WTF::OrdinalNumber::fromZeroBasedInt(0); + + bool isConstructor = false; + bool isGlobalCode = false; + bool isAsync = false; + }; + + WTF::StringView stack; + unsigned int offset = 0; + + V8StackTraceIterator(WTF::StringView stack_) + : stack(stack_) + { + } + + bool parseFrame(StackFrame& frame) + { + + if (offset >= stack.length()) + return false; + + auto start = stack.find("\n at "_s, offset); + + if (start == WTF::notFound) { + offset = stack.length(); + return false; + } + + start += 8; + auto end = stack.find("\n"_s, start); + + if (end == WTF::notFound) { + offset = stack.length(); + end = offset; + } + + if (start >= end || start == WTF::notFound) { + return false; + } + + StringView line = stack.substring(start, end - start); + offset = end; + + // the proper singular spelling is parenthesis + auto openingParentheses = line.reverseFind('('); + auto closingParentheses = line.reverseFind(')'); + + if (openingParentheses > closingParentheses) + openingParentheses = WTF::notFound; + + if (openingParentheses == WTF::notFound || closingParentheses == WTF::notFound) { + // Special case: "unknown" frames don't have parentheses but are valid + // These appear in stack traces from certain error paths + if (line == "unknown"_s) { + frame.sourceURL = line; + frame.functionName = StringView(); + return true; + } + + // For any other frame without parentheses, terminate parsing as before + offset = stack.length(); + return false; + } + + auto lineInner = StringView_slice(line, openingParentheses + 1, closingParentheses); + + { + auto marker1 = 0; + auto marker2 = lineInner.find(':', marker1); + + if (marker2 == WTF::notFound) { + frame.sourceURL = lineInner; + goto done_block; + } + + auto marker3 = lineInner.find(':', marker2 + 1); + if (marker3 == WTF::notFound) { + // /path/to/file.js: + // /path/to/file.js:1 + // node:child_process + // C:\Users\chloe\bun\file.js + + marker3 = lineInner.length(); + + auto segment1 = StringView_slice(lineInner, marker1, marker2); + auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3); + + if (auto int1 = WTF::parseIntegerAllowingTrailingJunk(segment2)) { + frame.sourceURL = segment1; + frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value()); + } else { + frame.sourceURL = StringView_slice(lineInner, marker1, marker3); + } + goto done_block; + } + + // /path/to/file.js:1: + // /path/to/file.js:1:2 + // node:child_process:1:2 + // C:\Users\chloe\bun\file.js: + // C:\Users\chloe\bun\file.js:1 + // C:\Users\chloe\bun\file.js:1:2 + + while (true) { + auto newcolon = lineInner.find(':', marker3 + 1); + if (newcolon == WTF::notFound) + break; + marker2 = marker3; + marker3 = newcolon; + } + + auto marker4 = lineInner.length(); + + auto segment1 = StringView_slice(lineInner, marker1, marker2); + auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3); + auto segment3 = StringView_slice(lineInner, marker3 + 1, marker4); + + if (auto int1 = WTF::parseIntegerAllowingTrailingJunk(segment2)) { + if (auto int2 = WTF::parseIntegerAllowingTrailingJunk(segment3)) { + frame.sourceURL = segment1; + frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value()); + frame.columnNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value()); + } else { + frame.sourceURL = segment1; + frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value()); + } + } else { + if (auto int2 = WTF::parseIntegerAllowingTrailingJunk(segment3)) { + frame.sourceURL = StringView_slice(lineInner, marker1, marker3); + frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value()); + } else { + frame.sourceURL = StringView_slice(lineInner, marker1, marker4); + } + } + } + done_block: + + StringView functionName = line.substring(0, openingParentheses - 1); + + if (functionName == "global code"_s) { + functionName = StringView(); + frame.isGlobalCode = true; + } + + if (functionName.startsWith("async "_s)) { + frame.isAsync = true; + functionName = functionName.substring(6); + } + + if (functionName.startsWith("new "_s)) { + frame.isConstructor = true; + functionName = functionName.substring(4); + } + + if (functionName == ""_s) { + functionName = StringView(); + } + + frame.functionName = functionName; + + return true; + } + + void forEachFrame(const WTF::Function callback) + { + bool stop = false; + while (!stop) { + StackFrame frame; + if (!parseFrame(frame)) + break; + callback(frame, stop); + } + } +}; + +static void populateStackTrace(JSC::VM& vm, const WTF::Vector& frames, ZigStackTrace& trace, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags) +{ + if (flags == PopulateStackTraceFlags::OnlyPosition) { + uint8_t frame_i = 0; + size_t stack_frame_i = 0; + const size_t total_frame_count = frames.size(); + const uint8_t frame_count = total_frame_count < trace.frames_cap ? total_frame_count : trace.frames_cap; + + while (frame_i < frame_count && stack_frame_i < total_frame_count) { + // Skip native frames + while (stack_frame_i < total_frame_count && !(frames.at(stack_frame_i).hasLineAndColumnInfo()) && !(frames.at(stack_frame_i).isWasmFrame())) { + stack_frame_i++; + } + if (stack_frame_i >= total_frame_count) + break; + + ZigStackFrame& frame = trace.frames_ptr[frame_i]; + frame.jsc_stack_frame_index = static_cast(stack_frame_i); + populateStackFrame(vm, trace, frames[stack_frame_i], frame, frame_i == 0, &trace.referenced_source_provider, globalObject, flags); + stack_frame_i++; + frame_i++; + } + trace.frames_len = frame_i; + } else if (flags == PopulateStackTraceFlags::OnlySourceLines) { + for (uint8_t i = 0; i < trace.frames_len; i++) { + ZigStackFrame& frame = trace.frames_ptr[i]; + // A call with flags set to OnlySourceLines always follows a call with flags set to OnlyPosition, + // so jsc_stack_frame_index is always a valid value here. + ASSERT(frame.jsc_stack_frame_index >= 0); + ASSERT(static_cast(frame.jsc_stack_frame_index) < frames.size()); + populateStackFrame(vm, trace, frames[frame.jsc_stack_frame_index], frame, i == 0, &trace.referenced_source_provider, globalObject, flags); + } + } +} + +static JSC::JSValue getNonObservable(JSC::VM& vm, JSC::JSGlobalObject* global, JSC::JSObject* obj, const JSC::PropertyName& propertyName) +{ + PropertySlot slot = PropertySlot(obj, PropertySlot::InternalMethodType::VMInquiry, &vm); + if (obj->getNonIndexPropertySlot(global, propertyName, slot)) { + if (slot.isAccessor()) { + return {}; + } + + JSValue value = slot.getValue(global, propertyName); + if (!value || value.isUndefinedOrNull()) { + return {}; + } + return value; + } + return {}; +} + +static void fromErrorInstance(ZigException& except, JSC::JSGlobalObject* global, + JSC::ErrorInstance* err, const Vector* stackTrace, + JSC::JSValue val, PopulateStackTraceFlags flags) +{ + JSC::JSObject* obj = JSC::jsDynamicCast(val); + auto& vm = JSC::getVM(global); + auto scope = DECLARE_CATCH_SCOPE(vm); + + bool getFromSourceURL = false; + if (stackTrace != nullptr && stackTrace->size() > 0) { + populateStackTrace(vm, *stackTrace, except.stack, global, flags); + + } else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) { + populateStackTrace(vm, *err->stackTrace(), except.stack, global, flags); + + } else { + getFromSourceURL = true; + } + except.type = (unsigned char)err->errorType(); + if (err->isStackOverflowError()) { + except.type = 253; + } + if (err->isOutOfMemoryError()) { + except.type = 8; + } + if (except.type == SYNTAX_ERROR_CODE) { + except.message = Bun::toStringRef(err->sanitizedMessageString(global)); + + } else if (JSC::JSValue message = obj->getIfPropertyExists(global, vm.propertyNames->message)) { + except.message = Bun::toStringRef(global, message); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + } else { + + except.message = Bun::toStringRef(err->sanitizedMessageString(global)); + } + + if (!scope.clearExceptionExceptTermination()) [[unlikely]] { + return; + } + + except.name = Bun::toStringRef(err->sanitizedNameString(global)); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] { + return; + } + + except.runtime_type = err->runtimeTypeForCause(); + + const auto& names = builtinNames(vm); + if (except.type != SYNTAX_ERROR_CODE) { + + JSC::JSValue syscall = getNonObservable(vm, global, obj, names.syscallPublicName()); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (syscall) { + if (syscall.isString()) { + except.syscall = Bun::toStringRef(global, syscall); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + } + } + + JSC::JSValue code = getNonObservable(vm, global, obj, names.codePublicName()); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (code) { + if (code.isString() || code.isNumber()) { + except.system_code = Bun::toStringRef(global, code); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + } + } + + JSC::JSValue path = getNonObservable(vm, global, obj, names.pathPublicName()); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (path) { + if (path.isString()) { + except.path = Bun::toStringRef(global, path); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + } + } + + JSC::JSValue fd = getNonObservable(vm, global, obj, names.fdPublicName()); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (fd) { + if (fd.isNumber()) { + except.fd = fd.toInt32(global); + } + } + + JSC::JSValue errno_ = getNonObservable(vm, global, obj, names.errnoPublicName()); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (errno_) { + if (errno_.isNumber()) { + except.errno_ = errno_.toInt32(global); + } + } + } + + if (getFromSourceURL) { + + // we don't want to serialize JSC::StackFrame longer than we need to + // so in this case, we parse the stack trace as a string + + // This one intentionally calls getters. + JSC::JSValue stackValue = obj->getIfPropertyExists(global, vm.propertyNames->stack); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (stackValue) { + // Prevent infinite recursion if stack property is the error object itself + if (stackValue == val) { + return; + } + if (stackValue.isString()) { + WTF::String stack = stackValue.toWTFString(global); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] { + return; + } + if (!stack.isEmpty()) { + + V8StackTraceIterator iterator(stack); + const uint8_t frame_count = except.stack.frames_cap; + + except.stack.frames_len = 0; + + iterator.forEachFrame([&](const V8StackTraceIterator::StackFrame& frame, bool& stop) -> void { + ASSERT(except.stack.frames_len < frame_count); + auto& current = except.stack.frames_ptr[except.stack.frames_len]; + current = {}; + + String functionName = frame.functionName.toString(); + String sourceURL = frame.sourceURL.toString(); + current.function_name = Bun::toStringRef(functionName); + current.source_url = Bun::toStringRef(sourceURL); + current.position.line_zero_based = frame.lineNumber.zeroBasedInt(); + current.position.column_zero_based = frame.columnNumber.zeroBasedInt(); + + current.remapped = true; + current.is_async = frame.isAsync; + + if (frame.isConstructor) { + current.code_type = ZigStackFrameCodeConstructor; + } else if (frame.isGlobalCode) { + current.code_type = ZigStackFrameCodeGlobal; + } + + except.stack.frames_len += 1; + + stop = except.stack.frames_len >= frame_count; + }); + + if (except.stack.frames_len > 0) { + getFromSourceURL = false; + except.remapped = true; + } + } + } + } + } + + if (except.stack.frames_len == 0 && getFromSourceURL) { + JSC::JSValue sourceURL = getNonObservable(vm, global, obj, vm.propertyNames->sourceURL); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (sourceURL) { + if (sourceURL.isString()) { + except.stack.frames_ptr[0].source_url.deref(); + except.stack.frames_ptr[0].source_url = Bun::toStringRef(global, sourceURL); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + + // Take care not to make these getter calls observable. + + JSC::JSValue column = getNonObservable(vm, global, obj, vm.propertyNames->column); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (column) { + if (column.isNumber()) { + except.stack.frames_ptr[0].position.column_zero_based = OrdinalNumber::fromOneBasedInt(column.toInt32(global)).zeroBasedInt(); + } + } + + JSC::JSValue line = getNonObservable(vm, global, obj, vm.propertyNames->line); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (line) { + if (line.isNumber()) { + except.stack.frames_ptr[0].position.line_zero_based = OrdinalNumber::fromOneBasedInt(line.toInt32(global)).zeroBasedInt(); + + JSC::JSValue lineText = getNonObservable(vm, global, obj, builtinNames(vm).lineTextPublicName()); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + if (lineText) { + if (lineText.isString()) { + if (JSC::JSString* jsStr = lineText.toStringOrNull(global)) { + auto str = jsStr->value(global); + except.stack.source_lines_ptr[0] = Bun::toStringRef(str); + except.stack.source_lines_numbers[0] = except.stack.frames_ptr[0].position.line(); + except.stack.source_lines_len = 1; + except.remapped = true; + } + } + } + } + } + } + + { + for (int i = 1; i < except.stack.frames_len; i++) { + auto frame = except.stack.frames_ptr[i]; + frame.function_name.deref(); + frame.source_url.deref(); + } + except.stack.frames_len = 1; + PropertySlot slot = PropertySlot(obj, PropertySlot::InternalMethodType::VMInquiry, &vm); + except.stack.frames_ptr[0].remapped = obj->getNonIndexPropertySlot(global, names.originalLinePublicName(), slot); + if (!scope.clearExceptionExceptTermination()) [[unlikely]] + return; + } + } + } +} + +void exceptionFromString(ZigException& except, JSC::JSValue value, JSC::JSGlobalObject* global) +{ + auto& vm = JSC::getVM(global); + if (vm.hasPendingTerminationException()) [[unlikely]] { + return; + } + + auto scope = DECLARE_CATCH_SCOPE(vm); + + // Fallback case for when it's a user-defined ErrorLike-object that doesn't inherit from + // ErrorInstance + if (JSC::JSObject* obj = JSC::jsDynamicCast(value)) { + auto name_value = obj->getIfPropertyExists(global, vm.propertyNames->name); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + if (name_value) { + if (name_value.isString()) { + auto name_str = name_value.toWTFString(global); + except.name = Bun::toStringRef(name_str); + if (name_str == "Error"_s) { + except.type = JSErrorCodeError; + } else if (name_str == "EvalError"_s) { + except.type = JSErrorCodeEvalError; + } else if (name_str == "RangeError"_s) { + except.type = JSErrorCodeRangeError; + } else if (name_str == "ReferenceError"_s) { + except.type = JSErrorCodeReferenceError; + } else if (name_str == "SyntaxError"_s) { + except.type = JSErrorCodeSyntaxError; + } else if (name_str == "TypeError"_s) { + except.type = JSErrorCodeTypeError; + } else if (name_str == "URIError"_s) { + except.type = JSErrorCodeURIError; + } else if (name_str == "AggregateError"_s) { + except.type = JSErrorCodeAggregateError; + } + } + } + + auto message = obj->getIfPropertyExists(global, vm.propertyNames->message); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + if (message) { + if (message.isString()) { + except.message = Bun::toStringRef(message.toWTFString(global)); + } + } + + if (except.stack.frames_len == 0) { + auto sourceURL = obj->getIfPropertyExists(global, vm.propertyNames->sourceURL); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + if (sourceURL) { + if (sourceURL.isString()) { + except.stack.frames_ptr[0].source_url = Bun::toStringRef(sourceURL.toWTFString(global)); + except.stack.frames_len = 1; + } + } + + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + + auto line = obj->getIfPropertyExists(global, vm.propertyNames->line); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + if (line) { + if (line.isNumber()) { + except.stack.frames_ptr[0].position.line_zero_based = OrdinalNumber::fromOneBasedInt(line.toInt32(global)).zeroBasedInt(); + + // TODO: don't sourcemap it twice + auto originalLine = obj->getIfPropertyExists(global, builtinNames(vm).originalLinePublicName()); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + if (originalLine) { + if (originalLine.isNumber()) { + except.stack.frames_ptr[0].position.line_zero_based = OrdinalNumber::fromOneBasedInt(originalLine.toInt32(global)).zeroBasedInt(); + } + } + except.stack.frames_len = 1; + } + } + } + + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + } + + 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: { + auto* symbol = asSymbol(cell); + if (symbol->description().isEmpty()) { + except.message = BunStringEmpty; + } else { + except.message = Bun::toStringRef(symbol->description()); + } + return; + } + + default: { + break; + } + } + } + + auto str = value.toWTFString(global); + if (scope.exception()) [[unlikely]] { + scope.clearExceptionExceptTermination(); + return; + } + + except.message = Bun::toStringRef(str); +} + +extern "C" void JSC__Exception__getStackTrace(JSC::Exception* arg0, JSC::JSGlobalObject* global, ZigStackTrace* trace) +{ + populateStackTrace(arg0->vm(), arg0->stack(), *trace, global, PopulateStackTraceFlags::OnlyPosition); +} + +extern "C" [[ZIG_EXPORT(check_slow)]] void JSC__JSValue__toZigException(JSC::EncodedJSValue jsException, JSC::JSGlobalObject* global, ZigException* exception) +{ + JSC::JSValue value = JSC::JSValue::decode(jsException); + if (value == JSC::JSValue {}) { + exception->type = JSErrorCodeError; + exception->name = Bun::toStringRef("Error"_s); + exception->message = Bun::toStringRef("Unknown error"_s); + return; + } + + 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, PopulateStackTraceFlags::OnlyPosition); + return; + } + + if (jscException->stack().size() > 0) { + populateStackTrace(global->vm(), jscException->stack(), exception->stack, global, PopulateStackTraceFlags::OnlyPosition); + } + + exceptionFromString(*exception, unwrapped, global); + return; + } + + if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { + fromErrorInstance(*exception, global, error, nullptr, value, PopulateStackTraceFlags::OnlyPosition); + return; + } + + exceptionFromString(*exception, value, global); +} + +extern "C" void ZigException__collectSourceLines(JSC::EncodedJSValue jsException, JSC::JSGlobalObject* global, ZigException* exception) +{ + JSC::JSValue value = JSC::JSValue::decode(jsException); + if (value == JSC::JSValue {}) { + return; + } + + if (value.classInfoOrNull() == JSC::Exception::info()) { + auto* jscException = jsCast(value); + JSValue unwrapped = jscException->value(); + + if (jscException->stack().size() > 0) { + populateStackTrace(global->vm(), jscException->stack(), exception->stack, global, PopulateStackTraceFlags::OnlySourceLines); + } + + exceptionFromString(*exception, unwrapped, global); + return; + } + + if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { + if (error->stackTrace() != nullptr && error->stackTrace()->size() > 0) { + populateStackTrace(global->vm(), *error->stackTrace(), exception->stack, global, PopulateStackTraceFlags::OnlySourceLines); + } + return; + } +} diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index e8147330ea..e0a81bd8b8 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -161,20 +161,10 @@ #endif #endif -static WTF::StringView StringView_slice(WTF::StringView sv, unsigned start, unsigned end) -{ - return sv.substring(start, end - start); -} - using namespace JSC; using namespace WebCore; -enum PopulateStackTraceFlags { - OnlyPosition, - OnlySourceLines, -}; - typedef uint8_t ExpectFlags; // Note: keep this in sync with Expect.Flags implementation in zig (at expect.zig) @@ -4414,770 +4404,6 @@ bool JSC__JSValue__stringIncludes(JSC::EncodedJSValue value, JSC::JSGlobalObject return stringToSearchIn.find(searchString, 0) != WTF::notFound; } -static void populateStackFrameMetadata(JSC::VM& vm, JSC::JSGlobalObject* globalObject, const JSC::StackFrame& stackFrame, ZigStackFrame& frame) -{ - - if (stackFrame.isWasmFrame()) { - frame.code_type = ZigStackFrameCodeWasm; - - auto name = Zig::functionName(vm, globalObject, stackFrame, false, nullptr); - if (!name.isEmpty()) { - frame.function_name = Bun::toStringRef(name); - } - - 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. - frame.source_url = Bun::toStringRef(sourceURL); - } - return; - } - - auto sourceURL = Zig::sourceURL(vm, stackFrame); - frame.source_url = Bun::toStringRef(sourceURL); - auto m_codeBlock = stackFrame.codeBlock(); - if (m_codeBlock) { - switch (m_codeBlock->codeType()) { - case JSC::EvalCode: { - frame.code_type = ZigStackFrameCodeEval; - return; - } - case JSC::ModuleCode: { - frame.code_type = ZigStackFrameCodeModule; - return; - } - case JSC::GlobalCode: { - frame.code_type = ZigStackFrameCodeGlobal; - return; - } - case JSC::FunctionCode: { - frame.code_type = !m_codeBlock->isConstructor() ? ZigStackFrameCodeFunction : ZigStackFrameCodeConstructor; - break; - } - default: - ASSERT_NOT_REACHED(); - } - } - - auto calleeCell = stackFrame.callee(); - if (!calleeCell) - return; - - JSC::JSObject* callee = calleeCell->getObject(); - if (!callee) - return; - - WTF::String functionName = Zig::functionName(vm, globalObject, callee); - if (!functionName.isEmpty()) { - frame.function_name = Bun::toStringRef(functionName); - } - - frame.is_async = stackFrame.isAsyncFrame(); -} - -static void populateStackFramePosition(const JSC::StackFrame& stackFrame, BunString* source_lines, - OrdinalNumber* source_line_numbers, uint8_t source_lines_count, - ZigStackFramePosition& position, JSC::SourceProvider** referenced_source_provider, PopulateStackTraceFlags flags) -{ - auto code = stackFrame.codeBlock(); - if (!code) - return; - - auto* provider = code->source().provider(); - if (!provider) [[unlikely]] - return; - // Make sure the range is valid: - // https://github.com/oven-sh/bun/issues/6951 - WTF::StringView sourceString = provider->source(); - if (sourceString.isNull()) [[unlikely]] - return; - - if (!stackFrame.hasBytecodeIndex()) { - if (stackFrame.hasLineAndColumnInfo()) { - auto lineColumn = stackFrame.computeLineAndColumn(); - position.line_zero_based = OrdinalNumber::fromOneBasedInt(lineColumn.line).zeroBasedInt(); - position.column_zero_based = OrdinalNumber::fromOneBasedInt(lineColumn.column).zeroBasedInt(); - } - - position.byte_position = -1; - return; - } - - auto location = Bun::getAdjustedPositionForBytecode(code, stackFrame.bytecodeIndex()); - memcpy(&position, &location, sizeof(ZigStackFramePosition)); - if (flags == PopulateStackTraceFlags::OnlyPosition) - return; - - if (source_lines_count > 1 && source_lines != nullptr && sourceString.is8Bit()) { - // Search for the beginning of the line - unsigned int lineStart = location.byte_position; - while (lineStart > 0 && sourceString[lineStart] != '\n') { - lineStart--; - } - - // Search for the end of the line - unsigned int lineEnd = location.byte_position; - unsigned int maxSearch = sourceString.length(); - while (lineEnd < maxSearch && sourceString[lineEnd] != '\n') { - lineEnd++; - } - - const unsigned char* bytes = sourceString.span8().data(); - - // Most of the time, when you look at a stack trace, you want a couple lines above. - - // It is key to not clone this data because source code strings are large. - // Usage of toStringView (non-owning) is safe as we ref the provider. - provider->ref(); - if (*referenced_source_provider != nullptr) { - (*referenced_source_provider)->deref(); - } - *referenced_source_provider = provider; - source_lines[0] = Bun::toStringView(sourceString.substring(lineStart, lineEnd - lineStart)); - source_line_numbers[0] = location.line(); - - if (lineStart > 0) { - auto byte_offset_in_source_string = lineStart - 1; - uint8_t source_line_i = 1; - auto remaining_lines_to_grab = source_lines_count - 1; - - { - // This should probably be code points instead of newlines - while (byte_offset_in_source_string > 0 && bytes[byte_offset_in_source_string] != '\n') { - byte_offset_in_source_string--; - } - - byte_offset_in_source_string -= byte_offset_in_source_string > 0; - } - - while (byte_offset_in_source_string > 0 && remaining_lines_to_grab > 0) { - unsigned int end_of_line_offset = byte_offset_in_source_string; - - // This should probably be code points instead of newlines - while (byte_offset_in_source_string > 0 && bytes[byte_offset_in_source_string] != '\n') { - byte_offset_in_source_string--; - } - - // We are at the beginning of the line - source_lines[source_line_i] = Bun::toStringView(sourceString.substring(byte_offset_in_source_string, end_of_line_offset - byte_offset_in_source_string + 1)); - - source_line_numbers[source_line_i] = location.line().fromZeroBasedInt(location.line().zeroBasedInt() - source_line_i); - source_line_i++; - - remaining_lines_to_grab--; - - byte_offset_in_source_string -= byte_offset_in_source_string > 0; - } - } - } -} - -static void populateStackFrame(JSC::VM& vm, ZigStackTrace& trace, const JSC::StackFrame& stackFrame, - ZigStackFrame& frame, bool is_top, JSC::SourceProvider** referenced_source_provider, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags) -{ - if (flags == PopulateStackTraceFlags::OnlyPosition) { - populateStackFrameMetadata(vm, globalObject, stackFrame, frame); - populateStackFramePosition(stackFrame, nullptr, - nullptr, - 0, frame.position, referenced_source_provider, flags); - } else if (flags == PopulateStackTraceFlags::OnlySourceLines) { - 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, flags); - } -} - -class V8StackTraceIterator { -public: - class StackFrame { - public: - StringView functionName {}; - StringView sourceURL {}; - WTF::OrdinalNumber lineNumber = WTF::OrdinalNumber::fromZeroBasedInt(0); - WTF::OrdinalNumber columnNumber = WTF::OrdinalNumber::fromZeroBasedInt(0); - - bool isConstructor = false; - bool isGlobalCode = false; - bool isAsync = false; - }; - - WTF::StringView stack; - unsigned int offset = 0; - - V8StackTraceIterator(WTF::StringView stack_) - : stack(stack_) - { - } - - bool parseFrame(StackFrame& frame) - { - - if (offset >= stack.length()) - return false; - - auto start = stack.find("\n at "_s, offset); - - if (start == WTF::notFound) { - offset = stack.length(); - return false; - } - - start += 8; - auto end = stack.find("\n"_s, start); - - if (end == WTF::notFound) { - offset = stack.length(); - end = offset; - } - - if (start >= end || start == WTF::notFound) { - return false; - } - - StringView line = stack.substring(start, end - start); - offset = end; - - // the proper singular spelling is parenthesis - auto openingParentheses = line.reverseFind('('); - auto closingParentheses = line.reverseFind(')'); - - if (openingParentheses > closingParentheses) - openingParentheses = WTF::notFound; - - if (openingParentheses == WTF::notFound || closingParentheses == WTF::notFound) { - // Special case: "unknown" frames don't have parentheses but are valid - // These appear in stack traces from certain error paths - if (line == "unknown"_s) { - frame.sourceURL = line; - frame.functionName = StringView(); - return true; - } - - // For any other frame without parentheses, terminate parsing as before - offset = stack.length(); - return false; - } - - auto lineInner = StringView_slice(line, openingParentheses + 1, closingParentheses); - - { - auto marker1 = 0; - auto marker2 = lineInner.find(':', marker1); - - if (marker2 == WTF::notFound) { - frame.sourceURL = lineInner; - goto done_block; - } - - auto marker3 = lineInner.find(':', marker2 + 1); - if (marker3 == WTF::notFound) { - // /path/to/file.js: - // /path/to/file.js:1 - // node:child_process - // C:\Users\chloe\bun\file.js - - marker3 = lineInner.length(); - - auto segment1 = StringView_slice(lineInner, marker1, marker2); - auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3); - - if (auto int1 = WTF::parseIntegerAllowingTrailingJunk(segment2)) { - frame.sourceURL = segment1; - frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value()); - } else { - frame.sourceURL = StringView_slice(lineInner, marker1, marker3); - } - goto done_block; - } - - // /path/to/file.js:1: - // /path/to/file.js:1:2 - // node:child_process:1:2 - // C:\Users\chloe\bun\file.js: - // C:\Users\chloe\bun\file.js:1 - // C:\Users\chloe\bun\file.js:1:2 - - while (true) { - auto newcolon = lineInner.find(':', marker3 + 1); - if (newcolon == WTF::notFound) - break; - marker2 = marker3; - marker3 = newcolon; - } - - auto marker4 = lineInner.length(); - - auto segment1 = StringView_slice(lineInner, marker1, marker2); - auto segment2 = StringView_slice(lineInner, marker2 + 1, marker3); - auto segment3 = StringView_slice(lineInner, marker3 + 1, marker4); - - if (auto int1 = WTF::parseIntegerAllowingTrailingJunk(segment2)) { - if (auto int2 = WTF::parseIntegerAllowingTrailingJunk(segment3)) { - frame.sourceURL = segment1; - frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value()); - frame.columnNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value()); - } else { - frame.sourceURL = segment1; - frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int1.value()); - } - } else { - if (auto int2 = WTF::parseIntegerAllowingTrailingJunk(segment3)) { - frame.sourceURL = StringView_slice(lineInner, marker1, marker3); - frame.lineNumber = WTF::OrdinalNumber::fromOneBasedInt(int2.value()); - } else { - frame.sourceURL = StringView_slice(lineInner, marker1, marker4); - } - } - } - done_block: - - StringView functionName = line.substring(0, openingParentheses - 1); - - if (functionName == "global code"_s) { - functionName = StringView(); - frame.isGlobalCode = true; - } - - if (functionName.startsWith("async "_s)) { - frame.isAsync = true; - functionName = functionName.substring(6); - } - - if (functionName.startsWith("new "_s)) { - frame.isConstructor = true; - functionName = functionName.substring(4); - } - - if (functionName == ""_s) { - functionName = StringView(); - } - - frame.functionName = functionName; - - return true; - } - - void forEachFrame(const WTF::Function callback) - { - bool stop = false; - while (!stop) { - StackFrame frame; - if (!parseFrame(frame)) - break; - callback(frame, stop); - } - } -}; - -static void populateStackTrace(JSC::VM& vm, const WTF::Vector& frames, ZigStackTrace& trace, JSC::JSGlobalObject* globalObject, PopulateStackTraceFlags flags) -{ - if (flags == PopulateStackTraceFlags::OnlyPosition) { - uint8_t frame_i = 0; - size_t stack_frame_i = 0; - const size_t total_frame_count = frames.size(); - const uint8_t frame_count = total_frame_count < trace.frames_cap ? total_frame_count : trace.frames_cap; - - while (frame_i < frame_count && stack_frame_i < total_frame_count) { - // Skip native frames - while (stack_frame_i < total_frame_count && !(frames.at(stack_frame_i).hasLineAndColumnInfo()) && !(frames.at(stack_frame_i).isWasmFrame())) { - stack_frame_i++; - } - if (stack_frame_i >= total_frame_count) - break; - - ZigStackFrame& frame = trace.frames_ptr[frame_i]; - frame.jsc_stack_frame_index = static_cast(stack_frame_i); - populateStackFrame(vm, trace, frames[stack_frame_i], frame, frame_i == 0, &trace.referenced_source_provider, globalObject, flags); - stack_frame_i++; - frame_i++; - } - trace.frames_len = frame_i; - } else if (flags == PopulateStackTraceFlags::OnlySourceLines) { - for (uint8_t i = 0; i < trace.frames_len; i++) { - ZigStackFrame& frame = trace.frames_ptr[i]; - // A call with flags set to OnlySourceLines always follows a call with flags set to OnlyPosition, - // so jsc_stack_frame_index is always a valid value here. - ASSERT(frame.jsc_stack_frame_index >= 0); - ASSERT(static_cast(frame.jsc_stack_frame_index) < frames.size()); - populateStackFrame(vm, trace, frames[frame.jsc_stack_frame_index], frame, i == 0, &trace.referenced_source_provider, globalObject, flags); - } - } -} - -static JSC::JSValue getNonObservable(JSC::VM& vm, JSC::JSGlobalObject* global, JSC::JSObject* obj, const JSC::PropertyName& propertyName) -{ - PropertySlot slot = PropertySlot(obj, PropertySlot::InternalMethodType::VMInquiry, &vm); - if (obj->getNonIndexPropertySlot(global, propertyName, slot)) { - if (slot.isAccessor()) { - return {}; - } - - JSValue value = slot.getValue(global, propertyName); - if (!value || value.isUndefinedOrNull()) { - return {}; - } - return value; - } - return {}; -} - -#define SYNTAX_ERROR_CODE 4 - -static void fromErrorInstance(ZigException& except, JSC::JSGlobalObject* global, - JSC::ErrorInstance* err, const Vector* stackTrace, - JSC::JSValue val, PopulateStackTraceFlags flags) -{ - JSC::JSObject* obj = JSC::jsDynamicCast(val); - auto& vm = JSC::getVM(global); - auto scope = DECLARE_CATCH_SCOPE(vm); - - bool getFromSourceURL = false; - if (stackTrace != nullptr && stackTrace->size() > 0) { - populateStackTrace(vm, *stackTrace, except.stack, global, flags); - - } else if (err->stackTrace() != nullptr && err->stackTrace()->size() > 0) { - populateStackTrace(vm, *err->stackTrace(), except.stack, global, flags); - - } else { - getFromSourceURL = true; - } - except.type = (unsigned char)err->errorType(); - if (err->isStackOverflowError()) { - except.type = 253; - } - if (err->isOutOfMemoryError()) { - except.type = 8; - } - if (except.type == SYNTAX_ERROR_CODE) { - except.message = Bun::toStringRef(err->sanitizedMessageString(global)); - - } else if (JSC::JSValue message = obj->getIfPropertyExists(global, vm.propertyNames->message)) { - except.message = Bun::toStringRef(global, message); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - } else { - - except.message = Bun::toStringRef(err->sanitizedMessageString(global)); - } - - if (!scope.clearExceptionExceptTermination()) [[unlikely]] { - return; - } - - except.name = Bun::toStringRef(err->sanitizedNameString(global)); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] { - return; - } - - except.runtime_type = err->runtimeTypeForCause(); - - const auto& names = builtinNames(vm); - if (except.type != SYNTAX_ERROR_CODE) { - - JSC::JSValue syscall = getNonObservable(vm, global, obj, names.syscallPublicName()); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (syscall) { - if (syscall.isString()) { - except.syscall = Bun::toStringRef(global, syscall); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - } - } - - JSC::JSValue code = getNonObservable(vm, global, obj, names.codePublicName()); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (code) { - if (code.isString() || code.isNumber()) { - except.system_code = Bun::toStringRef(global, code); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - } - } - - JSC::JSValue path = getNonObservable(vm, global, obj, names.pathPublicName()); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (path) { - if (path.isString()) { - except.path = Bun::toStringRef(global, path); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - } - } - - JSC::JSValue fd = getNonObservable(vm, global, obj, names.fdPublicName()); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (fd) { - if (fd.isNumber()) { - except.fd = fd.toInt32(global); - } - } - - JSC::JSValue errno_ = getNonObservable(vm, global, obj, names.errnoPublicName()); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (errno_) { - if (errno_.isNumber()) { - except.errno_ = errno_.toInt32(global); - } - } - } - - if (getFromSourceURL) { - - // we don't want to serialize JSC::StackFrame longer than we need to - // so in this case, we parse the stack trace as a string - - // This one intentionally calls getters. - JSC::JSValue stackValue = obj->getIfPropertyExists(global, vm.propertyNames->stack); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (stackValue) { - // Prevent infinite recursion if stack property is the error object itself - if (stackValue == val) { - return; - } - if (stackValue.isString()) { - WTF::String stack = stackValue.toWTFString(global); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] { - return; - } - if (!stack.isEmpty()) { - - V8StackTraceIterator iterator(stack); - const uint8_t frame_count = except.stack.frames_cap; - - except.stack.frames_len = 0; - - iterator.forEachFrame([&](const V8StackTraceIterator::StackFrame& frame, bool& stop) -> void { - ASSERT(except.stack.frames_len < frame_count); - auto& current = except.stack.frames_ptr[except.stack.frames_len]; - current = {}; - - String functionName = frame.functionName.toString(); - String sourceURL = frame.sourceURL.toString(); - current.function_name = Bun::toStringRef(functionName); - current.source_url = Bun::toStringRef(sourceURL); - current.position.line_zero_based = frame.lineNumber.zeroBasedInt(); - current.position.column_zero_based = frame.columnNumber.zeroBasedInt(); - - current.remapped = true; - current.is_async = frame.isAsync; - - if (frame.isConstructor) { - current.code_type = ZigStackFrameCodeConstructor; - } else if (frame.isGlobalCode) { - current.code_type = ZigStackFrameCodeGlobal; - } - - except.stack.frames_len += 1; - - stop = except.stack.frames_len >= frame_count; - }); - - if (except.stack.frames_len > 0) { - getFromSourceURL = false; - except.remapped = true; - } - } - } - } - } - - if (except.stack.frames_len == 0 && getFromSourceURL) { - JSC::JSValue sourceURL = getNonObservable(vm, global, obj, vm.propertyNames->sourceURL); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (sourceURL) { - if (sourceURL.isString()) { - except.stack.frames_ptr[0].source_url.deref(); - except.stack.frames_ptr[0].source_url = Bun::toStringRef(global, sourceURL); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - - // Take care not to make these getter calls observable. - - JSC::JSValue column = getNonObservable(vm, global, obj, vm.propertyNames->column); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (column) { - if (column.isNumber()) { - except.stack.frames_ptr[0].position.column_zero_based = OrdinalNumber::fromOneBasedInt(column.toInt32(global)).zeroBasedInt(); - } - } - - JSC::JSValue line = getNonObservable(vm, global, obj, vm.propertyNames->line); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (line) { - if (line.isNumber()) { - except.stack.frames_ptr[0].position.line_zero_based = OrdinalNumber::fromOneBasedInt(line.toInt32(global)).zeroBasedInt(); - - JSC::JSValue lineText = getNonObservable(vm, global, obj, builtinNames(vm).lineTextPublicName()); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - if (lineText) { - if (lineText.isString()) { - if (JSC::JSString* jsStr = lineText.toStringOrNull(global)) { - auto str = jsStr->value(global); - except.stack.source_lines_ptr[0] = Bun::toStringRef(str); - except.stack.source_lines_numbers[0] = except.stack.frames_ptr[0].position.line(); - except.stack.source_lines_len = 1; - except.remapped = true; - } - } - } - } - } - } - - { - for (int i = 1; i < except.stack.frames_len; i++) { - auto frame = except.stack.frames_ptr[i]; - frame.function_name.deref(); - frame.source_url.deref(); - } - except.stack.frames_len = 1; - PropertySlot slot = PropertySlot(obj, PropertySlot::InternalMethodType::VMInquiry, &vm); - except.stack.frames_ptr[0].remapped = obj->getNonIndexPropertySlot(global, names.originalLinePublicName(), slot); - if (!scope.clearExceptionExceptTermination()) [[unlikely]] - return; - } - } - } -} - -void exceptionFromString(ZigException& except, JSC::JSValue value, JSC::JSGlobalObject* global) -{ - auto& vm = JSC::getVM(global); - if (vm.hasPendingTerminationException()) [[unlikely]] { - return; - } - - auto scope = DECLARE_CATCH_SCOPE(vm); - - // Fallback case for when it's a user-defined ErrorLike-object that doesn't inherit from - // ErrorInstance - if (JSC::JSObject* obj = JSC::jsDynamicCast(value)) { - auto name_value = obj->getIfPropertyExists(global, vm.propertyNames->name); - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - if (name_value) { - if (name_value.isString()) { - auto name_str = name_value.toWTFString(global); - except.name = Bun::toStringRef(name_str); - if (name_str == "Error"_s) { - except.type = JSErrorCodeError; - } else if (name_str == "EvalError"_s) { - except.type = JSErrorCodeEvalError; - } else if (name_str == "RangeError"_s) { - except.type = JSErrorCodeRangeError; - } else if (name_str == "ReferenceError"_s) { - except.type = JSErrorCodeReferenceError; - } else if (name_str == "SyntaxError"_s) { - except.type = JSErrorCodeSyntaxError; - } else if (name_str == "TypeError"_s) { - except.type = JSErrorCodeTypeError; - } else if (name_str == "URIError"_s) { - except.type = JSErrorCodeURIError; - } else if (name_str == "AggregateError"_s) { - except.type = JSErrorCodeAggregateError; - } - } - } - - auto message = obj->getIfPropertyExists(global, vm.propertyNames->message); - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - if (message) { - if (message.isString()) { - except.message = Bun::toStringRef(message.toWTFString(global)); - } - } - - if (except.stack.frames_len == 0) { - auto sourceURL = obj->getIfPropertyExists(global, vm.propertyNames->sourceURL); - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - if (sourceURL) { - if (sourceURL.isString()) { - except.stack.frames_ptr[0].source_url = Bun::toStringRef(sourceURL.toWTFString(global)); - except.stack.frames_len = 1; - } - } - - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - - auto line = obj->getIfPropertyExists(global, vm.propertyNames->line); - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - if (line) { - if (line.isNumber()) { - except.stack.frames_ptr[0].position.line_zero_based = OrdinalNumber::fromOneBasedInt(line.toInt32(global)).zeroBasedInt(); - - // TODO: don't sourcemap it twice - auto originalLine = obj->getIfPropertyExists(global, builtinNames(vm).originalLinePublicName()); - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - if (originalLine) { - if (originalLine.isNumber()) { - except.stack.frames_ptr[0].position.line_zero_based = OrdinalNumber::fromOneBasedInt(originalLine.toInt32(global)).zeroBasedInt(); - } - } - except.stack.frames_len = 1; - } - } - } - - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - } - - 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: { - auto* symbol = asSymbol(cell); - if (symbol->description().isEmpty()) { - except.message = BunStringEmpty; - } else { - except.message = Bun::toStringRef(symbol->description()); - } - return; - } - - default: { - break; - } - } - } - - auto str = value.toWTFString(global); - if (scope.exception()) [[unlikely]] { - scope.clearExceptionExceptTermination(); - return; - } - - except.message = Bun::toStringRef(str); -} - extern "C" JSC::EncodedJSValue JSC__Exception__asJSValue(JSC::Exception* exception) { JSC::Exception* jscException = jsCast(exception); @@ -5323,68 +4549,6 @@ JSC::EncodedJSValue JSC__JSValue__toError_(JSC::EncodedJSValue JSValue0) return {}; } -extern "C" [[ZIG_EXPORT(check_slow)]] void JSC__JSValue__toZigException(JSC::EncodedJSValue jsException, JSC::JSGlobalObject* global, ZigException* exception) -{ - JSC::JSValue value = JSC::JSValue::decode(jsException); - if (value == JSC::JSValue {}) { - exception->type = JSErrorCodeError; - exception->name = Bun::toStringRef("Error"_s); - exception->message = Bun::toStringRef("Unknown error"_s); - return; - } - - 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, PopulateStackTraceFlags::OnlyPosition); - return; - } - - if (jscException->stack().size() > 0) { - populateStackTrace(global->vm(), jscException->stack(), exception->stack, global, PopulateStackTraceFlags::OnlyPosition); - } - - exceptionFromString(*exception, unwrapped, global); - return; - } - - if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { - fromErrorInstance(*exception, global, error, nullptr, value, PopulateStackTraceFlags::OnlyPosition); - return; - } - - exceptionFromString(*exception, value, global); -} - -void ZigException__collectSourceLines(JSC::EncodedJSValue jsException, JSC::JSGlobalObject* global, ZigException* exception) -{ - JSC::JSValue value = JSC::JSValue::decode(jsException); - if (value == JSC::JSValue {}) { - return; - } - - if (value.classInfoOrNull() == JSC::Exception::info()) { - auto* jscException = jsCast(value); - JSValue unwrapped = jscException->value(); - - if (jscException->stack().size() > 0) { - populateStackTrace(global->vm(), jscException->stack(), exception->stack, global, PopulateStackTraceFlags::OnlySourceLines); - } - - exceptionFromString(*exception, unwrapped, global); - return; - } - - if (JSC::ErrorInstance* error = JSC::jsDynamicCast(value)) { - if (error->stackTrace() != nullptr && error->stackTrace()->size() > 0) { - populateStackTrace(global->vm(), *error->stackTrace(), exception->stack, global, PopulateStackTraceFlags::OnlySourceLines); - } - return; - } -} - #pragma mark - JSC::VM size_t JSC__VM__runGC(JSC::VM* vm, bool sync) @@ -5449,11 +4613,6 @@ bool JSC__JSValue__isTerminationException(JSC::EncodedJSValue JSValue0) return exception->vm().isTerminationException(exception); } -extern "C" void JSC__Exception__getStackTrace(JSC::Exception* arg0, JSC::JSGlobalObject* global, ZigStackTrace* trace) -{ - populateStackTrace(arg0->vm(), arg0->stack(), *trace, global, PopulateStackTraceFlags::OnlyPosition); -} - void JSC__VM__shrinkFootprint(JSC::VM* arg0) { arg0->shrinkFootprintWhenIdle();