diff --git a/Meta/Lagom/Wasm/js_repl.cpp b/Meta/Lagom/Wasm/js_repl.cpp index 44e9f11e14..2ce4524761 100644 --- a/Meta/Lagom/Wasm/js_repl.cpp +++ b/Meta/Lagom/Wasm/js_repl.cpp @@ -165,31 +165,7 @@ static ErrorOr parse_and_run(JS::Realm& realm, StringView source, StringVi if (!thrown_value.is_object() || !is(thrown_value.as_object())) return; - auto& traceback = static_cast(thrown_value.as_object()).traceback(); - if (traceback.size() > 1) { - unsigned repetitions = 0; - for (size_t i = 0; i < traceback.size(); ++i) { - auto& traceback_frame = traceback[i]; - if (i + 1 < traceback.size()) { - auto& next_traceback_frame = traceback[i + 1]; - if (next_traceback_frame.function_name == traceback_frame.function_name) { - repetitions++; - continue; - } - } - if (repetitions > 4) { - // If more than 5 (1 + >4) consecutive function calls with the same name, print - // the name only once and show the number of repetitions instead. This prevents - // printing ridiculously large call stacks of recursive functions. - displayln(" -> {}", traceback_frame.function_name); - displayln(" {} more calls", repetitions); - } else { - for (size_t j = 0; j < repetitions + 1; ++j) - displayln(" -> {}", traceback_frame.function_name); - } - repetitions = 0; - } - } + displayln("{}", static_cast(thrown_value.as_object()).stack_string(JS::CompactTraceback::Yes)); }; if (!result.is_error()) diff --git a/Userland/Applications/Spreadsheet/Spreadsheet.cpp b/Userland/Applications/Spreadsheet/Spreadsheet.cpp index cb36d89351..71680df595 100644 --- a/Userland/Applications/Spreadsheet/Spreadsheet.cpp +++ b/Userland/Applications/Spreadsheet/Spreadsheet.cpp @@ -72,11 +72,7 @@ Sheet::Sheet(Workbook& workbook) if (thrown_value.is_object() && is(thrown_value.as_object())) { auto& error = static_cast(thrown_value.as_object()); warnln(" with message '{}'", error.get_without_side_effects(vm.names.message)); - for (auto& traceback_frame : error.traceback()) { - auto& function_name = traceback_frame.function_name; - auto& source_range = traceback_frame.source_range(); - dbgln(" {} at {}:{}:{}", function_name, source_range.filename(), source_range.start.line, source_range.start.column); - } + dbgln("{}", error.stack_string(JS::CompactTraceback::Yes)); } else { warnln(); } diff --git a/Userland/Libraries/LibJS/Runtime/Error.cpp b/Userland/Libraries/LibJS/Runtime/Error.cpp index e8d0368653..4c238b2749 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.cpp +++ b/Userland/Libraries/LibJS/Runtime/Error.cpp @@ -79,15 +79,11 @@ void Error::populate_stack() m_traceback.ensure_capacity(vm.execution_context_stack().size()); for (ssize_t i = vm.execution_context_stack().size() - 1; i >= 0; i--) { auto context = vm.execution_context_stack()[i]; - auto function_name = context->function_name; - if (function_name.is_empty()) - function_name = ""sv; - UnrealizedSourceRange range = {}; if (context->instruction_stream_iterator.has_value()) range = context->instruction_stream_iterator->source_range(); TracebackFrame frame { - .function_name = move(function_name), + .function_name = context->function_name, .source_range_storage = range, }; @@ -95,29 +91,63 @@ void Error::populate_stack() } } -String Error::stack_string() const +String Error::stack_string(CompactTraceback compact) const { StringBuilder stack_string_builder; // Note: We roughly follow V8's formatting - // Note: The error's name and message get prepended by ErrorPrototype::stack - // Note: We don't want to capture the global execution context, so we omit the last frame - // FIXME: We generate a stack-frame for the Errors constructor, other engines do not - for (size_t i = 0; i < m_traceback.size() - 1; ++i) { - auto const& frame = m_traceback[i]; + auto append_frame = [&](TracebackFrame const& frame) { auto function_name = frame.function_name; auto source_range = frame.source_range(); // Note: Since we don't know whether we have a valid SourceRange here we just check for some default values. if (!source_range.filename().is_empty() || source_range.start.offset != 0 || source_range.end.offset != 0) { - if (function_name == ""sv) + if (function_name.is_empty()) stack_string_builder.appendff(" at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column); else stack_string_builder.appendff(" at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column); } else { stack_string_builder.appendff(" at {}\n", function_name.is_empty() ? ""sv : function_name.view()); } + }; + + auto is_same_frame = [](TracebackFrame const& a, TracebackFrame const& b) { + if (a.function_name.is_empty() && b.function_name.is_empty()) { + auto source_range_a = a.source_range(); + auto source_range_b = b.source_range(); + return source_range_a.filename() == source_range_b.filename() && source_range_a.start.line == source_range_b.start.line; + } + return a.function_name == b.function_name; + }; + + // Note: We don't want to capture the global execution context, so we omit the last frame + // Note: The error's name and message get prepended by ErrorPrototype::stack + // FIXME: We generate a stack-frame for the Errors constructor, other engines do not + unsigned repetitions = 0; + size_t used_frames = m_traceback.size() - 1; + for (size_t i = 0; i < used_frames; ++i) { + auto const& frame = m_traceback[i]; + if (compact == CompactTraceback::Yes && i + 1 < used_frames) { + auto const& next_traceback_frame = m_traceback[i + 1]; + if (is_same_frame(frame, next_traceback_frame)) { + repetitions++; + continue; + } + } + if (repetitions > 4) { + // If more than 5 (1 + >4) consecutive function calls with the same name, print + // the name only once and show the number of repetitions instead. This prevents + // printing ridiculously large call stacks of recursive functions. + append_frame(frame); + stack_string_builder.appendff(" {} more calls\n", repetitions); + } else { + for (size_t j = 0; j < repetitions + 1; j++) + append_frame(frame); + } + repetitions = 0; } + for (size_t j = 0; j < repetitions; j++) + append_frame(m_traceback[used_frames - 1]); return MUST(stack_string_builder.to_string()); } diff --git a/Userland/Libraries/LibJS/Runtime/Error.h b/Userland/Libraries/LibJS/Runtime/Error.h index a93ad03f98..54a808c304 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.h +++ b/Userland/Libraries/LibJS/Runtime/Error.h @@ -22,6 +22,11 @@ struct TracebackFrame { mutable Variant source_range_storage; }; +enum CompactTraceback { + No, + Yes, +}; + class Error : public Object { JS_OBJECT(Error, Object); @@ -32,7 +37,7 @@ public: virtual ~Error() override = default; - [[nodiscard]] String stack_string() const; + [[nodiscard]] String stack_string(CompactTraceback compact = CompactTraceback::No) const; ThrowCompletionOr install_error_cause(Value options); diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp index 8db8aa2886..e031be2751 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp @@ -31,11 +31,7 @@ void report_exception_to_console(JS::Value value, JS::Realm& realm, ErrorInPromi } if (is(object)) { auto const& error_value = static_cast(object); - for (auto& traceback_frame : error_value.traceback()) { - auto& function_name = traceback_frame.function_name; - auto& source_range = traceback_frame.source_range(); - dbgln(" {} at {}:{}:{}", function_name, source_range.filename(), source_range.start.line, source_range.start.column); - } + dbgln("{}", error_value.stack_string(JS::CompactTraceback::Yes)); console.report_exception(error_value, error_in_promise == ErrorInPromise::Yes); return; diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index cc126a6343..7e663acf16 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -251,36 +251,7 @@ static ErrorOr parse_and_run(JS::Realm& realm, StringView source, StringVi if (!thrown_value.is_object() || !is(thrown_value.as_object())) return {}; - auto const& traceback = static_cast(thrown_value.as_object()).traceback(); - if (traceback.size() > 1) { - unsigned repetitions = 0; - for (size_t i = 0; i < traceback.size(); ++i) { - auto const& traceback_frame = traceback[i]; - if (i + 1 < traceback.size()) { - auto const& next_traceback_frame = traceback[i + 1]; - if (next_traceback_frame.function_name == traceback_frame.function_name) { - repetitions++; - continue; - } - } - if (repetitions > 4) { - // If more than 5 (1 + >4) consecutive function calls with the same name, print - // the name only once and show the number of repetitions instead. This prevents - // printing ridiculously large call stacks of recursive functions. - warnln(" -> {}", traceback_frame.function_name); - warnln(" {} more calls", repetitions); - } else { - for (size_t j = 0; j < repetitions + 1; ++j) { - warnln(" -> {} ({}:{},{})", - traceback_frame.function_name, - traceback_frame.source_range().code->filename(), - traceback_frame.source_range().start.line, - traceback_frame.source_range().start.column); - } - } - repetitions = 0; - } - } + warnln("{}", static_cast(thrown_value.as_object()).stack_string(JS::CompactTraceback::Yes)); return {}; };