diff --git a/Userland/Applications/Spreadsheet/CellSyntaxHighlighter.cpp b/Userland/Applications/Spreadsheet/CellSyntaxHighlighter.cpp index 1126dcd51f..7a2e0f2cee 100644 --- a/Userland/Applications/Spreadsheet/CellSyntaxHighlighter.cpp +++ b/Userland/Applications/Spreadsheet/CellSyntaxHighlighter.cpp @@ -36,7 +36,7 @@ void CellSyntaxHighlighter::rehighlight(Palette const& palette) if (m_cell && m_cell->thrown_value().has_value()) { if (auto value = m_cell->thrown_value().value(); value.is_object() && is(value.as_object())) { auto& error = static_cast(value.as_object()); - auto& range = error.traceback().first().source_range; + auto& range = error.traceback().first().source_range(); spans.prepend({ GUI::TextRange { { range.start.line - 1, range.start.column }, { range.end.line - 1, range.end.column } }, diff --git a/Userland/Applications/Spreadsheet/Spreadsheet.cpp b/Userland/Applications/Spreadsheet/Spreadsheet.cpp index f365ce94e9..1142574d35 100644 --- a/Userland/Applications/Spreadsheet/Spreadsheet.cpp +++ b/Userland/Applications/Spreadsheet/Spreadsheet.cpp @@ -70,7 +70,7 @@ Sheet::Sheet(Workbook& workbook) warnln(" with message '{}'", error.get_without_side_effects(interpreter().vm().names.message)); for (auto& traceback_frame : error.traceback()) { auto& function_name = traceback_frame.function_name; - auto& source_range = traceback_frame.source_range; + auto& source_range = traceback_frame.source_range(); dbgln(" {} at {}:{}:{}", function_name, source_range.filename(), source_range.start.line, source_range.start.column); } } else { diff --git a/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp b/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp index f1ca5fdf9e..cad08a711e 100644 --- a/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp +++ b/Userland/Applications/Spreadsheet/SpreadsheetModel.cpp @@ -122,13 +122,13 @@ GUI::Variant SheetModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) StringBuilder builder; builder.appendff("{}\n", error.get_without_side_effects(object.vm().names.message).to_string_without_side_effects().release_value_but_fixme_should_propagate_errors()); for (auto const& frame : trace.in_reverse()) { - if (frame.source_range.filename().contains("runtime.js"sv)) { + if (frame.source_range().filename().contains("runtime.js"sv)) { if (frame.function_name == "") - builder.appendff(" in a builtin function at line {}, column {}\n", frame.source_range.start.line, frame.source_range.start.column); + builder.appendff(" in a builtin function at line {}, column {}\n", frame.source_range().start.line, frame.source_range().start.column); else builder.appendff(" while evaluating builtin '{}'\n", frame.function_name); - } else if (frame.source_range.filename().starts_with("cell "sv)) { - builder.appendff(" in cell '{}', at line {}, column {}\n", frame.source_range.filename().substring_view(5), frame.source_range.start.line, frame.source_range.start.column); + } else if (frame.source_range().filename().starts_with("cell "sv)) { + builder.appendff(" in cell '{}', at line {}, column {}\n", frame.source_range().filename().substring_view(5), frame.source_range().start.line, frame.source_range().start.column); } } return builder.to_deprecated_string(); diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 0dcd6329d1..3f36831327 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -58,6 +58,9 @@ public: [[nodiscard]] SourceRange source_range() const; u32 start_offset() const { return m_start_offset; } + u32 end_offset() const { return m_end_offset; } + + SourceCode const& source_code() const { return *m_source_code; } void set_end_offset(Badge, u32 end_offset) { m_end_offset = end_offset; } diff --git a/Userland/Libraries/LibJS/MarkupGenerator.cpp b/Userland/Libraries/LibJS/MarkupGenerator.cpp index 98650548a4..c9bbb959d5 100644 --- a/Userland/Libraries/LibJS/MarkupGenerator.cpp +++ b/Userland/Libraries/LibJS/MarkupGenerator.cpp @@ -150,12 +150,12 @@ ErrorOr MarkupGenerator::date_to_html(Object const& date, StringBuilder& h ErrorOr MarkupGenerator::trace_to_html(TracebackFrame const& traceback_frame, StringBuilder& html_output) { auto function_name = escape_html_entities(traceback_frame.function_name); - auto [line, column, _] = traceback_frame.source_range.start; + auto [line, column, _] = traceback_frame.source_range().start; auto get_filename_from_path = [&](StringView filename) -> StringView { auto last_slash_index = filename.find_last('/'); return last_slash_index.has_value() ? filename.substring_view(*last_slash_index + 1) : filename; }; - auto filename = escape_html_entities(get_filename_from_path(traceback_frame.source_range.filename())); + auto filename = escape_html_entities(get_filename_from_path(traceback_frame.source_range().filename())); auto trace = TRY(String::formatted("at {} ({}:{}:{})", function_name, filename, line, column)); TRY(html_output.try_appendff("  {}
", trace)); diff --git a/Userland/Libraries/LibJS/Runtime/Error.cpp b/Userland/Libraries/LibJS/Runtime/Error.cpp index 8e33d839f9..bee4dce057 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.cpp +++ b/Userland/Libraries/LibJS/Runtime/Error.cpp @@ -15,6 +15,15 @@ namespace JS { +SourceRange const& TracebackFrame::source_range() const +{ + if (auto* unrealized = source_range_storage.get_pointer()) { + auto source_range = unrealized->source_code->range_from_offsets(unrealized->start_offset, unrealized->end_offset); + source_range_storage = move(source_range); + } + return source_range_storage.get(); +} + NonnullGCPtr Error::create(Realm& realm) { return realm.heap().allocate(realm, realm.intrinsics().error_prototype()).release_allocated_value_but_fixme_should_propagate_errors(); @@ -69,13 +78,24 @@ void Error::populate_stack() auto function_name = context->function_name; if (function_name.is_empty()) function_name = ""sv; - m_traceback.empend( - move(function_name), - // We might not have an AST node associated with the execution context, e.g. in promise - // reaction jobs (which aren't called anywhere from the source code). - // They're not going to generate any _unhandled_ exceptions though, so a meaningless - // source range is fine. - context->current_node ? context->current_node->source_range() : dummy_source_range); + + TracebackFrame frame { + .function_name = move(function_name), + .source_range_storage = TracebackFrame::UnrealizedSourceRange {}, + }; + + // We might not have an AST node associated with the execution context, e.g. in promise + // reaction jobs (which aren't called anywhere from the source code). + // They're not going to generate any _unhandled_ exceptions though, so a meaningless + // source range is fine. + if (context->current_node) { + auto* unrealized = frame.source_range_storage.get_pointer(); + unrealized->source_code = context->current_node->source_code(); + unrealized->start_offset = context->current_node->start_offset(); + unrealized->end_offset = context->current_node->end_offset(); + } + + m_traceback.append(move(frame)); } } @@ -90,13 +110,14 @@ ThrowCompletionOr Error::stack_string(VM& vm) const for (size_t i = 0; i < m_traceback.size() - 1; ++i) { auto const& frame = m_traceback[i]; 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 (!frame.source_range.filename().is_empty() || frame.source_range.start.offset != 0 || frame.source_range.end.offset != 0) { + if (!source_range.filename().is_empty() || source_range.start.offset != 0 || source_range.end.offset != 0) { if (function_name == ""sv) - MUST_OR_THROW_OOM(stack_string_builder.appendff(" at {}:{}:{}\n", frame.source_range.filename(), frame.source_range.start.line, frame.source_range.start.column)); + MUST_OR_THROW_OOM(stack_string_builder.appendff(" at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column)); else - MUST_OR_THROW_OOM(stack_string_builder.appendff(" at {} ({}:{}:{})\n", function_name, frame.source_range.filename(), frame.source_range.start.line, frame.source_range.start.column)); + MUST_OR_THROW_OOM(stack_string_builder.appendff(" at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column)); } else { MUST_OR_THROW_OOM(stack_string_builder.appendff(" at {}\n", function_name.is_empty() ? ""sv : function_name.view())); } diff --git a/Userland/Libraries/LibJS/Runtime/Error.h b/Userland/Libraries/LibJS/Runtime/Error.h index 26be07495f..cf9315dfc8 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.h +++ b/Userland/Libraries/LibJS/Runtime/Error.h @@ -17,7 +17,14 @@ namespace JS { struct TracebackFrame { DeprecatedFlyString function_name; - SourceRange source_range; + [[nodiscard]] SourceRange const& source_range() const; + + struct UnrealizedSourceRange { + u32 start_offset { 0 }; + u32 end_offset { 0 }; + RefPtr source_code; + }; + mutable Variant source_range_storage; }; class Error : public Object { diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp index 499377bc4c..d4931d9aa7 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/ExceptionReporter.cpp @@ -33,7 +33,7 @@ void report_exception_to_console(JS::Value value, JS::Realm& realm, ErrorInPromi 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; + auto& source_range = traceback_frame.source_range(); dbgln(" {} at {}:{}:{}", function_name, source_range.filename(), source_range.start.line, source_range.start.column); } console.report_exception(error_value, error_in_promise == ErrorInPromise::Yes);