diff options
Diffstat (limited to 'Userland')
80 files changed, 3894 insertions, 2114 deletions
diff --git a/Userland/Applications/Spreadsheet/JSIntegration.cpp b/Userland/Applications/Spreadsheet/JSIntegration.cpp index ab5a61a068..7fe9a2a77f 100644 --- a/Userland/Applications/Spreadsheet/JSIntegration.cpp +++ b/Userland/Applications/Spreadsheet/JSIntegration.cpp @@ -101,29 +101,29 @@ SheetGlobalObject::~SheetGlobalObject() { } -JS::Value SheetGlobalObject::get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const +JS::Value SheetGlobalObject::internal_get(const JS::PropertyName& property_name, JS::Value receiver) const { - if (name.is_string()) { - if (name.as_string() == "value") { + if (property_name.is_string()) { + if (property_name.as_string() == "value") { if (auto cell = m_sheet.current_evaluated_cell()) return cell->js_data(); return JS::js_undefined(); } - if (auto pos = m_sheet.parse_cell_name(name.as_string()); pos.has_value()) { + if (auto pos = m_sheet.parse_cell_name(property_name.as_string()); pos.has_value()) { auto& cell = m_sheet.ensure(pos.value()); cell.reference_from(m_sheet.current_evaluated_cell()); return cell.typed_js_data(); } } - return GlobalObject::get(name, receiver, allow_side_effects); + return Base::internal_get(property_name, receiver); } -bool SheetGlobalObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) +bool SheetGlobalObject::internal_set(const JS::PropertyName& property_name, JS::Value value, JS::Value receiver) { - if (name.is_string()) { - if (auto pos = m_sheet.parse_cell_name(name.as_string()); pos.has_value()) { + if (property_name.is_string()) { + if (auto pos = m_sheet.parse_cell_name(property_name.as_string()); pos.has_value()) { auto& cell = m_sheet.ensure(pos.value()); if (auto current = m_sheet.current_evaluated_cell()) current->reference_from(&cell); @@ -133,7 +133,7 @@ bool SheetGlobalObject::put(const JS::PropertyName& name, JS::Value value, JS::V } } - return GlobalObject::put(name, value, receiver); + return Base::internal_set(property_name, value, receiver); } void SheetGlobalObject::initialize_global_object() diff --git a/Userland/Applications/Spreadsheet/JSIntegration.h b/Userland/Applications/Spreadsheet/JSIntegration.h index b6a2bf322f..94c5e86049 100644 --- a/Userland/Applications/Spreadsheet/JSIntegration.h +++ b/Userland/Applications/Spreadsheet/JSIntegration.h @@ -26,8 +26,8 @@ public: virtual ~SheetGlobalObject() override; - virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override; - virtual bool put(const JS::PropertyName&, JS::Value value, JS::Value receiver = {}) override; + virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override; + virtual bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override; virtual void initialize_global_object() override; JS_DECLARE_NATIVE_FUNCTION(get_real_cell_contents); diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp index 965c1d4e69..0223993c7b 100644 --- a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp @@ -21,40 +21,40 @@ DebuggerGlobalJSObject::DebuggerGlobalJSObject() m_variables = lib->debug_info->get_variables_in_current_scope(regs); } -JS::Value DebuggerGlobalJSObject::get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const +JS::Value DebuggerGlobalJSObject::internal_get(JS::PropertyName const& property_name, JS::Value receiver) const { - if (m_variables.is_empty() || !name.is_string()) - return JS::Object::get(name, receiver, allow_side_effects); + if (m_variables.is_empty() || !property_name.is_string()) + return Base::internal_get(property_name, receiver); auto it = m_variables.find_if([&](auto& variable) { - return variable->name == name.as_string(); + return variable->name == property_name.as_string(); }); if (it.is_end()) - return JS::Object::get(name, receiver, allow_side_effects); + return Base::internal_get(property_name, receiver); auto& target_variable = **it; auto js_value = debugger_to_js(target_variable); if (js_value.has_value()) return js_value.value(); - auto error_string = String::formatted("Variable {} of type {} is not convertible to a JS Value", name.as_string(), target_variable.type_name); + auto error_string = String::formatted("Variable {} of type {} is not convertible to a JS Value", property_name.as_string(), target_variable.type_name); vm().throw_exception<JS::TypeError>(const_cast<DebuggerGlobalJSObject&>(*this), error_string); return {}; } -bool DebuggerGlobalJSObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) +bool DebuggerGlobalJSObject::internal_set(JS::PropertyName const& property_name, JS::Value value, JS::Value receiver) { - if (m_variables.is_empty() || !name.is_string()) - return JS::Object::put(name, value, receiver); + if (m_variables.is_empty() || !property_name.is_string()) + return Base::internal_set(property_name, value, receiver); auto it = m_variables.find_if([&](auto& variable) { - return variable->name == name.as_string(); + return variable->name == property_name.as_string(); }); if (it.is_end()) - return JS::Object::put(name, value, receiver); + return Base::internal_set(property_name, value, receiver); auto& target_variable = **it; auto debugger_value = js_to_debugger(value, target_variable); if (debugger_value.has_value()) return Debugger::the().session()->poke((u32*)target_variable.location_data.address, debugger_value.value()); - auto error_string = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name.as_string(), target_variable.type_name); + auto error_string = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), property_name.as_string(), target_variable.type_name); vm().throw_exception<JS::TypeError>(const_cast<DebuggerGlobalJSObject&>(*this), error_string); return {}; } diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h index df958b8ba6..255fb4fca6 100644 --- a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h @@ -20,8 +20,8 @@ class DebuggerGlobalJSObject final public: DebuggerGlobalJSObject(); - JS::Value get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override; - bool put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) override; + virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override; + virtual bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override; Optional<JS::Value> debugger_to_js(const Debug::DebugInfo::VariableInfo&) const; Optional<u32> js_to_debugger(JS::Value value, const Debug::DebugInfo::VariableInfo&) const; diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp index ecf95de914..fa3a1e7d17 100644 --- a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp @@ -29,19 +29,19 @@ DebuggerVariableJSObject::~DebuggerVariableJSObject() { } -bool DebuggerVariableJSObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) +bool DebuggerVariableJSObject::internal_set(const JS::PropertyName& property_name, JS::Value value, JS::Value receiver) { if (m_is_writing_properties) - return JS::Object::put(name, value, receiver); + return Base::internal_set(property_name, value, receiver); - if (!name.is_string()) { - vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", name.to_string())); + if (!property_name.is_string()) { + vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", property_name.to_string())); return false; } - auto property_name = name.as_string(); + auto name = property_name.as_string(); auto it = m_variable_info.members.find_if([&](auto& variable) { - return variable->name == property_name; + return variable->name == name; }); if (it.is_end()) { @@ -52,7 +52,7 @@ bool DebuggerVariableJSObject::put(const JS::PropertyName& name, JS::Value value auto& member = **it; auto new_value = debugger_object().js_to_debugger(value, member); if (!new_value.has_value()) { - auto string_error = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name.as_string(), member.type_name); + auto string_error = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name, member.type_name); vm().throw_exception<JS::TypeError>(global_object(), string_error); return false; } diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h index 0b81676a53..61f5e06fd2 100644 --- a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h @@ -24,7 +24,7 @@ public: virtual const char* class_name() const override { return m_variable_info.type_name.characters(); } - virtual bool put(const JS::PropertyName& name, JS::Value value, JS::Value) override; + bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override; void finish_writing_properties() { m_is_writing_properties = false; } private: diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index ffc7a2efb3..79d93b1bea 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -129,7 +129,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete auto property_name = member_expression.computed_property_name(interpreter, global_object); if (!property_name.is_valid()) return {}; - auto reference = Reference { super_base, property_name, super_base }; + auto reference = Reference { super_base, property_name, super_base, vm.in_strict_mode() }; callee = reference.get_value(global_object); if (vm.exception()) return {}; @@ -559,7 +559,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj return {}; auto* object = rhs_result.to_object(global_object); while (object) { - auto property_names = object->get_enumerable_own_property_names(Object::PropertyKind::Key); + auto property_names = object->enumerable_own_property_names(Object::PropertyKind::Key); for (auto& value : property_names) { interpreter.vm().assign(target, value, global_object, has_declaration); if (interpreter.exception()) @@ -578,7 +578,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj } } } - object = object->prototype(); + object = object->internal_get_prototype_of(); if (interpreter.exception()) return {}; } @@ -781,6 +781,9 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& if (interpreter.exception()) return {}; + // From here on equivalent to + // 13.3.4 EvaluatePropertyAccessWithIdentifierKey ( baseValue, identifierName, strict ), https://tc39.es/ecma262/#sec-evaluate-property-access-with-identifier-key + object_value = require_object_coercible(global_object, object_value); if (interpreter.exception()) return {}; @@ -789,7 +792,8 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject& if (!property_name.is_valid()) return Reference {}; - return Reference { object_value, property_name, {} }; + auto strict = interpreter.vm().in_strict_mode(); + return Reference { object_value, property_name, {}, strict }; } Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const @@ -898,7 +902,7 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob class_constructor->define_property(vm.names.prototype, prototype, Attribute::Writable); if (interpreter.exception()) return {}; - class_constructor->set_prototype(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object()); + class_constructor->internal_set_prototype_of(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object()); } auto class_prototype = class_constructor->get(vm.names.prototype); @@ -929,15 +933,15 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob switch (method.kind()) { case ClassMethod::Kind::Method: - target.define_property(property_key, method_value); + target.define_property_or_throw(property_key, { .value = method_value, .writable = true, .enumerable = false, .configurable = true }); break; case ClassMethod::Kind::Getter: update_function_name(method_value, String::formatted("get {}", get_function_name(global_object, key))); - target.define_accessor(property_key, &method_function, nullptr, Attribute::Configurable | Attribute::Enumerable); + target.define_property_or_throw(property_key, { .get = &method_function, .enumerable = true, .configurable = true }); break; case ClassMethod::Kind::Setter: update_function_name(method_value, String::formatted("set {}", get_function_name(global_object, key))); - target.define_accessor(property_key, nullptr, &method_function, Attribute::Configurable | Attribute::Enumerable); + target.define_property_or_throw(property_key, { .set = &method_function, .enumerable = true, .configurable = true }); break; default: VERIFY_NOT_REACHED(); @@ -1431,9 +1435,10 @@ Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object) InterpreterNodeScope node_scope { interpreter, *this }; auto value = interpreter.vm().get_variable(string(), global_object); + if (interpreter.exception()) + return {}; if (value.is_empty()) { - if (!interpreter.exception()) - interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string()); + interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string()); return {}; } return value; @@ -1815,7 +1820,10 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o if (key.is_object() && key.as_object().is_array()) { auto& array_to_spread = static_cast<Array&>(key.as_object()); for (auto& entry : array_to_spread.indexed_properties()) { - object->indexed_properties().put(object, entry.index(), entry.value_and_attributes(&array_to_spread).value); + auto value = array_to_spread.get(entry.index()); + if (interpreter.exception()) + return {}; + object->indexed_properties().put(entry.index(), value); if (interpreter.exception()) return {}; } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 660f60c713..7b3a16de98 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -193,7 +193,9 @@ void CopyObjectExcludingProperties::execute_impl(Bytecode::Interpreter& interpre return; } - auto own_keys = from_object->get_own_properties(Object::PropertyKind::Key, true); + auto own_keys = from_object->internal_own_property_keys(); + if (interpreter.vm().exception()) + return; for (auto& key : own_keys) { if (!excluded_names.contains(key)) { diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 1d384b1685..78f363405a 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -97,7 +97,6 @@ set(SOURCES Runtime/PromisePrototype.cpp Runtime/PromiseReaction.cpp Runtime/PromiseResolvingFunction.cpp - Runtime/PropertyAttributes.cpp Runtime/PropertyDescriptor.cpp Runtime/ProxyConstructor.cpp Runtime/ProxyObject.cpp @@ -106,7 +105,7 @@ set(SOURCES Runtime/RegExpConstructor.cpp Runtime/RegExpObject.cpp Runtime/RegExpPrototype.cpp - Runtime/OrdinaryFunctionObject.cpp + Runtime/OrdinaryFunctionObject.cpp Runtime/Set.cpp Runtime/SetConstructor.cpp Runtime/SetIterator.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index ef249ac01a..1c39061d03 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -175,11 +175,6 @@ class TypedArrayPrototype; // Tag type used to differentiate between u8 as used by Uint8Array and u8 as used by Uint8ClampedArray. struct ClampedU8; -enum class AllowSideEffects { - Yes, - No -}; - #define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName, ArrayType) \ class ClassName; \ class ConstructorName; \ diff --git a/Userland/Libraries/LibJS/MarkupGenerator.cpp b/Userland/Libraries/LibJS/MarkupGenerator.cpp index 7a6fe8f550..cce056286f 100644 --- a/Userland/Libraries/LibJS/MarkupGenerator.cpp +++ b/Userland/Libraries/LibJS/MarkupGenerator.cpp @@ -98,7 +98,7 @@ void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_outp html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); first = false; // FIXME: Exception check - value_to_html(it.value_and_attributes(const_cast<Array*>(&array)).value, html_output, seen_objects); + value_to_html(array.get(it.index()), html_output, seen_objects); } html_output.append(wrap_string_in_style(" ]", StyleType::Punctuation)); } @@ -114,7 +114,7 @@ void MarkupGenerator::object_to_html(const Object& object, StringBuilder& html_o html_output.append(wrap_string_in_style(String::number(entry.index()), StyleType::Number)); html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); // FIXME: Exception check - value_to_html(entry.value_and_attributes(const_cast<Object*>(&object)).value, html_output, seen_objects); + value_to_html(object.get(entry.index()), html_output, seen_objects); } if (!object.indexed_properties().is_empty() && object.shape().property_count()) diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 11d4302885..c6176fd17d 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -6,14 +6,15 @@ */ #include <AK/Function.h> +#include <AK/Optional.h> #include <AK/Result.h> #include <AK/TemporaryChange.h> #include <LibJS/Interpreter.h> #include <LibJS/Parser.h> #include <LibJS/Runtime/AbstractOperations.h> +#include <LibJS/Runtime/Accessor.h> #include <LibJS/Runtime/ArgumentsObject.h> #include <LibJS/Runtime/Array.h> -#include <LibJS/Runtime/ArrayPrototype.h> #include <LibJS/Runtime/BoundFunction.h> #include <LibJS/Runtime/DeclarativeEnvironment.h> #include <LibJS/Runtime/ErrorTypes.h> @@ -23,6 +24,7 @@ #include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/ObjectEnvironment.h> +#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/PropertyName.h> #include <LibJS/Runtime/ProxyObject.h> #include <LibJS/Runtime/Reference.h> @@ -111,27 +113,202 @@ GlobalObject* get_function_realm(GlobalObject& global_object, FunctionObject con { auto& vm = global_object.vm(); - if (function.realm()) + // 1. Assert: ! IsCallable(obj) is true. + + // 2. If obj has a [[Realm]] internal slot, then + if (function.realm()) { + // a. Return obj.[[Realm]]. return function.realm(); + } + + // 3. If obj is a bound function exotic object, then if (is<BoundFunction>(function)) { auto& bound_function = static_cast<BoundFunction const&>(function); + + // a. Let target be obj.[[BoundTargetFunction]]. auto& target = bound_function.target_function(); + + // b. Return ? GetFunctionRealm(target). return get_function_realm(global_object, target); } + + // 4. If obj is a Proxy exotic object, then if (is<ProxyObject>(function)) { auto& proxy = static_cast<ProxyObject const&>(function); + + // a. If obj.[[ProxyHandler]] is null, throw a TypeError exception. if (proxy.is_revoked()) { vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); return nullptr; } + + // b. Let proxyTarget be obj.[[ProxyTarget]]. auto& proxy_target = proxy.target(); + + // c. Return ? GetFunctionRealm(proxyTarget). VERIFY(proxy_target.is_function()); return get_function_realm(global_object, static_cast<FunctionObject const&>(proxy_target)); } + // 5. Return the current Realm Record. return &global_object; } +// 10.1.6.2 IsCompatiblePropertyDescriptor ( Extensible, Desc, Current ), https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor +bool is_compatible_property_descriptor(bool extensible, PropertyDescriptor const& descriptor, Optional<PropertyDescriptor> const& current) +{ + // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current). + return validate_and_apply_property_descriptor(nullptr, {}, extensible, descriptor, current); +} + +// 10.1.6.3 ValidateAndApplyPropertyDescriptor ( O, P, extensible, Desc, current ), +bool validate_and_apply_property_descriptor(Object* object, PropertyName const& property_name, bool extensible, PropertyDescriptor const& descriptor, Optional<PropertyDescriptor> const& current) +{ + // 1. Assert: If O is not undefined, then IsPropertyKey(P) is true. + if (object) + VERIFY(property_name.is_valid()); + + // 2. If current is undefined, then + if (!current.has_value()) { + // a. If extensible is false, return false. + if (!extensible) + return false; + + // b. Assert: extensible is true. + // c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then + if (descriptor.is_generic_descriptor() || descriptor.is_data_descriptor()) { + // i. If O is not undefined, create an own data property named P of object O whose [[Value]], [[Writable]], + // [[Enumerable]], and [[Configurable]] attribute values are described by Desc. + // If the value of an attribute field of Desc is absent, the attribute of the newly created property is set + // to its default value. + if (object) { + auto value = descriptor.value.value_or(js_undefined()); + object->storage_set(property_name, { value, descriptor.attributes() }); + } + } + // d. Else, + else { + // i. Assert: ! IsAccessorDescriptor(Desc) is true. + VERIFY(descriptor.is_accessor_descriptor()); + + // ii. If O is not undefined, create an own accessor property named P of object O whose [[Get]], [[Set]], + // [[Enumerable]], and [[Configurable]] attribute values are described by Desc. + // If the value of an attribute field of Desc is absent, the attribute of the newly created property is set + // to its default value. + if (object) { + auto accessor = Accessor::create(object->vm(), descriptor.get.value_or(nullptr), descriptor.set.value_or(nullptr)); + object->storage_set(property_name, { accessor, descriptor.attributes() }); + } + } + // e. Return true. + return true; + } + + // 3. If every field in Desc is absent, return true. + if (descriptor.is_empty()) + return true; + + // 4. If current.[[Configurable]] is false, then + if (!*current->configurable) { + // a. If Desc.[[Configurable]] is present and its value is true, return false. + if (descriptor.configurable.has_value() && *descriptor.configurable) + return false; + + // b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) is false, return false. + if (descriptor.enumerable.has_value() && *descriptor.enumerable != *current->enumerable) + return false; + } + + // 5. If ! IsGenericDescriptor(Desc) is true, then + if (descriptor.is_generic_descriptor()) { + // a. NOTE: No further validation is required. + } + // 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then + else if (current->is_data_descriptor() != descriptor.is_data_descriptor()) { + // a. If current.[[Configurable]] is false, return false. + if (!*current->configurable) + return false; + + // b. If IsDataDescriptor(current) is true, then + if (current->is_data_descriptor()) { + // If O is not undefined, convert the property named P of object O from a data property to an accessor property. + // Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and + // set the rest of the property's attributes to their default values. + if (object) { + auto accessor = Accessor::create(object->vm(), nullptr, nullptr); + object->storage_set(property_name, { accessor, current->attributes() }); + } + } + // c. Else, + else { + // If O is not undefined, convert the property named P of object O from an accessor property to a data property. + // Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and + // set the rest of the property's attributes to their default values. + if (object) { + auto value = js_undefined(); + object->storage_set(property_name, { value, current->attributes() }); + } + } + } + // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then + else if (current->is_data_descriptor() && descriptor.is_data_descriptor()) { + // a. If current.[[Configurable]] is false and current.[[Writable]] is false, then + if (!*current->configurable && !*current->writable) { + // i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false. + if (descriptor.writable.has_value() && *descriptor.writable) + return false; + + // ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false. + if (descriptor.value.has_value() && !same_value(*descriptor.value, *current->value)) + return false; + + // iii. Return true. + return true; + } + } + // 8. Else, + else { + // a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true. + VERIFY(current->is_accessor_descriptor()); + VERIFY(descriptor.is_accessor_descriptor()); + + // b. If current.[[Configurable]] is false, then + if (!*current->configurable) { + // i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false. + if (descriptor.set.has_value() && *descriptor.set != *current->set) + return false; + + // ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false. + if (descriptor.get.has_value() && *descriptor.get != *current->get) + return false; + + // iii. Return true. + return true; + } + } + + // 9. If O is not undefined, then + if (object) { + // a. For each field of Desc that is present, set the corresponding attribute of the property named P of object O to the value of the field. + Value value; + if (descriptor.is_accessor_descriptor() || (current->is_accessor_descriptor() && !descriptor.is_data_descriptor())) { + auto* getter = descriptor.get.value_or(current->get.value_or(nullptr)); + auto* setter = descriptor.set.value_or(current->set.value_or(nullptr)); + value = Accessor::create(object->vm(), getter, setter); + } else { + value = descriptor.value.value_or(current->value.value_or({})); + } + PropertyAttributes attributes; + attributes.set_writable(descriptor.writable.value_or(current->writable.value_or(false))); + attributes.set_enumerable(descriptor.enumerable.value_or(current->enumerable.value_or(false))); + attributes.set_configurable(descriptor.configurable.value_or(current->configurable.value_or(false))); + object->storage_set(property_name, { value, attributes }); + } + + // 10. Return true. + return true; +} + // 10.1.14 GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto ) Object* get_prototype_from_constructor(GlobalObject& global_object, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)()) { @@ -177,7 +354,7 @@ Object* get_super_constructor(VM& vm) { auto& env = get_this_environment(vm); auto& active_function = verify_cast<FunctionEnvironment>(env).function_object(); - auto* super_constructor = active_function.prototype(); + auto* super_constructor = active_function.internal_get_prototype_of(); return super_constructor; } @@ -230,26 +407,45 @@ Value perform_eval(Value x, GlobalObject& caller_realm, CallerMode strict_caller Object* create_unmapped_arguments_object(GlobalObject& global_object, Vector<Value> const& arguments) { auto& vm = global_object.vm(); - auto* object = Object::create(global_object, global_object.object_prototype()); - if (vm.exception()) - return nullptr; - for (auto& argument : arguments) - object->indexed_properties().append(argument); + // 1. Let len be the number of elements in argumentsList. + auto length = arguments.size(); + + // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »). + // 3. Set obj.[[ParameterMap]] to undefined. + auto* object = Object::create(global_object, global_object.object_prototype()); // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - auto length = arguments.size(); - object->define_property(vm.names.length, Value(length), Attribute::Writable | Attribute::Configurable); - if (vm.exception()) - return nullptr; + object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true }); + VERIFY(!vm.exception()); - object->define_property(*vm.well_known_symbol_iterator(), global_object.array_prototype()->get(vm.names.values), Attribute::Writable | Attribute::Configurable); + // 5. Let index be 0. + // 6. Repeat, while index < len, + for (size_t index = 0; index < length; ++index) { + // a. Let val be argumentsList[index]. + auto value = arguments[index]; - // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, [[Configurable]]: false }). - object->define_accessor(vm.names.callee, global_object.throw_type_error_function(), global_object.throw_type_error_function(), 0); + // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). + object->create_data_property_or_throw(index, value); + VERIFY(!vm.exception()); + + // c. Set index to index + 1. + } + + // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). + // FIXME: This is not guaranteed to be %Array.prototype.values%! + auto array_prototype_values = global_object.array_prototype()->get(vm.names.values); if (vm.exception()) - return nullptr; + return {}; + object->define_property_or_throw(*vm.well_known_symbol_iterator(), { .value = array_prototype_values, .writable = true, .enumerable = false, .configurable = true }); + VERIFY(!vm.exception()); + + // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, [[Configurable]]: false }). + auto* throw_type_error = global_object.throw_type_error_function(); + object->define_property_or_throw(vm.names.callee, { .get = throw_type_error, .set = throw_type_error, .enumerable = false, .configurable = false }); + VERIFY(!vm.exception()); + // 9. Return obj. return object; } @@ -260,33 +456,75 @@ Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObje (void)formals; auto& vm = global_object.vm(); + + // 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers. It may contain duplicate identifiers. + + // 2. Let len be the number of elements in argumentsList. + auto length = arguments.size(); + + // 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »). auto* object = vm.heap().allocate<ArgumentsObject>(global_object, global_object); - if (vm.exception()) - return nullptr; + VERIFY(!vm.exception()); + + // 4. Set obj.[[GetOwnProperty]] as specified in 10.4.4.1. + // 5. Set obj.[[DefineOwnProperty]] as specified in 10.4.4.2. + // 6. Set obj.[[Get]] as specified in 10.4.4.3. + // 7. Set obj.[[Set]] as specified in 10.4.4.4. + // 8. Set obj.[[Delete]] as specified in 10.4.4.5. + // 9. Set obj.[[Prototype]] to %Object.prototype%. // 14. Let index be 0. // 15. Repeat, while index < len, - // a. Let val be argumentsList[index]. - // b . Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). - // c. Set index to index + 1. - for (auto& argument : arguments) - object->indexed_properties().append(argument); + for (size_t index = 0; index < length; ++index) { + // a. Let val be argumentsList[index]. + auto value = arguments[index]; + + // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). + object->create_data_property_or_throw(index, value); + VERIFY(!vm.exception()); + + // c. Set index to index + 1. + } // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - auto length = arguments.size(); - object->define_property(vm.names.length, Value(length), Attribute::Writable | Attribute::Configurable); - if (vm.exception()) - return nullptr; + object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true }); + VERIFY(!vm.exception()); // 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - object->define_property(*vm.well_known_symbol_iterator(), global_object.array_prototype()->get(vm.names.values), Attribute::Writable | Attribute::Configurable); + // FIXME: This is not guaranteed to be %Array.prototype.values%! + auto array_prototype_values = global_object.array_prototype()->get(vm.names.values); + if (vm.exception()) + return {}; + object->define_property_or_throw(*vm.well_known_symbol_iterator(), { .value = array_prototype_values, .writable = true, .enumerable = false, .configurable = true }); + VERIFY(!vm.exception()); // 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). - object->define_property(vm.names.callee, Value(&function), Attribute::Writable | Attribute::Configurable); - if (vm.exception()) - return nullptr; + object->define_property_or_throw(vm.names.callee, { .value = &function, .writable = true, .enumerable = false, .configurable = true }); + VERIFY(!vm.exception()); + // 22. Return obj. return object; } +// 7.1.21 CanonicalNumericIndexString ( argument ), https://tc39.es/ecma262/#sec-canonicalnumericindexstring +Value canonical_numeric_index_string(GlobalObject& global_object, Value argument) +{ + // 1. Assert: Type(argument) is String. + VERIFY(argument.is_string()); + + // 2. If argument is "-0", return -0𝔽. + if (argument.as_string().string() == "-0") + return Value(-0.0); + + // 3. Let n be ! ToNumber(argument). + auto n = argument.to_number(global_object); + + // 4. If SameValue(! ToString(n), argument) is false, return undefined. + if (!same_value(n.to_primitive_string(global_object), argument)) + return js_undefined(); + + // 5. Return n. + return n; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h index 58f2aadee3..68e7c1355e 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h @@ -24,9 +24,12 @@ size_t length_of_array_like(GlobalObject&, Object const&); MarkedValueList create_list_from_array_like(GlobalObject&, Value, Function<Result<void, ErrorType>(Value)> = {}); FunctionObject* species_constructor(GlobalObject&, Object const&, FunctionObject& default_constructor); GlobalObject* get_function_realm(GlobalObject&, FunctionObject const&); +bool is_compatible_property_descriptor(bool extensible, PropertyDescriptor const&, Optional<PropertyDescriptor> const& current); +bool validate_and_apply_property_descriptor(Object*, PropertyName const&, bool extensible, PropertyDescriptor const&, Optional<PropertyDescriptor> const& current); Object* get_prototype_from_constructor(GlobalObject&, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)()); Object* create_unmapped_arguments_object(GlobalObject&, Vector<Value> const& arguments); Object* create_mapped_arguments_object(GlobalObject&, FunctionObject&, Vector<FunctionNode::Parameter> const&, Vector<Value> const& arguments, Environment&); +Value canonical_numeric_index_string(GlobalObject&, Value); enum class CallerMode { Strict, diff --git a/Userland/Libraries/LibJS/Runtime/Array.cpp b/Userland/Libraries/LibJS/Runtime/Array.cpp index 271b3b426f..50dbccef9d 100644 --- a/Userland/Libraries/LibJS/Runtime/Array.cpp +++ b/Userland/Libraries/LibJS/Runtime/Array.cpp @@ -23,16 +23,27 @@ Array* Array::create(GlobalObject& global_object, size_t length, Object* prototy if (!prototype) prototype = global_object.array_prototype(); auto* array = global_object.heap().allocate<Array>(global_object, *prototype); - array->put(vm.names.length, Value(length)); + array->internal_define_own_property(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = false }); return array; } // 7.3.17 CreateArrayFromList ( elements ), https://tc39.es/ecma262/#sec-createarrayfromlist -Array* Array::create_from(GlobalObject& global_object, const Vector<Value>& elements) +Array* Array::create_from(GlobalObject& global_object, Vector<Value> const& elements) { + // 1. Assert: elements is a List whose elements are all ECMAScript language values. + + // 2. Let array be ! ArrayCreate(0). auto* array = Array::create(global_object, 0); - for (size_t i = 0; i < elements.size(); ++i) - array->define_property(i, elements[i]); + + // 3. Let n be 0. + // 4. For each element e of elements, do + for (u32 n = 0; n < elements.size(); ++n) { + // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(n)), e). + array->create_data_property_or_throw(n, elements[n]); + // b. Set n to n + 1. + } + + // 5. Return array. return array; } @@ -72,10 +83,10 @@ JS_DEFINE_NATIVE_GETTER(Array::length_getter) // TODO: could be incorrect if receiver/this_value is fixed or changed if (!this_object->is_array()) { - Value val = this_object->get_own_property(vm.names.length.to_string_or_symbol(), this_object); + auto value = this_object->internal_get(vm.names.length.to_string_or_symbol(), this_object); if (vm.exception()) return {}; - return val; + return value; } return Value(this_object->indexed_properties().array_like_size()); diff --git a/Userland/Libraries/LibJS/Runtime/Array.h b/Userland/Libraries/LibJS/Runtime/Array.h index c98f3853d7..077a2ad29b 100644 --- a/Userland/Libraries/LibJS/Runtime/Array.h +++ b/Userland/Libraries/LibJS/Runtime/Array.h @@ -15,7 +15,7 @@ class Array : public Object { public: static Array* create(GlobalObject&, size_t length, Object* prototype = nullptr); - static Array* create_from(GlobalObject&, const Vector<Value>&); + static Array* create_from(GlobalObject&, Vector<Value> const&); explicit Array(Object& prototype); virtual void initialize(GlobalObject&) override; diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h index 1e43e608bb..4d712e56f4 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h @@ -102,7 +102,7 @@ static Value raw_bytes_to_numeric(GlobalObject& global_object, ByteBuffer raw_va } } -// 25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] ), https://tc39.es/ecma262/#sec-getvaluefrombuffer +// Implementation for 25.1.2.10 GetValueFromBuffer, used in TypedArray<T>::get_value_from_buffer(). template<typename T> Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_array, Order, bool is_little_endian) { diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp index ab173340ad..1dcf312880 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp @@ -67,7 +67,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target) auto* array = Array::create(global_object(), 0, proto); size_t int_length; if (!length.is_number()) { - array->define_property(0, length); + array->create_data_property_or_throw(0, length); int_length = 1; } else { int_length = length.to_u32(global_object()); @@ -76,7 +76,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target) return {}; } } - array->put(vm.names.length, Value(int_length)); + array->set(vm.names.length, Value(int_length), true); return array; } @@ -85,7 +85,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target) return {}; for (size_t k = 0; k < vm.argument_count(); ++k) - array->define_property(k, vm.argument(k)); + array->create_data_property_or_throw(k, vm.argument(k)); return array; } @@ -139,7 +139,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from) return {}; if (!next) { - array_object.put(vm.names.length, Value(k)); + array_object.set(vm.names.length, Value(k), true); if (vm.exception()) return {}; return array; @@ -160,7 +160,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from) mapped_value = next_value; } - array_object.define_property(k, mapped_value); + array_object.create_data_property_or_throw(k, mapped_value); if (vm.exception()) { iterator_close(*iterator); return {}; @@ -201,10 +201,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from) } else { mapped_value = k_value; } - array_object.define_property(k, mapped_value); + array_object.create_data_property_or_throw(k, mapped_value); } - array_object.put(vm.names.length, Value(length)); + array_object.set(vm.names.length, Value(length), true); if (vm.exception()) return {}; @@ -236,11 +236,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of) } auto& array_object = array.as_object(); for (size_t k = 0; k < vm.argument_count(); ++k) { - array_object.define_property(k, vm.argument(k)); + array_object.create_data_property_or_throw(k, vm.argument(k)); if (vm.exception()) return {}; } - array_object.put(vm.names.length, Value(vm.argument_count())); + array_object.set(vm.names.length, Value(vm.argument_count()), true); if (vm.exception()) return {}; return array; diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 01167c8416..0f51bdf008 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -78,16 +78,16 @@ void ArrayPrototype::initialize(GlobalObject& global_object) // 23.1.3.34 Array.prototype [ @@unscopables ], https://tc39.es/ecma262/#sec-array.prototype-@@unscopables auto* unscopable_list = Object::create(global_object, nullptr); - unscopable_list->define_property(vm.names.copyWithin, Value(true)); - unscopable_list->define_property(vm.names.entries, Value(true)); - unscopable_list->define_property(vm.names.fill, Value(true)); - unscopable_list->define_property(vm.names.find, Value(true)); - unscopable_list->define_property(vm.names.findIndex, Value(true)); - unscopable_list->define_property(vm.names.flat, Value(true)); - unscopable_list->define_property(vm.names.flatMap, Value(true)); - unscopable_list->define_property(vm.names.includes, Value(true)); - unscopable_list->define_property(vm.names.keys, Value(true)); - unscopable_list->define_property(vm.names.values, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.copyWithin, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.entries, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.fill, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.find, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.findIndex, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.flat, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.flatMap, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.includes, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.keys, Value(true)); + unscopable_list->create_data_property_or_throw(vm.names.values, Value(true)); define_property(*vm.well_known_symbol_unscopables(), unscopable_list, Attribute::Configurable); } @@ -96,56 +96,6 @@ ArrayPrototype::~ArrayPrototype() { } -static FunctionObject* callback_from_args(GlobalObject& global_object, const String& name) -{ - auto& vm = global_object.vm(); - if (vm.argument_count() < 1) { - vm.throw_exception<TypeError>(global_object, ErrorType::ArrayPrototypeOneArg, name); - return nullptr; - } - auto callback = vm.argument(0); - if (!callback.is_function()) { - vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects()); - return nullptr; - } - return &callback.as_function(); -} - -static void for_each_item(VM& vm, GlobalObject& global_object, const String& name, Function<IterationDecision(size_t index, Value value, Value callback_result)> callback, bool skip_empty = true) -{ - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) - return; - - auto initial_length = length_of_array_like(global_object, *this_object); - if (vm.exception()) - return; - - auto* callback_function = callback_from_args(global_object, name); - if (!callback_function) - return; - - auto this_value = vm.argument(1); - - for (size_t i = 0; i < initial_length; ++i) { - auto value = this_object->get(i); - if (vm.exception()) - return; - if (value.is_empty()) { - if (skip_empty) - continue; - value = js_undefined(); - } - - auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), this_object); - if (vm.exception()) - return; - - if (callback(i, value, callback_result) == IterationDecision::Break) - break; - } -} - // 10.4.2.3 ArraySpeciesCreate ( originalArray, length ), https://tc39.es/ecma262/#sec-arrayspeciescreate static Object* array_species_create(GlobalObject& global_object, Object& original_array, size_t length) { @@ -203,54 +153,189 @@ static Object* array_species_create(GlobalObject& global_object, Object& origina // 23.1.3.7 Array.prototype.filter ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.filter JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::filter) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) return {}; - auto* new_array = array_species_create(global_object, *this_object, 0); + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); if (vm.exception()) return {}; - size_t to_index = 0; + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + return {}; + } + + // 4. Let A be ? ArraySpeciesCreate(O, 0). + auto* array = array_species_create(global_object, *object, 0); + if (vm.exception()) + return {}; - for_each_item(vm, global_object, "filter", [&](auto, auto value, auto callback_result) { - if (callback_result.to_boolean()) { - new_array->define_property(to_index, value); - ++to_index; + // 5. Let k be 0. + size_t k = 0; + + // 6. Let to be 0. + size_t to = 0; + + // 7. Repeat, while k < len, + for (; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); + if (vm.exception()) + return {}; + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(k); + if (vm.exception()) + return {}; + + // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto selected = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + + // iii. If selected is true, then + if (selected.to_boolean()) { + // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue). + array->create_data_property_or_throw(to, k_value); + + // 2. Set to to to + 1. + ++to; + } } - return IterationDecision::Continue; - }); - return Value(new_array); + + // d. Set k to k + 1. + } + + // 8. Return A. + return array; } // 23.1.3.12 Array.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.foreach JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each) { - for_each_item(vm, global_object, "forEach", [](auto, auto, auto) { - return IterationDecision::Continue; - }); + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) + return {}; + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); + if (vm.exception()) + return {}; + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + return {}; + } + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); + if (vm.exception()) + return {}; + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + (void)vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + } + + // d. Set k to k + 1. + } + + // 6. Return undefined. return js_undefined(); } // 23.1.3.18 Array.prototype.map ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.map JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::map) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) return {}; - auto initial_length = length_of_array_like(global_object, *this_object); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); if (vm.exception()) return {}; - auto* new_array = array_species_create(global_object, *this_object, initial_length); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + return {}; + } + + // 4. Let A be ? ArraySpeciesCreate(O, len). + auto* array = array_species_create(global_object, *object, length); if (vm.exception()) return {}; - for_each_item(vm, global_object, "map", [&](auto index, auto, auto callback_result) { + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); if (vm.exception()) - return IterationDecision::Break; - new_array->define_property(index, callback_result); - return IterationDecision::Continue; - }); - return Value(new_array); + return {}; + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + auto mapped_value = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + + // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). + array->create_data_property_or_throw(property_name, mapped_value); + if (vm.exception()) + return {}; + } + + // d. Set k to k + 1. + } + + // 7. Return A. + return array; } // 23.1.3.20 Array.prototype.push ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.push @@ -318,7 +403,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::unshift) if (vm.exception()) return {}; } else { - this_object->delete_property(to, true); + this_object->delete_property_or_throw(to); if (vm.exception()) return {}; } @@ -360,7 +445,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::pop) auto element = this_object->get(index).value_or(js_undefined()); if (vm.exception()) return {}; - this_object->delete_property(index, true); + this_object->delete_property_or_throw(index); if (vm.exception()) return {}; this_object->put(vm.names.length, Value((i32)index)); @@ -402,13 +487,13 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift) if (vm.exception()) return {}; } else { - this_object->delete_property(to, true); + this_object->delete_property_or_throw(to); if (vm.exception()) return {}; } } - this_object->delete_property(length - 1, true); + this_object->delete_property_or_throw(length - 1); if (vm.exception()) return {}; @@ -571,10 +656,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat) if (vm.exception()) return; if (k_exists) { - auto k_value = obj.get(k).value_or(js_undefined()); + auto k_value = obj.get(k); if (vm.exception()) return; - new_array->define_property(n, k_value); + new_array->create_data_property_or_throw(n, k_value); if (vm.exception()) return; } @@ -586,7 +671,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat) vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize); return; } - new_array->define_property(n, arg); + new_array->create_data_property_or_throw(n, arg); if (vm.exception()) return; ++n; @@ -667,11 +752,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice) return {}; if (present) { - auto value = this_object->get((u32)actual_start).value_or(js_undefined()); + auto value = this_object->get((u32)actual_start); if (vm.exception()) return {}; - new_array->define_property(index, value); + new_array->create_data_property_or_throw(index, value); if (vm.exception()) return {}; } @@ -690,140 +775,284 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice) // 23.1.3.14 Array.prototype.indexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.indexof JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::index_of) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) + auto search_element = vm.argument(0); + auto from_index = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) return {}; - i32 length = length_of_array_like(global_object, *this_object); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); if (vm.exception()) return {}; + + // 3. If len is 0, return -1𝔽. if (length == 0) return Value(-1); - i32 from_index = 0; - if (vm.argument_count() >= 2) { - from_index = vm.argument(1).to_i32(global_object); - if (vm.exception()) - return {}; - if (from_index >= length) - return Value(-1); - if (from_index < 0) - from_index = max(length + from_index, 0); + + // 4. Let n be ? ToIntegerOrInfinity(fromIndex). + auto n = from_index.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + // 5. Assert: If fromIndex is undefined, then n is 0. + if (from_index.is_undefined()) + VERIFY(n == 0); + + // 6. If n is +∞, return -1𝔽. + if (Value(n).is_positive_infinity()) + return Value(-1); + + // 7. Else if n is -∞, set n to 0. + if (Value(n).is_negative_infinity()) + n = 0; + + u32 k; + + // 8. If n ≥ 0, then + if (n >= 0) { + // a. Let k be n. + k = (u32)n; } - auto search_element = vm.argument(0); - for (i32 i = from_index; i < length; ++i) { - auto element = this_object->get(i); + // 9. Else, + else { + // a. Let k be len + n. + // b. If k < 0, set k to 0. + k = max((i32)length + (i32)n, 0); + } + + // 10. Repeat, while k < len, + for (; k < length; ++k) { + auto property_name = PropertyName { k }; + + // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))). + auto k_present = object->has_property(property_name); if (vm.exception()) return {}; - if (strict_eq(element, search_element)) - return Value(i); + + // b. If kPresent is true, then + if (k_present) { + // i. Let elementK be ? Get(O, ! ToString(𝔽(k))). + auto element_k = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + auto same = strict_eq(search_element, element_k); + + // iii. If same is true, return 𝔽(k). + if (same) + return Value(k); + } + + // c. Set k to k + 1. } + + // 11. Return -1𝔽. return Value(-1); } // 23.1.3.21 Array.prototype.reduce ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-array.prototype.reduce JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) + auto callback_function = vm.argument(0); + auto initial_value = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) return {}; - auto initial_length = length_of_array_like(global_object, *this_object); + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); if (vm.exception()) return {}; - auto* callback_function = callback_from_args(global_object, "reduce"); - if (!callback_function) + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + return {}; + } + + // 4. If len = 0 and initialValue is not present, throw a TypeError exception. + if (length == 0 && vm.argument_count() <= 1) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial); return {}; + } - size_t start = 0; + // 5. Let k be 0. + size_t k = 0; + // 6. Let accumulator be undefined. auto accumulator = js_undefined(); + + // 7. If initialValue is present, then if (vm.argument_count() > 1) { - accumulator = vm.argument(1); - } else { - bool start_found = false; - while (!start_found && start < initial_length) { - auto value = this_object->get(start); + // a. Set accumulator to initialValue. + accumulator = initial_value; + } + // 8. Else, + else { + // a. Let kPresent be false. + bool k_present = false; + + // b. Repeat, while kPresent is false and k < len, + for (; !k_present && k < length; ++k) { + // i. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // ii. Set kPresent to ? HasProperty(O, Pk). + k_present = object->has_property(property_name); if (vm.exception()) return {}; - start_found = !value.is_empty(); - if (start_found) - accumulator = value; - start += 1; + + // iii. If kPresent is true, then + if (k_present) { + // 1. Set accumulator to ? Get(O, Pk). + accumulator = object->get(property_name); + if (vm.exception()) + return {}; + } + + // iv. Set k to k + 1. } - if (!start_found) { + + // c. If kPresent is false, throw a TypeError exception. + if (!k_present) { vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial); return {}; } } - auto this_value = js_undefined(); + // 9. Repeat, while k < len, + for (; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; - for (size_t i = start; i < initial_length; ++i) { - auto value = this_object->get(i); - if (vm.exception()) - return {}; - if (value.is_empty()) - continue; + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); - accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object); - if (vm.exception()) - return {}; + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). + accumulator = vm.call(callback_function.as_function(), js_undefined(), accumulator, k_value, Value(k), object); + if (vm.exception()) + return {}; + } + + // d. Set k to k + 1. } + // 10. Return accumulator. return accumulator; } // 23.1.3.22 Array.prototype.reduceRight ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-array.prototype.reduceright JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce_right) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) + auto callback_function = vm.argument(0); + auto initial_value = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) return {}; - auto initial_length = length_of_array_like(global_object, *this_object); + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); if (vm.exception()) return {}; - auto* callback_function = callback_from_args(global_object, "reduceRight"); - if (!callback_function) + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); return {}; + } - int start = initial_length - 1; + // 4. If len = 0 and initialValue is not present, throw a TypeError exception. + if (length == 0 && vm.argument_count() <= 1) { + vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial); + return {}; + } + // 5. Let k be len - 1. + ssize_t k = length - 1; + + // 6. Let accumulator be undefined. auto accumulator = js_undefined(); + + // 7. If initialValue is present, then if (vm.argument_count() > 1) { - accumulator = vm.argument(1); - } else { - bool start_found = false; - while (!start_found && start >= 0) { - auto value = this_object->get(start); + // a. Set accumulator to initialValue. + accumulator = initial_value; + } + // 8. Else, + else { + // a. Let kPresent be false. + bool k_present = false; + + // b. Repeat, while kPresent is false and k ≥ 0, + for (; !k_present && k >= 0; --k) { + // i. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // ii. Set kPresent to ? HasProperty(O, Pk). + k_present = object->has_property(property_name); if (vm.exception()) return {}; - start_found = !value.is_empty(); - if (start_found) - accumulator = value; - start -= 1; + + // iii. If kPresent is true, then + if (k_present) { + // 1. Set accumulator to ? Get(O, Pk). + accumulator = object->get(property_name); + if (vm.exception()) + return {}; + } + + // iv. Set k to k - 1. } - if (!start_found) { + + // c. If kPresent is false, throw a TypeError exception. + if (!k_present) { vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial); return {}; } } - auto this_value = js_undefined(); + // 9. Repeat, while k ≥ 0, + for (; k >= 0; --k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; - for (int i = start; i >= 0; --i) { - auto value = this_object->get(i); + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); if (vm.exception()) return {}; - if (value.is_empty()) - continue; - accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object); - if (vm.exception()) - return {}; + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »). + accumulator = vm.call(callback_function.as_function(), js_undefined(), accumulator, k_value, Value((i32)k), object); + if (vm.exception()) + return {}; + } + + // d. Set k to k - 1. } + // 10. Return accumulator. return accumulator; } @@ -872,11 +1101,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reverse) this_object->define_property(lower, upper_value); if (vm.exception()) return {}; - this_object->delete_property(upper, true); + this_object->delete_property_or_throw(upper); if (vm.exception()) return {}; } else if (lower_exists && !upper_exists) { - this_object->delete_property(lower, true); + this_object->delete_property_or_throw(lower); if (vm.exception()) return {}; this_object->define_property(upper, lower_value); @@ -937,13 +1166,14 @@ static void array_merge_sort(VM& vm, GlobalObject& global_object, FunctionObject if (vm.exception()) return; - if (call_result.is_nan()) { + auto number = call_result.to_number(global_object); + if (vm.exception()) + return; + + if (number.is_nan()) comparison_result = 0; - } else { - comparison_result = call_result.to_double(global_object); - if (vm.exception()) - return; - } + else + comparison_result = number.as_double(); } else { // FIXME: It would probably be much better to be smarter about this and implement // the Abstract Relational Comparison in line once iterating over code points, rather @@ -998,29 +1228,33 @@ static void array_merge_sort(VM& vm, GlobalObject& global_object, FunctionObject // 23.1.3.27 Array.prototype.sort ( comparefn ), https://tc39.es/ecma262/#sec-array.prototype.sort JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort) { - auto* array = vm.this_value(global_object).to_object(global_object); - if (vm.exception()) - return {}; - auto callback = vm.argument(0); if (!callback.is_undefined() && !callback.is_function()) { vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects()); return {}; } - auto original_length = length_of_array_like(global_object, *array); + auto* object = vm.this_value(global_object).to_object(global_object); if (vm.exception()) return {}; - MarkedValueList values_to_sort(vm.heap()); + auto length = length_of_array_like(global_object, *object); + if (vm.exception()) + return {}; - for (size_t i = 0; i < original_length; ++i) { - auto element_val = array->get(i); + MarkedValueList items(vm.heap()); + for (size_t k = 0; k < length; ++k) { + auto k_present = object->has_property(k); if (vm.exception()) return {}; - if (!element_val.is_empty()) - values_to_sort.append(element_val); + if (k_present) { + auto k_value = object->get(k); + if (vm.exception()) + return {}; + + items.append(k_value); + } } // Perform sorting by merge sort. This isn't as efficient compared to quick sort, but @@ -1028,12 +1262,12 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort) // to be stable. FIXME: when initially scanning through the array, maintain a flag // for if an unstable sort would be indistinguishable from a stable sort (such as just // just strings or numbers), and in that case use quick sort instead for better performance. - array_merge_sort(vm, global_object, callback.is_undefined() ? nullptr : &callback.as_function(), values_to_sort); + array_merge_sort(vm, global_object, callback.is_undefined() ? nullptr : &callback.as_function(), items); if (vm.exception()) return {}; - for (size_t i = 0; i < values_to_sort.size(); ++i) { - array->put(i, values_to_sort[i]); + for (size_t j = 0; j < items.size(); ++j) { + object->set(j, items[j], true); if (vm.exception()) return {}; } @@ -1041,47 +1275,91 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort) // The empty parts of the array are always sorted to the end, regardless of the // compare function. FIXME: For performance, a similar process could be used // for undefined, which are sorted to right before the empty values. - for (size_t i = values_to_sort.size(); i < original_length; ++i) { - array->delete_property(i, true); + for (size_t j = items.size(); j < length; ++j) { + object->delete_property_or_throw(j); if (vm.exception()) return {}; } - return array; + return object; } // 23.1.3.17 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.lastindexof JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::last_index_of) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) + auto search_element = vm.argument(0); + auto from_index = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) return {}; - i32 length = length_of_array_like(global_object, *this_object); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); if (vm.exception()) return {}; + + // 3. If len is 0, return -1𝔽. if (length == 0) return Value(-1); - i32 from_index = length - 1; + + double n; + + // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. if (vm.argument_count() >= 2) { - double from_argument = vm.argument(1).to_integer_or_infinity(global_object); + n = from_index.to_integer_or_infinity(global_object); if (vm.exception()) return {}; - if (vm.argument(1).is_negative_infinity()) { - return Value(-1); - } - if (from_argument >= 0) - from_index = min(from_argument, length - 1.); - else - from_index = length + from_argument; + } else { + n = (double)length - 1; } - auto search_element = vm.argument(0); - for (i32 i = from_index; i >= 0; --i) { - auto element = this_object->get(i); + + // 5. If n is -∞, return -1𝔽. + if (Value(n).is_negative_infinity()) + return Value(-1); + + i32 k; + + // 6. If n ≥ 0, then + if (n >= 0) { + // a. Let k be min(n, len - 1). + k = min((i32)n, (i32)length - 1); + } + // 7. Else, + else { + // a. Let k be len + n. + k = (i32)length + (i32)n; + } + + // 8. Repeat, while k ≥ 0, + for (; k >= 0; --k) { + auto property_name = PropertyName { k }; + + // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))). + auto k_present = object->has_property(property_name); if (vm.exception()) return {}; - if (strict_eq(element, search_element)) - return Value(i); + + // b. If kPresent is true, then + if (k_present) { + // i. Let elementK be ? Get(O, ! ToString(𝔽(k))). + auto element_k = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + auto same = strict_eq(search_element, element_k); + + // iii. If same is true, return 𝔽(k). + if (same) + return Value(k); + } + + // c. Set k to k - 1. } + + // 9. Return -1𝔽. return Value(-1); } @@ -1120,61 +1398,213 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes) // 23.1.3.8 Array.prototype.find ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.find JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find) { - auto result = js_undefined(); - for_each_item( - vm, global_object, "find", [&](auto, auto value, auto callback_result) { - if (callback_result.to_boolean()) { - result = value; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }, - false); - return result; + auto predicate = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) + return {}; + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); + if (vm.exception()) + return {}; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!predicate.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); + return {}; + } + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = vm.call(predicate.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + + // d. If testResult is true, return kValue. + if (test_result.to_boolean()) + return k_value; + + // e. Set k to k + 1. + } + + // 6. Return undefined. + return js_undefined(); } // 23.1.3.9 Array.prototype.findIndex ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.findindex JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_index) { - auto result_index = -1; - for_each_item( - vm, global_object, "findIndex", [&](auto index, auto, auto callback_result) { - if (callback_result.to_boolean()) { - result_index = index; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }, - false); - return Value(result_index); + auto predicate = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) + return {}; + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); + if (vm.exception()) + return {}; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!predicate.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); + return {}; + } + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = vm.call(predicate.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + + // d. If testResult is true, return 𝔽(k). + if (test_result.to_boolean()) + return Value(k); + + // e. Set k to k + 1. + } + + // 6. Return -1𝔽. + return Value(-1); } // 23.1.3.26 Array.prototype.some ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.some JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::some) { - auto result = false; - for_each_item(vm, global_object, "some", [&](auto, auto, auto callback_result) { - if (callback_result.to_boolean()) { - result = true; - return IterationDecision::Break; + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) + return {}; + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); + if (vm.exception()) + return {}; + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + return {}; + } + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); + if (vm.exception()) + return {}; + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + + // iii. If testResult is true, return true. + if (test_result.to_boolean()) + return Value(true); } - return IterationDecision::Continue; - }); - return Value(result); + + // d. Set k to k + 1. + } + + // 6. Return false. + return Value(false); } // 23.1.3.5 Array.prototype.every ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.every JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::every) { - auto result = true; - for_each_item(vm, global_object, "every", [&](auto, auto, auto callback_result) { - if (!callback_result.to_boolean()) { - result = false; - return IterationDecision::Break; + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto object = vm.this_value(global_object).to_object(global_object); + if (vm.exception()) + return {}; + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = length_of_array_like(global_object, *object); + if (vm.exception()) + return {}; + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + return {}; + } + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_name = PropertyName { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = object->has_property(property_name); + if (vm.exception()) + return {}; + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = object->get(property_name); + if (vm.exception()) + return {}; + + // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object); + if (vm.exception()) + return {}; + + // iii. If testResult is false, return false. + if (!test_result.to_boolean()) + return Value(false); } - return IterationDecision::Continue; - }); - return Value(result); + + // d. Set k to k + 1. + } + + // 6. Return true. + return Value(true); } // 23.1.3.28 Array.prototype.splice ( start, deleteCount, ...items ), https://tc39.es/ecma262#sec-array.prototype.splice @@ -1235,7 +1665,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice) if (vm.exception()) return {}; - removed_elements->define_property(i, from_value); + removed_elements->create_data_property_or_throw(i, from_value); if (vm.exception()) return {}; } @@ -1256,14 +1686,14 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice) if (!from.is_empty()) { this_object->define_property(to, from); } else { - this_object->delete_property(to, true); + this_object->delete_property_or_throw(to); } if (vm.exception()) return {}; } for (size_t i = initial_length; i > new_length; --i) { - this_object->delete_property(i - 1, true); + this_object->delete_property_or_throw(i - 1); if (vm.exception()) return {}; } @@ -1278,7 +1708,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice) if (!from.is_empty()) { this_object->define_property(to, from); } else { - this_object->delete_property(to, true); + this_object->delete_property_or_throw(to); } if (vm.exception()) return {}; @@ -1413,7 +1843,7 @@ static size_t flatten_into_array(GlobalObject& global_object, Object& new_array, return {}; } - new_array.define_property(target_index, value); + new_array.create_data_property_or_throw(target_index, value); if (vm.exception()) return {}; @@ -1454,29 +1884,37 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat) // 23.1.3.11 Array.prototype.flatMap ( mapperFunction [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.flatmap JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map) { - auto* this_object = vm.this_value(global_object).to_object(global_object); - if (!this_object) - return {}; + auto mapper_function = vm.argument(0); + auto this_arg = vm.argument(1); - auto length = length_of_array_like(global_object, *this_object); + // 1. Let O be ? ToObject(this value). + auto* object = vm.this_value(global_object).to_object(global_object); if (vm.exception()) return {}; - auto* mapper_function = callback_from_args(global_object, "flatMap"); - if (!mapper_function) + // 2. Let sourceLen be ? LengthOfArrayLike(O). + auto source_length = length_of_array_like(global_object, *object); + if (vm.exception()) return {}; - auto this_argument = vm.argument(1); + // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception. + if (!mapper_function.is_function()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, mapper_function.to_string_without_side_effects()); + return {}; + } - auto* new_array = array_species_create(global_object, *this_object, 0); + // 4. Let A be ? ArraySpeciesCreate(O, 0). + auto* array = array_species_create(global_object, *object, 0); if (vm.exception()) return {}; - flatten_into_array(global_object, *new_array, *this_object, length, 0, 1, mapper_function, this_argument); + // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg). + flatten_into_array(global_object, *array, *object, source_length, 0, 1, &mapper_function.as_function(), this_arg); if (vm.exception()) return {}; - return new_array; + // 6. Return A. + return array; } // 23.1.3.3 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin @@ -1550,7 +1988,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within) if (vm.exception()) return {}; } else { - this_object->delete_property(to_i, true); + this_object->delete_property_or_throw(to_i); if (vm.exception()) return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp index 81478d9987..a38c247c35 100644 --- a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp @@ -40,7 +40,7 @@ static Value this_bigint_value(GlobalObject& global_object, Value value) if (value.is_bigint()) return value; if (value.is_object() && is<BigIntObject>(value.as_object())) - return static_cast<BigIntObject&>(value.as_object()).value_of(); + return &static_cast<BigIntObject&>(value.as_object()).bigint(); auto& vm = global_object.vm(); vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "BigInt"); return {}; diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp index f5c35e9200..18aadb3448 100644 --- a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp +++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp @@ -37,7 +37,7 @@ Value BoundFunction::call() Value BoundFunction::construct(FunctionObject& new_target) { if (auto this_value = vm().this_value(global_object()); m_constructor_prototype && this_value.is_object()) { - this_value.as_object().set_prototype(m_constructor_prototype); + this_value.as_object().internal_set_prototype_of(m_constructor_prototype); if (vm().exception()) return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 01a9d07d35..ef41a6deab 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -8,7 +8,6 @@ #define JS_ENUMERATE_ERROR_TYPES(M) \ M(ArrayMaxSize, "Maximum array size exceeded") \ - M(ArrayPrototypeOneArg, "Array.prototype.{}() requires at least one argument") \ M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \ M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \ M(BigIntBadOperator, "Cannot use {} operator with BigInt") \ @@ -23,7 +22,6 @@ M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \ M(Convert, "Cannot convert {} to {}") \ M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \ - M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \ M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \ M(DetachedArrayBuffer, "ArrayBuffer is detached") \ M(DivisionByZero, "Division by zero") \ @@ -60,9 +58,11 @@ M(NotASymbol, "{} is not a symbol") \ M(NotIterable, "{} is not iterable") \ M(NotObjectCoercible, "{} cannot be converted to an object") \ - M(ObjectDefinePropertyReturnedFalse, "Object's [[DefineProperty]] method returned false") \ + M(ObjectDefineOwnPropertyReturnedFalse, "Object's [[DefineOwnProperty]] method returned false") \ + M(ObjectDeleteReturnedFalse, "Object's [[Delete]] method returned false") \ M(ObjectFreezeFailed, "Could not freeze object") \ M(ObjectSealFailed, "Could not seal object") \ + M(ObjectSetReturnedFalse, "Object's [[Set]] method returned false") \ M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \ M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \ M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \ @@ -81,11 +81,17 @@ M(ProxyDefinePropNonConfigurableNonExisting, "Proxy handler's defineProperty trap " \ "violates invariant: a property cannot be defined as non-configurable if it does not " \ "already exist on the target object") \ + M(ProxyDefinePropNonWritable, "Proxy handler's defineProperty trap violates invariant: a non-configurable property cannot be " \ + "non-writable, unless there exists a corresponding non-configurable, non-writable own property of " \ + "the target object") \ M(ProxyDefinePropNonExtensible, "Proxy handler's defineProperty trap violates invariant: " \ "a property cannot be reported as being defined if the property does not exist on " \ "the target and the target is non-extensible") \ M(ProxyDeleteNonConfigurable, "Proxy handler's deleteProperty trap violates invariant: " \ "cannot report a non-configurable own property of the target as deleted") \ + M(ProxyDeleteNonExtensible, "Proxy handler's deleteProperty trap violates invariant: " \ + "a property cannot be reported as deleted, if it exists as an own property of the target object and " \ + "the target object is non-extensible. ") \ M(ProxyGetImmutableDataProperty, "Proxy handler's get trap violates invariant: the " \ "returned value must match the value on the target if the property exists on the " \ "target as a non-writable, non-configurable own data property") \ @@ -103,6 +109,10 @@ M(ProxyGetOwnDescriptorNonConfigurable, "Proxy handler's getOwnPropertyDescriptor trap " \ "violates invariant: cannot return undefined for a property on the target which is " \ "a non-configurable property") \ + M(ProxyGetOwnDescriptorNonConfigurableNonWritable, "Proxy handler's getOwnPropertyDescriptor trap " \ + "violates invariant: cannot a property as both non-configurable and " \ + "non-writable, unless it exists as a non-configurable, non-writable own " \ + "property of the target object") \ M(ProxyGetOwnDescriptorReturn, "Proxy handler's getOwnPropertyDescriptor trap violates " \ "invariant: must return an object or undefined") \ M(ProxyGetOwnDescriptorUndefinedReturn, "Proxy handler's getOwnPropertyDescriptor trap " \ @@ -120,6 +130,8 @@ "non-extensible") \ M(ProxyIsExtensibleReturn, "Proxy handler's isExtensible trap violates invariant: " \ "return value must match the target's extensibility") \ + M(ProxyOwnPropertyKeysNotStringOrSymbol, "Proxy handler's ownKeys trap violates invariant: " \ + "the type of each result list element is either String or Symbol") \ M(ProxyPreventExtensionsReturn, "Proxy handler's preventExtensions trap violates " \ "invariant: cannot return true if the target object is extensible") \ M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \ @@ -138,11 +150,6 @@ M(ReferenceNullishSetProperty, "Cannot set property '{}' of {}") \ M(ReferencePrimitiveSetProperty, "Cannot set property '{}' of {} '{}'") \ M(ReferenceUnresolvable, "Unresolvable reference") \ - M(ReflectArgumentMustBeAConstructor, "First argument of Reflect.{}() must be a constructor") \ - M(ReflectArgumentMustBeAFunction, "First argument of Reflect.{}() must be a function") \ - M(ReflectArgumentMustBeAnObject, "First argument of Reflect.{}() must be an object") \ - M(ReflectBadNewTarget, "Optional third argument of Reflect.construct() must be a constructor") \ - M(ReflectBadDescriptorArgument, "Descriptor argument is not an object") \ M(RegExpCompileError, "RegExp compile error: {}") \ M(RegExpObjectBadFlag, "Invalid RegExp flag '{}'") \ M(RegExpObjectRepeatedFlag, "Repeated RegExp flag '{}'") \ @@ -151,6 +158,7 @@ M(SpeciesConstructorDidNotCreate, "Species constructor did not create {}") \ M(SpeciesConstructorReturned, "Species constructor returned {}") \ M(StringMatchAllNonGlobalRegExp, "RegExp argument is non-global") \ + M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \ M(StringRepeatCountMustBe, "repeat count must be a {} number") \ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ M(ThisIsAlreadyInitialized, "|this| is already initialized") \ diff --git a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp index 722a5e55c7..62ebfb403c 100644 --- a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp @@ -35,7 +35,7 @@ Value FunctionEnvironment::get_super_base() const auto home_object = m_function_object->home_object(); if (home_object.is_undefined()) return js_undefined(); - return home_object.as_object().prototype(); + return home_object.as_object().internal_get_prototype_of(); } // 9.1.1.3.2 HasThisBinding ( ), https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp index 2e4a5c5393..732dec0c0e 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -143,10 +144,14 @@ bool GlobalEnvironment::has_lexical_declaration(FlyString const& name) const // 9.1.1.4.14 HasRestrictedGlobalProperty ( N ), https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty bool GlobalEnvironment::has_restricted_global_property(FlyString const& name) const { - auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name); - if (!existing_prop.has_value() || existing_prop.value().value.is_undefined()) + auto& vm = this->vm(); + auto& global_object = m_object_record->binding_object(); + auto existing_prop = global_object.internal_get_own_property(name); + if (vm.exception()) + return {}; + if (!existing_prop.has_value()) return false; - if (existing_prop.value().attributes.is_configurable()) + if (*existing_prop->configurable) return false; return true; } @@ -154,21 +159,29 @@ bool GlobalEnvironment::has_restricted_global_property(FlyString const& name) co // 9.1.1.4.15 CanDeclareGlobalVar ( N ), https://tc39.es/ecma262/#sec-candeclareglobalvar bool GlobalEnvironment::can_declare_global_var(FlyString const& name) const { - bool has_property = m_object_record->binding_object().has_own_property(name); + auto& vm = this->vm(); + auto& global_object = m_object_record->binding_object(); + bool has_property = global_object.has_own_property(name); + if (vm.exception()) + return {}; if (has_property) return true; - return m_object_record->binding_object().is_extensible(); + return global_object.is_extensible(); } // 9.1.1.4.16 CanDeclareGlobalFunction ( N ), https://tc39.es/ecma262/#sec-candeclareglobalfunction bool GlobalEnvironment::can_declare_global_function(FlyString const& name) const { - auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name); - if (!existing_prop.has_value() || existing_prop.value().value.is_undefined()) - return m_object_record->binding_object().is_extensible(); - if (existing_prop.value().attributes.is_configurable()) + auto& vm = this->vm(); + auto& global_object = m_object_record->binding_object(); + auto existing_prop = global_object.internal_get_own_property(name); + if (vm.exception()) + return {}; + if (!existing_prop.has_value()) + return global_object.is_extensible(); + if (*existing_prop->configurable) return true; - if (existing_prop.value().is_data_descriptor() && existing_prop.value().attributes.is_writable() && existing_prop.value().attributes.is_enumerable()) + if (existing_prop->is_data_descriptor() && *existing_prop->writable && *existing_prop->enumerable) return true; return false; } @@ -176,11 +189,21 @@ bool GlobalEnvironment::can_declare_global_function(FlyString const& name) const // 9.1.1.4.17 CreateGlobalVarBinding ( N, D ), https://tc39.es/ecma262/#sec-createglobalvarbinding void GlobalEnvironment::create_global_var_binding(FlyString const& name, bool can_be_deleted) { - bool has_property = m_object_record->binding_object().has_own_property(name); - bool extensible = m_object_record->binding_object().is_extensible(); + auto& vm = this->vm(); + auto& global_object = m_object_record->binding_object(); + bool has_property = global_object.has_own_property(name); + if (vm.exception()) + return; + bool extensible = global_object.is_extensible(); + if (vm.exception()) + return; if (!has_property && extensible) { - m_object_record->create_mutable_binding(static_cast<GlobalObject&>(m_object_record->binding_object()), name, can_be_deleted); + m_object_record->create_mutable_binding(m_object_record->global_object(), name, can_be_deleted); + if (vm.exception()) + return; m_object_record->initialize_binding(m_object_record->global_object(), name, js_undefined()); + if (vm.exception()) + return; } if (!m_var_names.contains_slow(name)) m_var_names.append(name); @@ -189,23 +212,21 @@ void GlobalEnvironment::create_global_var_binding(FlyString const& name, bool ca // 9.1.1.4.18 CreateGlobalFunctionBinding ( N, V, D ), https://tc39.es/ecma262/#sec-createglobalfunctionbinding void GlobalEnvironment::create_global_function_binding(FlyString const& name, Value value, bool can_be_deleted) { - auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name); + auto& vm = this->vm(); + auto& global_object = m_object_record->binding_object(); + auto existing_prop = global_object.internal_get_own_property(name); + if (vm.exception()) + return; PropertyDescriptor desc; - if (!existing_prop.has_value() || existing_prop.value().value.is_undefined() || existing_prop.value().attributes.is_configurable()) { - desc.value = value; - desc.attributes.set_has_writable(); - desc.attributes.set_writable(); - desc.attributes.set_has_enumerable(); - desc.attributes.set_enumerable(); - desc.attributes.set_has_configurable(); - if (can_be_deleted) - desc.attributes.set_configurable(); - } else { - desc.value = value; - } - // FIXME: This should be DefinePropertyOrThrow, followed by Set - m_object_record->binding_object().define_property(name, value, desc.attributes); - if (vm().exception()) + if (!existing_prop.has_value() || *existing_prop->configurable) + desc = { .value = value, .writable = true, .enumerable = true, .configurable = can_be_deleted }; + else + desc = { .value = value }; + global_object.define_property_or_throw(name, desc); + if (vm.exception()) + return; + global_object.set(name, value, false); + if (vm.exception()) return; if (!m_var_names.contains_slow(name)) m_var_names.append(name); diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 81c25365bd..4c30e70a1f 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -111,7 +111,8 @@ void GlobalObject::initialize_global_object() static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this); static_cast<ObjectPrototype*>(m_object_prototype)->initialize(*this); - Object::set_prototype(m_object_prototype); + auto success = Object::internal_set_prototype_of(m_object_prototype); + VERIFY(success); // This must be initialized before allocating AggregateErrorPrototype, which uses ErrorPrototype as its prototype. m_error_prototype = heap().allocate<ErrorPrototype>(*this, *this); @@ -147,9 +148,9 @@ void GlobalObject::initialize_global_object() vm.throw_exception<TypeError>(global_object, ErrorType::RestrictedFunctionPropertiesAccess); return Value(); }); - m_throw_type_error_function->prevent_extensions(); m_throw_type_error_function->define_property_without_transition(vm.names.length, Value(0), 0, false); m_throw_type_error_function->define_property_without_transition(vm.names.name, js_string(vm, ""), 0, false); + m_throw_type_error_function->internal_prevent_extensions(); // 10.2.4 AddRestrictedFunctionProperties ( F, realm ), https://tc39.es/ecma262/#sec-addrestrictedfunctionproperties m_function_prototype->define_accessor(vm.names.caller, throw_type_error_function(), throw_type_error_function(), Attribute::Configurable); diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp index ae492d600c..56ca07ec40 100644 --- a/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp +++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp @@ -52,15 +52,8 @@ void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttribute void SimpleIndexedPropertyStorage::remove(u32 index) { - if (index < m_array_size) - m_packed_elements[index] = {}; -} - -void SimpleIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes) -{ - VERIFY(attributes == default_attributes); - m_array_size++; - m_packed_elements.insert(index, value); + VERIFY(index < m_array_size); + m_packed_elements[index] = {}; } ValueAndAttributes SimpleIndexedPropertyStorage::take_first() @@ -87,7 +80,9 @@ GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedProper { m_array_size = storage.array_like_size(); for (size_t i = 0; i < storage.m_packed_elements.size(); ++i) { - m_sparse_elements.set(i, { storage.m_packed_elements[i], default_attributes }); + auto value = storage.m_packed_elements[i]; + if (!value.is_empty()) + m_sparse_elements.set(i, { value, default_attributes }); } } @@ -112,34 +107,10 @@ void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttribut void GenericIndexedPropertyStorage::remove(u32 index) { - if (index >= m_array_size) - return; - if (index + 1 == m_array_size) { - take_last(); - return; - } + VERIFY(index < m_array_size); m_sparse_elements.remove(index); } -void GenericIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes) -{ - if (index >= m_array_size) { - put(index, value, attributes); - return; - } - - m_array_size++; - - if (!m_sparse_elements.is_empty()) { - HashMap<u32, ValueAndAttributes> new_sparse_elements; - for (auto& entry : m_sparse_elements) - new_sparse_elements.set(entry.key >= index ? entry.key + 1 : entry.key, entry.value); - m_sparse_elements = move(new_sparse_elements); - } - - m_sparse_elements.set(index, { value, attributes }); -} - ValueAndAttributes GenericIndexedPropertyStorage::take_first() { VERIFY(m_array_size > 0); @@ -226,10 +197,10 @@ bool IndexedPropertyIterator::operator!=(const IndexedPropertyIterator& other) c return m_index != other.m_index; } -ValueAndAttributes IndexedPropertyIterator::value_and_attributes(Object* this_object, AllowSideEffects allow_side_effects) +ValueAndAttributes IndexedPropertyIterator::value_and_attributes() { if (m_index < m_indexed_properties.array_like_size()) - return m_indexed_properties.get(this_object, m_index, allow_side_effects).value_or({}); + return m_indexed_properties.get(m_index).value_or({}); return {}; } @@ -245,62 +216,24 @@ void IndexedPropertyIterator::skip_empty_indices() m_index = m_indexed_properties.array_like_size(); } -Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 index, AllowSideEffects allow_side_effects) const +Optional<ValueAndAttributes> IndexedProperties::get(u32 index) const { - auto result = m_storage->get(index); - if (allow_side_effects == AllowSideEffects::No) - return result; - if (!result.has_value()) - return {}; - auto& value = result.value(); - if (value.value.is_accessor()) { - VERIFY(this_object); - auto& accessor = value.value.as_accessor(); - return ValueAndAttributes { accessor.call_getter(this_object), value.attributes }; - } - return result; + return m_storage->get(index); } -void IndexedProperties::put(Object* this_object, u32 index, Value value, PropertyAttributes attributes, AllowSideEffects allow_side_effects) +void IndexedProperties::put(u32 index, Value value, PropertyAttributes attributes) { if (m_storage->is_simple_storage() && (attributes != default_attributes || index > (array_like_size() + SPARSE_ARRAY_HOLE_THRESHOLD))) { switch_to_generic_storage(); } - if (m_storage->is_simple_storage() || allow_side_effects == AllowSideEffects::No) { - m_storage->put(index, value, attributes); - return; - } - - auto value_here = m_storage->get(index); - if (value_here.has_value() && value_here.value().value.is_accessor()) { - VERIFY(this_object); - value_here.value().value.as_accessor().call_setter(this_object, value); - } else { - m_storage->put(index, value, attributes); - } + m_storage->put(index, value, attributes); } -bool IndexedProperties::remove(u32 index) +void IndexedProperties::remove(u32 index) { - auto result = m_storage->get(index); - if (!result.has_value()) - return true; - if (!result.value().attributes.is_configurable()) - return false; + VERIFY(m_storage->has_index(index)); m_storage->remove(index); - return true; -} - -void IndexedProperties::insert(u32 index, Value value, PropertyAttributes attributes) -{ - if (m_storage->is_simple_storage()) { - if (attributes != default_attributes - || index > (array_like_size() + SPARSE_ARRAY_HOLE_THRESHOLD)) { - switch_to_generic_storage(); - } - } - m_storage->insert(index, value, attributes); } ValueAndAttributes IndexedProperties::take_first(Object* this_object) diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.h b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h index 88466fc3a2..67561e8092 100644 --- a/Userland/Libraries/LibJS/Runtime/IndexedProperties.h +++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h @@ -30,7 +30,6 @@ public: virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0; virtual void remove(u32 index) = 0; - virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0; virtual ValueAndAttributes take_first() = 0; virtual ValueAndAttributes take_last() = 0; @@ -51,7 +50,6 @@ public: virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; virtual void remove(u32 index) override; - virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; virtual ValueAndAttributes take_first() override; virtual ValueAndAttributes take_last() override; @@ -80,7 +78,6 @@ public: virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; virtual void remove(u32 index) override; - virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override; virtual ValueAndAttributes take_first() override; virtual ValueAndAttributes take_last() override; @@ -104,7 +101,7 @@ public: bool operator!=(const IndexedPropertyIterator&) const; u32 index() const { return m_index; }; - ValueAndAttributes value_and_attributes(Object* this_object, AllowSideEffects = AllowSideEffects::Yes); + ValueAndAttributes value_and_attributes(); private: void skip_empty_indices(); @@ -124,15 +121,14 @@ public: } bool has_index(u32 index) const { return m_storage->has_index(index); } - Optional<ValueAndAttributes> get(Object* this_object, u32 index, AllowSideEffects = AllowSideEffects::Yes) const; - void put(Object* this_object, u32 index, Value value, PropertyAttributes attributes = default_attributes, AllowSideEffects allow_side_effects = AllowSideEffects::Yes); - bool remove(u32 index); + Optional<ValueAndAttributes> get(u32 index) const; + void put(u32 index, Value value, PropertyAttributes attributes = default_attributes); + void remove(u32 index); - void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes); ValueAndAttributes take_first(Object* this_object); ValueAndAttributes take_last(Object* this_object); - void append(Value value, PropertyAttributes attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, AllowSideEffects::No); } + void append(Value value, PropertyAttributes attributes = default_attributes) { put(array_like_size(), value, attributes); } IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); }; IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); }; diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp index a737d84e3a..91616f805f 100644 --- a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp @@ -117,7 +117,7 @@ String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Valu } auto* wrapper = Object::create(global_object, global_object.object_prototype()); - wrapper->define_property(String::empty(), value); + wrapper->create_data_property_or_throw(String::empty(), value); auto result = serialize_json_property(global_object, state, String::empty(), wrapper); if (vm.exception()) return {}; @@ -256,7 +256,7 @@ String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyS return {}; } } else { - auto property_list = object.get_enumerable_own_property_names(PropertyKind::Key); + auto property_list = object.enumerable_own_property_names(PropertyKind::Key); if (vm.exception()) return {}; for (auto& property : property_list) { @@ -428,7 +428,7 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse) if (reviver.is_function()) { auto* root = Object::create(global_object, global_object.object_prototype()); auto root_name = String::empty(); - root->define_property(root_name, unfiltered); + root->create_data_property_or_throw(root_name, unfiltered); auto result = internalize_json_property(global_object, root, root_name, reviver.as_function()); if (vm.exception()) return {}; @@ -493,9 +493,9 @@ Value JSONObject::internalize_json_property(GlobalObject& global_object, Object* if (vm.exception()) return; if (element.is_undefined()) - value_object.delete_property(key); + value_object.internal_delete(key); else - value_object.define_property(key, element, default_attributes); + value_object.create_data_property(key, element); }; if (is_array) { @@ -508,7 +508,7 @@ Value JSONObject::internalize_json_property(GlobalObject& global_object, Object* return {}; } } else { - auto property_list = value_object.get_enumerable_own_property_names(Object::PropertyKind::Key); + auto property_list = value_object.enumerable_own_property_names(Object::PropertyKind::Key); for (auto& property_name : property_list) { process_property(property_name.as_string().string()); if (vm.exception()) diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 46ef56ed1f..a641e6e6c9 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -5,11 +5,11 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include <AK/Debug.h> #include <AK/String.h> #include <AK/TemporaryChange.h> #include <LibJS/Heap/Heap.h> #include <LibJS/Interpreter.h> +#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/Accessor.h> #include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Error.h> @@ -17,60 +17,14 @@ #include <LibJS/Runtime/NativeFunction.h> #include <LibJS/Runtime/NativeProperty.h> #include <LibJS/Runtime/Object.h> +#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/ProxyObject.h> #include <LibJS/Runtime/Shape.h> -#include <LibJS/Runtime/StringObject.h> #include <LibJS/Runtime/TemporaryClearException.h> #include <LibJS/Runtime/Value.h> namespace JS { -PropertyDescriptor PropertyDescriptor::from_dictionary(VM& vm, const Object& object) -{ - PropertyAttributes attributes; - if (object.has_property(vm.names.configurable)) { - attributes.set_has_configurable(); - if (object.get(vm.names.configurable).value_or(Value(false)).to_boolean()) - attributes.set_configurable(); - if (vm.exception()) - return {}; - } - if (object.has_property(vm.names.enumerable)) { - attributes.set_has_enumerable(); - if (object.get(vm.names.enumerable).value_or(Value(false)).to_boolean()) - attributes.set_enumerable(); - if (vm.exception()) - return {}; - } - if (object.has_property(vm.names.writable)) { - attributes.set_has_writable(); - if (object.get(vm.names.writable).value_or(Value(false)).to_boolean()) - attributes.set_writable(); - if (vm.exception()) - return {}; - } - PropertyDescriptor descriptor { attributes, object.get(vm.names.value), nullptr, nullptr }; - if (vm.exception()) - return {}; - auto getter = object.get(vm.names.get); - if (vm.exception()) - return {}; - if (getter.is_function()) - descriptor.getter = &getter.as_function(); - if (!getter.is_empty()) - descriptor.attributes.set_has_getter(); - - auto setter = object.get(vm.names.set); - if (vm.exception()) - return {}; - if (setter.is_function()) - descriptor.setter = &setter.as_function(); - if (!setter.is_empty()) - descriptor.attributes.set_has_setter(); - - return descriptor; -} - // 10.1.12 OrdinaryObjectCreate ( proto [ , additionalInternalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinaryobjectcreate Object* Object::create(GlobalObject& global_object, Object* prototype) { @@ -96,7 +50,8 @@ Object::Object(ConstructWithoutPrototypeTag, GlobalObject& global_object) Object::Object(Object& prototype) { m_shape = prototype.global_object().empty_object_shape(); - set_prototype(&prototype); + auto success = internal_set_prototype_of(&prototype); + VERIFY(success); } Object::Object(Shape& shape) @@ -113,881 +68,1018 @@ Object::~Object() { } -Object* Object::prototype() +// 7.2 Testing and Comparison Operations, https://tc39.es/ecma262/#sec-testing-and-comparison-operations + +// 7.2.5 IsExtensible ( O ), https://tc39.es/ecma262/#sec-isextensible-o +bool Object::is_extensible() const { - return shape().prototype(); + return internal_is_extensible(); } -const Object* Object::prototype() const +// 7.3 Operations on Objects, https://tc39.es/ecma262/#sec-operations-on-objects + +// 7.3.2 Get ( O, P ), https://tc39.es/ecma262/#sec-get-o-p +Value Object::get(PropertyName const& property_name) const { - return shape().prototype(); + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Return ? O.[[Get]](P, O). + return internal_get(property_name, this); } -// 10.1.2.1 OrdinarySetPrototypeOf ( O, V ), https://tc39.es/ecma262/#sec-ordinarysetprototypeof -bool Object::set_prototype(Object* new_prototype) +// 7.3.3 GetV ( V, P ) is defined as Value::get(). + +// 7.3.4 Set ( O, P, V, Throw ), https://tc39.es/ecma262/#sec-set-o-p-v-throw +bool Object::set(PropertyName const& property_name, Value value, bool throw_exceptions) { - if (prototype() == new_prototype) - return true; - if (!m_is_extensible) - return false; - auto* prototype = new_prototype; - while (prototype) { - if (prototype == this) - return false; - // NOTE: This is a best-effort implementation of the following step: - // "If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1, - // set done to true." - // We don't have a good way of detecting whether certain virtual Object methods have been - // overridden by a given object, but as ProxyObject is the only one doing that, this check - // does the trick. - if (is<ProxyObject>(prototype)) - break; - prototype = prototype->prototype(); + VERIFY(!value.is_empty()); + auto& vm = this->vm(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Assert: Type(Throw) is Boolean. + + // 4. Let success be ? O.[[Set]](P, V, O). + auto success = internal_set(property_name, value, this); + if (vm.exception()) + return {}; + + // 5. If success is false and Throw is true, throw a TypeError exception. + if (!success && throw_exceptions) { + // FIXME: Improve/contextualize error message + vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectSetReturnedFalse); + return {}; } - auto& shape = this->shape(); - if (shape.is_unique()) - shape.set_prototype_without_transition(new_prototype); - else - m_shape = shape.create_prototype_transition(new_prototype); - return true; + + // 6. Return success. + return success; } -bool Object::has_prototype(const Object* prototype) const +// 7.3.5 CreateDataProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createdataproperty +bool Object::create_data_property(PropertyName const& property_name, Value value) { - for (auto* object = this->prototype(); object; object = object->prototype()) { - if (vm().exception()) - return false; - if (object == prototype) - return true; + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + auto new_descriptor = PropertyDescriptor { + .value = value, + .writable = true, + .enumerable = true, + .configurable = true, + }; + + // 4. Return ? O.[[DefineOwnProperty]](P, newDesc). + return internal_define_own_property(property_name, new_descriptor); +} + +// 7.3.6 CreateMethodProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createmethodproperty +bool Object::create_method_property(PropertyName const& property_name, Value value) +{ + VERIFY(!value.is_empty()); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }. + auto new_descriptor = PropertyDescriptor { + .value = value, + .writable = true, + .enumerable = false, + .configurable = true, + }; + + // 4. Return ? O.[[DefineOwnProperty]](P, newDesc). + return internal_define_own_property(property_name, new_descriptor); +} + +// 7.3.7 CreateDataPropertyOrThrow ( O, P, V ), https://tc39.es/ecma262/#sec-createdatapropertyorthrow +bool Object::create_data_property_or_throw(PropertyName const& property_name, Value value) +{ + VERIFY(!value.is_empty()); + auto& vm = this->vm(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Let success be ? CreateDataProperty(O, P, V). + auto success = create_data_property(property_name, value); + if (vm.exception()) + return {}; + + // 4. If success is false, throw a TypeError exception. + if (!success) { + // FIXME: Improve/contextualize error message + vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectDefineOwnPropertyReturnedFalse); + return {}; } - return false; + + // 5. Return success. + return success; } -bool Object::prevent_extensions() +// 7.3.8 DefinePropertyOrThrow ( O, P, desc ), https://tc39.es/ecma262/#sec-definepropertyorthrow +bool Object::define_property_or_throw(PropertyName const& property_name, PropertyDescriptor const& property_descriptor) { - m_is_extensible = false; + auto& vm = this->vm(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Let success be ? O.[[DefineOwnProperty]](P, desc). + auto success = internal_define_own_property(property_name, property_descriptor); + if (vm.exception()) + return {}; + + // 4. If success is false, throw a TypeError exception. + if (!success) { + // FIXME: Improve/contextualize error message + vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectDefineOwnPropertyReturnedFalse); + return {}; + } + + // 5. Return success. + return success; +} + +// 7.3.9 DeletePropertyOrThrow ( O, P ), https://tc39.es/ecma262/#sec-deletepropertyorthrow +bool Object::delete_property_or_throw(PropertyName const& property_name) +{ + auto& vm = this->vm(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Let success be ? O.[[Delete]](P). + auto success = internal_delete(property_name); + if (vm.exception()) + return {}; + + // 4. If success is false, throw a TypeError exception. + if (!success) { + // FIXME: Improve/contextualize error message + vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectDeleteReturnedFalse); + return {}; + } + + // 5. Return success. + return success; +} + +// 7.3.11 HasProperty ( O, P ), https://tc39.es/ecma262/#sec-hasproperty +bool Object::has_property(PropertyName const& property_name) const +{ + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Return ? O.[[HasProperty]](P). + return internal_has_property(property_name); +} + +// 7.3.12 HasOwnProperty ( O, P ), https://tc39.es/ecma262/#sec-hasownproperty +bool Object::has_own_property(PropertyName const& property_name) const +{ + auto& vm = this->vm(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. Let desc be ? O.[[GetOwnProperty]](P). + auto descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 4. If desc is undefined, return false. + if (!descriptor.has_value()) + return false; + + // 5. Return true. return true; } // 7.3.15 SetIntegrityLevel ( O, level ), https://tc39.es/ecma262/#sec-setintegritylevel bool Object::set_integrity_level(IntegrityLevel level) { - // FIXME: This feels clunky and should get nicer abstractions. - auto update_property = [this](auto& property_name, auto new_attributes) { - if (property_name.is_number()) { - auto value_and_attributes = m_indexed_properties.get(nullptr, property_name.as_number(), AllowSideEffects::No).value(); - auto value = value_and_attributes.value; - auto attributes = value_and_attributes.attributes.bits() & new_attributes; - m_indexed_properties.put(nullptr, property_name.as_number(), value, attributes, AllowSideEffects::No); - } else { - auto metadata = shape().lookup(property_name.to_string_or_symbol()).value(); - auto attributes = metadata.attributes.bits() & new_attributes; - if (m_shape->is_unique()) - m_shape->reconfigure_property_in_unique_shape(property_name.to_string_or_symbol(), attributes); - else - set_shape(*m_shape->create_configure_transition(property_name.to_string_or_symbol(), attributes)); - } - }; - auto& vm = this->vm(); - auto status = prevent_extensions(); + auto& global_object = this->global_object(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: level is either sealed or frozen. + VERIFY(level == IntegrityLevel::Sealed || level == IntegrityLevel::Frozen); + + // 3. Let status be ? O.[[PreventExtensions]](). + auto status = internal_prevent_extensions(); if (vm.exception()) - return false; + return {}; + + // 4. If status is false, return false. if (!status) return false; - auto keys = get_own_properties(PropertyKind::Key); + + // 5. Let keys be ? O.[[OwnPropertyKeys]](). + auto keys = internal_own_property_keys(); if (vm.exception()) - return false; - switch (level) { - case IntegrityLevel::Sealed: + return {}; + + // 6. If level is sealed, then + if (level == IntegrityLevel::Sealed) { + // a. For each element k of keys, do for (auto& key : keys) { - auto property_name = PropertyName::from_value(global_object(), key); - update_property(property_name, ~Attribute::Configurable); + auto property_name = PropertyName::from_value(global_object, key); + + // i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }). + define_property_or_throw(property_name, { .configurable = false }); if (vm.exception()) return {}; } - break; - case IntegrityLevel::Frozen: + } + // 7. Else, + else { + // a. Assert: level is frozen. + + // b. For each element k of keys, do for (auto& key : keys) { - auto property_name = PropertyName::from_value(global_object(), key); - auto property_descriptor = get_own_property_descriptor(property_name); - if (!property_descriptor.has_value()) + auto property_name = PropertyName::from_value(global_object, key); + + // i. Let currentDesc be ? O.[[GetOwnProperty]](k). + auto current_descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // ii. If currentDesc is not undefined, then + if (!current_descriptor.has_value()) continue; - u8 attributes = property_descriptor->is_accessor_descriptor() - ? ~Attribute::Configurable - : ~Attribute::Configurable & ~Attribute::Writable; - update_property(property_name, attributes); + + PropertyDescriptor descriptor; + + // 1. If IsAccessorDescriptor(currentDesc) is true, then + if (current_descriptor->is_accessor_descriptor()) { + // a. Let desc be the PropertyDescriptor { [[Configurable]]: false }. + descriptor = { .configurable = false }; + } + // 2. Else, + else { + // a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }. + descriptor = { .writable = false, .configurable = false }; + } + + // 3. Perform ? DefinePropertyOrThrow(O, k, desc). + define_property_or_throw(property_name, descriptor); if (vm.exception()) return {}; } - break; - default: - VERIFY_NOT_REACHED(); } + + // 8. Return true. return true; } // 7.3.16 TestIntegrityLevel ( O, level ), https://tc39.es/ecma262/#sec-testintegritylevel -bool Object::test_integrity_level(IntegrityLevel level) +bool Object::test_integrity_level(IntegrityLevel level) const { auto& vm = this->vm(); + + // 1. Assert: Type(O) is Object. + + // 2. Assert: level is either sealed or frozen. + VERIFY(level == IntegrityLevel::Sealed || level == IntegrityLevel::Frozen); + + // 3. Let extensible be ? IsExtensible(O). auto extensible = is_extensible(); if (vm.exception()) - return false; + return {}; + + // 4. If extensible is true, return false. + // 5. NOTE: If the object is extensible, none of its properties are examined. if (extensible) return false; - auto keys = get_own_properties(PropertyKind::Key); + + // 6. Let keys be ? O.[[OwnPropertyKeys]](). + auto keys = internal_own_property_keys(); if (vm.exception()) - return false; + return {}; + + // 7. For each element k of keys, do for (auto& key : keys) { auto property_name = PropertyName::from_value(global_object(), key); - auto property_descriptor = get_own_property_descriptor(property_name); - if (!property_descriptor.has_value()) + + // a. Let currentDesc be ? O.[[GetOwnProperty]](k). + auto current_descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // b. If currentDesc is not undefined, then + if (!current_descriptor.has_value()) continue; - if (property_descriptor->attributes.is_configurable()) + // i. If currentDesc.[[Configurable]] is true, return false. + if (*current_descriptor->configurable) return false; - if (level == IntegrityLevel::Frozen && property_descriptor->is_data_descriptor()) { - if (property_descriptor->attributes.is_writable()) + + // ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then + if (level == IntegrityLevel::Frozen && current_descriptor->is_data_descriptor()) { + // 1. If currentDesc.[[Writable]] is true, return false. + if (*current_descriptor->writable) return false; } } + + // 8. Return true. return true; } -Value Object::get_own_property(const PropertyName& property_name, Value receiver, AllowSideEffects allow_side_effects) const +// 7.3.23 EnumerableOwnPropertyNames ( O, kind ), https://tc39.es/ecma262/#sec-enumerableownpropertynames +MarkedValueList Object::enumerable_own_property_names(PropertyKind kind) const { - VERIFY(property_name.is_valid()); + // NOTE: This has been flattened for readability, so some `else` branches in the + // spec text have been replaced with `continue`s in the loop below. - Value value_here; + auto& vm = this->vm(); + auto& global_object = this->global_object(); - if (property_name.is_number()) { - auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), AllowSideEffects::No); - if (!existing_property.has_value()) - return {}; - value_here = existing_property.value().value.value_or(js_undefined()); - } else { - auto metadata = shape().lookup(property_name.to_string_or_symbol()); - if (!metadata.has_value()) - return {}; - value_here = m_storage[metadata.value().offset].value_or(js_undefined()); - } + // 1. Assert: Type(O) is Object. - VERIFY(!value_here.is_empty()); - if (allow_side_effects == AllowSideEffects::Yes) { - VERIFY(!receiver.is_empty()); - if (value_here.is_accessor()) - return value_here.as_accessor().call_getter(receiver); - if (value_here.is_native_property()) - return call_native_property_getter(value_here.as_native_property(), receiver); - } - return value_here; -} + // 2. Let ownKeys be ? O.[[OwnPropertyKeys]](). + auto own_keys = internal_own_property_keys(); + if (vm.exception()) + return MarkedValueList { heap() }; -MarkedValueList Object::get_own_properties(PropertyKind kind, bool only_enumerable_properties, GetOwnPropertyReturnType return_type) const -{ - MarkedValueList properties(heap()); + // 3. Let properties be a new empty List. + auto properties = MarkedValueList { heap() }; - // FIXME: Support generic iterables - if (is<StringObject>(*this)) { - auto str = static_cast<const StringObject&>(*this).primitive_string().string(); + // 4. For each element key of ownKeys, do + for (auto& key : own_keys) { + // a. If Type(key) is String, then + if (!key.is_string()) + continue; + auto property_name = PropertyName::from_value(global_object, key); - for (size_t i = 0; i < str.length(); ++i) { + // i. Let desc be ? O.[[GetOwnProperty]](key). + auto descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return MarkedValueList { heap() }; + + // ii. If desc is not undefined and desc.[[Enumerable]] is true, then + if (descriptor.has_value() && *descriptor->enumerable) { + // 1. If kind is key, append key to properties. if (kind == PropertyKind::Key) { - properties.append(js_string(vm(), String::number(i))); - } else if (kind == PropertyKind::Value) { - properties.append(js_string(vm(), String::formatted("{:c}", str[i]))); - } else { - auto* entry_array = Array::create(global_object(), 0); - entry_array->define_property(0, js_string(vm(), String::number(i))); - entry_array->define_property(1, js_string(vm(), String::formatted("{:c}", str[i]))); - properties.append(entry_array); + properties.append(key); + continue; } - if (vm().exception()) + // 2. Else, + + // a. Let value be ? Get(O, key). + auto value = get(property_name); + if (vm.exception()) return MarkedValueList { heap() }; - } - } - if (return_type != GetOwnPropertyReturnType::SymbolOnly) { - for (auto& entry : m_indexed_properties) { - auto value_and_attributes = entry.value_and_attributes(const_cast<Object*>(this)); - if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable()) + // b. If kind is value, append value to properties. + if (kind == PropertyKind::Value) { + properties.append(value); continue; - - if (kind == PropertyKind::Key) { - properties.append(js_string(vm(), String::number(entry.index()))); - } else if (kind == PropertyKind::Value) { - properties.append(value_and_attributes.value); - } else { - auto* entry_array = Array::create(global_object(), 0); - entry_array->define_property(0, js_string(vm(), String::number(entry.index()))); - entry_array->define_property(1, value_and_attributes.value); - properties.append(entry_array); } - if (vm().exception()) - return MarkedValueList { heap() }; - } - } + // c. Else, - auto add_property_to_results = [&](auto& property) { - if (kind == PropertyKind::Key) { - properties.append(property.key.to_value(vm())); - } else if (kind == PropertyKind::Value) { - Value v = get(property.key); - // Value may just have been deleted - if (!v.is_empty()) - properties.append(v); - } else { - Value val = get(property.key); - if (val.is_empty()) - return; - - auto* entry_array = Array::create(global_object(), 0); - entry_array->define_property(0, property.key.to_value(vm())); - entry_array->define_property(1, val); - properties.append(entry_array); - } - }; + // i. Assert: kind is key+value. + VERIFY(kind == PropertyKind::KeyAndValue); - // NOTE: Most things including for..in/of and Object.{keys,values,entries}() use StringOnly, and in those - // cases we won't be iterating the ordered property table twice. We can certainly improve this though. - if (return_type == GetOwnPropertyReturnType::All || return_type == GetOwnPropertyReturnType::StringOnly) { - for (auto& it : shape().property_table_ordered()) { - if (only_enumerable_properties && !it.value.attributes.is_enumerable()) - continue; - if (it.key.is_symbol()) - continue; - add_property_to_results(it); - if (vm().exception()) - return MarkedValueList { heap() }; - } - } - if (return_type == GetOwnPropertyReturnType::All || return_type == GetOwnPropertyReturnType::SymbolOnly) { - for (auto& it : shape().property_table_ordered()) { - if (only_enumerable_properties && !it.value.attributes.is_enumerable()) - continue; - if (it.key.is_string()) - continue; - add_property_to_results(it); - if (vm().exception()) - return MarkedValueList { heap() }; + // ii. Let entry be ! CreateArrayFromList(« key, value »). + auto entry = Array::create_from(global_object, { key, value }); + + // iii. Append entry to properties. + properties.append(entry); } } + // 5. Return properties. return properties; } -// 7.3.23 EnumerableOwnPropertyNames ( O, kind ), https://tc39.es/ecma262/#sec-enumerableownpropertynames -MarkedValueList Object::get_enumerable_own_property_names(PropertyKind kind) const +// 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots + +// 10.1.1 [[GetPrototypeOf]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof +Object* Object::internal_get_prototype_of() const { - return get_own_properties(kind, true, Object::GetOwnPropertyReturnType::StringOnly); + // 1. Return O.[[Prototype]]. + return const_cast<Object*>(prototype()); } -Optional<PropertyDescriptor> Object::get_own_property_descriptor(const PropertyName& property_name) const +// 10.1.2 [[SetPrototypeOf]] ( V ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v +bool Object::internal_set_prototype_of(Object* new_prototype) { - VERIFY(property_name.is_valid()); + // 1. Assert: Either Type(V) is Object or Type(V) is Null. - Value value; - PropertyAttributes attributes; - - if (property_name.is_number()) { - auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), AllowSideEffects::No); - if (!existing_value.has_value()) - return {}; - value = existing_value.value().value; - attributes = existing_value.value().attributes; - } else { - auto metadata = shape().lookup(property_name.to_string_or_symbol()); - if (!metadata.has_value()) - return {}; - value = m_storage[metadata.value().offset]; - attributes = metadata.value().attributes; - } + // 2. Let current be O.[[Prototype]]. + // 3. If SameValue(V, current) is true, return true. + if (prototype() == new_prototype) + return true; - PropertyDescriptor descriptor { attributes, {}, nullptr, nullptr }; - if (value.is_native_property()) { - auto result = call_native_property_getter(value.as_native_property(), const_cast<Object*>(this)); - descriptor.value = result.value_or(js_undefined()); - } else if (value.is_accessor()) { - auto& pair = value.as_accessor(); - if (pair.getter()) - descriptor.getter = pair.getter(); - if (pair.setter()) - descriptor.setter = pair.setter(); - } else { - descriptor.value = value.value_or(js_undefined()); - } + // 4. Let extensible be O.[[Extensible]]. + // 5. If extensible is false, return false. + if (!m_is_extensible) + return false; - return descriptor; -} + // 6. Let p be V. + auto* prototype = new_prototype; -// Equivalent to: -// 6.2.5.4 FromPropertyDescriptor ( Desc ), https://tc39.es/ecma262/#sec-frompropertydescriptor -Value Object::get_own_property_descriptor_object(const PropertyName& property_name) const -{ - VERIFY(property_name.is_valid()); + // 7. Let done be false. + // 8. Repeat, while done is false, + while (prototype) { + // a. If p is null, set done to true. - auto& vm = this->vm(); - auto& global_object = this->global_object(); + // b. Else if SameValue(p, O) is true, return false. + if (prototype == this) + return false; + // c. Else, - auto descriptor_opt = get_own_property_descriptor(property_name); - if (!descriptor_opt.has_value()) - return js_undefined(); - auto descriptor = descriptor_opt.value(); + // i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1, set done to true. + // NOTE: This is a best-effort implementation; we don't have a good way of detecting whether certain virtual + // Object methods have been overridden by a given object, but as ProxyObject is the only one doing that for + // [[SetPrototypeOf]], this check does the trick. + if (is<ProxyObject>(prototype)) + break; - auto* descriptor_object = Object::create(global_object, global_object.object_prototype()); - if (descriptor.is_data_descriptor()) { - descriptor_object->define_property(vm.names.value, descriptor.value.value_or(js_undefined())); - descriptor_object->define_property(vm.names.writable, Value(descriptor.attributes.is_writable())); + // ii. Else, set p to p.[[Prototype]]. + prototype = prototype->prototype(); } - if (descriptor.is_accessor_descriptor()) { - descriptor_object->define_property(vm.names.get, descriptor.getter ? Value(descriptor.getter) : js_undefined()); - descriptor_object->define_property(vm.names.set, descriptor.setter ? Value(descriptor.setter) : js_undefined()); - } + // 9. Set O.[[Prototype]] to V. + auto& shape = this->shape(); + if (shape.is_unique()) + shape.set_prototype_without_transition(new_prototype); + else + m_shape = shape.create_prototype_transition(new_prototype); - descriptor_object->define_property(vm.names.enumerable, Value(descriptor.attributes.is_enumerable())); - descriptor_object->define_property(vm.names.configurable, Value(descriptor.attributes.is_configurable())); - return descriptor_object; + // 10. Return true. + return true; } -void Object::set_shape(Shape& new_shape) +// 10.1.3 [[IsExtensible]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible +bool Object::internal_is_extensible() const { - m_storage.resize(new_shape.property_count()); - m_shape = &new_shape; + // 1. Return O.[[Extensible]]. + return m_is_extensible; } -bool Object::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) +// 10.1.4 [[PreventExtensions]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions +bool Object::internal_prevent_extensions() { - auto& vm = this->vm(); - bool is_accessor_property = descriptor.has_property(vm.names.get) || descriptor.has_property(vm.names.set); - PropertyAttributes attributes; - if (descriptor.has_property(vm.names.configurable)) { - attributes.set_has_configurable(); - if (descriptor.get(vm.names.configurable).value_or(Value(false)).to_boolean()) - attributes.set_configurable(); - if (vm.exception()) - return false; - } - if (descriptor.has_property(vm.names.enumerable)) { - attributes.set_has_enumerable(); - if (descriptor.get(vm.names.enumerable).value_or(Value(false)).to_boolean()) - attributes.set_enumerable(); - if (vm.exception()) - return false; - } + // 1. Set O.[[Extensible]] to false. + m_is_extensible = false; - if (is_accessor_property) { - if (descriptor.has_property(vm.names.value) || descriptor.has_property(vm.names.writable)) { - if (throw_exceptions) - vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorValueOrWritable); - return false; - } + // 2. Return true. + return true; +} - auto getter = descriptor.get(vm.names.get).value_or(js_undefined()); - if (vm.exception()) - return {}; - auto setter = descriptor.get(vm.names.set).value_or(js_undefined()); - if (vm.exception()) - return {}; +// 10.1.5 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p +Optional<PropertyDescriptor> Object::internal_get_own_property(PropertyName const& property_name) const +{ + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); - FunctionObject* getter_function { nullptr }; - FunctionObject* setter_function { nullptr }; + // 2. If O does not have an own property with key P, return undefined. + if (!storage_has(property_name)) + return {}; - // We should only take previous getters for our own object not from any prototype - auto existing_property = get_own_property(property_name, {}, AllowSideEffects::No).value_or(js_undefined()); + // 3. Let D be a newly created Property Descriptor with no fields. + PropertyDescriptor descriptor; - if (getter.is_function()) { - getter_function = &getter.as_function(); - } else if (!getter.is_undefined()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "get"); - return false; - } else if (existing_property.is_accessor()) { - // FIXME: This is a hack, since we store Accessor as a getter & setter tuple value, instead of as separate entries in the property - getter_function = existing_property.as_accessor().getter(); - } + // 4. Let X be O's own property whose key is P. + auto [value, attributes] = *storage_get(property_name); - if (setter.is_function()) { - setter_function = &setter.as_function(); - } else if (!setter.is_undefined()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "set"); - return false; - } else if (existing_property.is_accessor()) { - // FIXME: See above - setter_function = existing_property.as_accessor().setter(); - } + // 5. If X is a data property, then + if (!value.is_accessor()) { + // a. Set D.[[Value]] to the value of X's [[Value]] attribute. + descriptor.value = value.value_or(js_undefined()); + + // b. Set D.[[Writable]] to the value of X's [[Writable]] attribute. + descriptor.writable = attributes.is_writable(); + } + // 6. Else, + else { + // a. Assert: X is an accessor property. - dbgln_if(OBJECT_DEBUG, "Defining new property {} with accessor descriptor {{ attributes={}, getter={}, setter={} }}", property_name.to_display_string(), attributes, getter, setter); + // b. Set D.[[Get]] to the value of X's [[Get]] attribute. + descriptor.get = value.as_accessor().getter(); - return define_property(property_name, Accessor::create(vm, getter_function, setter_function), attributes, throw_exceptions); + // c. Set D.[[Set]] to the value of X's [[Set]] attribute. + descriptor.set = value.as_accessor().setter(); } - auto value = descriptor.get(vm.names.value); + // 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. + descriptor.enumerable = attributes.is_enumerable(); + + // 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute. + descriptor.configurable = attributes.is_configurable(); + + // 9. Return D. + return descriptor; +} + +// 10.1.6 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc +bool Object::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor) +{ + VERIFY(property_name.is_valid()); + auto& vm = this->vm(); + + // 1. Let current be ? O.[[GetOwnProperty]](P). + auto current = internal_get_own_property(property_name); if (vm.exception()) return {}; - if (descriptor.has_property(vm.names.writable)) { - attributes.set_has_writable(); - if (descriptor.get(vm.names.writable).value_or(Value(false)).to_boolean()) - attributes.set_writable(); - if (vm.exception()) - return false; - } + + // 2. Let extensible be ? IsExtensible(O). + auto extensible = is_extensible(); if (vm.exception()) return {}; - dbgln_if(OBJECT_DEBUG, "Defining new property {} with data descriptor {{ attributes={}, value={} }}", property_name.to_display_string(), attributes, value); - - return define_property(property_name, value, attributes, throw_exceptions); + // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current). + return validate_and_apply_property_descriptor(this, property_name, extensible, property_descriptor, current); } -bool Object::define_property_without_transition(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) +// 10.1.7 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p +bool Object::internal_has_property(PropertyName const& property_name) const { - TemporaryChange change(m_transitions_enabled, false); - return define_property(property_name, value, attributes, throw_exceptions); -} + auto& vm = this->vm(); -bool Object::define_property(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) -{ + // 1. Assert: IsPropertyKey(P) is true. VERIFY(property_name.is_valid()); - if (property_name.is_number()) - return put_own_property_by_index(property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); + // 2. Let hasOwn be ? O.[[GetOwnProperty]](P). + auto has_own = internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 3. If hasOwn is not undefined, return true. + if (has_own.has_value()) + return true; + + // 4. Let parent be ? O.[[GetPrototypeOf]](). + auto parent = internal_get_prototype_of(); + if (vm.exception()) + return {}; + + // 5. If parent is not null, then + if (parent) { + // a. Return ? parent.[[HasProperty]](P). + return parent->internal_has_property(property_name); + } - return put_own_property(property_name.to_string_or_symbol(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); + // 6. Return false. + return false; } -bool Object::define_native_accessor(PropertyName const& property_name, Function<Value(VM&, GlobalObject&)> getter, Function<Value(VM&, GlobalObject&)> setter, PropertyAttributes attribute) +// 10.1.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver +Value Object::internal_get(PropertyName const& property_name, Value receiver) const { + VERIFY(!receiver.is_empty()); auto& vm = this->vm(); - String formatted_property_name; - if (property_name.is_string()) { - formatted_property_name = property_name.as_string(); - } else { - formatted_property_name = String::formatted("[{}]", property_name.as_symbol()->description()); - } - FunctionObject* getter_function = nullptr; - if (getter) { - auto name = String::formatted("get {}", formatted_property_name); - getter_function = NativeFunction::create(global_object(), name, move(getter)); - getter_function->define_property_without_transition(vm.names.length, Value(0), Attribute::Configurable); - if (vm.exception()) - return {}; - getter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable); - if (vm.exception()) - return {}; - } - FunctionObject* setter_function = nullptr; - if (setter) { - auto name = String::formatted("set {}", formatted_property_name); - setter_function = NativeFunction::create(global_object(), name, move(setter)); - setter_function->define_property_without_transition(vm.names.length, Value(1), Attribute::Configurable); - if (vm.exception()) - return {}; - setter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let desc be ? O.[[GetOwnProperty]](P). + auto descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 3. If desc is undefined, then + if (!descriptor.has_value()) { + // a. Let parent be ? O.[[GetPrototypeOf]](). + auto parent = internal_get_prototype_of(); if (vm.exception()) return {}; + + // b. If parent is null, return undefined. + if (!parent) + return js_undefined(); + + // c. Return ? parent.[[Get]](P, Receiver). + return parent->internal_get(property_name, receiver); } - return define_accessor(property_name, getter_function, setter_function, attribute); + + // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]]. + if (descriptor->is_data_descriptor()) + return *descriptor->value; + + // 5. Assert: IsAccessorDescriptor(desc) is true. + VERIFY(descriptor->is_accessor_descriptor()); + + // 6. Let getter be desc.[[Get]]. + auto* getter = *descriptor->get; + + // 7. If getter is undefined, return undefined. + if (!getter) + return js_undefined(); + + // 8. Return ? Call(getter, Receiver). + return vm.call(*getter, receiver); } -bool Object::define_accessor(const PropertyName& property_name, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes, bool throw_exceptions) +static bool ordinary_set_with_own_descriptor(Object&, PropertyName const&, Value, Value, Optional<PropertyDescriptor>); + +// 10.1.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver +bool Object::internal_set(PropertyName const& property_name, Value value, Value receiver) { + VERIFY(!value.is_empty()); + VERIFY(!receiver.is_empty()); + auto& vm = this->vm(); + + // 1. Assert: IsPropertyKey(P) is true. VERIFY(property_name.is_valid()); - auto existing_property = get_own_property(property_name, this, AllowSideEffects::No); - auto* accessor = existing_property.is_accessor() ? &existing_property.as_accessor() : nullptr; - if (!accessor) { - accessor = Accessor::create(vm(), getter, setter); - bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions); - if (vm().exception()) - return {}; - if (!definition_success) - return false; - } else { - if (getter) - accessor->set_getter(getter); - if (setter) - accessor->set_setter(setter); - } - return true; + // 2. Let ownDesc be ? O.[[GetOwnProperty]](P). + auto own_descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc). + return ordinary_set_with_own_descriptor(*this, property_name, value, receiver, own_descriptor); } -bool Object::put_own_property(const StringOrSymbol& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions) +// 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ), https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor +bool ordinary_set_with_own_descriptor(Object& object, PropertyName const& property_name, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor) { - VERIFY(!(mode == PutOwnPropertyMode::Put && value.is_accessor())); - - if (value.is_accessor()) { - auto& accessor = value.as_accessor(); - if (accessor.getter()) - attributes.set_has_getter(); - if (accessor.setter()) - attributes.set_has_setter(); - } + auto& vm = object.vm(); - // NOTE: We disable transitions during initialize(), this makes building common runtime objects significantly faster. - // Transitions are primarily interesting when scripts add properties to objects. - if (!m_transitions_enabled && !m_shape->is_unique()) { - m_shape->add_property_without_transition(property_name, attributes); - m_storage.resize(m_shape->property_count()); - m_storage[m_shape->property_count() - 1] = value; - return true; - } - - auto metadata = shape().lookup(property_name); - bool new_property = !metadata.has_value(); + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); - if (!is_extensible() && new_property) { - dbgln_if(OBJECT_DEBUG, "Disallow define_property of non-extensible object"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_name.to_display_string()); - return false; - } + // 2. If ownDesc is undefined, then + if (!own_descriptor.has_value()) { + // a. Let parent be ? O.[[GetPrototypeOf]](). + auto parent = object.internal_get_prototype_of(); + if (vm.exception()) + return {}; - if (new_property) { - if (!m_shape->is_unique() && shape().property_count() > 100) { - // If you add more than 100 properties to an object, let's stop doing - // transitions to avoid filling up the heap with shapes. - ensure_shape_is_unique(); + // b. If parent is not null, then + if (parent) { + // i. Return ? parent.[[Set]](P, V, Receiver). + return parent->internal_set(property_name, value, receiver); } - - if (m_shape->is_unique()) { - m_shape->add_property_to_unique_shape(property_name, attributes); - m_storage.resize(m_shape->property_count()); - } else if (m_transitions_enabled) { - set_shape(*m_shape->create_put_transition(property_name, attributes)); - } else { - m_shape->add_property_without_transition(property_name, attributes); - m_storage.resize(m_shape->property_count()); + // c. Else, + else { + // i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + own_descriptor = PropertyDescriptor { + .value = js_undefined(), + .writable = true, + .enumerable = true, + .configurable = true, + }; } - metadata = shape().lookup(property_name); - VERIFY(metadata.has_value()); } - auto value_here = m_storage[metadata.value().offset]; - if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !metadata.value().attributes.is_configurable()) { - if ((attributes.has_configurable() && attributes.is_configurable()) || (attributes.has_enumerable() && attributes.is_enumerable() != metadata.value().attributes.is_enumerable())) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string()); + // 3. If IsDataDescriptor(ownDesc) is true, then + if (own_descriptor->is_data_descriptor()) { + // a. If ownDesc.[[Writable]] is false, return false. + if (!*own_descriptor->writable) return false; - } - if (value_here.is_accessor() != value.is_accessor()) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string()); + // b. If Type(Receiver) is not Object, return false. + if (!receiver.is_object()) return false; - } - if (!value_here.is_accessor() && !metadata.value().attributes.is_writable() && ((attributes.has_writable() && attributes.is_writable()) || (!value.is_empty() && !same_value(value, value_here)))) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string()); - return false; - } + // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). + auto existing_descriptor = receiver.as_object().internal_get_own_property(property_name); + if (vm.exception()) + return {}; - if (value_here.is_accessor() && ((attributes.has_setter() && value.as_accessor().setter() != value_here.as_accessor().setter()) || (attributes.has_getter() && value.as_accessor().getter() != value_here.as_accessor().getter()))) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string()); - return false; - } - } + // d. If existingDescriptor is not undefined, then + if (existing_descriptor.has_value()) { + // i. If IsAccessorDescriptor(existingDescriptor) is true, return false. + if (existing_descriptor->is_accessor_descriptor()) + return false; - // FIXME: Instead of adding back existing attributes we should just stop overwriting them - if (!attributes.has_configurable() && metadata.value().attributes.is_configurable()) { - attributes.set_has_configurable(); - attributes.set_configurable(); - } - if (!attributes.has_enumerable() && metadata.value().attributes.is_enumerable()) { - attributes.set_has_enumerable(); - attributes.set_enumerable(); - } - if (!value.is_accessor() && !attributes.has_writable() && metadata.value().attributes.is_writable()) { - attributes.set_has_writable(); - attributes.set_writable(); - } + // ii. If existingDescriptor.[[Writable]] is false, return false. + if (!*existing_descriptor->writable) + return false; - if (mode == PutOwnPropertyMode::DefineProperty && attributes != metadata.value().attributes) { - if (m_shape->is_unique()) { - m_shape->reconfigure_property_in_unique_shape(property_name, attributes); - } else { - set_shape(*m_shape->create_configure_transition(property_name, attributes)); + // iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }. + auto value_descriptor = PropertyDescriptor { .value = value }; + + // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). + return receiver.as_object().internal_define_own_property(property_name, value_descriptor); } - metadata = shape().lookup(property_name); + // e. Else, + else { + // i. Assert: Receiver does not currently have a property P. + VERIFY(!receiver.as_object().storage_has(property_name)); - dbgln_if(OBJECT_DEBUG, "Reconfigured property {}, new shape says offset is {} and my storage capacity is {}", property_name.to_display_string(), metadata.value().offset, m_storage.size()); + // ii. Return ? CreateDataProperty(Receiver, P, V). + return receiver.as_object().create_data_property(property_name, value); + } } - if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !metadata.value().attributes.is_writable()) { - dbgln_if(OBJECT_DEBUG, "Disallow write to non-writable property"); - if (throw_exceptions && vm().in_strict_mode()) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescWriteNonWritable, property_name.to_display_string()); + // 4. Assert: IsAccessorDescriptor(ownDesc) is true. + VERIFY(own_descriptor->is_accessor_descriptor()); + + // 5. Let setter be ownDesc.[[Set]]. + auto* setter = *own_descriptor->set; + + // 6. If setter is undefined, return false. + if (!setter) return false; - } - if (value.is_empty()) - return true; + // 7. Perform ? Call(setter, Receiver, « V »). + (void)vm.call(*setter, receiver, value); - if (value_here.is_native_property()) { - call_native_property_setter(value_here.as_native_property(), this, value); - } else { - m_storage[metadata.value().offset] = value; - } + // 8. Return true. return true; } -bool Object::put_own_property_by_index(u32 property_index, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions) +// 10.1.10 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p +bool Object::internal_delete(PropertyName const& property_name) { - VERIFY(!(mode == PutOwnPropertyMode::Put && value.is_accessor())); - - auto existing_property = m_indexed_properties.get(nullptr, property_index, AllowSideEffects::No); - auto new_property = !existing_property.has_value(); + auto& vm = this->vm(); - if (!is_extensible() && new_property) { - dbgln_if(OBJECT_DEBUG, "Disallow define_property of non-extensible object"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_index); - return false; - } + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); - if (value.is_accessor()) { - auto& accessor = value.as_accessor(); - if (accessor.getter()) - attributes.set_has_getter(); - if (accessor.setter()) - attributes.set_has_setter(); - } + // 2. Let desc be ? O.[[GetOwnProperty]](P). + auto descriptor = internal_get_own_property(property_name); + if (vm.exception()) + return {}; - if (new_property) { - if (!is_extensible()) { - dbgln_if(OBJECT_DEBUG, "Disallow define_property of non-extensible object"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_index); - return false; - } + // 3. If desc is undefined, return true. + if (!descriptor.has_value()) + return true; - m_indexed_properties.put(this, property_index, value, attributes, mode == PutOwnPropertyMode::Put ? AllowSideEffects::Yes : AllowSideEffects::No); + // 4. If desc.[[Configurable]] is true, then + if (*descriptor->configurable) { + // a. Remove the own property with name P from O. + storage_delete(property_name); + // b. Return true. return true; } - if (attributes == 0 && value.is_empty()) - return true; - - PropertyAttributes existing_attributes = existing_property.value().attributes; - auto value_here = existing_property.value().value; + // 5. Return false. + return false; +} - if (mode == PutOwnPropertyMode::DefineProperty && !existing_attributes.is_configurable()) { - if ((attributes.has_configurable() && attributes.is_configurable()) || (attributes.has_enumerable() && attributes.is_enumerable() != existing_attributes.is_enumerable())) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index); - return false; - } +// 10.1.11 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys +MarkedValueList Object::internal_own_property_keys() const +{ + auto& vm = this->vm(); - if (value_here.is_accessor() != value.is_accessor()) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index); - return false; - } + // 1. Let keys be a new empty List. + MarkedValueList keys { heap() }; - if (!value_here.is_accessor() && !existing_attributes.is_writable() && ((attributes.has_writable() && attributes.is_writable()) || (!value.is_empty() && !same_value(value, value_here)))) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index); - return false; - } + // 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do + for (auto& entry : m_indexed_properties) { + // a. Add P as the last element of keys. + keys.append(js_string(vm, String::number(entry.index()))); + } - if (value_here.is_accessor() && ((attributes.has_setter() && value.as_accessor().setter() != value_here.as_accessor().setter()) || (attributes.has_getter() && value.as_accessor().getter() != value_here.as_accessor().getter()))) { - dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index); - return false; + // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_string()) { + // a. Add P as the last element of keys. + keys.append(it.key.to_value(vm)); } } - if (mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !existing_attributes.is_writable()) { - dbgln_if(OBJECT_DEBUG, "Disallow write to non-writable property"); - if (throw_exceptions) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescWriteNonWritable, property_index); - return false; + // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_symbol()) { + // a. Add P as the last element of keys. + keys.append(it.key.to_value(vm)); + } } - PropertyAttributes combined_attributes = existing_attributes.overwrite(attributes); + // 5. Return keys. + return keys; +} - if (value.is_empty()) { - if (combined_attributes == existing_attributes) { - return true; - } - value = value_here.value_or(js_undefined()); - } +Optional<ValueAndAttributes> Object::storage_get(PropertyName const& property_name, CallNativeProperty call_native_property) const +{ + VERIFY(property_name.is_valid()); - attributes = combined_attributes; + Value value; + PropertyAttributes attributes; - if (value_here.is_native_property()) { - call_native_property_setter(value_here.as_native_property(), this, value); + if (property_name.is_number()) { + auto value_and_attributes = m_indexed_properties.get(property_name.as_number()); + if (!value_and_attributes.has_value()) + return {}; + value = value_and_attributes->value; + attributes = value_and_attributes->attributes; } else { - m_indexed_properties.put(this, property_index, value, attributes, mode == PutOwnPropertyMode::Put ? AllowSideEffects::Yes : AllowSideEffects::No); + auto metadata = shape().lookup(property_name.to_string_or_symbol()); + if (!metadata.has_value()) + return {}; + value = m_storage[metadata->offset]; + attributes = metadata->attributes; } - return true; + if (value.is_native_property() && call_native_property == CallNativeProperty::Yes) + value = call_native_property_getter(value.as_native_property(), this); + return ValueAndAttributes { .value = value, .attributes = attributes }; +} + +bool Object::storage_has(PropertyName const& property_name) const +{ + VERIFY(property_name.is_valid()); + if (property_name.is_number()) + return m_indexed_properties.has_index(property_name.as_number()); + return shape().lookup(property_name.to_string_or_symbol()).has_value(); } -bool Object::delete_property(PropertyName const& property_name, bool force_throw_exception) +void Object::storage_set(PropertyName const& property_name, ValueAndAttributes const& value_and_attributes) { VERIFY(property_name.is_valid()); + auto [value, attributes] = value_and_attributes; + if (property_name.is_number()) { - if (!m_indexed_properties.remove(property_name.as_number())) { - if (force_throw_exception || vm().in_strict_mode()) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.as_number()); - return false; + auto index = property_name.as_number(); + if (value.is_native_property()) { + m_indexed_properties.put(index, value, attributes); + } else { + auto existing_value = m_indexed_properties.get(index); + if (existing_value.has_value() && existing_value->value.is_native_property()) + call_native_property_setter(existing_value->value.as_native_property(), this, value); + else + m_indexed_properties.put(index, value, attributes); } - return true; + return; } - auto metadata = shape().lookup(property_name.to_string_or_symbol()); - if (!metadata.has_value()) - return true; - if (!metadata.value().attributes.is_configurable()) { - if (force_throw_exception || vm().in_strict_mode()) - vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_string_or_symbol().to_display_string()); - return false; + // NOTE: We disable transitions during initialize(), this makes building common runtime objects significantly faster. + // Transitions are primarily interesting when scripts add properties to objects. + if (!m_transitions_enabled && !m_shape->is_unique()) { + m_shape->add_property_without_transition(property_name, attributes); + m_storage.resize(m_shape->property_count()); + m_storage[m_shape->property_count() - 1] = value; + return; } - size_t deleted_offset = metadata.value().offset; - - ensure_shape_is_unique(); + auto property_name_string_or_symbol = property_name.to_string_or_symbol(); + auto metadata = shape().lookup(property_name_string_or_symbol); - shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), deleted_offset); - m_storage.remove(deleted_offset); - return true; -} - -void Object::ensure_shape_is_unique() -{ - if (shape().is_unique()) - return; + if (!metadata.has_value()) { + if (!m_shape->is_unique() && shape().property_count() > 100) { + // If you add more than 100 properties to an object, let's stop doing + // transitions to avoid filling up the heap with shapes. + ensure_shape_is_unique(); + } - m_shape = m_shape->create_unique_clone(); -} + if (m_shape->is_unique()) { + m_shape->add_property_to_unique_shape(property_name_string_or_symbol, attributes); + m_storage.resize(m_shape->property_count()); + } else if (m_transitions_enabled) { + set_shape(*m_shape->create_put_transition(property_name_string_or_symbol, attributes)); + } else { + m_shape->add_property_without_transition(property_name, attributes); + m_storage.resize(m_shape->property_count()); + } + metadata = shape().lookup(property_name_string_or_symbol); + VERIFY(metadata.has_value()); + } -Value Object::get_by_index(u32 property_index, AllowSideEffects allow_side_effects) const -{ - const Object* object = this; - while (object) { - if (is<StringObject>(*object)) { - auto& string = static_cast<const StringObject&>(*object).primitive_string().string(); - if (property_index < string.length()) - return js_string(heap(), string.substring(property_index, 1)); - } else if (static_cast<size_t>(property_index) < object->m_indexed_properties.array_like_size()) { - auto result = object->m_indexed_properties.get(const_cast<Object*>(this), property_index, allow_side_effects); - if (vm().exception()) - return {}; - if (result.has_value() && !result.value().value.is_empty()) - return result.value().value; + if (attributes != metadata->attributes) { + if (m_shape->is_unique()) { + m_shape->reconfigure_property_in_unique_shape(property_name_string_or_symbol, attributes); + } else { + set_shape(*m_shape->create_configure_transition(property_name_string_or_symbol, attributes)); } - object = object->prototype(); - if (vm().exception()) - return {}; + metadata = shape().lookup(property_name_string_or_symbol); + VERIFY(metadata.has_value()); + } + + if (value.is_native_property()) { + m_storage[metadata->offset] = value; + } else { + auto existing_value = m_storage[metadata->offset]; + if (existing_value.is_native_property()) + call_native_property_setter(existing_value.as_native_property(), this, value); + else + m_storage[metadata->offset] = value; } - return {}; } -Value Object::get(const PropertyName& property_name, Value receiver, AllowSideEffects allow_side_effects) const +void Object::storage_delete(PropertyName const& property_name) { VERIFY(property_name.is_valid()); + VERIFY(storage_has(property_name)); if (property_name.is_number()) - return get_by_index(property_name.as_number(), allow_side_effects); + return m_indexed_properties.remove(property_name.as_number()); - if (receiver.is_empty()) - receiver = Value(this); + auto metadata = shape().lookup(property_name.to_string_or_symbol()); + VERIFY(metadata.has_value()); - const Object* object = this; - while (object) { - auto value = object->get_own_property(property_name, receiver, allow_side_effects); - if (vm().exception()) + ensure_shape_is_unique(); + + shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), metadata->offset); + m_storage.remove(metadata->offset); +} + +void Object::set_shape(Shape& new_shape) +{ + m_storage.resize(new_shape.property_count()); + m_shape = &new_shape; +} + +bool Object::define_native_accessor(PropertyName const& property_name, Function<Value(VM&, GlobalObject&)> getter, Function<Value(VM&, GlobalObject&)> setter, PropertyAttributes attribute) +{ + auto& vm = this->vm(); + String formatted_property_name; + if (property_name.is_string()) { + formatted_property_name = property_name.as_string(); + } else { + formatted_property_name = String::formatted("[{}]", property_name.as_symbol()->description()); + } + FunctionObject* getter_function = nullptr; + if (getter) { + auto name = String::formatted("get {}", formatted_property_name); + getter_function = NativeFunction::create(global_object(), name, move(getter)); + getter_function->define_property_without_transition(vm.names.length, Value(0), Attribute::Configurable); + if (vm.exception()) return {}; - if (!value.is_empty()) - return value; - object = object->prototype(); - if (vm().exception()) + getter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable); + if (vm.exception()) return {}; } - return {}; + FunctionObject* setter_function = nullptr; + if (setter) { + auto name = String::formatted("set {}", formatted_property_name); + setter_function = NativeFunction::create(global_object(), name, move(setter)); + setter_function->define_property_without_transition(vm.names.length, Value(1), Attribute::Configurable); + if (vm.exception()) + return {}; + setter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable); + if (vm.exception()) + return {}; + } + return define_accessor(property_name, getter_function, setter_function, attribute); } -Value Object::get_without_side_effects(const PropertyName& property_name) const +bool Object::define_property_without_transition(PropertyName const& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) { - TemporaryClearException clear_exception(vm()); - return get(property_name, {}, AllowSideEffects::No); + TemporaryChange change(m_transitions_enabled, false); + return define_property(property_name, value, attributes, throw_exceptions); } -bool Object::put_by_index(u32 property_index, Value value) +bool Object::define_accessor(PropertyName const& property_name, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes, bool throw_exceptions) { - VERIFY(!value.is_empty()); + VERIFY(property_name.is_valid()); - // If there's a setter in the prototype chain, we go to the setter. - // Otherwise, it goes in the own property storage. - Object* object = this; - while (object) { - auto existing_value = object->m_indexed_properties.get(nullptr, property_index, AllowSideEffects::No); - if (existing_value.has_value()) { - auto value_here = existing_value.value(); - if (value_here.value.is_accessor()) { - value_here.value.as_accessor().call_setter(object, value); - return true; - } - if (value_here.value.is_native_property()) { - // FIXME: Why doesn't put_by_index() receive the receiver value from put()?! - auto receiver = this; - call_native_property_setter(value_here.value.as_native_property(), receiver, value); - return true; - } - } - object = object->prototype(); + auto existing_property = storage_get(property_name).value_or({}).value; + auto* accessor = existing_property.is_accessor() ? &existing_property.as_accessor() : nullptr; + if (!accessor) { + accessor = Accessor::create(vm(), getter, setter); + bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions); if (vm().exception()) return {}; + if (!definition_success) + return false; + } else { + if (getter) + accessor->set_getter(getter); + if (setter) + accessor->set_setter(setter); } - return put_own_property_by_index(property_index, value, default_attributes, PutOwnPropertyMode::Put, vm().in_strict_mode()); + return true; } -bool Object::put(const PropertyName& property_name, Value value, Value receiver) +void Object::ensure_shape_is_unique() { - VERIFY(property_name.is_valid()); - - if (property_name.is_number()) - return put_by_index(property_name.as_number(), value); - - VERIFY(!value.is_empty()); - - auto string_or_symbol = property_name.to_string_or_symbol(); + if (shape().is_unique()) + return; - if (receiver.is_empty()) - receiver = Value(this); + m_shape = m_shape->create_unique_clone(); +} - // If there's a setter in the prototype chain, we go to the setter. - // Otherwise, it goes in the own property storage. - Object* object = this; +// Simple side-effect free property lookup, following the prototype chain. Non-standard. +Value Object::get_without_side_effects(const PropertyName& property_name) const +{ + auto* object = this; while (object) { - auto metadata = object->shape().lookup(string_or_symbol); - if (metadata.has_value()) { - auto value_here = object->m_storage[metadata.value().offset]; - if (value_here.is_accessor()) { - value_here.as_accessor().call_setter(receiver, value); - return true; - } - if (value_here.is_native_property()) { - call_native_property_setter(value_here.as_native_property(), receiver, value); - return true; - } - } + auto value_and_attributes = object->storage_get(property_name, CallNativeProperty::No); + if (value_and_attributes.has_value()) + return value_and_attributes->value; object = object->prototype(); - if (vm().exception()) - return false; } - return put_own_property(string_or_symbol, value, default_attributes, PutOwnPropertyMode::Put, vm().in_strict_mode()); + return {}; } bool Object::define_native_function(PropertyName const& property_name, Function<Value(VM&, GlobalObject&)> native_function, i32 length, PropertyAttributes attribute) @@ -1015,46 +1107,70 @@ bool Object::define_native_property(PropertyName const& property_name, Function< } // 20.1.2.3.1 ObjectDefineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-objectdefineproperties -void Object::define_properties(Value properties) +Object* Object::define_properties(Value properties) { auto& vm = this->vm(); - auto* props = properties.to_object(global_object()); - if (!props) - return; - auto keys = props->get_own_properties(PropertyKind::Key); + auto& global_object = this->global_object(); + + // 1. Assert: Type(O) is Object. + + // 2. Let props be ? ToObject(Properties). + auto* props = properties.to_object(global_object); if (vm.exception()) - return; + return {}; + + // 3. Let keys be ? props.[[OwnPropertyKeys]](). + auto keys = props->internal_own_property_keys(); + if (vm.exception()) + return {}; + struct NameAndDescriptor { PropertyName name; PropertyDescriptor descriptor; }; + + // 4. Let descriptors be a new empty List. Vector<NameAndDescriptor> descriptors; - for (auto& key : keys) { - auto property_name = PropertyName::from_value(global_object(), key); - auto property_descriptor = props->get_own_property_descriptor(property_name); - if (property_descriptor.has_value() && property_descriptor->attributes.is_enumerable()) { + + // 5. For each element nextKey of keys, do + for (auto& next_key : keys) { + auto property_name = PropertyName::from_value(global_object, next_key); + + // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey). + auto property_descriptor = props->internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then + if (property_descriptor.has_value() && *property_descriptor->enumerable) { + // i. Let descObj be ? Get(props, nextKey). auto descriptor_object = props->get(property_name); if (vm.exception()) - return; - if (!descriptor_object.is_object()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::NotAnObject, descriptor_object.to_string_without_side_effects()); - return; - } - auto descriptor = PropertyDescriptor::from_dictionary(vm, descriptor_object.as_object()); + return {}; + + // ii. Let desc be ? ToPropertyDescriptor(descObj). + auto descriptor = to_property_descriptor(global_object, descriptor_object); if (vm.exception()) - return; + return {}; + + // iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors. descriptors.append({ property_name, descriptor }); } } + + // 6. For each element pair of descriptors, do for (auto& [name, descriptor] : descriptors) { - // FIXME: The spec has both of this handled by DefinePropertyOrThrow(O, P, desc). - // We should invest some time in improving object property handling, it not being - // super close to the spec makes this and other things unnecessarily complicated. - if (descriptor.is_accessor_descriptor()) - define_accessor(name, descriptor.getter, descriptor.setter, descriptor.attributes); - else - define_property(name, descriptor.value, descriptor.attributes); + // a. Let P be the first element of pair. + // b. Let desc be the second element of pair. + + // c. Perform ? DefinePropertyOrThrow(O, P, desc). + define_property_or_throw(name, descriptor); + if (vm.exception()) + return {}; } + + // 7. Return O. + return this; } void Object::visit_edges(Cell::Visitor& visitor) @@ -1070,46 +1186,18 @@ void Object::visit_edges(Cell::Visitor& visitor) }); } -bool Object::has_property(const PropertyName& property_name) const -{ - const Object* object = this; - while (object) { - if (object->has_own_property(property_name)) - return true; - object = object->prototype(); - if (vm().exception()) - return false; - } - return false; -} - -bool Object::has_own_property(const PropertyName& property_name) const -{ - VERIFY(property_name.is_valid()); - - auto has_indexed_property = [&](u32 index) -> bool { - if (is<StringObject>(*this)) - return index < static_cast<const StringObject*>(this)->primitive_string().string().length(); - return m_indexed_properties.has_index(index); - }; - - if (property_name.is_number()) - return has_indexed_property(property_name.as_number()); - - return shape().lookup(property_name.to_string_or_symbol()).has_value(); -} - +// 7.1.1.1 OrdinaryToPrimitive ( O, hint ), https://tc39.es/ecma262/#sec-ordinarytoprimitive Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const { VERIFY(preferred_type == Value::PreferredType::String || preferred_type == Value::PreferredType::Number); auto& vm = this->vm(); - Vector<FlyString, 2> method_names; + AK::Array<PropertyName, 2> method_names; if (preferred_type == Value::PreferredType::String) - method_names = { vm.names.toString.as_string(), vm.names.valueOf.as_string() }; + method_names = { vm.names.toString, vm.names.valueOf }; else - method_names = { vm.names.valueOf.as_string(), vm.names.toString.as_string() }; + method_names = { vm.names.valueOf, vm.names.toString }; for (auto& method_name : method_names) { auto method = get(method_name); diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index c959f92cb0..6659a886f1 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -14,6 +14,7 @@ #include <LibJS/Runtime/IndexedProperties.h> #include <LibJS/Runtime/MarkedValueList.h> #include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/PropertyName.h> #include <LibJS/Runtime/Shape.h> #include <LibJS/Runtime/Value.h> @@ -25,19 +26,6 @@ public: \ using Base = base_class; \ virtual const char* class_name() const override { return #class_; } -struct PropertyDescriptor { - PropertyAttributes attributes; - Value value; - FunctionObject* getter { nullptr }; - FunctionObject* setter { nullptr }; - - static PropertyDescriptor from_dictionary(VM&, const Object&); - - bool is_accessor_descriptor() const { return getter || setter; } - bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); } - bool is_generic_descriptor() const { return !is_accessor_descriptor() && !is_data_descriptor(); } -}; - class Object : public Cell { public: static Object* create(GlobalObject&, Object* prototype); @@ -53,12 +41,6 @@ public: KeyAndValue, }; - enum class GetOwnPropertyReturnType { - All, - StringOnly, - SymbolOnly, - }; - enum class PutOwnPropertyMode { Put, DefineProperty, @@ -69,27 +51,84 @@ public: Frozen, }; - Shape& shape() { return *m_shape; } - const Shape& shape() const { return *m_shape; } + // Please DO NOT make up your own non-standard methods unless you + // have a very good reason to do so. If any object abstract + // operation from the spec is missing, add it instead. + // Functionality for implementation details like shapes and + // property storage are obviously exempt from this rule :^) + // + // Methods named [[Foo]]() in the spec are named internal_foo() + // here, as they are "The [[Foo]] internal method of a ... object". + // They must be virtual and may be overridden. All other methods + // follow the the regular PascalCase name converted to camel_case + // naming convention and must not be virtual. + + // 7.1 Type Conversion, https://tc39.es/ecma262/#sec-type-conversion + + Value ordinary_to_primitive(Value::PreferredType preferred_type) const; + + // 7.2 Testing and Comparison Operations, https://tc39.es/ecma262/#sec-testing-and-comparison-operations + + bool is_extensible() const; + + // 7.3 Operations on Objects, https://tc39.es/ecma262/#sec-operations-on-objects + + Value get(PropertyName const&) const; + bool set(PropertyName const&, Value, bool throw_exceptions); + bool create_data_property(PropertyName const&, Value); + bool create_method_property(PropertyName const&, Value); + bool create_data_property_or_throw(PropertyName const&, Value); + bool define_property_or_throw(PropertyName const&, PropertyDescriptor const&); + bool delete_property_or_throw(PropertyName const&); + bool has_property(PropertyName const&) const; + bool has_own_property(PropertyName const&) const; + bool set_integrity_level(IntegrityLevel); + bool test_integrity_level(IntegrityLevel) const; + MarkedValueList enumerable_own_property_names(PropertyKind kind) const; - GlobalObject& global_object() const { return *shape().global_object(); } + // 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots - virtual Value get(const PropertyName&, Value receiver = {}, AllowSideEffects = AllowSideEffects::Yes) const; - Value get_without_side_effects(const PropertyName&) const; + virtual Object* internal_get_prototype_of() const; + virtual bool internal_set_prototype_of(Object* prototype); + virtual bool internal_is_extensible() const; + virtual bool internal_prevent_extensions(); + virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const&) const; + virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor const&); + virtual bool internal_has_property(PropertyName const&) const; + virtual Value internal_get(PropertyName const&, Value receiver) const; + virtual bool internal_set(PropertyName const&, Value value, Value receiver); + virtual bool internal_delete(PropertyName const&); + virtual MarkedValueList internal_own_property_keys() const; + + // 20.1 Object Objects, https://tc39.es/ecma262/#sec-object-objects + + Object* define_properties(Value properties); + + // Implementation-specific storage abstractions - virtual bool has_property(const PropertyName&) const; - bool has_own_property(const PropertyName&) const; + enum class CallNativeProperty { + Yes, + No, + }; + + Optional<ValueAndAttributes> storage_get(PropertyName const&, CallNativeProperty = CallNativeProperty::Yes) const; + bool storage_has(PropertyName const&) const; + void storage_set(PropertyName const&, ValueAndAttributes const&); + void storage_delete(PropertyName const&); - virtual bool put(const PropertyName&, Value, Value receiver = {}); + // Non-standard methods + + // - Helpers using old, non-standard names but wrapping the standard methods. + // FIXME: Update all the code relying on these and remove them. + bool put(PropertyName const& property_name, Value value, Value receiver = {}) { return internal_set(property_name, value, receiver.value_or(this)); } + Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName const& property_name) const { return internal_get_own_property(property_name); } + bool define_property(PropertyName const& property_name, Value value, PropertyAttributes attributes = default_attributes, bool = true) + { + return internal_define_own_property(property_name, { .value = value, .writable = attributes.is_writable(), .enumerable = attributes.is_enumerable(), .configurable = attributes.is_configurable() }); + }; - Value get_own_property(const PropertyName&, Value receiver, AllowSideEffects = AllowSideEffects::Yes) const; - MarkedValueList get_own_properties(PropertyKind, bool only_enumerable_properties = false, GetOwnPropertyReturnType = GetOwnPropertyReturnType::All) const; - MarkedValueList get_enumerable_own_property_names(PropertyKind) const; - virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const; - Value get_own_property_descriptor_object(const PropertyName&) const; + Value get_without_side_effects(const PropertyName&) const; - virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true); - bool define_property(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); bool define_property_without_transition(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); bool define_accessor(const PropertyName&, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); @@ -97,10 +136,6 @@ public: bool define_native_property(PropertyName const&, Function<Value(VM&, GlobalObject&)> getter, Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes); bool define_native_accessor(PropertyName const&, Function<Value(VM&, GlobalObject&)> getter, Function<Value(VM&, GlobalObject&)> setter, PropertyAttributes attributes = default_attributes); - void define_properties(Value properties); - - virtual bool delete_property(PropertyName const&, bool force_throw_exception = false); - virtual bool is_array() const { return false; } virtual bool is_function() const { return false; } virtual bool is_typed_array() const { return false; } @@ -115,20 +150,7 @@ public: virtual const char* class_name() const override { return "Object"; } virtual void visit_edges(Cell::Visitor&) override; - - virtual Object* prototype(); - virtual const Object* prototype() const; - virtual bool set_prototype(Object* prototype); - bool has_prototype(const Object* prototype) const; - - virtual bool is_extensible() const { return m_is_extensible; } - virtual bool prevent_extensions(); - - bool set_integrity_level(IntegrityLevel); - bool test_integrity_level(IntegrityLevel); - virtual Value value_of() const { return Value(const_cast<Object*>(this)); } - virtual Value ordinary_to_primitive(Value::PreferredType preferred_type) const; Value get_direct(size_t index) const { return m_storage[index]; } @@ -150,6 +172,11 @@ public: return invoke(property_name); } + Shape& shape() { return *m_shape; } + Shape const& shape() const { return *m_shape; } + + GlobalObject& global_object() const { return *shape().global_object(); } + void ensure_shape_is_unique(); void enable_transitions() { m_transitions_enabled = true; } @@ -164,19 +191,17 @@ protected: explicit Object(GlobalObjectTag); Object(ConstructWithoutPrototypeTag, GlobalObject&); - virtual Value get_by_index(u32 property_index, AllowSideEffects = AllowSideEffects::Yes) const; - virtual bool put_by_index(u32 property_index, Value); + bool m_is_extensible { true }; private: - bool put_own_property(const StringOrSymbol& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); - bool put_own_property_by_index(u32 property_index, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); - Value call_native_property_getter(NativeProperty& property, Value this_value) const; void call_native_property_setter(NativeProperty& property, Value this_value, Value) const; void set_shape(Shape&); - bool m_is_extensible { true }; + Object* prototype() { return shape().prototype(); } + Object const* prototype() const { return shape().prototype(); } + bool m_transitions_enabled { true }; Shape* m_shape { nullptr }; Vector<Value> m_storage; diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp index df1bc62b19..70f28e4c19 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -80,55 +80,102 @@ Value ObjectConstructor::construct(FunctionObject& new_target) return value.to_object(global_object); } -// 20.1.2.10 Object.getOwnPropertyNames ( O ), https://tc39.es/ecma262/#sec-object.getownpropertynames -JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names) +enum class GetOwnPropertyKeysType { + String, + Symbol, +}; + +// 20.1.2.11.1 GetOwnPropertyKeys ( O, type ), https://tc39.es/ecma262/#sec-getownpropertykeys +static Array* get_own_property_keys(GlobalObject& global_object, Value value, GetOwnPropertyKeysType type) { - auto* object = vm.argument(0).to_object(global_object); + auto& vm = global_object.vm(); + + // 1. Let obj be ? ToObject(O). + auto* object = value.to_object(global_object); + if (vm.exception()) + return {}; + + // 2. Let keys be ? obj.[[OwnPropertyKeys]](). + auto keys = object->internal_own_property_keys(); if (vm.exception()) return {}; - return Array::create_from(global_object, object->get_own_properties(PropertyKind::Key, false, GetOwnPropertyReturnType::StringOnly)); + + // 3. Let nameList be a new empty List. + auto name_list = MarkedValueList { vm.heap() }; + + // 4. For each element nextKey of keys, do + for (auto& next_key : keys) { + // a. If Type(nextKey) is Symbol and type is symbol or Type(nextKey) is String and type is string, then + if ((next_key.is_symbol() && type == GetOwnPropertyKeysType::Symbol) || (next_key.is_string() && type == GetOwnPropertyKeysType::String)) { + // i. Append nextKey as the last element of nameList. + name_list.append(next_key); + } + } + + // 5. Return CreateArrayFromList(nameList). + return Array::create_from(global_object, name_list); +} + +// 20.1.2.10 Object.getOwnPropertyNames ( O ), https://tc39.es/ecma262/#sec-object.getownpropertynames +JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names) +{ + // 1. Return ? GetOwnPropertyKeys(O, string). + return get_own_property_keys(global_object, vm.argument(0), GetOwnPropertyKeysType::String); } // 20.1.2.11 Object.getOwnPropertySymbols ( O ), https://tc39.es/ecma262/#sec-object.getownpropertysymbols JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_symbols) { - auto* object = vm.argument(0).to_object(global_object); - if (vm.exception()) - return {}; - return Array::create_from(global_object, object->get_own_properties(PropertyKind::Key, false, GetOwnPropertyReturnType::SymbolOnly)); + // 1. Return ? GetOwnPropertyKeys(O, symbol). + return get_own_property_keys(global_object, vm.argument(0), GetOwnPropertyKeysType::Symbol); } // 20.1.2.12 Object.getPrototypeOf ( O ), https://tc39.es/ecma262/#sec-object.getprototypeof JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of) { + // 1. Let obj be ? ToObject(O). auto* object = vm.argument(0).to_object(global_object); if (vm.exception()) return {}; - return object->prototype(); + + // 2. Return ? obj.[[GetPrototypeOf]](). + return object->internal_get_prototype_of(); } // 20.1.2.21 Object.setPrototypeOf ( O, proto ), https://tc39.es/ecma262/#sec-object.setprototypeof JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::set_prototype_of) { - auto argument = require_object_coercible(global_object, vm.argument(0)); + auto proto = vm.argument(1); + + // 1. Set O to ? RequireObjectCoercible(O). + auto object = require_object_coercible(global_object, vm.argument(0)); if (vm.exception()) return {}; - auto prototype_value = vm.argument(1); - if (!prototype_value.is_object() && !prototype_value.is_null()) { + + // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception. + if (!proto.is_object() && !proto.is_null()) { vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType); return {}; } - if (!argument.is_object()) - return argument; - auto* prototype = prototype_value.is_null() ? nullptr : &prototype_value.as_object(); - auto status = argument.as_object().set_prototype(prototype); + + // 3. If Type(O) is not Object, return O. + if (!object.is_object()) + return object; + + // 4. Let status be ? O.[[SetPrototypeOf]](proto). + auto status = object.as_object().internal_set_prototype_of(proto.is_null() ? nullptr : &proto.as_object()); if (vm.exception()) return {}; + + // 5. If status is false, throw a TypeError exception. if (!status) { + // FIXME: Improve/contextualize error message vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse); return {}; } - return argument; + + // 6. Return O. + return object; } // 20.1.2.14 Object.isExtensible ( O ), https://tc39.es/ecma262/#sec-object.isextensible @@ -164,10 +211,11 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::prevent_extensions) auto argument = vm.argument(0); if (!argument.is_object()) return argument; - auto status = argument.as_object().prevent_extensions(); + auto status = argument.as_object().internal_prevent_extensions(); if (vm.exception()) return {}; if (!status) { + // FIXME: Improve/contextualize error message vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse); return {}; } @@ -247,54 +295,48 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_descriptor) auto* object = vm.argument(0).to_object(global_object); if (vm.exception()) return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + auto key = vm.argument(1).to_property_key(global_object); + if (vm.exception()) + return {}; + auto descriptor = object->internal_get_own_property(key); if (vm.exception()) return {}; - return object->get_own_property_descriptor_object(property_key); + return from_property_descriptor(global_object, descriptor); } // 20.1.2.4 Object.defineProperty ( O, P, Attributes ), https://tc39.es/ecma262/#sec-object.defineproperty JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_) { if (!vm.argument(0).is_object()) { - vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument"); + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, vm.argument(0).to_string_without_side_effects()); return {}; } - auto property_key = vm.argument(1).to_property_key(global_object); + auto key = vm.argument(1).to_property_key(global_object); if (vm.exception()) return {}; - if (!vm.argument(2).is_object()) { - vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Descriptor argument"); + auto descriptor = to_property_descriptor(global_object, vm.argument(2)); + if (vm.exception()) return {}; - } - auto& object = vm.argument(0).as_object(); - auto& descriptor = vm.argument(2).as_object(); - if (!object.define_property(property_key, descriptor)) { - if (!vm.exception()) { - if (AK::is<ProxyObject>(object)) { - vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse); - } else { - vm.throw_exception<TypeError>(global_object, ErrorType::NonExtensibleDefine, property_key.to_display_string()); - } - } + vm.argument(0).as_object().define_property_or_throw(key, descriptor); + if (vm.exception()) return {}; - } - return &object; + return vm.argument(0); } // 20.1.2.3 Object.defineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-object.defineproperties JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_properties) { - if (!vm.argument(0).is_object()) { + auto object = vm.argument(0); + auto properties = vm.argument(1); + + // 1. If Type(O) is not Object, throw a TypeError exception. + if (!object.is_object()) { vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument"); return {}; } - auto& object = vm.argument(0).as_object(); - auto properties = vm.argument(1); - object.define_properties(properties); - if (vm.exception()) - return {}; - return &object; + + // 2. Return ? ObjectDefineProperties(O, Properties). + return object.as_object().define_properties(properties); } // 20.1.2.13 Object.is ( value1, value2 ), https://tc39.es/ecma262/#sec-object.is @@ -306,56 +348,61 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is) // 20.1.2.17 Object.keys ( O ), https://tc39.es/ecma262/#sec-object.keys JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::keys) { - auto* obj_arg = vm.argument(0).to_object(global_object); + auto* object = vm.argument(0).to_object(global_object); if (vm.exception()) return {}; - - return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::Key)); + auto name_list = object->enumerable_own_property_names(PropertyKind::Key); + if (vm.exception()) + return {}; + return Array::create_from(global_object, name_list); } // 20.1.2.22 Object.values ( O ), https://tc39.es/ecma262/#sec-object.values JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::values) { - auto* obj_arg = vm.argument(0).to_object(global_object); + auto* object = vm.argument(0).to_object(global_object); if (vm.exception()) return {}; - - return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::Value)); + auto name_list = object->enumerable_own_property_names(PropertyKind::Value); + if (vm.exception()) + return {}; + return Array::create_from(global_object, name_list); } // 20.1.2.5 Object.entries ( O ), https://tc39.es/ecma262/#sec-object.entries JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries) { - auto* obj_arg = vm.argument(0).to_object(global_object); + auto* object = vm.argument(0).to_object(global_object); if (vm.exception()) return {}; - - return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::KeyAndValue)); + auto name_list = object->enumerable_own_property_names(PropertyKind::KeyAndValue); + if (vm.exception()) + return {}; + return Array::create_from(global_object, name_list); } // 20.1.2.2 Object.create ( O, Properties ), https://tc39.es/ecma262/#sec-object.create JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::create) { - auto prototype_value = vm.argument(0); + auto proto = vm.argument(0); auto properties = vm.argument(1); - Object* prototype; - if (prototype_value.is_null()) { - prototype = nullptr; - } else if (prototype_value.is_object()) { - prototype = &prototype_value.as_object(); - } else { + // 1. If Type(O) is neither Object nor Null, throw a TypeError exception. + if (!proto.is_object() && !proto.is_null()) { vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType); return {}; } - auto* object = Object::create(global_object, prototype); + // 2. Let obj be ! OrdinaryObjectCreate(O). + auto* object = Object::create(global_object, proto.is_null() ? nullptr : &proto.as_object()); + // 3. If Properties is not undefined, then if (!properties.is_undefined()) { - object->define_properties(properties); - if (vm.exception()) - return {}; + // a. Return ? ObjectDefineProperties(obj, Properties). + return object->define_properties(properties); } + + // 4. Return obj. return object; } @@ -385,18 +432,18 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::assign) continue; auto from = next_source.to_object(global_object); VERIFY(!vm.exception()); - auto keys = from->get_own_properties(PropertyKind::Key); + auto keys = from->internal_own_property_keys(); if (vm.exception()) return {}; - for (auto& key : keys) { - auto property_name = PropertyName::from_value(global_object, key); - auto property_descriptor = from->get_own_property_descriptor(property_name); - if (!property_descriptor.has_value() || !property_descriptor->attributes.is_enumerable()) + for (auto& next_key : keys) { + auto property_name = PropertyName::from_value(global_object, next_key); + auto property_descriptor = from->internal_get_own_property(property_name); + if (!property_descriptor.has_value() || !*property_descriptor->enumerable) continue; - auto value = from->get(property_name); + auto prop_value = from->get(property_name); if (vm.exception()) return {}; - to->put(property_name, value); + to->set(property_name, prop_value, true); if (vm.exception()) return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp index 7e02959898..5a30b09c1e 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp +++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -25,9 +26,9 @@ void ObjectEnvironment::visit_edges(Cell::Visitor& visitor) Optional<Variable> ObjectEnvironment::get_from_environment(FlyString const& name) const { - auto value = m_binding_object.get(name); - if (value.is_empty()) + if (!m_binding_object.storage_has(name)) return {}; + auto value = m_binding_object.get(name); return Variable { value, DeclarationKind::Var }; } @@ -38,33 +39,39 @@ bool ObjectEnvironment::put_into_environment(FlyString const& name, Variable var bool ObjectEnvironment::delete_from_environment(FlyString const& name) { - return m_binding_object.delete_property(name); + return m_binding_object.internal_delete(name); } // 9.1.1.2.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n bool ObjectEnvironment::has_binding(FlyString const& name) const { + auto& vm = this->vm(); bool found_binding = m_binding_object.has_property(name); + if (vm.exception()) + return {}; if (!found_binding) return false; - - // FIXME: Implement the rest of this operation. - + if (!m_with_environment) + return true; + auto unscopables = m_binding_object.get(*vm.well_known_symbol_unscopables()); + if (vm.exception()) + return {}; + if (unscopables.is_object()) { + auto blocked = unscopables.as_object().get(name); + if (vm.exception()) + return {}; + if (blocked.to_boolean()) + return false; + } return true; } // 9.1.1.2.2 CreateMutableBinding ( N, D ), https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d void ObjectEnvironment::create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted) { - PropertyAttributes attributes; - attributes.set_enumerable(); - attributes.set_has_enumerable(); - attributes.set_writable(); - attributes.set_has_writable(); - attributes.set_has_configurable(); - if (can_be_deleted) - attributes.set_configurable(); - m_binding_object.define_property(name, js_undefined(), attributes, true); + // 1. Let bindingObject be envRec.[[BindingObject]]. + // 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }). + m_binding_object.define_property_or_throw(name, { .value = js_undefined(), .writable = true, .enumerable = true, .configurable = can_be_deleted }); } // 9.1.1.2.3 CreateImmutableBinding ( N, S ), https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s @@ -83,34 +90,38 @@ void ObjectEnvironment::initialize_binding(GlobalObject& global_object, FlyStrin // 9.1.1.2.5 SetMutableBinding ( N, V, S ), https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s void ObjectEnvironment::set_mutable_binding(GlobalObject& global_object, FlyString const& name, Value value, bool strict) { + auto& vm = this->vm(); bool still_exists = m_binding_object.has_property(name); + if (vm.exception()) + return; if (!still_exists && strict) { global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name); return; } - // FIXME: This should use the Set abstract operation. - // FIXME: Set returns a bool, so this may need to return a bool as well. - m_binding_object.put(name, value); + m_binding_object.set(name, value, strict); } // 9.1.1.2.6 GetBindingValue ( N, S ), https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s Value ObjectEnvironment::get_binding_value(GlobalObject& global_object, FlyString const& name, bool strict) { - if (!m_binding_object.has_property(name)) { + auto& vm = this->vm(); + auto value = m_binding_object.has_property(name); + if (vm.exception()) + return {}; + if (!value) { if (!strict) return js_undefined(); global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name); return {}; } - // FIXME: This should use the Get abstract operation. return m_binding_object.get(name); } // 9.1.1.2.7 DeleteBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n bool ObjectEnvironment::delete_binding(GlobalObject&, FlyString const& name) { - return m_binding_object.delete_property(name); + return m_binding_object.internal_delete(name); } } diff --git a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp index 863315cf9f..367ed5d815 100644 --- a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp @@ -130,7 +130,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::property_is_enumerable) auto property_descriptor = this_object->get_own_property_descriptor(property_key); if (!property_descriptor.has_value()) return Value(false); - return Value(property_descriptor.value().attributes.is_enumerable()); + return Value(*property_descriptor->enumerable); } // 20.1.3.3 Object.prototype.isPrototypeOf ( V ), https://tc39.es/ecma262/#sec-object.prototype.isprototypeof @@ -145,7 +145,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::is_prototype_of) return {}; for (;;) { - object = object->prototype(); + object = object->internal_get_prototype_of(); if (!object) return Value(false); if (same_value(this_object, object)) @@ -166,22 +166,16 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_getter) return {}; } + auto descriptor = PropertyDescriptor { .get = &getter.as_function(), .enumerable = true, .configurable = true }; + auto key = vm.argument(0).to_property_key(global_object); if (vm.exception()) return {}; - auto descriptor = Object::create(global_object, global_object.object_prototype()); - descriptor->define_property(vm.names.get, getter); - descriptor->define_property(vm.names.enumerable, Value(true)); - descriptor->define_property(vm.names.configurable, Value(true)); - - auto success = object->define_property(key, *descriptor); + object->define_property_or_throw(key, descriptor); if (vm.exception()) return {}; - if (!success) { - vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse); - return {}; - } + return js_undefined(); } @@ -198,22 +192,16 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_setter) return {}; } + auto descriptor = PropertyDescriptor { .set = &setter.as_function(), .enumerable = true, .configurable = true }; + auto key = vm.argument(0).to_property_key(global_object); if (vm.exception()) return {}; - auto descriptor = Object::create(global_object, global_object.object_prototype()); - descriptor->define_property(vm.names.set, setter); - descriptor->define_property(vm.names.enumerable, Value(true)); - descriptor->define_property(vm.names.configurable, Value(true)); - - auto success = object->define_property(key, *descriptor); + object->define_property_or_throw(key, descriptor); if (vm.exception()) return {}; - if (!success) { - vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse); - return {}; - } + return js_undefined(); } @@ -229,12 +217,15 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::lookup_getter) return {}; while (object) { - auto desc = object->get_own_property_descriptor(key); + auto desc = object->internal_get_own_property(key); if (vm.exception()) return {}; - if (desc.has_value()) - return desc->getter ?: js_undefined(); - object = object->prototype(); + if (desc.has_value()) { + if (desc->is_accessor_descriptor()) + return *desc->get ?: js_undefined(); + return js_undefined(); + } + object = object->internal_get_prototype_of(); if (vm.exception()) return {}; } @@ -254,12 +245,15 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::lookup_setter) return {}; while (object) { - auto desc = object->get_own_property_descriptor(key); + auto desc = object->internal_get_own_property(key); if (vm.exception()) return {}; - if (desc.has_value()) - return desc->setter ?: js_undefined(); - object = object->prototype(); + if (desc.has_value()) { + if (desc->is_accessor_descriptor()) + return *desc->set ?: js_undefined(); + return js_undefined(); + } + object = object->internal_get_prototype_of(); if (vm.exception()) return {}; } @@ -273,7 +267,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::proto_getter) auto object = vm.this_value(global_object).to_object(global_object); if (vm.exception()) return {}; - auto proto = object->prototype(); + auto proto = object->internal_get_prototype_of(); if (vm.exception()) return {}; return proto; @@ -293,10 +287,11 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::proto_setter) if (!object.is_object()) return js_undefined(); - auto status = object.as_object().set_prototype(proto.is_object() ? &proto.as_object() : nullptr); + auto status = object.as_object().internal_set_prototype_of(proto.is_object() ? &proto.as_object() : nullptr); if (vm.exception()) return {}; if (!status) { + // FIXME: Improve/contextualize error message vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse); return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp index 1f0d1cdcd4..02a5c06df2 100644 --- a/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp @@ -94,7 +94,7 @@ void OrdinaryFunctionObject::initialize(GlobalObject& global_object) break; case FunctionKind::Generator: // prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) - prototype->set_prototype(global_object.generator_object_prototype()); + prototype->internal_set_prototype_of(global_object.generator_object_prototype()); break; } define_property(vm.names.prototype, prototype, Attribute::Writable); diff --git a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp deleted file mode 100644 index 5a68b567d3..0000000000 --- a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021, David Tuin <david.tuin@gmail.com> - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include "PropertyAttributes.h" - -namespace JS { - -PropertyAttributes PropertyAttributes::overwrite(PropertyAttributes attr) const -{ - PropertyAttributes combined = m_bits; - if (attr.has_configurable()) { - if (attr.is_configurable()) { - combined.set_configurable(); - } else { - combined.m_bits &= ~Attribute::Configurable; - } - combined.set_has_configurable(); - } - - if (attr.has_enumerable()) { - if (attr.is_enumerable()) { - combined.set_enumerable(); - } else { - combined.m_bits &= ~Attribute::Enumerable; - } - combined.set_has_configurable(); - } - - if (attr.has_writable()) { - if (attr.is_writable()) { - combined.set_writable(); - } else { - combined.m_bits &= ~Attribute::Writable; - } - combined.set_has_writable(); - } - - if (attr.has_getter()) { - combined.set_has_getter(); - } - - if (attr.has_setter()) { - combined.set_has_setter(); - } - return combined; -} - -} diff --git a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h index 44792ee9a7..e0313d6a16 100644 --- a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h +++ b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -13,73 +14,72 @@ namespace JS { struct Attribute { enum { - Configurable = 1 << 0, + Writable = 1 << 0, Enumerable = 1 << 1, - Writable = 1 << 2, - HasGetter = 1 << 3, - HasSetter = 1 << 4, - HasConfigurable = 1 << 5, - HasEnumerable = 1 << 6, - HasWritable = 1 << 7, + Configurable = 1 << 2, }; }; +// 6.1.7.1 Property Attributes, https://tc39.es/ecma262/#sec-property-attributes class PropertyAttributes { public: PropertyAttributes(u8 bits = 0) + : m_bits(bits) { - m_bits = bits; - if (bits & Attribute::Configurable) - m_bits |= Attribute::HasConfigurable; - if (bits & Attribute::Enumerable) - m_bits |= Attribute::HasEnumerable; - if (bits & Attribute::Writable) - m_bits |= Attribute::HasWritable; } - bool is_empty() const { return !m_bits; } + [[nodiscard]] bool is_writable() const { return m_bits & Attribute::Writable; } + [[nodiscard]] bool is_enumerable() const { return m_bits & Attribute::Enumerable; } + [[nodiscard]] bool is_configurable() const { return m_bits & Attribute::Configurable; } - bool has_configurable() const { return m_bits & Attribute::HasConfigurable; } - bool has_enumerable() const { return m_bits & Attribute::HasEnumerable; } - bool has_writable() const { return m_bits & Attribute::HasWritable; } - bool has_getter() const { return m_bits & Attribute::HasGetter; } - bool has_setter() const { return m_bits & Attribute::HasSetter; } + void set_writable(bool writable = true) + { + if (writable) + m_bits |= Attribute::Writable; + else + m_bits &= ~Attribute::Writable; + } - bool is_configurable() const { return m_bits & Attribute::Configurable; } - bool is_enumerable() const { return m_bits & Attribute::Enumerable; } - bool is_writable() const { return m_bits & Attribute::Writable; } + void set_enumerable(bool enumerable = true) + { + if (enumerable) + m_bits |= Attribute::Enumerable; + else + m_bits &= ~Attribute::Enumerable; + } - void set_has_configurable() { m_bits |= Attribute::HasConfigurable; } - void set_has_enumerable() { m_bits |= Attribute::HasEnumerable; } - void set_has_writable() { m_bits |= Attribute::HasWritable; } - void set_configurable() { m_bits |= Attribute::Configurable; } - void set_enumerable() { m_bits |= Attribute::Enumerable; } - void set_writable() { m_bits |= Attribute::Writable; } - void set_has_getter() { m_bits |= Attribute::HasGetter; } - void set_has_setter() { m_bits |= Attribute::HasSetter; } + void set_configurable(bool configurable = true) + { + if (configurable) + m_bits |= Attribute::Configurable; + else + m_bits &= ~Attribute::Configurable; + } bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; } bool operator!=(const PropertyAttributes& other) const { return m_bits != other.m_bits; } - PropertyAttributes overwrite(PropertyAttributes attr) const; - - u8 bits() const { return m_bits; } + [[nodiscard]] u8 bits() const { return m_bits; } private: u8 m_bits; }; -const PropertyAttributes default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable; +PropertyAttributes const default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable; } namespace AK { template<> -struct Formatter<JS::PropertyAttributes> : Formatter<u8> { - void format(FormatBuilder& builder, const JS::PropertyAttributes& attributes) +struct Formatter<JS::PropertyAttributes> : Formatter<StringView> { + void format(FormatBuilder& builder, JS::PropertyAttributes const& property_attributes) { - Formatter<u8>::format(builder, attributes.bits()); + Vector<String> parts; + parts.append(String::formatted("[[Writable]]: {}", property_attributes.is_writable())); + parts.append(String::formatted("[[Enumerable]]: {}", property_attributes.is_enumerable())); + parts.append(String::formatted("[[Configurable]]: {}", property_attributes.is_configurable())); + Formatter<StringView>::format(builder, String::formatted("PropertyAttributes {{ {} }}", String::join(", ", parts))); } }; diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp index ef69b75a7e..dfdaeae9cc 100644 --- a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp +++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp @@ -13,9 +13,6 @@ namespace JS { -// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit, -// but the names conflict - this is the easiest solution. :^) -#if 0 // 6.2.5.1 IsAccessorDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isaccessordescriptor bool PropertyDescriptor::is_accessor_descriptor() const { @@ -190,6 +187,5 @@ PropertyAttributes PropertyDescriptor::attributes() const attributes |= Attribute::Configurable; return { attributes }; } -#endif } diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h index f505b94a21..7ff5447ee6 100644 --- a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h +++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h @@ -12,9 +12,6 @@ namespace JS { -// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit, -// but the names conflict - this is the easiest solution. :^) -#if 0 // 6.2.5 The Property Descriptor Specification Type, https://tc39.es/ecma262/#sec-property-descriptor-specification-type Value from_property_descriptor(GlobalObject&, Optional<PropertyDescriptor> const&); @@ -68,6 +65,5 @@ struct Formatter<JS::PropertyDescriptor> : Formatter<StringView> { Formatter<StringView>::format(builder, String::formatted("PropertyDescriptor {{ {} }}", String::join(", ", parts))); } }; -#endif } diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp index 13d6b76882..540a691a21 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp @@ -82,8 +82,8 @@ JS_DEFINE_NATIVE_FUNCTION(ProxyConstructor::revocable) revoker->define_property(vm.names.length, Value(0), Attribute::Configurable); auto* result = Object::create(global_object, global_object.object_prototype()); - result->define_property(vm.names.proxy, proxy); - result->define_property(vm.names.revoke, revoker); + result->create_data_property_or_throw(vm.names.proxy, proxy); + result->create_data_property_or_throw(vm.names.revoke, revoker); return result; } diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp index 1288dbf402..38e852bd3f 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -10,35 +10,11 @@ #include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/ProxyObject.h> namespace JS { -bool static is_compatible_property_descriptor(bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional) -{ - if (!current_descriptor_optional.has_value()) - return is_extensible; - auto current_descriptor = current_descriptor_optional.value(); - if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter) - return true; - if (!current_descriptor.attributes.is_configurable()) { - if (new_descriptor.attributes.is_configurable()) - return false; - if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable()) - return false; - } - if (new_descriptor.is_generic_descriptor()) - return true; - if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable()) - return false; - if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) { - if (new_descriptor.attributes.is_writable()) - return false; - return new_descriptor.value.is_empty() && same_value(new_descriptor.value, current_descriptor.value); - } - return true; -} - ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler) { return global_object.heap().allocate<ProxyObject>(global_object, target, handler, *global_object.object_prototype()); @@ -55,423 +31,961 @@ ProxyObject::~ProxyObject() { } -Object* ProxyObject::prototype() +// 10.5.1 [[GetPrototypeOf]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof +Object* ProxyObject::internal_get_prototype_of() const { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Let handler be O.[[ProxyHandler]]. + + // 2. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return nullptr; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.getPrototypeOf); - if (vm.exception()) - return nullptr; - if (!trap) - return m_target.prototype(); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target)); + + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + + // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.getPrototypeOf); if (vm.exception()) - return nullptr; - if (!trap_result.is_object() && !trap_result.is_null()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfReturn); - return nullptr; + return {}; + + // 6. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[GetPrototypeOf]](). + return m_target.internal_get_prototype_of(); } - if (m_target.is_extensible()) { - if (vm.exception()) - return nullptr; - if (trap_result.is_null()) - return nullptr; - return &trap_result.as_object(); + + // 7. Let handlerProto be ? Call(trap, handler, « target »). + auto handler_proto = vm.call(*trap, &m_handler, &m_target); + if (vm.exception()) + return {}; + + // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. + if (!handler_proto.is_object() && !handler_proto.is_null()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetPrototypeOfReturn); + return {}; } - auto target_proto = m_target.prototype(); + + // 9. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); if (vm.exception()) - return nullptr; - if (!same_value(trap_result, Value(target_proto))) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfNonExtensible); - return nullptr; + return {}; + + // 10. If extensibleTarget is true, return handlerProto. + if (extensible_target) + return handler_proto.is_null() ? nullptr : &handler_proto.as_object(); + + // 11. Let targetProto be ? target.[[GetPrototypeOf]](). + auto target_proto = m_target.internal_get_prototype_of(); + if (vm.exception()) + return {}; + + // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception. + if (!same_value(handler_proto, target_proto)) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetPrototypeOfNonExtensible); + return {}; } - return &trap_result.as_object(); + + // 13. Return handlerProto. + return handler_proto.is_null() ? nullptr : &handler_proto.as_object(); } -const Object* ProxyObject::prototype() const +// 10.5.2 [[SetPrototypeOf]] ( V ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v +bool ProxyObject::internal_set_prototype_of(Object* prototype) { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Assert: Either Type(V) is Object or Type(V) is Null. + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return nullptr; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype()); -} -bool ProxyObject::set_prototype(Object* object) -{ - auto& vm = this->vm(); - if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "setPrototypeOf"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.setPrototypeOf); + if (vm.exception()) + return {}; + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[SetPrototypeOf]](V). + return m_target.internal_set_prototype_of(prototype); } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.setPrototypeOf); + + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, V »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target, prototype); if (vm.exception()) + return {}; + + // 9. If booleanTrapResult is false, return false. + if (!trap_result.to_boolean()) return false; - if (!trap) - return m_target.set_prototype(object); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), Value(object)); - if (vm.exception() || !trap_result.to_boolean()) - return false; - if (m_target.is_extensible()) + + // 10. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // 11. If extensibleTarget is true, return true. + if (extensible_target) return true; - auto* target_proto = m_target.prototype(); + + // 12. Let targetProto be ? target.[[GetPrototypeOf]](). + auto* target_proto = m_target.internal_get_prototype_of(); if (vm.exception()) - return false; - if (!same_value(Value(object), Value(target_proto))) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetPrototypeOfNonExtensible); - return false; + return {}; + + // 13. If SameValue(V, targetProto) is false, throw a TypeError exception. + if (!same_value(prototype, target_proto)) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxySetPrototypeOfNonExtensible); + return {}; } + + // 14. Return true. return true; } -bool ProxyObject::is_extensible() const +// 10.5.3 [[IsExtensible]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible +bool ProxyObject::internal_is_extensible() const { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Let handler be O.[[ProxyHandler]]. + + // 2. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.isExtensible); + + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + + // 5. Let trap be ? GetMethod(handler, "isExtensible"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.isExtensible); if (vm.exception()) - return false; - if (!trap) + return {}; + + // 6. If trap is undefined, then + if (!trap) { + // a. Return ? IsExtensible(target). return m_target.is_extensible(); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target)); + } + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target); if (vm.exception()) - return false; - if (trap_result.to_boolean() != m_target.is_extensible()) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyIsExtensibleReturn); - return false; + return {}; + + // 8. Let targetResult be ? IsExtensible(target). + auto target_result = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception. + if (trap_result.to_boolean() != target_result) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyIsExtensibleReturn); + return {}; } + + // 10. Return booleanTrapResult. return trap_result.to_boolean(); } -bool ProxyObject::prevent_extensions() +// 10.5.4 [[PreventExtensions]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions +bool ProxyObject::internal_prevent_extensions() { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Let handler be O.[[ProxyHandler]]. + + // 2. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.preventExtensions); + + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + + // 5. Let trap be ? GetMethod(handler, "preventExtensions"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.preventExtensions); if (vm.exception()) - return false; - if (!trap) - return m_target.prevent_extensions(); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target)); + return {}; + + // 6. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[PreventExtensions]](). + return m_target.internal_prevent_extensions(); + } + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target); if (vm.exception()) - return false; - if (trap_result.to_boolean() && m_target.is_extensible()) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyPreventExtensionsReturn); - return false; + return {}; + + // 8. If booleanTrapResult is true, then + if (trap_result.to_boolean()) { + // a. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // b. If extensibleTarget is true, throw a TypeError exception. + if (extensible_target) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyPreventExtensionsReturn); + return {}; + } } + + // 9. Return booleanTrapResult. return trap_result.to_boolean(); } -Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(const PropertyName& name) const +// 10.5.5 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p +Optional<PropertyDescriptor> ProxyObject::internal_get_own_property(const PropertyName& property_name) const { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.getOwnPropertyDescriptor); + + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.getOwnPropertyDescriptor); if (vm.exception()) return {}; - if (!trap) - return m_target.get_own_property_descriptor(name); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm)); + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[GetOwnProperty]](P). + return m_target.internal_get_own_property(property_name); + } + + // 8. Let trapResultObj be ? Call(trap, handler, « target, P »). + auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm)); if (vm.exception()) return {}; + + // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. if (!trap_result.is_object() && !trap_result.is_undefined()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorReturn); + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorReturn); return {}; } - auto target_desc = m_target.get_own_property_descriptor(name); + + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + auto target_descriptor = m_target.internal_get_own_property(property_name); if (vm.exception()) return {}; + + // 11. If trapResultObj is undefined, then if (trap_result.is_undefined()) { - if (!target_desc.has_value()) + // a. If targetDesc is undefined, return undefined. + if (!target_descriptor.has_value()) return {}; - if (!target_desc.value().attributes.is_configurable()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorNonConfigurable); + + // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if (!*target_descriptor->configurable) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorNonConfigurable); return {}; } - if (!m_target.is_extensible()) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorUndefinedReturn); + + // c. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // d. If extensibleTarget is false, throw a TypeError exception. + if (!extensible_target) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorUndefinedReturn); return {}; } + + // e. Return undefined. return {}; } - auto result_desc = PropertyDescriptor::from_dictionary(vm, trap_result.as_object()); + + // 12. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). + auto result_desc = to_property_descriptor(global_object, trap_result); if (vm.exception()) return {}; - if (!is_compatible_property_descriptor(m_target.is_extensible(), result_desc, target_desc)) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidDescriptor); + + // 14. Call CompletePropertyDescriptor(resultDesc). + result_desc.complete(); + + // 15. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc). + auto valid = is_compatible_property_descriptor(extensible_target, result_desc, target_descriptor); + + // 16. If valid is false, throw a TypeError exception. + if (!valid) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorInvalidDescriptor); return {}; } - if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidNonConfig); - return {}; + + // 17. If resultDesc.[[Configurable]] is false, then + if (!*result_desc.configurable) { + // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then + if (!target_descriptor.has_value() || *target_descriptor->configurable) { + // i. Throw a TypeError exception. + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorInvalidNonConfig); + return {}; + } + // b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then + if (result_desc.writable.has_value() && !*result_desc.writable) { + // i. If targetDesc.[[Writable]] is true, throw a TypeError exception. + if (*target_descriptor->writable) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorNonConfigurableNonWritable); + return {}; + } + } } + + // 18. Return resultDesc. return result_desc; } -bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) +// 10.5.6 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc +bool ProxyObject::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor) { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.defineProperty); + + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "defineProperty"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.defineProperty); if (vm.exception()) - return false; - if (!trap) - return m_target.define_property(property_name, descriptor, throw_exceptions); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), property_name.to_value(vm), Value(const_cast<Object*>(&descriptor))); - if (vm.exception() || !trap_result.to_boolean()) - return false; - auto target_desc = m_target.get_own_property_descriptor(property_name); + return {}; + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[DefineOwnProperty]](P, Desc). + return m_target.internal_define_own_property(property_name, property_descriptor); + } + + // 8. Let descObj be FromPropertyDescriptor(Desc). + auto descriptor_object = from_property_descriptor(global_object, property_descriptor); + + // 9. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm), descriptor_object); if (vm.exception()) + return {}; + + // 10. If booleanTrapResult is false, return false. + if (!trap_result.to_boolean()) return false; + + // 11. Let targetDesc be ? target.[[GetOwnProperty]](P). + auto target_descriptor = m_target.internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 12. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // 14. Else, let settingConfigFalse be false. bool setting_config_false = false; - if (descriptor.has_property(vm.names.configurable) && !descriptor.get(vm.names.configurable).to_boolean()) + + // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then + if (property_descriptor.configurable.has_value() && !*property_descriptor.configurable) { + // a. Let settingConfigFalse be true. setting_config_false = true; - if (vm.exception()) - return false; - if (!target_desc.has_value()) { - if (!m_target.is_extensible()) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonExtensible); - return false; + } + + // 15. If targetDesc is undefined, then + if (!target_descriptor.has_value()) { + // a. If extensibleTarget is false, throw a TypeError exception. + if (!extensible_target) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropNonExtensible); + return {}; } + // b. If settingConfigFalse is true, throw a TypeError exception. if (setting_config_false) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonConfigurableNonExisting); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropNonConfigurableNonExisting); + return {}; } - } else { - if (!is_compatible_property_descriptor(m_target.is_extensible(), PropertyDescriptor::from_dictionary(vm, descriptor), target_desc)) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropIncompatibleDescriptor); - return false; + } + // 16. Else, + else { + // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception. + if (!is_compatible_property_descriptor(extensible_target, property_descriptor, target_descriptor)) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropIncompatibleDescriptor); + return {}; } - if (setting_config_false && target_desc.value().attributes.is_configurable()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropExistingConfigurable); - return false; + // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. + if (setting_config_false && *target_descriptor->configurable) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropExistingConfigurable); + return {}; + } + // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then + if (target_descriptor->is_data_descriptor() && !*target_descriptor->configurable && *target_descriptor->writable) { + // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception. + if (property_descriptor.writable.has_value() && !*property_descriptor.writable) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropNonWritable); + return {}; + } } } + + // 17. Return true. return true; } -bool ProxyObject::has_property(const PropertyName& name) const +// 10.5.7 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p +bool ProxyObject::internal_has_property(PropertyName const& property_name) const { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.has); + + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "has"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.has); if (vm.exception()) - return false; - if (!trap) - return m_target.has_property(name); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm)); + return {}; + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[HasProperty]](P). + return m_target.internal_has_property(property_name); + } + + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm)); if (vm.exception()) - return false; + return {}; + + // 9. If booleanTrapResult is false, then if (!trap_result.to_boolean()) { - auto target_desc = m_target.get_own_property_descriptor(name); + // a. Let targetDesc be ? target.[[GetOwnProperty]](P). + auto target_descriptor = m_target.internal_get_own_property(property_name); if (vm.exception()) - return false; - if (target_desc.has_value()) { - if (!target_desc.value().attributes.is_configurable()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonConfigurable); - return false; + return {}; + + // b. If targetDesc is not undefined, then + if (target_descriptor.has_value()) { + // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if (!*target_descriptor->configurable) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyHasExistingNonConfigurable); + return {}; } - if (!m_target.is_extensible()) { - if (!vm.exception()) - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonExtensible); + + // ii. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // iii. If extensibleTarget is false, throw a TypeError exception. + if (!extensible_target) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyHasExistingNonExtensible); return false; } } } + + // 10. Return booleanTrapResult. return trap_result.to_boolean(); } -Value ProxyObject::get(const PropertyName& name, Value receiver, AllowSideEffects allow_side_effects) const +// 10.5.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver +Value ProxyObject::internal_get(PropertyName const& property_name, Value receiver) const { + VERIFY(!receiver.is_empty()); + auto& vm = this->vm(); - if (allow_side_effects == AllowSideEffects::No) { - // Sorry, we're not going to call anything on this proxy. - return js_string(vm, "<ProxyObject>"); - } + auto& global_object = this->global_object(); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); return {}; } - if (receiver.is_empty()) - receiver = Value(const_cast<ProxyObject*>(this)); - auto trap = Value(&m_handler).get_method(global_object(), vm.names.get); + + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "get"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.get); if (vm.exception()) return {}; - if (!trap) - return m_target.get(name, receiver); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm), receiver); + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[Get]](P, Receiver). + return m_target.internal_get(property_name, receiver); + } + + // 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). + auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm), receiver); if (vm.exception()) return {}; - auto target_desc = m_target.get_own_property_descriptor(name); - if (target_desc.has_value()) { - if (vm.exception()) - return {}; - if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(trap_result, target_desc.value().value)) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetImmutableDataProperty); - return {}; + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + auto target_descriptor = m_target.internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then + if (target_descriptor.has_value() && !*target_descriptor->configurable) { + // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then + if (target_descriptor->is_data_descriptor() && !*target_descriptor->writable) { + // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception. + if (!same_value(trap_result, *target_descriptor->value)) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetImmutableDataProperty); + return {}; + } } - if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetNonConfigurableAccessor); - return {}; + // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] is undefined, then + if (target_descriptor->is_accessor_descriptor() && !*target_descriptor->get) { + // i. If trapResult is not undefined, throw a TypeError exception. + if (!trap_result.is_undefined()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetNonConfigurableAccessor); + return {}; + } } } + + // 11. Return trapResult. return trap_result; } -bool ProxyObject::put(const PropertyName& name, Value value, Value receiver) +// 10.5.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver +bool ProxyObject::internal_set(PropertyName const& property_name, Value value, Value receiver) { + VERIFY(!value.is_empty()); + VERIFY(!receiver.is_empty()); + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - if (receiver.is_empty()) - receiver = Value(const_cast<ProxyObject*>(this)); - auto trap = Value(&m_handler).get_method(global_object(), vm.names.set); + + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "set"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.set); if (vm.exception()) - return false; - if (!trap) - return m_target.put(name, value, receiver); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm), value, receiver); - if (vm.exception() || !trap_result.to_boolean()) - return false; - auto target_desc = m_target.get_own_property_descriptor(name); + return {}; + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[Set]](P, V, Receiver). + return m_target.internal_set(property_name, value, receiver); + } + + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, V, Receiver »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm), value, receiver); if (vm.exception()) + return {}; + + // 9. If booleanTrapResult is false, return false. + if (!trap_result.to_boolean()) return false; - if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) { - if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(value, target_desc.value().value)) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetImmutableDataProperty); - return false; + + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + auto target_descriptor = m_target.internal_get_own_property(property_name); + if (vm.exception()) + return {}; + + // 11. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then + if (target_descriptor.has_value() && !*target_descriptor->configurable) { + // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then + if (target_descriptor->is_data_descriptor() && !*target_descriptor->writable) { + // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception. + if (!same_value(value, *target_descriptor->value)) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxySetImmutableDataProperty); + return {}; + } } - if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetNonConfigurableAccessor); + // b. If IsAccessorDescriptor(targetDesc) is true, then + if (target_descriptor->is_accessor_descriptor()) { + // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. + if (!*target_descriptor->set) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxySetNonConfigurableAccessor); + return {}; + } } } + + // 12. Return true. return true; } -bool ProxyObject::delete_property(PropertyName const& name, bool force_throw_exception) +// 10.5.10 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p +bool ProxyObject::internal_delete(PropertyName const& property_name) { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let handler be O.[[ProxyHandler]]. + + // 3. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); - return false; + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.deleteProperty); + + // 4. Assert: Type(handler) is Object. + // 5. Let target be O.[[ProxyTarget]]. + + // 6. Let trap be ? GetMethod(handler, "deleteProperty"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.deleteProperty); if (vm.exception()) - return false; - if (!trap) - return m_target.delete_property(name, force_throw_exception); - auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm)); + return {}; + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[Delete]](P). + return m_target.internal_delete(property_name); + } + + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm)); if (vm.exception()) - return false; + return {}; + + // 9. If booleanTrapResult is false, return false. if (!trap_result.to_boolean()) return false; - auto target_desc = m_target.get_own_property_descriptor(name); + + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + auto target_descriptor = m_target.internal_get_own_property(property_name); if (vm.exception()) - return false; - if (!target_desc.has_value()) + return {}; + + // 11. If targetDesc is undefined, return true. + if (!target_descriptor.has_value()) return true; - if (!target_desc.value().attributes.is_configurable()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDeleteNonConfigurable); - return false; + + // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if (!*target_descriptor->configurable) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDeleteNonConfigurable); + return {}; + } + + // 13. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return {}; + + // 14. If extensibleTarget is false, throw a TypeError exception. + if (!extensible_target) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDeleteNonExtensible); + return {}; } + + // 15. Return true. return true; } -void ProxyObject::visit_edges(Cell::Visitor& visitor) +// 10.5.11 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys +MarkedValueList ProxyObject::internal_own_property_keys() const { - Base::visit_edges(visitor); - visitor.visit(&m_target); - visitor.visit(&m_handler); + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 1. Let handler be O.[[ProxyHandler]]. + + // 2. If handler is null, throw a TypeError exception. + if (m_is_revoked) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); + return MarkedValueList { heap() }; + } + + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + + // 5. Let trap be ? GetMethod(handler, "ownKeys"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.ownKeys); + if (vm.exception()) + return MarkedValueList { heap() }; + + // 6. If trap is undefined, then + if (!trap) { + // a. Return ? target.[[OwnPropertyKeys]](). + return Object::internal_own_property_keys(); + } + + // 7. Let trapResultArray be ? Call(trap, handler, « target »). + auto trap_result_array = vm.call(*trap, &m_handler, &m_target); + if (vm.exception()) + return MarkedValueList { heap() }; + + // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, Symbol »). + auto trap_result = create_list_from_array_like(global_object, trap_result_array, [](auto value) -> Result<void, ErrorType> { + if (!value.is_string() && !value.is_symbol()) + return ErrorType::ProxyOwnPropertyKeysNotStringOrSymbol; + return {}; + }); + + // FIXME: 9. If trapResult contains any duplicate entries, throw a TypeError exception. + + // 10. Let extensibleTarget be ? IsExtensible(target). + auto extensible_target = m_target.is_extensible(); + if (vm.exception()) + return MarkedValueList { heap() }; + + // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). + auto target_keys = m_target.internal_own_property_keys(); + if (vm.exception()) + return MarkedValueList { heap() }; + + // 12. Assert: targetKeys is a List whose elements are only String and Symbol values. + // 13. Assert: targetKeys contains no duplicate entries. + + // 14. Let targetConfigurableKeys be a new empty List. + auto target_configurable_keys = MarkedValueList { heap() }; + + // 15. Let targetNonconfigurableKeys be a new empty List. + auto target_nonconfigurable_keys = MarkedValueList { heap() }; + + // 16. For each element key of targetKeys, do + for (auto& key : target_keys) { + // a. Let desc be ? target.[[GetOwnProperty]](key). + auto descriptor = m_target.internal_get_own_property(PropertyName::from_value(global_object, key)); + + // b. If desc is not undefined and desc.[[Configurable]] is false, then + if (descriptor.has_value() && !*descriptor->configurable) { + // i. Append key as an element of targetNonconfigurableKeys. + target_nonconfigurable_keys.append(key); + } + // c. Else, + else { + // i. Append key as an element of targetConfigurableKeys. + target_configurable_keys.append(key); + } + } + + // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, then + if (extensible_target && target_nonconfigurable_keys.is_empty()) { + // a. Return trapResult. + return trap_result; + } + + // 18. Let uncheckedResultKeys be a List whose elements are the elements of trapResult. + auto unchecked_result_keys = MarkedValueList { heap() }; + unchecked_result_keys.extend(trap_result); + + // 19. For each element key of targetNonconfigurableKeys, do + for (auto& key : target_nonconfigurable_keys) { + // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. + if (!unchecked_result_keys.contains_slow(key)) { + vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString); + return MarkedValueList { heap() }; + } + + // b. Remove key from uncheckedResultKeys. + unchecked_result_keys.remove_first_matching([&](auto& value) { + return same_value(value, key); + }); + } + + // 20. If extensibleTarget is true, return trapResult. + if (extensible_target) + return trap_result; + + // 21. For each element key of targetConfigurableKeys, do + for (auto& key : target_configurable_keys) { + // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. + if (!unchecked_result_keys.contains_slow(key)) { + vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString); + return MarkedValueList { heap() }; + } + + // b. Remove key from uncheckedResultKeys. + unchecked_result_keys.remove_first_matching([&](auto& value) { + return same_value(value, key); + }); + } + + // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. + if (!unchecked_result_keys.is_empty()) { + vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString); + return MarkedValueList { heap() }; + } + + // 23. Return trapResult. + return trap_result; } +// 10.5.12 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist Value ProxyObject::call() { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // A Proxy exotic object only has a [[Call]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Call]] internal method. if (!is_function()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, Value(this).to_string_without_side_effects()); + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, Value(this).to_string_without_side_effects()); return {}; } + + // 1. Let handler be O.[[ProxyHandler]]. + + // 2. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.apply); + + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + + // 5. Let trap be ? GetMethod(handler, "apply"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.apply); if (vm.exception()) return {}; - if (!trap) + + // 6. If trap is undefined, then + if (!trap) { + // a. Return ? Call(target, thisArgument, argumentsList). return static_cast<FunctionObject&>(m_target).call(); - MarkedValueList arguments(heap()); - arguments.append(Value(&m_target)); - arguments.append(Value(&m_handler)); - // FIXME: Pass global object - auto arguments_array = Array::create(global_object(), 0); + } + + // 7. Let argArray be ! CreateArrayFromList(argumentsList). + auto arguments_array = Array::create(global_object, 0); vm.for_each_argument([&](auto& argument) { arguments_array->indexed_properties().append(argument); }); - arguments.append(arguments_array); - return vm.call(*trap, Value(&m_handler), move(arguments)); + // 8. Return ? Call(trap, handler, « target, thisArgument, argArray »). + return vm.call(*trap, &m_handler, &m_target, &m_handler, arguments_array); } +// 10.5.13 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget Value ProxyObject::construct(FunctionObject& new_target) { auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // A Proxy exotic object only has a [[Construct]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Construct]] internal method. if (!is_function()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, Value(this).to_string_without_side_effects()); + vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, Value(this).to_string_without_side_effects()); return {}; } + + // 1. Let handler be O.[[ProxyHandler]]. + + // 2. If handler is null, throw a TypeError exception. if (m_is_revoked) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked); + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); return {}; } - auto trap = Value(&m_handler).get_method(global_object(), vm.names.construct); + + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + // 5. Assert: IsConstructor(target) is true. + + // 6. Let trap be ? GetMethod(handler, "construct"). + auto trap = Value(&m_handler).get_method(global_object, vm.names.construct); if (vm.exception()) return {}; - if (!trap) + + // 7. If trap is undefined, then + if (!trap) { + // a. Return ? Construct(target, argumentsList, newTarget). return static_cast<FunctionObject&>(m_target).construct(new_target); - MarkedValueList arguments(vm.heap()); - arguments.append(Value(&m_target)); - auto arguments_array = Array::create(global_object(), 0); + } + + // 8. Let argArray be ! CreateArrayFromList(argumentsList). + auto arguments_array = Array::create(global_object, 0); vm.for_each_argument([&](auto& argument) { arguments_array->indexed_properties().append(argument); }); - arguments.append(arguments_array); - arguments.append(Value(&new_target)); - auto result = vm.call(*trap, Value(&m_handler), move(arguments)); + + // 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). + auto result = vm.call(*trap, &m_handler, &m_target, arguments_array, &new_target); + + // 10. If Type(newObj) is not Object, throw a TypeError exception. if (!result.is_object()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructBadReturnType); + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyConstructBadReturnType); return {}; } + + // 11. Return newObj. return result; } +void ProxyObject::visit_edges(Cell::Visitor& visitor) +{ + FunctionObject::visit_edges(visitor); + visitor.visit(&m_target); + visitor.visit(&m_handler); +} + const FlyString& ProxyObject::name() const { VERIFY(is_function()); diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.h b/Userland/Libraries/LibJS/Runtime/ProxyObject.h index da89558591..d96b5f2822 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -27,21 +28,23 @@ public: const Object& target() const { return m_target; } const Object& handler() const { return m_handler; } - virtual Object* prototype() override; - virtual const Object* prototype() const override; - virtual bool set_prototype(Object* object) override; - virtual bool is_extensible() const override; - virtual bool prevent_extensions() override; - virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const override; - virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true) override; - virtual bool has_property(const PropertyName& name) const override; - virtual Value get(const PropertyName& name, Value receiver, AllowSideEffects = AllowSideEffects::Yes) const override; - virtual bool put(const PropertyName& name, Value value, Value receiver) override; - virtual bool delete_property(PropertyName const& name, bool force_throw_exception = false) override; - bool is_revoked() const { return m_is_revoked; } void revoke() { m_is_revoked = true; } + // 10.5 Proxy Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots + + virtual Object* internal_get_prototype_of() const override; + virtual bool internal_set_prototype_of(Object* prototype) override; + virtual bool internal_is_extensible() const override; + virtual bool internal_prevent_extensions() override; + virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const&) const override; + virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor const&) override; + virtual bool internal_has_property(PropertyName const&) const override; + virtual Value internal_get(PropertyName const&, Value receiver) const override; + virtual bool internal_set(PropertyName const&, Value value, Value receiver) override; + virtual bool internal_delete(PropertyName const&) override; + virtual MarkedValueList internal_own_property_keys() const override; + private: virtual void visit_edges(Visitor&) override; diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp index 8b0311ea29..ac3152631e 100644 --- a/Userland/Libraries/LibJS/Runtime/Reference.cpp +++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp @@ -21,7 +21,7 @@ void Reference::put_value(GlobalObject& global_object, Value value) throw_reference_error(global_object); return; } - global_object.put(m_name, value); + global_object.set(m_name, value, false); return; } @@ -39,7 +39,9 @@ void Reference::put_value(GlobalObject& global_object, Value value) if (!base_obj) return; - bool succeeded = base_obj->put(m_name, value); + bool succeeded = base_obj->internal_set(m_name, value, get_this_value()); + if (vm.exception()) + return; if (!succeeded && m_strict) { vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects()); return; @@ -108,6 +110,7 @@ Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined) return value->value; } +// 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation bool Reference::delete_(GlobalObject& global_object) { // 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation @@ -136,21 +139,22 @@ bool Reference::delete_(GlobalObject& global_object) // b. If IsSuperReference(ref) is true, throw a ReferenceError exception. if (is_super_reference()) { vm.throw_exception<ReferenceError>(global_object, ErrorType::UnsupportedDeleteSuperProperty); - return false; + return {}; } // c. Let baseObj be ! ToObject(ref.[[Base]]). auto* base_obj = m_base_value.to_object(global_object); - if (!base_obj) - return false; + VERIFY(base_obj); // d. Let deleteStatus be ? baseObj.[[Delete]](ref.[[ReferencedName]]). - bool delete_status = base_obj->delete_property(m_name); + bool delete_status = base_obj->internal_delete(m_name); + if (vm.exception()) + return {}; // e. If deleteStatus is false and ref.[[Strict]] is true, throw a TypeError exception. if (!delete_status && m_strict) { vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects()); - return false; + return {}; } // f. Return deleteStatus. diff --git a/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp index 4294aa40e2..0c0d642b68 100644 --- a/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp @@ -15,17 +15,6 @@ namespace JS { -static Object* get_target_object_from(GlobalObject& global_object, const String& name) -{ - auto& vm = global_object.vm(); - auto target = vm.argument(0); - if (!target.is_object()) { - vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAnObject, name); - return nullptr; - } - return static_cast<Object*>(&target.as_object()); -} - ReflectObject::ReflectObject(GlobalObject& global_object) : Object(*global_object.object_prototype()) { @@ -62,182 +51,294 @@ ReflectObject::~ReflectObject() JS_DEFINE_NATIVE_FUNCTION(ReflectObject::apply) { auto target = vm.argument(0); + auto this_argument = vm.argument(1); + auto arguments_list = vm.argument(2); + + // 1. If IsCallable(target) is false, throw a TypeError exception. if (!target.is_function()) { - vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAFunction, vm.names.apply); + vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, target.to_string_without_side_effects()); return {}; } - auto this_arg = vm.argument(1); - auto arguments = create_list_from_array_like(global_object, vm.argument(2)); + // 2. Let args be ? CreateListFromArrayLike(argumentsList). + auto args = create_list_from_array_like(global_object, arguments_list); if (vm.exception()) return {}; - return vm.call(target.as_function(), this_arg, move(arguments)); + + // 3. Perform PrepareForTailCall(). + // 4. Return ? Call(target, thisArgument, args). + return vm.call(target.as_function(), this_argument, move(args)); } // 28.1.2 Reflect.construct ( target, argumentsList [ , newTarget ] ), https://tc39.es/ecma262/#sec-reflect.construct JS_DEFINE_NATIVE_FUNCTION(ReflectObject::construct) { auto target = vm.argument(0); + auto arguments_list = vm.argument(1); + auto new_target = vm.argument(2); + + // 1. If IsConstructor(target) is false, throw a TypeError exception. if (!target.is_constructor()) { - vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAConstructor, vm.names.construct); + vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, target.to_string_without_side_effects()); return {}; } - auto* new_target = &target.as_function(); - if (vm.argument_count() > 2) { - auto new_target_value = vm.argument(2); - if (!new_target_value.is_constructor()) { - vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadNewTarget); - return {}; - } - new_target = &new_target_value.as_function(); + // 2. If newTarget is not present, set newTarget to target. + if (vm.argument_count() < 3) { + new_target = target; + } + // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception. + else if (!new_target.is_constructor()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, new_target.to_string_without_side_effects()); + return {}; } - auto arguments = create_list_from_array_like(global_object, vm.argument(1)); + // 4. Let args be ? CreateListFromArrayLike(argumentsList). + auto args = create_list_from_array_like(global_object, arguments_list); if (vm.exception()) return {}; - return vm.construct(target.as_function(), *new_target, move(arguments)); + // 5. Return ? Construct(target, args, newTarget). + return vm.construct(target.as_function(), new_target.as_function(), move(args)); } // 28.1.3 Reflect.defineProperty ( target, propertyKey, attributes ), https://tc39.es/ecma262/#sec-reflect.defineproperty JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property) { - auto* target = get_target_object_from(global_object, "defineProperty"); - if (!target) + auto target = vm.argument(0); + auto property_key = vm.argument(1); + auto attributes = vm.argument(2); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + } + + // 2. Let key be ? ToPropertyKey(propertyKey). + auto key = property_key.to_property_key(global_object); if (vm.exception()) return {}; - if (!vm.argument(2).is_object()) { - vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadDescriptorArgument); - return {}; - } - auto& descriptor = vm.argument(2).as_object(); - auto success = target->define_property(property_key, descriptor, false); + + // 3. Let desc be ? ToPropertyDescriptor(attributes). + auto descriptor = to_property_descriptor(global_object, attributes); if (vm.exception()) return {}; - return Value(success); + + // 4. Return ? target.[[DefineOwnProperty]](key, desc). + return Value(target.as_object().internal_define_own_property(key, descriptor)); } // 28.1.4 Reflect.deleteProperty ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.deleteproperty JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property) { - auto* target = get_target_object_from(global_object, "deleteProperty"); - if (!target) + auto target = vm.argument(0); + auto property_key = vm.argument(1); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + } + + // 2. Let key be ? ToPropertyKey(propertyKey). + auto key = property_key.to_property_key(global_object); if (vm.exception()) return {}; - return Value(target->delete_property(property_key)); + + // 3. Return ? target.[[Delete]](key). + return Value(target.as_object().internal_delete(key)); } // 28.1.5 Reflect.get ( target, propertyKey [ , receiver ] ), https://tc39.es/ecma262/#sec-reflect.get JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get) { - auto* target = get_target_object_from(global_object, "get"); - if (!target) + auto target = vm.argument(0); + auto property_key = vm.argument(1); + auto receiver = vm.argument(2); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + } + + // 2. Let key be ? ToPropertyKey(propertyKey). + auto key = property_key.to_property_key(global_object); if (vm.exception()) return {}; - Value receiver = {}; - if (vm.argument_count() > 2) - receiver = vm.argument(2); - return target->get(property_key, receiver).value_or(js_undefined()); + + // 3. If receiver is not present, then + if (vm.argument_count() < 3) { + // a. Set receiver to target. + receiver = target; + } + + // 4. Return ? target.[[Get]](key, receiver). + return target.as_object().internal_get(key, receiver); } // 28.1.6 Reflect.getOwnPropertyDescriptor ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor) { - auto* target = get_target_object_from(global_object, "getOwnPropertyDescriptor"); - if (!target) + auto target = vm.argument(0); + auto property_key = vm.argument(1); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + } + + // 2. Let key be ? ToPropertyKey(propertyKey). + auto key = property_key.to_property_key(global_object); if (vm.exception()) return {}; - return target->get_own_property_descriptor_object(property_key); + + // 3. Let desc be ? target.[[GetOwnProperty]](key). + auto descriptor = target.as_object().internal_get_own_property(key); + if (vm.exception()) + return {}; + + // 4. Return FromPropertyDescriptor(desc). + return from_property_descriptor(global_object, descriptor); } // 28.1.7 Reflect.getPrototypeOf ( target ), https://tc39.es/ecma262/#sec-reflect.getprototypeof JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_prototype_of) { - auto* target = get_target_object_from(global_object, "getPrototypeOf"); - if (!target) + auto target = vm.argument(0); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - return target->prototype(); + } + + // 2. Return ? target.[[GetPrototypeOf]](). + return target.as_object().internal_get_prototype_of(); } // 28.1.8 Reflect.has ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.has JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has) { - auto* target = get_target_object_from(global_object, "has"); - if (!target) + auto target = vm.argument(0); + auto property_key = vm.argument(1); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + } + + // 2. Let key be ? ToPropertyKey(propertyKey). + auto key = property_key.to_property_key(global_object); if (vm.exception()) return {}; - return Value(target->has_property(property_key)); + + // 3. Return ? target.[[HasProperty]](key). + return Value(target.as_object().internal_has_property(key)); } // 28.1.9 Reflect.isExtensible ( target ), https://tc39.es/ecma262/#sec-reflect.isextensible JS_DEFINE_NATIVE_FUNCTION(ReflectObject::is_extensible) { - auto* target = get_target_object_from(global_object, "isExtensible"); - if (!target) + auto target = vm.argument(0); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - return Value(target->is_extensible()); + } + + // 2. Return ? target.[[IsExtensible]](). + return Value(target.as_object().internal_is_extensible()); } // 28.1.10 Reflect.ownKeys ( target ), https://tc39.es/ecma262/#sec-reflect.ownkeys JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys) { - auto* target = get_target_object_from(global_object, "ownKeys"); - if (!target) + auto target = vm.argument(0); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); + return {}; + } + + // 2. Let keys be ? target.[[OwnPropertyKeys]](). + auto keys = target.as_object().internal_own_property_keys(); + if (vm.exception()) return {}; - return Array::create_from(global_object, target->get_own_properties(PropertyKind::Key)); + + // 3. Return CreateArrayFromList(keys). + return Array::create_from(global_object, keys); } // 28.1.11 Reflect.preventExtensions ( target ), https://tc39.es/ecma262/#sec-reflect.preventextensions JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions) { - auto* target = get_target_object_from(global_object, "preventExtensions"); - if (!target) + auto target = vm.argument(0); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - return Value(target->prevent_extensions()); + } + + // 2. Return ? target.[[PreventExtensions]](). + return Value(target.as_object().internal_prevent_extensions()); } // 28.1.12 Reflect.set ( target, propertyKey, V [ , receiver ] ), https://tc39.es/ecma262/#sec-reflect.set JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set) { - auto* target = get_target_object_from(global_object, "set"); - if (!target) + auto target = vm.argument(0); + auto property_key = vm.argument(1); + auto value = vm.argument(2); + auto receiver = vm.argument(3); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto property_key = vm.argument(1).to_property_key(global_object); + } + + // 2. Let key be ? ToPropertyKey(propertyKey). + auto key = property_key.to_property_key(global_object); if (vm.exception()) return {}; - auto value = vm.argument(2); - Value receiver = {}; - if (vm.argument_count() > 3) - receiver = vm.argument(3); - return Value(target->put(property_key, value, receiver)); + + // 3. If receiver is not present, then + if (vm.argument_count() < 4) { + // a. Set receiver to target. + receiver = target; + } + + // 4. Return ? target.[[Set]](key, V, receiver). + return Value(target.as_object().internal_set(key, value, receiver)); } // 28.1.13 Reflect.setPrototypeOf ( target, proto ), https://tc39.es/ecma262/#sec-reflect.setprototypeof JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set_prototype_of) { - auto* target = get_target_object_from(global_object, "setPrototypeOf"); - if (!target) + auto target = vm.argument(0); + auto proto = vm.argument(1); + + // 1. If Type(target) is not Object, throw a TypeError exception. + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects()); return {}; - auto prototype_value = vm.argument(1); - if (!prototype_value.is_object() && !prototype_value.is_null()) { + } + + // 2. If Type(proto) is not Object and proto is not null, throw a TypeError exception. + if (!proto.is_object() && !proto.is_null()) { vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType); return {}; } - Object* prototype = nullptr; - if (!prototype_value.is_null()) - prototype = const_cast<Object*>(&prototype_value.as_object()); - return Value(target->set_prototype(prototype)); + + // 3. Return ? target.[[SetPrototypeOf]](proto). + return Value(target.as_object().internal_set_prototype_of(proto.is_null() ? nullptr : &proto.as_object())); } } diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp index a8ec9db24a..29cd6f1b99 100644 --- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp @@ -178,27 +178,27 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec) auto* array = Array::create(global_object, result.n_capture_groups + 1); if (vm.exception()) return {}; - array->define_property(vm.names.index, Value((i32)match.global_offset)); - array->define_property(vm.names.input, js_string(vm, str)); - array->indexed_properties().put(array, 0, js_string(vm, match.view.to_string())); + array->create_data_property_or_throw(vm.names.index, Value((i32)match.global_offset)); + array->create_data_property_or_throw(vm.names.input, js_string(vm, str)); + array->create_data_property_or_throw(0, js_string(vm, match.view.to_string())); for (size_t i = 0; i < result.n_capture_groups; ++i) { auto capture_value = js_undefined(); auto& capture = result.capture_group_matches[0][i + 1]; if (!capture.view.is_null()) capture_value = js_string(vm, capture.view.to_string()); - array->indexed_properties().put(array, i + 1, capture_value); + array->create_data_property_or_throw(i + 1, capture_value); } Value groups = js_undefined(); if (result.n_named_capture_groups > 0) { auto groups_object = Object::create(global_object, nullptr); for (auto& entry : result.named_capture_group_matches[0]) - groups_object->define_property(entry.key, js_string(vm, entry.value.view.to_string())); + groups_object->create_data_property_or_throw(entry.key, js_string(vm, entry.value.view.to_string())); groups = move(groups_object); } - array->define_property(vm.names.groups, groups); + array->create_data_property_or_throw(vm.names.groups, groups); return array; } diff --git a/Userland/Libraries/LibJS/Runtime/Shape.cpp b/Userland/Libraries/LibJS/Runtime/Shape.cpp index e0cf827d35..3ea10d6da5 100644 --- a/Userland/Libraries/LibJS/Runtime/Shape.cpp +++ b/Userland/Libraries/LibJS/Runtime/Shape.cpp @@ -207,8 +207,9 @@ void Shape::remove_property_from_unique_shape(const StringOrSymbol& property_nam } } -void Shape::add_property_without_transition(const StringOrSymbol& property_name, PropertyAttributes attributes) +void Shape::add_property_without_transition(StringOrSymbol const& property_name, PropertyAttributes attributes) { + VERIFY(property_name.is_valid()); ensure_property_table(); if (m_property_table->set(property_name, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) ++m_property_count; @@ -216,7 +217,8 @@ void Shape::add_property_without_transition(const StringOrSymbol& property_name, FLATTEN void Shape::add_property_without_transition(PropertyName const& property_name, PropertyAttributes attributes) { - add_property_without_transition(StringOrSymbol(property_name.as_string()), attributes); + VERIFY(property_name.is_valid()); + add_property_without_transition(property_name.to_string_or_symbol(), attributes); } } diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp index 52c300121b..36cdb96c78 100644 --- a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp @@ -98,7 +98,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw) StringBuilder builder; for (size_t i = 0; i < literal_segments; ++i) { auto next_key = String::number(i); - auto next_segment_value = raw->get(next_key).value_or(js_undefined()); + auto next_segment_value = raw->get(next_key); if (vm.exception()) return {}; auto next_segment = next_segment_value.to_string(global_object); diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.cpp b/Userland/Libraries/LibJS/Runtime/StringObject.cpp index cef42a85d6..046c14335d 100644 --- a/Userland/Libraries/LibJS/Runtime/StringObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringObject.cpp @@ -5,8 +5,10 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/PropertyDescriptor.h> #include <LibJS/Runtime/StringObject.h> namespace JS { @@ -40,18 +42,149 @@ void StringObject::visit_edges(Cell::Visitor& visitor) visitor.visit(&m_string); } -Optional<PropertyDescriptor> StringObject::get_own_property_descriptor(PropertyName const& property_name) const +// 10.4.3.5 StringGetOwnProperty ( S, P ),https://tc39.es/ecma262/#sec-stringgetownproperty +static Optional<PropertyDescriptor> string_get_own_property(GlobalObject& global_object, StringObject const& string, PropertyName const& property_name) { - if (!property_name.is_number() || property_name.as_number() >= m_string.string().length()) - return Base::get_own_property_descriptor(property_name); + auto& vm = global_object.vm(); - PropertyDescriptor descriptor; - descriptor.value = js_string(heap(), m_string.string().substring(property_name.as_number(), 1)); - descriptor.attributes.set_has_configurable(); - descriptor.attributes.set_has_enumerable(); - descriptor.attributes.set_has_writable(); - descriptor.attributes.set_enumerable(); - return descriptor; + // 1. Assert: S is an Object that has a [[StringData]] internal slot. + // 2. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 3. If Type(P) is not String, return undefined. + // NOTE: The spec only uses string and symbol keys, and later coerces to numbers - + // this is not the case for PropertyName, so '!property_name.is_string()' would be wrong. + if (property_name.is_symbol()) + return {}; + + // 4. Let index be ! CanonicalNumericIndexString(P). + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + Value index; + if (property_name.is_string()) + index = canonical_numeric_index_string(global_object, property_name.to_value(vm)); + else + index = Value(property_name.as_number()); + // 5. If index is undefined, return undefined. + if (index.is_undefined()) + return {}; + // 6. If IsIntegralNumber(index) is false, return undefined. + if (!index.is_integral_number()) + return {}; + // 7. If index is -0𝔽, return undefined. + if (index.is_negative_zero()) + return {}; + + // 8. Let str be S.[[StringData]]. + // 9. Assert: Type(str) is String. + auto& str = string.primitive_string().string(); + + // 10. Let len be the length of str. + auto length = str.length(); + + // 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined. + if (index.as_double() < 0 || length <= index.as_double()) + return {}; + + // 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index). + auto result_str = js_string(string.vm(), str.substring(index.as_double(), 1)); + + // 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }. + return PropertyDescriptor { + .value = result_str, + .writable = false, + .enumerable = true, + .configurable = false, + }; +} + +// 10.4.3.1 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p +Optional<PropertyDescriptor> StringObject::internal_get_own_property(PropertyName const& property_name) const +{ + // Assert: IsPropertyKey(P) is true. + + // 2. Let desc be OrdinaryGetOwnProperty(S, P). + auto descriptor = Object::internal_get_own_property(property_name); + + // 3. If desc is not undefined, return desc. + if (descriptor.has_value()) + return descriptor; + + // 4. Return ! StringGetOwnProperty(S, P). + return string_get_own_property(global_object(), *this, property_name); +} + +// 10.4.3.2 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc +bool StringObject::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor) +{ + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Let stringDesc be ! StringGetOwnProperty(S, P). + auto string_descriptor = string_get_own_property(global_object(), *this, property_name); + + // 3. If stringDesc is not undefined, then + if (string_descriptor.has_value()) { + // a. Let extensible be S.[[Extensible]]. + auto extensible = m_is_extensible; + + // b. Return ! IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc). + return is_compatible_property_descriptor(extensible, property_descriptor, string_descriptor); + } + + // 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc). + return Object::internal_define_own_property(property_name, property_descriptor); +} + +// 10.4.3.3 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys +MarkedValueList StringObject::internal_own_property_keys() const +{ + auto& vm = this->vm(); + + // 1. Let keys be a new empty List. + auto keys = MarkedValueList { heap() }; + + // 2. Let str be O.[[StringData]]. + auto& str = m_string.string(); + + // 3. Assert: Type(str) is String. + + // 4. Let len be the length of str. + auto length = str.length(); + + // 5. For each integer i starting with 0 such that i < len, in ascending order, do + for (size_t i = 0; i < length; ++i) { + // a. Add ! ToString(𝔽(i)) as the last element of keys. + keys.append(js_string(vm, String::number(i))); + } + + // 6. For each own property key P of O such that P is an array index and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do + for (auto& entry : indexed_properties()) { + if (entry.index() >= length) { + // a. Add P as the last element of keys. + keys.append(js_string(vm, String::number(entry.index()))); + } + } + + // 7. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_string()) { + // a. Add P as the last element of keys. + keys.append(it.key.to_value(vm)); + } + } + + // 8. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_symbol()) { + // a. Add P as the last element of keys. + keys.append(it.key.to_value(vm)); + } + } + + // 9. Return keys. + return keys; } } diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.h b/Userland/Libraries/LibJS/Runtime/StringObject.h index d05d171c7e..2fd5fb77e6 100644 --- a/Userland/Libraries/LibJS/Runtime/StringObject.h +++ b/Userland/Libraries/LibJS/Runtime/StringObject.h @@ -27,9 +27,12 @@ public: } private: + virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const&) const override; + virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor const&) override; + virtual MarkedValueList internal_own_property_keys() const override; + virtual bool is_string_object() const final { return true; } virtual void visit_edges(Visitor&) override; - virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName const&) const override; PrimitiveString& m_string; }; diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index dbd76c07d3..4e443d02cb 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -596,7 +596,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) size_t result_len = 0; auto limit = NumericLimits<u32>::max(); - if (!vm.argument(1).is_undefined()) { + if (!limit_argument.is_undefined()) { limit = limit_argument.to_u32(global_object); if (vm.exception()) return {}; @@ -609,8 +609,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) if (limit == 0) return result; - if (vm.argument(0).is_undefined()) { - result->define_property(0, js_string(vm, string)); + if (separator_argument.is_undefined()) { + result->create_data_property_or_throw(0, js_string(vm, string)); return result; } @@ -618,7 +618,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) auto separator_len = separator.length(); if (len == 0) { if (separator_len > 0) - result->define_property(0, js_string(vm, string)); + result->create_data_property_or_throw(0, js_string(vm, string)); return result; } @@ -638,7 +638,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) } auto segment = string.substring_view(start, pos - start); - result->define_property(result_len, js_string(vm, segment)); + result->create_data_property_or_throw(result_len, js_string(vm, segment)); result_len++; if (result_len == limit) return result; @@ -647,7 +647,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) } auto rest = string.substring(start, len - start); - result->define_property(result_len, js_string(vm, rest)); + result->create_data_property_or_throw(result_len, js_string(vm, rest)); return result; } @@ -731,6 +731,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match) if (!regexp.is_nullish()) { if (auto* matcher = regexp.get_method(global_object, *vm.well_known_symbol_match())) return vm.call(*matcher, regexp, this_object); + if (vm.exception()) + return {}; } auto s = this_object.to_string(global_object); if (vm.exception()) @@ -793,6 +795,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace) if (!search_value.is_nullish()) { if (auto* replacer = search_value.get_method(global_object, *vm.well_known_symbol_replace())) return vm.call(*replacer, search_value, this_object, replace_value); + if (vm.exception()) + return {}; } auto string = this_object.to_string(global_object); diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp index 61154f7362..efd5a145e7 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp +++ b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp @@ -96,6 +96,8 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object, } auto element_length = src_array.array_length(); + auto src_element_size = src_array.element_size(); + auto src_byte_offset = src_array.byte_offset(); auto element_size = dest_array.element_size(); Checked<size_t> byte_length = element_size; byte_length *= element_length; @@ -104,6 +106,7 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object, return; } + // FIXME: Determine and use bufferConstructor auto data = ArrayBuffer::create(global_object, byte_length.value()); if (src_data->is_detached()) { @@ -116,26 +119,19 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object, return; } + u64 src_byte_index = src_byte_offset; + u64 target_byte_index = 0; + for (u32 i = 0; i < element_length; ++i) { + auto value = src_array.get_value_from_buffer(src_byte_index, ArrayBuffer::Order::Unordered); + data->template set_value<T>(target_byte_index, value, true, ArrayBuffer::Order::Unordered); + src_byte_index += src_element_size; + target_byte_index += element_size; + } + dest_array.set_viewed_array_buffer(data); dest_array.set_byte_length(byte_length.value()); dest_array.set_byte_offset(0); dest_array.set_array_length(element_length); - - for (u32 i = 0; i < element_length; i++) { - Value v; -#undef __JS_ENUMERATE -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ - if (is<JS::ClassName>(src_array)) { \ - auto& src = static_cast<JS::ClassName&>(src_array); \ - v = src.get_by_index(i); \ - } - JS_ENUMERATE_TYPED_ARRAYS -#undef __JS_ENUMERATE - - VERIFY(!v.is_empty()); - - dest_array.put_by_index(i, v); - } } // 23.2.5.1.5 InitializeTypedArrayFromArrayLike, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike @@ -160,10 +156,10 @@ static void initialize_typed_array_from_array_like(GlobalObject& global_object, typed_array.set_array_length(length); for (size_t k = 0; k < length; k++) { - auto value = array_like.get(k).value_or(js_undefined()); + auto value = array_like.get(k); if (vm.exception()) return; - typed_array.put_by_index(k, value); + typed_array.set(k, value, true); if (vm.exception()) return; } @@ -188,7 +184,7 @@ static void initialize_typed_array_from_list(GlobalObject& global_object, TypedA auto& vm = global_object.vm(); for (size_t k = 0; k < list.size(); k++) { auto value = list[k]; - typed_array.put_by_index(k, value); + typed_array.set(k, value, true); if (vm.exception()) return; } diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.h b/Userland/Libraries/LibJS/Runtime/TypedArray.h index c5ef9a28e7..c0db745a3e 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArray.h +++ b/Userland/Libraries/LibJS/Runtime/TypedArray.h @@ -1,13 +1,17 @@ /* * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PropertyDescriptor.h> +#include <LibJS/Runtime/PropertyName.h> #include <LibJS/Runtime/TypedArrayConstructor.h> #include <LibJS/Runtime/VM.h> @@ -57,6 +61,114 @@ private: virtual void visit_edges(Visitor&) override; }; +// 10.4.5.9 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex +inline bool is_valid_integer_index(TypedArrayBase const& typed_array, Value property_index) +{ + if (typed_array.viewed_array_buffer()->is_detached()) + return false; + + // TODO: This can be optimized by skipping the following 3 out of 4 checks if property_index + // came from a number-type PropertyName instead of a canonicalized string-type PropertyName + + // If ! IsIntegralNumber(index) is false, return false. + if (!property_index.is_integral_number()) + return false; + // If index is -0𝔽, return false. + if (property_index.is_negative_zero()) + return false; + + // If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false. + if (property_index.as_double() < 0 || property_index.as_double() >= typed_array.array_length()) + return false; + + return true; +} + +// 10.4.5.10 IntegerIndexedElementGet ( O, index ), https://tc39.es/ecma262/#sec-integerindexedelementget +template<typename T> +inline Value integer_indexed_element_get(TypedArrayBase const& typed_array, Value property_index) +{ + // 1. Assert: O is an Integer-Indexed exotic object. + + // 2. If ! IsValidIntegerIndex(O, index) is false, return undefined. + if (!is_valid_integer_index(typed_array, property_index)) + return js_undefined(); + + // 3. Let offset be O.[[ByteOffset]]. + auto offset = typed_array.byte_offset(); + + // 4. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // 5. Let elementSize be the Element Size value specified in Table 64 for arrayTypeName. + // 6. Let indexedPosition be (ℝ(index) × elementSize) + offset. + Checked<size_t> indexed_position = (i64)property_index.as_double(); + indexed_position *= typed_array.element_size(); + indexed_position += offset; + // FIXME: Not exactly sure what we should do when overflow occurs. + // Just return as if it's an invalid index for now. + if (indexed_position.has_overflow()) { + dbgln("integer_indexed_element_get(): indexed_position overflowed, returning as if it's an invalid index."); + return js_undefined(); + } + + // 7. Let elementType be the Element Type value in Table 64 for arrayTypeName. + // 8. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). + return typed_array.viewed_array_buffer()->template get_value<T>(indexed_position.value(), true, ArrayBuffer::Order::Unordered); +} + +// 10.4.5.11 IntegerIndexedElementSet ( O, index, value ), https://tc39.es/ecma262/#sec-integerindexedelementset +// NOTE: In error cases, the function will return as if it succeeded. +template<typename T> +inline void integer_indexed_element_set(TypedArrayBase& typed_array, Value property_index, Value value) +{ + VERIFY(!value.is_empty()); + auto& vm = typed_array.vm(); + auto& global_object = typed_array.global_object(); + + // 1. Assert: O is an Integer-Indexed exotic object. + + Value num_value; + + // 2. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). + if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) { + num_value = value.to_bigint(global_object); + if (vm.exception()) + return; + } + // 3. Otherwise, let numValue be ? ToNumber(value). + else { + num_value = value.to_number(global_object); + if (vm.exception()) + return; + } + + // 4. If ! IsValidIntegerIndex(O, index) is true, then + // NOTE: Inverted for flattened logic. + if (!is_valid_integer_index(typed_array, property_index)) + return; + + // a. Let offset be O.[[ByteOffset]]. + auto offset = typed_array.byte_offset(); + + // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // c. Let elementSize be the Element Size value specified in Table 64 for arrayTypeName. + // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. + Checked<size_t> indexed_position = (i64)property_index.as_double(); + indexed_position *= typed_array.element_size(); + indexed_position += offset; + // FIXME: Not exactly sure what we should do when overflow occurs. + // Just return as if it succeeded for now. + if (indexed_position.has_overflow()) { + dbgln("integer_indexed_element_set(): indexed_position overflowed, returning as if succeeded."); + return; + } + + // e. Let elementType be the Element Type value in Table 64 for arrayTypeName. + // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). + typed_array.viewed_array_buffer()->template set_value<T>(indexed_position.value(), num_value, true, ArrayBuffer::Order::Unordered); + + // 5. Return NormalCompletion(undefined). +} + template<typename T> class TypedArray : public TypedArrayBase { JS_OBJECT(TypedArray, TypedArrayBase); @@ -64,72 +176,286 @@ class TypedArray : public TypedArrayBase { using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>; public: - // 10.4.5.11 IntegerIndexedElementSet ( O, index, value ), https://tc39.es/ecma262/#sec-integerindexedelementset - // NOTE: In error cases, the function will return as if it succeeded. - virtual bool put_by_index(u32 property_index, Value value) override + // 10.4.5.1 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p + virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const& property_name) const override { - auto& vm = this->vm(); - auto& global_object = this->global_object(); - - Value num_value; - if (content_type() == TypedArrayBase::ContentType::BigInt) { - num_value = value.to_bigint(global_object); - if (vm.exception()) - return {}; - } else { - num_value = value.to_number(global_object); - if (vm.exception()) - return {}; + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Assert: O is an Integer-Indexed exotic object. + + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + + // 3. If Type(P) is String, then + // NOTE: This includes an implementation-defined optimization, see note above! + if (property_name.is_string() || property_name.is_number()) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // NOTE: This includes an implementation-defined optimization, see note above! + Value numeric_index; + if (property_name.is_string()) + numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm())); + else + numeric_index = Value(property_name.as_number()); + // b. If numericIndex is not undefined, then + if (!numeric_index.is_undefined()) { + // i. Let value be ! IntegerIndexedElementGet(O, numericIndex). + auto value = integer_indexed_element_get<T>(*this, numeric_index); + + // ii. If value is undefined, return undefined. + if (value.is_undefined()) + return {}; + + // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + return PropertyDescriptor { + .value = value, + .writable = true, + .enumerable = true, + .configurable = true, + }; + } } - if (!is_valid_integer_index(property_index)) - return true; + // 4. Return OrdinaryGetOwnProperty(O, P). + return Object::internal_get_own_property(property_name); + } - auto offset = byte_offset(); + // 10.4.5.2 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p + virtual bool internal_has_property(PropertyName const& property_name) const override + { + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Assert: O is an Integer-Indexed exotic object. + + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + + // 3. If Type(P) is String, then + // NOTE: This includes an implementation-defined optimization, see note above! + if (property_name.is_string() || property_name.is_number()) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // NOTE: This includes an implementation-defined optimization, see note above! + Value numeric_index; + if (property_name.is_string()) + numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm())); + else + numeric_index = Value(property_name.as_number()); + // b. If numericIndex is not undefined, return ! IsValidIntegerIndex(O, numericIndex). + if (!numeric_index.is_undefined()) + return is_valid_integer_index(*this, numeric_index); + } - // FIXME: Not exactly sure what we should do when overflow occurs. - // Just return as if it succeeded for now. - Checked<size_t> indexed_position = property_index; - indexed_position *= sizeof(UnderlyingBufferDataType); - indexed_position += offset; - if (indexed_position.has_overflow()) { - dbgln("TypedArray::put_by_index: indexed_position overflowed, returning as if succeeded."); - return true; + // 4. Return ? OrdinaryHasProperty(O, P). + return Object::internal_has_property(property_name); + } + + // 10.4.5.3 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc + virtual bool internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor) override + { + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Assert: O is an Integer-Indexed exotic object. + + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + + // 3. If Type(P) is String, then + // NOTE: This includes an implementation-defined optimization, see note above! + if (property_name.is_string() || property_name.is_number()) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // NOTE: This includes an implementation-defined optimization, see note above! + Value numeric_index; + if (property_name.is_string()) + numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm())); + else + numeric_index = Value(property_name.as_number()); + // b. If numericIndex is not undefined, then + if (!numeric_index.is_undefined()) { + // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false. + if (!is_valid_integer_index(*this, numeric_index)) + return false; + + // ii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, return false. + if (property_descriptor.configurable.has_value() && !*property_descriptor.configurable) + return false; + + // iii. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] is false, return false. + if (property_descriptor.enumerable.has_value() && !*property_descriptor.enumerable) + return false; + + // iv. If ! IsAccessorDescriptor(Desc) is true, return false. + if (property_descriptor.is_accessor_descriptor()) + return false; + + // v. If Desc has a [[Writable]] field and if Desc.[[Writable]] is false, return false. + if (property_descriptor.writable.has_value() && !*property_descriptor.writable) + return false; + + // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). + if (property_descriptor.value.has_value()) { + integer_indexed_element_set<T>(*this, numeric_index, *property_descriptor.value); + if (vm().exception()) + return {}; + } + + // vii. Return true. + return true; + } } - viewed_array_buffer()->template set_value<T>(indexed_position.value(), num_value, true, ArrayBuffer::Order::Unordered); + // 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc). + return Object::internal_define_own_property(property_name, property_descriptor); + } - return true; + // 10.4.5.4 [[Get]] ( P, Receiver ), 10.4.5.4 [[Get]] ( P, Receiver ) + virtual Value internal_get(PropertyName const& property_name, Value receiver) const override + { + VERIFY(!receiver.is_empty()); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + + // 2. If Type(P) is String, then + // NOTE: This includes an implementation-defined optimization, see note above! + if (property_name.is_string() || property_name.is_number()) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // NOTE: This includes an implementation-defined optimization, see note above! + Value numeric_index; + if (property_name.is_string()) + numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm())); + else + numeric_index = Value(property_name.as_number()); + // b. If numericIndex is not undefined, then + if (!numeric_index.is_undefined()) { + // i. Return ! IntegerIndexedElementGet(O, numericIndex). + return integer_indexed_element_get<T>(*this, numeric_index); + } + } + + // 3. Return ? OrdinaryGet(O, P, Receiver). + return Object::internal_get(property_name, receiver); } - // 10.4.5.10 IntegerIndexedElementGet ( O, index ), https://tc39.es/ecma262/#sec-integerindexedelementget - virtual Value get_by_index(u32 property_index, AllowSideEffects = AllowSideEffects::Yes) const override + // 10.4.5.5 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver + virtual bool internal_set(PropertyName const& property_name, Value value, Value receiver) override { - if (!is_valid_integer_index(property_index)) - return js_undefined(); - - auto offset = byte_offset(); - - // FIXME: Not exactly sure what we should do when overflow occurs. - // Just return as if it's an invalid index for now. - Checked<size_t> indexed_position = property_index; - indexed_position *= sizeof(UnderlyingBufferDataType); - indexed_position += offset; - if (indexed_position.has_overflow()) { - dbgln("TypedArray::get_by_index: indexed_position overflowed, returning as if it's an invalid index."); - return js_undefined(); + VERIFY(!value.is_empty()); + VERIFY(!receiver.is_empty()); + + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + + // 2. If Type(P) is String, then + // NOTE: This includes an implementation-defined optimization, see note above! + if (property_name.is_string() || property_name.is_number()) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // NOTE: This includes an implementation-defined optimization, see note above! + Value numeric_index; + if (property_name.is_string()) + numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm())); + else + numeric_index = Value(property_name.as_number()); + // b. If numericIndex is not undefined, then + if (!numeric_index.is_undefined()) { + // i. Perform ? IntegerIndexedElementSet(O, numericIndex, V). + integer_indexed_element_set<T>(*this, numeric_index, value); + if (vm().exception()) + return {}; + + // ii. Return true. + return true; + } } - return viewed_array_buffer()->template get_value<T>(indexed_position.value(), true, ArrayBuffer::Order::Unordered); + // 3. Return ? OrdinarySet(O, P, V, Receiver). + return Object::internal_set(property_name, value, receiver); } - // 10.4.5.2 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p - bool has_property(const PropertyName& name) const override + // 10.4.5.6 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p + virtual bool internal_delete(PropertyName const& property_name) override { - if (name.is_number()) { - return is_valid_integer_index(name.as_number()); + // 1. Assert: IsPropertyKey(P) is true. + VERIFY(property_name.is_valid()); + + // 2. Assert: O is an Integer-Indexed exotic object. + + // NOTE: If the property name is a number type (An implementation-defined optimized + // property key type), it can be treated as a string property that has already been + // converted successfully into a canonical numeric index. + + // 3. If Type(P) is String, then + // NOTE: This includes an implementation-defined optimization, see note above! + if (property_name.is_string() || property_name.is_number()) { + // a. Let numericIndex be ! CanonicalNumericIndexString(P). + // NOTE: This includes an implementation-defined optimization, see note above! + Value numeric_index; + if (property_name.is_string()) + numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm())); + else + numeric_index = Value(property_name.as_number()); + // b. If numericIndex is not undefined, then + if (!numeric_index.is_undefined()) { + // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. + if (!is_valid_integer_index(*this, numeric_index)) + return true; + return false; + } + } + + // 4. Return ? OrdinaryDelete(O, P). + return Object::internal_delete(property_name); + } + + // 10.4.5.7 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys + virtual MarkedValueList internal_own_property_keys() const override + { + auto& vm = this->vm(); + + // 1. Let keys be a new empty List. + auto keys = MarkedValueList { heap() }; + + // 2. Assert: O is an Integer-Indexed exotic object. + + // 3. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then + if (!m_viewed_array_buffer->is_detached()) { + // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do + for (size_t i = 0; i < m_array_length; ++i) { + // i. Add ! ToString(𝔽(i)) as the last element of keys. + keys.append(js_string(vm, String::number(i))); + } + } + + // 4. For each own property key P of O such that Type(P) is String and P is not an integer index, in ascending chronological order of property creation, do + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_string()) { + // a. Add P as the last element of keys. + keys.append(it.key.to_value(vm)); + } } - return Object::has_property(name); + + // 5. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do + for (auto& it : shape().property_table_ordered()) { + if (it.key.is_symbol()) { + // a. Add P as the last element of keys. + keys.append(it.key.to_value(vm)); + } + } + + // 6. Return keys. + return keys; } Span<const UnderlyingBufferDataType> data() const @@ -160,22 +486,6 @@ protected: private: virtual bool is_typed_array() const final { return true; } - - // 10.4.5.9 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex - bool is_valid_integer_index(u32 property_index) const - { - if (viewed_array_buffer()->is_detached()) - return false; - - // FIXME: If ! IsIntegralNumber(index) is false, return false. - - // FIXME: If index is -0𝔽, return false. - - if (property_index >= m_array_length /* FIXME: or less than 0 (index is currently unsigned) */) - return false; - - return true; - } }; #define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp index 0ceae521d0..0ba42530a6 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.cpp +++ b/Userland/Libraries/LibJS/Runtime/VM.cpp @@ -294,7 +294,7 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global auto* rest_object = Object::create(global_object, global_object.object_prototype()); for (auto& object_property : object->shape().property_table()) { - if (!object_property.value.attributes.has_enumerable()) + if (!object_property.value.attributes.is_enumerable()) continue; if (seen_names.contains(object_property.key.to_display_string())) continue; @@ -386,10 +386,13 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object) return possible_match.value().value; } } - auto value = global_object.get(name); - if (m_underscore_is_last_value && name == "_" && value.is_empty()) - return m_last_value; - return value; + + if (!global_object.storage_has(name)) { + if (m_underscore_is_last_value && name == "_") + return m_last_value; + return {}; + } + return global_object.get(name); } // 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference @@ -472,9 +475,6 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option callee_context.this_value = this_argument; auto result = function.construct(new_target); - Value this_value = this_argument; - if (auto* environment = callee_context.lexical_environment) - this_value = environment->get_this_binding(global_object); pop_execution_context(); pop_guard.disarm(); @@ -487,7 +487,7 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option if (exception()) return {}; if (prototype.is_object()) { - result.as_object().set_prototype(&prototype.as_object()); + result.as_object().internal_set_prototype_of(&prototype.as_object()); if (exception()) return {}; } @@ -500,7 +500,9 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option if (result.is_object()) return result; - return this_value; + if (auto* environment = callee_context.lexical_environment) + return environment->get_this_binding(global_object); + return this_argument; } void VM::throw_exception(Exception& exception) diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp index a622beb72c..e626eb401a 100644 --- a/Userland/Libraries/LibJS/Runtime/Value.cpp +++ b/Userland/Libraries/LibJS/Runtime/Value.cpp @@ -258,7 +258,7 @@ bool Value::is_regexp(GlobalObject& global_object) const auto matcher = as_object().get(*vm.well_known_symbol_match()); if (vm.exception()) return false; - if (!matcher.is_empty() && !matcher.is_undefined()) + if (!matcher.is_undefined()) return matcher.to_boolean(); return is<RegExpObject>(as_object()); @@ -807,7 +807,7 @@ Value Value::get(GlobalObject& global_object, PropertyName const& property_name) return {}; // 3. Return ? O.[[Get]](P, V). - return object->get(property_name, *this); + return object->internal_get(property_name, *this); } // 7.3.10 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod @@ -819,7 +819,7 @@ FunctionObject* Value::get_method(GlobalObject& global_object, PropertyName cons VERIFY(property_name.is_valid()); // 2. Let func be ? GetV(V, P). - auto function = get(global_object, property_name).value_or(js_undefined()); + auto function = get(global_object, property_name); if (vm.exception()) return nullptr; @@ -1252,6 +1252,7 @@ Value instance_of(GlobalObject& global_object, Value lhs, Value rhs) return ordinary_has_instance(global_object, lhs, rhs); } +// 7.3.21 OrdinaryHasInstance ( C, O ), https://tc39.es/ecma262/#sec-ordinaryhasinstance Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs) { auto& vm = global_object.vm(); @@ -1277,7 +1278,7 @@ Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs) return {}; } while (true) { - lhs_object = lhs_object->prototype(); + lhs_object = lhs_object->internal_get_prototype_of(); if (vm.exception()) return {}; if (!lhs_object) diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js index 644091c90c..84259b9258 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js @@ -167,10 +167,7 @@ describe("errors", () => { expect(() => { Object.defineProperty(o, "foo", { value: 2, writable: true, enumerable: false }); - }).toThrowWithMessage( - TypeError, - "Cannot change attributes of non-configurable property 'foo'" - ); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); }); test("redefine non-configurable symbol property", () => { @@ -180,10 +177,7 @@ describe("errors", () => { expect(() => { Object.defineProperty(o, s, { value: 2, writable: true, enumerable: false }); - }).toThrowWithMessage( - TypeError, - "Cannot change attributes of non-configurable property 'Symbol(foo)'" - ); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); }); test("cannot define 'value' and 'get' in the same descriptor", () => { @@ -234,9 +228,6 @@ describe("errors", () => { return this.secret_foo + 2; }, }); - }).toThrowWithMessage( - TypeError, - "Cannot change attributes of non-configurable property 'foo'" - ); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js index c523614873..f8b7a89f69 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js @@ -34,10 +34,7 @@ describe("normal behavior", () => { // expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o); expect(() => { Object.defineProperty(o, "foo", { configurable: true }); - }).toThrowWithMessage( - TypeError, - "Cannot change attributes of non-configurable property 'foo'" - ); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); }); test("prevents changing value of existing properties", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js index 292c3f5999..165e86f414 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js @@ -47,7 +47,7 @@ describe("errors", () => { expect(() => { Object.defineProperty(o, "baz", { value: "baz" }); - }).toThrowWithMessage(TypeError, "Cannot define property baz on non-extensible object"); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); expect(o.baz).toBeUndefined(); }); @@ -59,7 +59,7 @@ describe("errors", () => { expect(() => { "use strict"; o.foo = "foo"; - }).toThrowWithMessage(TypeError, "Cannot define property foo on non-extensible object"); + }).toThrowWithMessage(TypeError, "Cannot set property 'foo' of [object Object]"); expect((o.foo = "foo")).toBe("foo"); expect(o.foo).toBeUndefined(); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js index 1df167766d..d48b0bd9f6 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js @@ -34,10 +34,7 @@ describe("normal behavior", () => { // expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o); expect(() => { Object.defineProperty(o, "foo", { configurable: true }); - }).toThrowWithMessage( - TypeError, - "Cannot change attributes of non-configurable property 'foo'" - ); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); }); test("doesn't prevent changing value of existing properties", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js index 2044ea8084..f933566593 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js @@ -77,7 +77,7 @@ describe("[[DefineProperty]] invariants", () => { expect(() => { Object.defineProperty(p, "foo", {}); - }).toThrowWithMessage(TypeError, "Object's [[DefineProperty]] method returned false"); + }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false"); }); test("trap cannot return true for a non-extensible target if the property does not exist", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js index 504a95bba0..d8787b9b6a 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => { expect(() => { Reflect.apply(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.apply() must be a function" - ); + }).toThrowWithMessage(TypeError, `${value} is not a function`); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js index 27529f296d..c0313392bb 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js @@ -3,14 +3,11 @@ test("length is 2", () => { }); describe("errors", () => { - test("target must be a function", () => { + test("target must be a constructor", () => { [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => { expect(() => { Reflect.construct(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.construct() must be a constructor" - ); + }).toThrowWithMessage(TypeError, `${value} is not a constructor`); }); }); @@ -22,14 +19,11 @@ describe("errors", () => { }); }); - test("new target must be a function", () => { + test("new target must be a constructor", () => { [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => { expect(() => { Reflect.construct(() => {}, [], value); - }).toThrowWithMessage( - TypeError, - "Optional third argument of Reflect.construct() must be a constructor" - ); + }).toThrowWithMessage(TypeError, `${value} is not a constructor`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js index 786d4c0178..c81da19678 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.defineProperty(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.defineProperty() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); @@ -18,7 +15,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.defineProperty({}, "foo", value); - }).toThrowWithMessage(TypeError, "Descriptor argument is not an object"); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js index a0dcbbada7..beb95eff0e 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.deleteProperty(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.deleteProperty() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js index b26e9fe888..0b649a3d25 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js @@ -7,7 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.get(value); - }).toThrowWithMessage(TypeError, "First argument of Reflect.get() must be an object"); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js index e343a3f6fc..bb611edecb 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.getOwnPropertyDescriptor(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.getOwnPropertyDescriptor() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js index 87c9a63e87..ddc15d2c96 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.getPrototypeOf(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.getPrototypeOf() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js index f343daeafa..72f617b4fb 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js @@ -7,7 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.has(value); - }).toThrowWithMessage(TypeError, "First argument of Reflect.has() must be an object"); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js index 2f873da66c..c4baf5f552 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.isExtensible(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.isExtensible() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js index 53ca14796f..7839584391 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.ownKeys(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.ownKeys() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js index 6b0bfdc1bc..dab332e122 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js @@ -3,10 +3,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.preventExtensions(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.preventExtensions() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js index 854fb23888..50635f33d3 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js @@ -7,7 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.set(value); - }).toThrowWithMessage(TypeError, "First argument of Reflect.set() must be an object"); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js index 70bbdf9d9c..75424daf39 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js @@ -7,10 +7,7 @@ describe("errors", () => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => { expect(() => { Reflect.setPrototypeOf(value); - }).toThrowWithMessage( - TypeError, - "First argument of Reflect.setPrototypeOf() must be an object" - ); + }).toThrowWithMessage(TypeError, `${value} is not an object`); }); }); diff --git a/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js b/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js index ece57fe404..bc8dbe3d8c 100644 --- a/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js +++ b/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js @@ -13,7 +13,7 @@ test("extending null", () => { expect(() => { new A(); - }).toThrowWithMessage(ReferenceError, "|this| has not been initialized"); + }).toThrowWithMessage(TypeError, "Super constructor is not a constructor"); }); test("extending String", () => { diff --git a/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js b/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js index 2540beaf68..c0808ce0fc 100644 --- a/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js +++ b/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js @@ -1,8 +1,8 @@ test("basic functionality", () => { function Foo() {} - Foo[Symbol.hasInstance] = value => { - return value === 2; - }; + Object.defineProperty(Foo, Symbol.hasInstance, { + value: instance => instance === 2, + }); expect(new Foo() instanceof Foo).toBeFalse(); expect(2 instanceof Foo).toBeTrue(); diff --git a/Userland/Libraries/LibTest/JavaScriptTestRunner.h b/Userland/Libraries/LibTest/JavaScriptTestRunner.h index 940ad41e54..0c04276d7b 100644 --- a/Userland/Libraries/LibTest/JavaScriptTestRunner.h +++ b/Userland/Libraries/LibTest/JavaScriptTestRunner.h @@ -357,7 +357,7 @@ inline JSFileResult TestRunner::run_file_test(const String& test_path) // Collect logged messages auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array(); for (auto& entry : arr.indexed_properties()) { - auto message = entry.value_and_attributes(&interpreter->global_object()).value; + auto message = arr.get(entry.index()); file_result.logged_messages.append(message.to_string_without_side_effects()); } diff --git a/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp b/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp index 8de111ab1b..c265cce00a 100644 --- a/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp +++ b/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp @@ -11,12 +11,12 @@ namespace Web::Bindings { -JS::Value CSSStyleDeclarationWrapper::get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const +JS::Value CSSStyleDeclarationWrapper::internal_get(const JS::PropertyName& name, JS::Value receiver) 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, allow_side_effects); + return Base::internal_get(name, receiver); for (auto& property : impl().properties()) { if (property.property_id == property_id) return js_string(vm(), property.value->to_string()); @@ -24,12 +24,12 @@ JS::Value CSSStyleDeclarationWrapper::get(const JS::PropertyName& name, JS::Valu return js_string(vm(), String::empty()); } -bool CSSStyleDeclarationWrapper::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) +bool CSSStyleDeclarationWrapper::internal_set(const JS::PropertyName& name, JS::Value value, JS::Value receiver) { // 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::put(name, value, receiver); + return Base::internal_set(name, value, receiver); auto css_text = value.to_string(global_object()); if (vm().exception()) diff --git a/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp b/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp index 950ddb1d06..5c51e06203 100644 --- a/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp +++ b/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp @@ -12,21 +12,17 @@ namespace Web::Bindings { -JS::Value HTMLCollectionWrapper::get(JS::PropertyName const& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const +JS::Value HTMLCollectionWrapper::internal_get(JS::PropertyName const& property_name, JS::Value receiver) const { - if (!name.is_string()) - return Base::get(name, receiver, allow_side_effects); - auto* item = const_cast<DOM::HTMLCollection&>(impl()).named_item(name.to_string()); + if (property_name.is_symbol()) + return Base::internal_get(property_name, receiver); + DOM::Element* item = nullptr; + if (property_name.is_string()) + item = const_cast<DOM::HTMLCollection&>(impl()).named_item(property_name.to_string()); + else if (property_name.is_number()) + item = const_cast<DOM::HTMLCollection&>(impl()).item(property_name.as_number()); if (!item) - return Base::get(name, receiver, allow_side_effects); - return JS::Value { wrap(global_object(), *item) }; -} - -JS::Value HTMLCollectionWrapper::get_by_index(u32 property_index, JS::AllowSideEffects allow_side_effects) const -{ - auto* item = const_cast<DOM::HTMLCollection&>(impl()).item(property_index); - if (!item) - return Base::get_by_index(property_index, allow_side_effects); + return Base::internal_get(property_name, receiver); return wrap(global_object(), *item); } diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp index b5c54ef232..5029fd2cdf 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp +++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -43,7 +43,8 @@ void WindowObject::initialize_global_object() { Base::initialize_global_object(); - Object::set_prototype(&ensure_web_prototype<EventTargetPrototype>("EventTarget")); + auto success = Object::internal_set_prototype_of(&ensure_web_prototype<EventTargetPrototype>("EventTarget")); + VERIFY(success); define_property("window", this, JS::Attribute::Enumerable); define_property("frames", this, JS::Attribute::Enumerable); diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl index 6b5ad0ffce..d4f425e9b4 100644 --- a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl +++ b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl @@ -1,4 +1,4 @@ -[CustomGet,CustomPut] +[CustomGet,CustomSet] interface CSSStyleDeclaration { readonly attribute unsigned long length; diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp index e32366e33d..381c369007 100644 --- a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp +++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp @@ -786,17 +786,12 @@ public: if (interface.extended_attributes.contains("CustomGet")) { generator.append(R"~~~( - virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override; + virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override; )~~~"); } - if (interface.extended_attributes.contains("CustomGetByIndex")) { + if (interface.extended_attributes.contains("CustomSet")) { generator.append(R"~~~( - virtual JS::Value get_by_index(u32 property_index, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override; -)~~~"); - } - if (interface.extended_attributes.contains("CustomPut")) { - generator.append(R"~~~( - virtual bool put(const JS::PropertyName&, JS::Value, JS::Value receiver = {}) override; + virtual bool internal_set(const JS::PropertyName&, JS::Value, JS::Value receiver) override; )~~~"); } @@ -911,7 +906,8 @@ namespace Web::Bindings { @wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl) : @wrapper_base_class@(global_object, impl) { - set_prototype(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@")); + auto success = internal_set_prototype_of(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@")); + VERIFY(success); } )~~~"); } @@ -1277,11 +1273,13 @@ namespace Web::Bindings { // https://heycam.github.io/webidl/#es-DOMException-specialness // Object.getPrototypeOf(DOMException.prototype) === Error.prototype generator.append(R"~~~( - set_prototype(global_object.error_prototype()); + auto success = internal_set_prototype_of(global_object.error_prototype()); + VERIFY(success); )~~~"); } else if (!interface.parent_name.is_empty()) { generator.append(R"~~~( - set_prototype(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_base_class@>("@parent_name@")); + auto success = internal_set_prototype_of(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_base_class@>("@parent_name@")); + VERIFY(success); )~~~"); } diff --git a/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl b/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl index 89cc874c8a..73bcf070f1 100644 --- a/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl +++ b/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl @@ -1,4 +1,4 @@ -[CustomGet,CustomGetByIndex] +[CustomGet] interface HTMLCollection { readonly attribute unsigned long length; diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp index 603550da79..730e2cf12b 100644 --- a/Userland/Utilities/js.cpp +++ b/Userland/Utilities/js.cpp @@ -192,7 +192,7 @@ static void print_array(JS::Array& array, HashTable<JS::Object*>& seen_objects) bool first = true; for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) { print_separator(first); - auto value = it.value_and_attributes(&array).value; + auto value = array.get(it.index()); // The V8 repl doesn't throw an exception here, and instead just // prints 'undefined'. We may choose to replicate that behavior in // the future, but for now lets just catch the error @@ -212,7 +212,7 @@ static void print_object(JS::Object& object, HashTable<JS::Object*>& seen_object for (auto& entry : object.indexed_properties()) { print_separator(first); out("\"\033[33;1m{}\033[0m\": ", entry.index()); - auto value = entry.value_and_attributes(&object).value; + auto value = object.get(entry.index()); // The V8 repl doesn't throw an exception here, and instead just // prints 'undefined'. We may choose to replicate that behavior in // the future, but for now lets just catch the error |