
Before this change, each AST node had a 64-byte SourceRange member. This SourceRange had the following layout: filename: StringView (16 bytes) start: Position (24 bytes) end: Position (24 bytes) The Position structs have { line, column, offset }, all members size_t. To reduce memory consumption, AST nodes now only store the following: source_code: NonnullRefPtr<SourceCode> (8 bytes) start_offset: u32 (4 bytes) end_offset: u32 (4 bytes) SourceCode is a new ref-counted data structure that keeps the filename and original parsed source code in a single location, and all AST nodes have a pointer to it. The start_offset and end_offset can be turned into (line, column) when necessary by calling SourceCode::range_from_offsets(). This will walk the source code string and compute line/column numbers on the fly, so it's not necessarily fast, but it should be rare since this information is primarily used for diagnostics and exception stack traces. With this, ASTNode shrinks from 80 bytes to 32 bytes. This gives us a ~23% reduction in memory usage when loading twitter.com/awesomekling (330 MiB before, 253 MiB after!) :^)
58 lines
2.6 KiB
C++
58 lines
2.6 KiB
C++
/*
|
|
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/TypeCasts.h>
|
|
#include <LibJS/Console.h>
|
|
#include <LibJS/Runtime/ConsoleObject.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
#include <LibJS/Runtime/Value.h>
|
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
|
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
|
|
|
|
namespace Web::HTML {
|
|
|
|
void report_exception_to_console(JS::Value value, JS::Realm& realm, ErrorInPromise error_in_promise)
|
|
{
|
|
auto& console = realm.intrinsics().console_object()->console();
|
|
|
|
if (value.is_object()) {
|
|
auto& object = value.as_object();
|
|
auto& vm = object.vm();
|
|
auto name = object.get_without_side_effects(vm.names.name).value_or(JS::js_undefined());
|
|
auto message = object.get_without_side_effects(vm.names.message).value_or(JS::js_undefined());
|
|
if (name.is_accessor() || message.is_accessor()) {
|
|
// The result is not going to be useful, let's just print the value. This affects DOMExceptions, for example.
|
|
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", JS::Value(&object));
|
|
} else {
|
|
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m [{}] {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", name, message);
|
|
}
|
|
if (is<JS::Error>(object)) {
|
|
auto const& error_value = static_cast<JS::Error const&>(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);
|
|
}
|
|
console.report_exception(error_value, error_in_promise == ErrorInPromise::Yes);
|
|
|
|
return;
|
|
}
|
|
} else {
|
|
dbgln("\033[31;1mUnhandled JavaScript exception{}:\033[0m {}", error_in_promise == ErrorInPromise::Yes ? " (in promise)" : "", value);
|
|
}
|
|
|
|
console.report_exception(*JS::Error::create(realm, value.to_string_without_side_effects()), error_in_promise == ErrorInPromise::Yes);
|
|
}
|
|
|
|
// https://html.spec.whatwg.org/#report-the-exception
|
|
void report_exception(JS::Completion const& throw_completion, JS::Realm& realm)
|
|
{
|
|
VERIFY(throw_completion.type() == JS::Completion::Type::Throw);
|
|
VERIFY(throw_completion.value().has_value());
|
|
report_exception_to_console(*throw_completion.value(), realm, ErrorInPromise::No);
|
|
}
|
|
|
|
}
|