mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibJS: Finish implementing mapped arguments exotic objects :^)
Now that the Object rewrite is in place, we have enough tools to implement the mapped `arguments` propreties according to spec. The basic mechanism is that the `arguments` object installs a hidden parameter mapping object that property accesses get filtered through. This is how accessing numeric properties on `arguments` are proxied to the named identifier in the function scope. When `arguments` is instantiated, getters and setters are created for all the numeric properties on the object that correspond to function arguments. These getters and setters can be deleted from the object. This is all pretty intricate, so refer to the spec for details. Note that the `arguments` object itself is still lazily instantiated on first access within a function. This is non-conforming, and we'll have to revisit this once we get around to improving function calls.
This commit is contained in:
parent
36cc011ae4
commit
77fa33e61a
Notes:
sideshowbarker
2024-07-18 10:25:10 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/77fa33e61ac
4 changed files with 224 additions and 12 deletions
|
@ -450,32 +450,30 @@ Object* create_unmapped_arguments_object(GlobalObject& global_object, Vector<Val
|
|||
}
|
||||
|
||||
// 10.4.4.7 CreateMappedArgumentsObject ( func, formals, argumentsList, env ), https://tc39.es/ecma262/#sec-createmappedargumentsobject
|
||||
Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObject& function, Vector<FunctionNode::Parameter> const& formals, Vector<Value> const& arguments, Environment&)
|
||||
Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObject& function, Vector<FunctionNode::Parameter> const& formals, Vector<Value> 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<i32>::max());
|
||||
i32 length = static_cast<i32>(arguments.size());
|
||||
|
||||
// 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »).
|
||||
auto* object = vm.heap().allocate<ArgumentsObject>(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<ArgumentsObject>(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<FlyString> mapped_names;
|
||||
|
||||
// 18. Set index to numberOfParameters - 1.
|
||||
// 19. Repeat, while index ≥ 0,
|
||||
VERIFY(formals.size() <= NumericLimits<i32>::max());
|
||||
for (i32 index = static_cast<i32>(formals.size()) - 1; index >= 0; --index) {
|
||||
// a. Let name be parameterNames[index].
|
||||
auto const& name = formals[index].binding.get<FlyString>();
|
||||
|
||||
// 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);
|
||||
|
|
|
@ -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<PropertyDescriptor> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/Environment.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
|
||||
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<PropertyDescriptor> 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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Reference in a new issue