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 1cba4760e6..8c27df29d3 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 b62a5985e9..0b6e700bbc 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 9cb7e0bd4c..8c786bdc5d 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 6766b1b858..fb8e8fb6e5 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 834c094539..467f9f2ac9 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 7f30ab872c..7caa6f9143 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 3605825f1d..0c7e9ac66c 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 549c74855c..bc0ff5965f 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 2626623aaa..c24cca73cb 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 68affb5745..9b8883eac3 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 3b461942ab..3a996f5db7 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 1ecf5377bd..8b1a5c41a2 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 1988e20afa..702ec9580a 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 177bb2d675..c4e3d6f5f9 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 46fd905787..3d3817e7fb 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 a9f9c8b900..611fe08135 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 74d348a965..f8916fd3b3 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 49e22f313d..5cfb28e4b1 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); };