diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 85b11d06fc..27e443a825 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -450,32 +450,30 @@ Object* create_unmapped_arguments_object(GlobalObject& global_object, Vector const& formals, Vector const& arguments, Environment&) +Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObject& function, Vector const& formals, Vector const& arguments, Environment& environment) { - // FIXME: This implementation is incomplete and doesn't support the actual identifier mappings yet. - (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(); + VERIFY(arguments.size() <= NumericLimits::max()); + i32 length = static_cast(arguments.size()); // 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »). - auto* object = vm.heap().allocate(global_object, global_object); - 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%. + auto* object = vm.heap().allocate(global_object, global_object, environment); + if (vm.exception()) + return nullptr; // 14. Let index be 0. // 15. Repeat, while index < len, - for (size_t index = 0; index < length; ++index) { + for (i32 index = 0; index < length; ++index) { // a. Let val be argumentsList[index]. auto value = arguments[index]; @@ -490,6 +488,45 @@ Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObje object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true }); VERIFY(!vm.exception()); + // 17. Let mappedNames be a new empty List. + HashTable mapped_names; + + // 18. Set index to numberOfParameters - 1. + // 19. Repeat, while index ≥ 0, + VERIFY(formals.size() <= NumericLimits::max()); + for (i32 index = static_cast(formals.size()) - 1; index >= 0; --index) { + // a. Let name be parameterNames[index]. + auto const& name = formals[index].binding.get(); + + // b. If name is not an element of mappedNames, then + if (mapped_names.contains(name)) + continue; + + // i. Add name as an element of the list mappedNames. + mapped_names.set(name); + + // ii. If index < len, then + if (index < length) { + // 1. Let g be MakeArgGetter(name, env). + // 2. Let p be MakeArgSetter(name, env). + // 3. Perform map.[[DefineOwnProperty]](! ToString(𝔽(index)), PropertyDescriptor { [[Set]]: p, [[Get]]: g, [[Enumerable]]: false, [[Configurable]]: true }). + object->parameter_map().define_native_accessor( + String::number(index), + [&environment, name](VM&, GlobalObject&) -> Value { + auto variable = environment.get_from_environment(name); + if (!variable.has_value()) + return {}; + return variable->value; + }, + [&environment, name](VM& vm, GlobalObject&) { + auto value = vm.argument(0); + environment.put_into_environment(name, Variable { value, DeclarationKind::Var }); + return js_undefined(); + }, + Attribute::Configurable); + } + } + // 20. 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); diff --git a/Userland/Libraries/LibJS/Runtime/ArgumentsObject.cpp b/Userland/Libraries/LibJS/Runtime/ArgumentsObject.cpp index 1d81384a56..c49d9719b8 100644 --- a/Userland/Libraries/LibJS/Runtime/ArgumentsObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArgumentsObject.cpp @@ -9,18 +9,173 @@ namespace JS { -ArgumentsObject::ArgumentsObject(GlobalObject& global_object) +ArgumentsObject::ArgumentsObject(GlobalObject& global_object, Environment& environment) : Object(*global_object.object_prototype()) + , m_environment(environment) { } void ArgumentsObject::initialize(GlobalObject& global_object) { Base::initialize(global_object); + m_parameter_map = Object::create(global_object, nullptr); } ArgumentsObject::~ArgumentsObject() { } +void ArgumentsObject::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(&m_environment); + visitor.visit(m_parameter_map); +} + +// 10.4.4.3 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver +Value ArgumentsObject::internal_get(const PropertyName& property_name, Value receiver) const +{ + // 1. Let map be args.[[ParameterMap]]. + auto& map = *m_parameter_map; + // 2. Let isMapped be ! HasOwnProperty(map, P). + bool is_mapped = m_parameter_map->has_own_property(property_name); + // 3. If isMapped is false, then + if (!is_mapped) { + // a. Return ? OrdinaryGet(args, P, Receiver). + return Object::internal_get(property_name, receiver); + } + + // FIXME: a. Assert: map contains a formal parameter mapping for P. + + // b. Return Get(map, P). + return map.get(property_name); +} + +// 10.4.4.4 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver +bool ArgumentsObject::internal_set(PropertyName const& property_name, Value value, Value receiver) +{ + bool is_mapped = false; + + // 1. If SameValue(args, Receiver) is false, then + if (same_value(this, receiver)) { + // a. Let isMapped be false. + is_mapped = false; + } else { + // a. Let map be args.[[ParameterMap]]. + // b. Let isMapped be ! HasOwnProperty(map, P). + is_mapped = parameter_map().has_own_property(property_name); + } + + // 3. If isMapped is true, then + if (is_mapped) { + // a. Let setStatus be Set(map, P, V, false). + auto set_status = m_parameter_map->set(property_name, value, false); + // b. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. + VERIFY(set_status); + } + + // 4. Return ? OrdinarySet(args, P, V, Receiver). + return Object::internal_set(property_name, value, receiver); +} + +// 10.4.4.5 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-delete-p +bool ArgumentsObject::internal_delete(PropertyName const& property_name) +{ + // 1. Let map be args.[[ParameterMap]]. + auto& map = parameter_map(); + // 2. Let isMapped be ! HasOwnProperty(map, P). + bool is_mapped = map.has_own_property(property_name); + // 3. Let result be ? OrdinaryDelete(args, P). + bool result = Object::internal_delete(property_name); + if (vm().exception()) + return false; + + // 4. If result is true and isMapped is true, then + if (result && is_mapped) { + // a. Call map.[[Delete]](P). + map.internal_delete(property_name); + } + + // 5. Return result. + return result; +} + +// 10.4.4.1 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-getownproperty-p +Optional ArgumentsObject::internal_get_own_property(PropertyName const& property_name) const +{ + // 1. Let desc be OrdinaryGetOwnProperty(args, P). + auto desc = Object::internal_get_own_property(property_name); + // 2. If desc is undefined, return desc. + if (!desc.has_value()) + return desc; + // 3. Let map be args.[[ParameterMap]]. + // 4. Let isMapped be ! HasOwnProperty(map, P). + bool is_mapped = m_parameter_map->has_own_property(property_name); + // 5. If isMapped is true, then + if (is_mapped) { + // a. Set desc.[[Value]] to Get(map, P). + desc->value = m_parameter_map->get(property_name); + } + // 6. Return desc. + return desc; +} + +// 10.4.4.2 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-defineownproperty-p-desc +bool ArgumentsObject::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& descriptor) +{ + // 1. Let map be args.[[ParameterMap]]. + auto& map = parameter_map(); + + // 2. Let isMapped be HasOwnProperty(map, P). + bool is_mapped = map.has_own_property(property_name); + + // 3. Let newArgDesc be Desc. + auto new_arg_desc = descriptor; + + // 4. If isMapped is true and IsDataDescriptor(Desc) is true, then + if (is_mapped && descriptor.is_data_descriptor()) { + // a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its value is false, then + if (!descriptor.value.has_value() && descriptor.writable.has_value() && descriptor.writable == false) { + // i. Set newArgDesc to a copy of Desc. + new_arg_desc = descriptor; + // ii. Set newArgDesc.[[Value]] to Get(map, P). + new_arg_desc.value = map.get(property_name); + } + } + + // 5. Let allowed be ? OrdinaryDefineOwnProperty(args, P, newArgDesc). + bool allowed = Object::internal_define_own_property(property_name, new_arg_desc); + if (vm().exception()) + return false; + + // 6. If allowed is false, return false. + if (!allowed) + return false; + + // 7. If isMapped is true, then + if (is_mapped) { + // a. If IsAccessorDescriptor(Desc) is true, then + if (descriptor.is_accessor_descriptor()) { + // i. Call map.[[Delete]](P). + map.internal_delete(property_name); + } else { + // i. If Desc.[[Value]] is present, then + if (descriptor.value.has_value()) { + // 1. Let setStatus be Set(map, P, Desc.[[Value]], false). + bool set_status = map.set(property_name, descriptor.value.value(), false); + // 2. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. + VERIFY(set_status); + } + // ii. If Desc.[[Writable]] is present and its value is false, then + if (descriptor.writable == false) { + // 1. Call map.[[Delete]](P). + map.internal_delete(property_name); + } + } + } + + // 8. Return true. + return true; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ArgumentsObject.h b/Userland/Libraries/LibJS/Runtime/ArgumentsObject.h index 9fc9bfb91e..363cf4f5cc 100644 --- a/Userland/Libraries/LibJS/Runtime/ArgumentsObject.h +++ b/Userland/Libraries/LibJS/Runtime/ArgumentsObject.h @@ -6,6 +6,7 @@ #pragma once +#include #include namespace JS { @@ -14,10 +15,27 @@ class ArgumentsObject final : public Object { JS_OBJECT(ArgumentsObject, Object); public: - explicit ArgumentsObject(GlobalObject&); + ArgumentsObject(GlobalObject&, Environment&); virtual void initialize(GlobalObject&) override; virtual ~ArgumentsObject() override; + + Environment& environment() { return m_environment; } + + virtual Optional internal_get_own_property(PropertyName const&) const override; + virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor 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; + + // [[ParameterMap]] + Object& parameter_map() { return *m_parameter_map; } + +private: + virtual void visit_edges(Cell::Visitor&) override; + + Environment& m_environment; + Object* m_parameter_map { nullptr }; }; } diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 6850e7198a..6975ddc9d8 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -1023,7 +1023,9 @@ bool Object::define_native_accessor(PropertyName const& property_name, Function< { auto& vm = this->vm(); String formatted_property_name; - if (property_name.is_string()) { + if (property_name.is_number()) { + formatted_property_name = property_name.to_string(); + } else if (property_name.is_string()) { formatted_property_name = property_name.as_string(); } else { formatted_property_name = String::formatted("[{}]", property_name.as_symbol()->description());