diff options
author | Linus Groh <mail@linusgroh.de> | 2021-07-04 18:14:16 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-07-04 22:07:36 +0100 |
commit | 09bd5f8772156f0031a3fa914bed2371429d09a0 (patch) | |
tree | b92031ab80db7b45e23f761066b065b35b087046 /Userland/Libraries | |
parent | 4e5362b7cb4160205b0b4ab2adaab73f20346b7a (diff) | |
download | serenity-09bd5f8772156f0031a3fa914bed2371429d09a0.zip |
LibJS: Rewrite most of Object for spec compliance :^)
This is a huge patch, I know. In hindsight this perhaps could've been
done slightly more incremental, but I started and then fixed everything
until it worked, and here we are. I tried splitting of some completely
unrelated changes into separate commits, however. Anyway.
This is a rewrite of most of Object, and by extension large parts of
Array, Proxy, Reflect, String, TypedArray, and some other things.
What we already had worked fine for about 90% of things, but getting the
last 10% right proved to be increasingly difficult with the current code
that sort of grew organically and is only very loosely based on the
spec - this became especially obvious when we started fixing a large
number of test262 failures.
Key changes include:
- 1:1 matching function names and parameters of all object-related
functions, to avoid ambiguity. Previously we had things like put(),
which the spec doesn't have - as a result it wasn't always clear which
need to be used.
- Better separation between object abstract operations and internal
methods - the former are always the same, the latter can be overridden
(and are therefore virtual). The internal methods (i.e. [[Foo]] in the
spec) are now prefixed with 'internal_' for clarity - again, it was
previously not always clear which AO a certain method represents,
get() could've been both Get and [[Get]] (I don't know which one it
was closer to right now).
Note that some of the old names have been kept until all code relying
on them is updated, but they are now simple wrappers around the
closest matching standard abstract operation.
- Simplifications of the storage layer: functions that write values to
storage are now prefixed with 'storage_' to make their purpose clear,
and as they are not part of the spec they should not contain any steps
specified by it. Much functionality is now covered by the layers above
it and was removed (e.g. handling of accessors, attribute checks).
- PropertyAttributes has been greatly simplified, and is being replaced
by PropertyDescriptor - a concept similar to the current
implementation, but more aligned with the actual spec. See the commit
message of the previous commit where it was introduced for details.
- As a bonus, and since I had to look at the spec a whole lot anyway, I
introduced more inline comments with the exact steps from the spec -
this makes it super easy to verify correctness.
- East-const all the things.
As a result of all of this, things are much more correct but a bit
slower now. Retaining speed wasn't a consideration at all, I have done
no profiling of the new code - there might be low hanging fruits, which
we can then harvest separately.
Special thanks to Idan for helping me with this by tracking down bugs,
updating everything outside of LibJS to work with these changes (LibWeb,
Spreadsheet, HackStudio), as well as providing countless patches to fix
regressions I introduced - there still are very few (we got it down to
5), but we also get many new passing test262 tests in return. :^)
Co-authored-by: Idan Horowitz <idan.horowitz@gmail.com>
Diffstat (limited to 'Userland/Libraries')
73 files changed, 3859 insertions, 2079 deletions
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; |