From da177c6517ab0ba77facfe4ff5a078562f887e96 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Mon, 12 Apr 2021 00:08:28 +0200 Subject: [PATCH] LibJS: Make Errors fully spec compliant The previous handling of the name and message properties specifically was breaking websites that created their own error types and relied on the error prototype working correctly - not assuming an JS::Error this object, that is. The way it works now, and it is supposed to work, is: - Error.prototype.name and Error.prototype.message just have initial string values and are no longer getters/setters - When constructing an error with a message, we create a regular property on the newly created object, so a lookup of the message property will either get it from the object directly or go though the prototype chain - Internal m_name/m_message properties are no longer needed and removed This makes printing errors slightly more complicated, as we can no longer rely on the (safe) internal properties, and cannot trust a property lookup either - get_without_side_effects() is used to solve this, it's not perfect but something we can revisit later. I did some refactoring along the way, there was some really old stuff in there - accessing vm.call_frame().arguments[0] is not something we (have to) do anymore :^) Fixes #6245. --- .../Applications/Browser/ConsoleWidget.cpp | 17 +++-- .../Spreadsheet/JSIntegration.cpp | 4 +- .../Applications/Spreadsheet/JSIntegration.h | 2 +- Userland/Libraries/LibJS/MarkupGenerator.cpp | 22 ++++-- Userland/Libraries/LibJS/MarkupGenerator.h | 1 + Userland/Libraries/LibJS/Runtime/Error.cpp | 41 ++++++----- Userland/Libraries/LibJS/Runtime/Error.h | 22 ++---- .../LibJS/Runtime/ErrorConstructor.cpp | 71 +++++++++--------- .../LibJS/Runtime/ErrorConstructor.h | 4 +- .../LibJS/Runtime/ErrorPrototype.cpp | 72 ++++--------------- .../Libraries/LibJS/Runtime/ErrorPrototype.h | 9 +-- Userland/Libraries/LibJS/Runtime/VM.cpp | 13 +++- Userland/Libraries/LibJS/Runtime/VM.h | 2 +- .../Tests/runtime-error-call-stack-size.js | 2 +- .../CSSStyleDeclarationWrapperCustom.cpp | 4 +- .../CodeGenerators/WrapperGenerator.cpp | 2 +- .../WebContent/WebContentConsoleClient.cpp | 17 +++-- Userland/Utilities/js.cpp | 32 +++++---- 18 files changed, 157 insertions(+), 180 deletions(-) diff --git a/Userland/Applications/Browser/ConsoleWidget.cpp b/Userland/Applications/Browser/ConsoleWidget.cpp index 1cba4760e6b..8c27df29d3f 100644 --- a/Userland/Applications/Browser/ConsoleWidget.cpp +++ b/Userland/Applications/Browser/ConsoleWidget.cpp @@ -106,16 +106,15 @@ ConsoleWidget::ConsoleWidget() } if (m_interpreter->exception()) { - output_html.append("Uncaught exception: "); - auto error = m_interpreter->exception()->value(); - if (error.is_object() && is(error.as_object())) { - auto& dom_exception_wrapper = static_cast(error.as_object()); - error = JS::Error::create(m_interpreter->global_object(), dom_exception_wrapper.impl().name(), dom_exception_wrapper.impl().message()); - } - output_html.append(JS::MarkupGenerator::html_from_value(error)); - print_html(output_html.string_view()); - + auto* exception = m_interpreter->exception(); m_interpreter->vm().clear_exception(); + output_html.append("Uncaught exception: "); + auto error = exception->value(); + if (error.is_object()) + output_html.append(JS::MarkupGenerator::html_from_error(error.as_object())); + else + output_html.append(JS::MarkupGenerator::html_from_value(error)); + print_html(output_html.string_view()); return; } diff --git a/Userland/Applications/Spreadsheet/JSIntegration.cpp b/Userland/Applications/Spreadsheet/JSIntegration.cpp index b62a5985e9a..0b6e700bbc5 100644 --- a/Userland/Applications/Spreadsheet/JSIntegration.cpp +++ b/Userland/Applications/Spreadsheet/JSIntegration.cpp @@ -121,7 +121,7 @@ SheetGlobalObject::~SheetGlobalObject() { } -JS::Value SheetGlobalObject::get(const JS::PropertyName& name, JS::Value receiver) const +JS::Value SheetGlobalObject::get(const JS::PropertyName& name, JS::Value receiver, bool without_side_effects) const { if (name.is_string()) { if (name.as_string() == "value") { @@ -137,7 +137,7 @@ JS::Value SheetGlobalObject::get(const JS::PropertyName& name, JS::Value receive } } - return GlobalObject::get(name, receiver); + return GlobalObject::get(name, receiver, without_side_effects); } bool SheetGlobalObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) diff --git a/Userland/Applications/Spreadsheet/JSIntegration.h b/Userland/Applications/Spreadsheet/JSIntegration.h index 9cb7e0bd4cc..8c786bdc5d6 100644 --- a/Userland/Applications/Spreadsheet/JSIntegration.h +++ b/Userland/Applications/Spreadsheet/JSIntegration.h @@ -46,7 +46,7 @@ public: virtual ~SheetGlobalObject() override; - virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}) const override; + virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}, bool without_side_effects = false) const override; virtual bool put(const JS::PropertyName&, JS::Value value, JS::Value receiver = {}) override; virtual void initialize_global_object() override; diff --git a/Userland/Libraries/LibJS/MarkupGenerator.cpp b/Userland/Libraries/LibJS/MarkupGenerator.cpp index 6766b1b8589..fb8e8fb6e50 100644 --- a/Userland/Libraries/LibJS/MarkupGenerator.cpp +++ b/Userland/Libraries/LibJS/MarkupGenerator.cpp @@ -53,6 +53,14 @@ String MarkupGenerator::html_from_value(Value value) return output_html.to_string(); } +String MarkupGenerator::html_from_error(Object& object) +{ + StringBuilder output_html; + HashTable seen_objects; + error_to_html(object, output_html, seen_objects); + return output_html.to_string(); +} + void MarkupGenerator::value_to_html(Value value, StringBuilder& output_html, HashTable seen_objects) { if (value.is_empty()) { @@ -156,10 +164,16 @@ void MarkupGenerator::date_to_html(const Object& date, StringBuilder& html_outpu void MarkupGenerator::error_to_html(const Object& object, StringBuilder& html_output, HashTable&) { - auto& error = static_cast(object); - html_output.append(wrap_string_in_style(String::formatted("[{}]", error.name()), StyleType::Invalid)); - if (!error.message().is_empty()) { - html_output.appendff(": {}", escape_html_entities(error.message())); + auto name = object.get_without_side_effects("name").value_or(JS::js_undefined()); + auto message = object.get_without_side_effects("message").value_or(JS::js_undefined()); + if (name.is_accessor() || name.is_native_property() || message.is_accessor() || message.is_native_property()) { + html_output.append(wrap_string_in_style(JS::Value(&object).to_string_without_side_effects(), StyleType::Invalid)); + } else { + auto name_string = name.to_string_without_side_effects(); + auto message_string = message.to_string_without_side_effects(); + html_output.append(wrap_string_in_style(String::formatted("[{}]", name_string), StyleType::Invalid)); + if (!message_string.is_empty()) + html_output.appendff(": {}", escape_html_entities(message_string)); } } diff --git a/Userland/Libraries/LibJS/MarkupGenerator.h b/Userland/Libraries/LibJS/MarkupGenerator.h index 834c094539d..467f9f2ac94 100644 --- a/Userland/Libraries/LibJS/MarkupGenerator.h +++ b/Userland/Libraries/LibJS/MarkupGenerator.h @@ -36,6 +36,7 @@ class MarkupGenerator { public: static String html_from_source(const StringView&); static String html_from_value(Value); + static String html_from_error(Object&); private: enum class StyleType { diff --git a/Userland/Libraries/LibJS/Runtime/Error.cpp b/Userland/Libraries/LibJS/Runtime/Error.cpp index 7f30ab872ce..7caa6f91431 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.cpp +++ b/Userland/Libraries/LibJS/Runtime/Error.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,32 +30,34 @@ namespace JS { -Error* Error::create(GlobalObject& global_object, const FlyString& name, const String& message) +Error* Error::create(GlobalObject& global_object, const String& message) { - return global_object.heap().allocate(global_object, name, message, *global_object.error_prototype()); + auto& vm = global_object.vm(); + auto* error = global_object.heap().allocate(global_object, *global_object.error_prototype()); + if (!message.is_null()) + error->define_property(vm.names.message, js_string(vm, message)); + return error; } -Error::Error(const FlyString& name, const String& message, Object& prototype) +Error::Error(Object& prototype) : Object(prototype) - , m_name(name) - , m_message(message) { } -Error::~Error() -{ -} - -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ - ClassName* ClassName::create(GlobalObject& global_object, const String& message) \ - { \ - return global_object.heap().allocate(global_object, message, *global_object.snake_name##_prototype()); \ - } \ - ClassName::ClassName(const String& message, Object& prototype) \ - : Error(vm().names.ClassName, message, prototype) \ - { \ - } \ - ClassName::~ClassName() { } +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ClassName* ClassName::create(GlobalObject& global_object, const String& message) \ + { \ + auto& vm = global_object.vm(); \ + auto* error = global_object.heap().allocate(global_object, *global_object.snake_name##_prototype()); \ + if (!message.is_null()) \ + error->define_property(vm.names.message, js_string(vm, message)); \ + return error; \ + } \ + \ + ClassName::ClassName(Object& prototype) \ + : Error(prototype) \ + { \ + } JS_ENUMERATE_ERROR_SUBCLASSES #undef __JS_ENUMERATE diff --git a/Userland/Libraries/LibJS/Runtime/Error.h b/Userland/Libraries/LibJS/Runtime/Error.h index 3605825f1d9..0c7e9ac66cd 100644 --- a/Userland/Libraries/LibJS/Runtime/Error.h +++ b/Userland/Libraries/LibJS/Runtime/Error.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +36,10 @@ class Error : public Object { JS_OBJECT(Error, Object); public: - static Error* create(GlobalObject&, const FlyString& name, const String& message); + static Error* create(GlobalObject&, const String& message = {}); - Error(const FlyString& name, const String& message, Object& prototype); - virtual ~Error() override; - - const FlyString& name() const { return m_name; } - const String& message() const { return m_message; } - - void set_name(const FlyString& name) { m_name = name; } - -private: - FlyString m_name; - String m_message; + explicit Error(Object& prototype); + virtual ~Error() override = default; }; #define DECLARE_ERROR_SUBCLASS(ClassName, snake_name, PrototypeName, ConstructorName) \ @@ -55,10 +47,10 @@ private: JS_OBJECT(ClassName, Error); \ \ public: \ - static ClassName* create(GlobalObject&, const String& message); \ + static ClassName* create(GlobalObject&, const String& message = {}); \ \ - ClassName(const String& message, Object& prototype); \ - virtual ~ClassName() override; \ + explicit ClassName(Object& prototype); \ + virtual ~ClassName() override = default; \ }; #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp index 549c74855c4..bc0ff5965f3 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Linus Groh + * Copyright (c) 2020-2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,6 @@ void ErrorConstructor::initialize(GlobalObject& global_object) define_property(vm.names.length, Value(1), Attribute::Configurable); } -ErrorConstructor::~ErrorConstructor() -{ -} - Value ErrorConstructor::call() { return construct(*this); @@ -55,41 +51,46 @@ Value ErrorConstructor::call() Value ErrorConstructor::construct(Function&) { auto& vm = this->vm(); - String message = ""; - if (!vm.call_frame().arguments.is_empty() && !vm.call_frame().arguments[0].is_undefined()) { - message = vm.call_frame().arguments[0].to_string(global_object()); + String message; + if (!vm.argument(0).is_undefined()) { + message = vm.argument(0).to_string(global_object()); if (vm.exception()) return {}; } - return Error::create(global_object(), vm.names.Error, message); + return Error::create(global_object(), message); } -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ - ConstructorName::ConstructorName(GlobalObject& global_object) \ - : NativeFunction(*global_object.function_prototype()) \ - { \ - } \ - void ConstructorName::initialize(GlobalObject& global_object) \ - { \ - auto& vm = this->vm(); \ - NativeFunction::initialize(global_object); \ - define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \ - define_property(vm.names.length, Value(1), Attribute::Configurable); \ - } \ - ConstructorName::~ConstructorName() { } \ - Value ConstructorName::call() \ - { \ - return construct(*this); \ - } \ - Value ConstructorName::construct(Function&) \ - { \ - String message = ""; \ - if (!vm().call_frame().arguments.is_empty() && !vm().call_frame().arguments[0].is_undefined()) { \ - message = vm().call_frame().arguments[0].to_string(global_object()); \ - if (vm().exception()) \ - return {}; \ - } \ - return ClassName::create(global_object(), message); \ +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + ConstructorName::ConstructorName(GlobalObject& global_object) \ + : NativeFunction(*global_object.function_prototype()) \ + { \ + } \ + \ + void ConstructorName::initialize(GlobalObject& global_object) \ + { \ + auto& vm = this->vm(); \ + NativeFunction::initialize(global_object); \ + define_property(vm.names.prototype, global_object.snake_name##_prototype(), 0); \ + define_property(vm.names.length, Value(1), Attribute::Configurable); \ + } \ + \ + ConstructorName::~ConstructorName() { } \ + \ + Value ConstructorName::call() \ + { \ + return construct(*this); \ + } \ + \ + Value ConstructorName::construct(Function&) \ + { \ + auto& vm = this->vm(); \ + String message = ""; \ + if (!vm.argument(0).is_undefined()) { \ + message = vm.argument(0).to_string(global_object()); \ + if (vm.exception()) \ + return {}; \ + } \ + return ClassName::create(global_object(), message); \ } JS_ENUMERATE_ERROR_SUBCLASSES diff --git a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h index 2626623aaa1..c24cca73cb8 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorConstructor.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Linus Groh + * Copyright (c) 2020-2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ class ErrorConstructor final : public NativeFunction { public: explicit ErrorConstructor(GlobalObject&); virtual void initialize(GlobalObject&) override; - virtual ~ErrorConstructor() override; + virtual ~ErrorConstructor() override = default; virtual Value call() override; virtual Value construct(Function& new_target) override; diff --git a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp index 68affb57455..9b8883eac35 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2021, Linus Groh * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,7 +26,6 @@ */ #include -#include #include #include #include @@ -44,85 +44,44 @@ void ErrorPrototype::initialize(GlobalObject& global_object) auto& vm = this->vm(); Object::initialize(global_object); u8 attr = Attribute::Writable | Attribute::Configurable; - define_native_property(vm.names.name, name_getter, name_setter, attr); - define_native_property(vm.names.message, message_getter, {}, attr); + define_property(vm.names.name, js_string(vm, "Error"), attr); + define_property(vm.names.message, js_string(vm, ""), attr); define_native_function(vm.names.toString, to_string, 0, attr); } -ErrorPrototype::~ErrorPrototype() -{ -} - -JS_DEFINE_NATIVE_GETTER(ErrorPrototype::name_getter) -{ - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) - return {}; - if (!is(this_object)) { - vm.throw_exception(global_object, ErrorType::NotAn, "Error"); - return {}; - } - return js_string(vm, static_cast(this_object)->name()); -} - -JS_DEFINE_NATIVE_SETTER(ErrorPrototype::name_setter) -{ - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) - return; - if (!is(this_object)) { - vm.throw_exception(global_object, ErrorType::NotAn, "Error"); - return; - } - auto name = value.to_string(global_object); - if (vm.exception()) - return; - static_cast(this_object)->set_name(name); -} - -JS_DEFINE_NATIVE_GETTER(ErrorPrototype::message_getter) -{ - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) - return {}; - if (!is(this_object)) { - vm.throw_exception(global_object, ErrorType::NotAn, "Error"); - return {}; - } - return js_string(vm, static_cast(this_object)->message()); -} - +// 20.5.3.4 Error.prototype.toString, https://tc39.es/ecma262/#sec-error.prototype.tostring JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::to_string) { - if (!vm.this_value(global_object).is_object()) { - vm.throw_exception(global_object, ErrorType::NotAnObject, vm.this_value(global_object).to_string_without_side_effects()); + auto this_value = vm.this_value(global_object); + if (!this_value.is_object()) { + vm.throw_exception(global_object, ErrorType::NotAnObject, this_value.to_string_without_side_effects()); return {}; } - auto& this_object = vm.this_value(global_object).as_object(); + auto& this_object = this_value.as_object(); String name = "Error"; - auto name_property = this_object.get(vm.names.name); + auto name_property = this_object.get(vm.names.name).value_or(js_undefined()); if (vm.exception()) return {}; - if (!name_property.is_empty() && !name_property.is_undefined()) { + if (!name_property.is_undefined()) { name = name_property.to_string(global_object); if (vm.exception()) return {}; } String message = ""; - auto message_property = this_object.get(vm.names.message); + auto message_property = this_object.get(vm.names.message).value_or(js_undefined()); if (vm.exception()) return {}; - if (!message_property.is_empty() && !message_property.is_undefined()) { + if (!message_property.is_undefined()) { message = message_property.to_string(global_object); if (vm.exception()) return {}; } - if (name.length() == 0) + if (name.is_empty()) return js_string(vm, message); - if (message.length() == 0) + if (message.is_empty()) return js_string(vm, name); return js_string(vm, String::formatted("{}: {}", name, message)); } @@ -131,8 +90,7 @@ JS_DEFINE_NATIVE_FUNCTION(ErrorPrototype::to_string) PrototypeName::PrototypeName(GlobalObject& global_object) \ : Object(*global_object.error_prototype()) \ { \ - } \ - PrototypeName::~PrototypeName() { } + } JS_ENUMERATE_ERROR_SUBCLASSES #undef __JS_ENUMERATE diff --git a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h index 3b461942ab6..3a996f5db76 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorPrototype.h @@ -36,15 +36,10 @@ class ErrorPrototype final : public Object { public: explicit ErrorPrototype(GlobalObject&); virtual void initialize(GlobalObject&) override; - virtual ~ErrorPrototype() override; + virtual ~ErrorPrototype() override = default; private: JS_DECLARE_NATIVE_FUNCTION(to_string); - - JS_DECLARE_NATIVE_GETTER(name_getter); - JS_DECLARE_NATIVE_SETTER(name_setter); - - JS_DECLARE_NATIVE_GETTER(message_getter); }; #define DECLARE_ERROR_SUBCLASS_PROTOTYPE(ClassName, snake_name, PrototypeName, ConstructorName) \ @@ -54,7 +49,7 @@ private: public: \ explicit PrototypeName(GlobalObject&); \ virtual void initialize(GlobalObject&) override { } \ - virtual ~PrototypeName() override; \ + virtual ~PrototypeName() override = default; \ }; #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index 1ecf5377bd1..8b1a5c41a2e 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -294,9 +294,16 @@ void VM::throw_exception(Exception* exception) { if (should_log_exceptions()) { auto value = exception->value(); - if (value.is_object() && is(value.as_object())) { - auto& error = static_cast(value.as_object()); - dbgln("Throwing JavaScript exception: [{}] {}", error.name(), error.message()); + if (value.is_object()) { + auto& object = value.as_object(); + auto name = object.get_without_side_effects(names.name).value_or(js_undefined()); + auto message = object.get_without_side_effects(names.message).value_or(js_undefined()); + if (name.is_accessor() || name.is_native_property() || message.is_accessor() || message.is_native_property()) { + // The result is not going to be useful, let's just print the value. This affects DOMExceptions, for example. + dbgln("Throwing JavaScript exception: {}", value); + } else { + dbgln("Throwing JavaScript exception: [{}] {}", name, message); + } } else { dbgln("Throwing JavaScript exception: {}", value); } diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index 1988e20afa1..702ec9580af 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -124,7 +124,7 @@ public: // Ensure we got some stack space left, so the next function call doesn't kill us. // This value is merely a guess and might need tweaking at a later point. if (m_stack_info.size_free() < 16 * KiB) - throw_exception(global_object, "RuntimeError", "Call stack size limit exceeded"); + throw_exception(global_object, "Call stack size limit exceeded"); else m_call_stack.append(&call_frame); } diff --git a/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js b/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js index 177bb2d675d..c4e3d6f5f9c 100644 --- a/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js +++ b/Userland/Libraries/LibJS/Tests/runtime-error-call-stack-size.js @@ -7,7 +7,7 @@ test("infinite recursion", () => { infiniteRecursion(); } catch (e) { expect(e).toBeInstanceOf(Error); - expect(e.name).toBe("RuntimeError"); + expect(e.name).toBe("Error"); expect(e.message).toBe("Call stack size limit exceeded"); } diff --git a/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp b/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp index 46fd9057870..3d3817e7fb9 100644 --- a/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp +++ b/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp @@ -31,12 +31,12 @@ namespace Web::Bindings { -JS::Value CSSStyleDeclarationWrapper::get(const JS::PropertyName& name, JS::Value receiver) const +JS::Value CSSStyleDeclarationWrapper::get(const JS::PropertyName& name, JS::Value receiver, bool without_side_effects) const { // FIXME: These should actually use camelCase versions of the property names! auto property_id = CSS::property_id_from_string(name.to_string()); if (property_id == CSS::PropertyID::Invalid) - return Base::get(name, receiver); + return Base::get(name, receiver, without_side_effects); for (auto& property : impl().properties()) { if (property.property_id == property_id) return js_string(vm(), property.value->to_string()); diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp index a9f9c8b9009..611fe08135a 100644 --- a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp +++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp @@ -761,7 +761,7 @@ public: if (interface.extended_attributes.contains("CustomGet")) { generator.append(R"~~~( - virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}) const override; + virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}, bool without_side_effects = false) const override; )~~~"); } if (interface.extended_attributes.contains("CustomPut")) { diff --git a/Userland/Services/WebContent/WebContentConsoleClient.cpp b/Userland/Services/WebContent/WebContentConsoleClient.cpp index 74d348a9659..f8916fd3b3c 100644 --- a/Userland/Services/WebContent/WebContentConsoleClient.cpp +++ b/Userland/Services/WebContent/WebContentConsoleClient.cpp @@ -54,16 +54,15 @@ void WebContentConsoleClient::handle_input(const String& js_source) } if (m_interpreter->exception()) { - output_html.append("Uncaught exception: "); - auto error = m_interpreter->exception()->value(); - if (error.is_object() && is(error.as_object())) { - auto& dom_exception_wrapper = static_cast(error.as_object()); - error = JS::Error::create(m_interpreter->global_object(), dom_exception_wrapper.impl().name(), dom_exception_wrapper.impl().message()); - } - output_html.append(JS::MarkupGenerator::html_from_value(error)); - print_html(output_html.string_view()); - + auto* exception = m_interpreter->exception(); m_interpreter->vm().clear_exception(); + output_html.append("Uncaught exception: "); + auto error = exception->value(); + if (error.is_object()) + output_html.append(JS::MarkupGenerator::html_from_error(error.as_object())); + else + output_html.append(JS::MarkupGenerator::html_from_value(error)); + print_html(output_html.string_view()); return; } diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index 49e22f313d6..5cfb28e4b13 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -239,18 +239,25 @@ static void print_function(const JS::Object& object, HashTable&) out(" {}", static_cast(object).name()); } -static void print_date(const JS::Object& date, HashTable&) +static void print_date(const JS::Object& object, HashTable&) { print_type("Date"); - out(" \033[34;1m{}\033[0m", static_cast(date).string()); + out(" \033[34;1m{}\033[0m", static_cast(object).string()); } -static void print_error(const JS::Object& object, HashTable&) +static void print_error(const JS::Object& object, HashTable& seen_objects) { - auto& error = static_cast(object); - print_type(error.name()); - if (!error.message().is_empty()) - out(" \033[31;1m{}\033[0m", error.message()); + 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() || name.is_native_property() || message.is_accessor() || message.is_native_property()) { + print_value(&object, seen_objects); + } else { + auto name_string = name.to_string_without_side_effects(); + auto message_string = message.to_string_without_side_effects(); + print_type(name_string); + if (!message_string.is_empty()) + out(" \033[31;1m{}\033[0m", message_string); + } } static void print_regexp_object(const JS::Object& object, HashTable&) @@ -498,9 +505,11 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source } auto handle_exception = [&] { + auto* exception = vm->exception(); + vm->clear_exception(); out("Uncaught exception: "); - print(vm->exception()->value()); - auto trace = vm->exception()->trace(); + print(exception->value()); + auto& trace = exception->trace(); if (trace.size() > 1) { unsigned repetitions = 0; for (size_t i = 0; i < trace.size(); ++i) { @@ -522,7 +531,6 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source repetitions = 0; } } - vm->clear_exception(); }; if (vm->exception()) { @@ -532,8 +540,8 @@ static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source if (s_print_last_result) print(vm->last_value()); if (vm->exception()) { - return false; handle_exception(); + return false; } return true; } @@ -738,7 +746,7 @@ int main(int argc, char** argv) OwnPtr interpreter; interrupt_interpreter = [&] { - auto error = JS::Error::create(interpreter->global_object(), "Error", "Received SIGINT"); + auto error = JS::Error::create(interpreter->global_object(), "Received SIGINT"); vm->throw_exception(interpreter->global_object(), error); };