ladybird/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp
Linus Groh 317b88a8c3 LibJS: Replace Object's create_empty() with create() taking a prototype
This now matches the spec's OrdinaryObjectCreate() across the board:
instead of implicitly setting the created object's prototype to
%Object.prototype% and then in many cases setting it to a nullptr right
away, it now has an 'Object* prototype' parameter with _no default
value_. This makes the code easier to compare with the spec, very clear
in terms of what prototype is being used as well as avoiding unnecessary
shape transitions.

Also fixes a couple of cases were we weren't setting the correct
prototype.

There's no reason to assume that the object would not be empty (as in
having own properties), so let's follow our existing pattern of
Type::create(...) and simply call it 'create'.
2021-06-16 22:49:04 +01:00

404 lines
15 KiB
C++

/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Function.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/ObjectConstructor.h>
#include <LibJS/Runtime/ProxyObject.h>
#include <LibJS/Runtime/Shape.h>
namespace JS {
ObjectConstructor::ObjectConstructor(GlobalObject& global_object)
: NativeFunction(vm().names.Object, *global_object.function_prototype())
{
}
void ObjectConstructor::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
NativeFunction::initialize(global_object);
// 20.1.2.19 Object.prototype, https://tc39.es/ecma262/#sec-object.prototype
define_property(vm.names.prototype, global_object.object_prototype(), 0);
define_property(vm.names.length, Value(1), Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(vm.names.defineProperty, define_property_, 3, attr);
define_native_function(vm.names.defineProperties, define_properties, 2, attr);
define_native_function(vm.names.is, is, 2, attr);
define_native_function(vm.names.getOwnPropertyDescriptor, get_own_property_descriptor, 2, attr);
define_native_function(vm.names.getOwnPropertyNames, get_own_property_names, 1, attr);
define_native_function(vm.names.getOwnPropertySymbols, get_own_property_symbols, 1, attr);
define_native_function(vm.names.getPrototypeOf, get_prototype_of, 1, attr);
define_native_function(vm.names.setPrototypeOf, set_prototype_of, 2, attr);
define_native_function(vm.names.isExtensible, is_extensible, 1, attr);
define_native_function(vm.names.isFrozen, is_frozen, 1, attr);
define_native_function(vm.names.isSealed, is_sealed, 1, attr);
define_native_function(vm.names.preventExtensions, prevent_extensions, 1, attr);
define_native_function(vm.names.freeze, freeze, 1, attr);
define_native_function(vm.names.fromEntries, from_entries, 1, attr);
define_native_function(vm.names.seal, seal, 1, attr);
define_native_function(vm.names.keys, keys, 1, attr);
define_native_function(vm.names.values, values, 1, attr);
define_native_function(vm.names.entries, entries, 1, attr);
define_native_function(vm.names.create, create, 2, attr);
define_native_function(vm.names.hasOwn, has_own, 2, attr);
define_native_function(vm.names.assign, assign, 2, attr);
}
ObjectConstructor::~ObjectConstructor()
{
}
// 20.1.1.1 Object ( [ value ] ), https://tc39.es/ecma262/#sec-object-value
Value ObjectConstructor::call()
{
auto& vm = this->vm();
auto& global_object = this->global_object();
auto value = vm.argument(0);
if (value.is_nullish())
return Object::create(global_object, global_object.object_prototype());
return value.to_object(global_object);
}
// 20.1.1.1 Object ( [ value ] ), https://tc39.es/ecma262/#sec-object-value
Value ObjectConstructor::construct(Function&)
{
return call();
}
// 20.1.2.10 Object.getOwnPropertyNames ( O ), https://tc39.es/ecma262/#sec-object.getownpropertynames
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names)
{
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::StringOnly));
}
// 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));
}
// 20.1.2.12 Object.getPrototypeOf ( O ), https://tc39.es/ecma262/#sec-object.getprototypeof
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of)
{
auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
return object->prototype();
}
// 20.1.2.21 Object.setPrototypeOf ( O, proto ), https://tc39.es/ecma262/#sec-object.setprototypeof
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::set_prototype_of)
{
auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
auto prototype_value = vm.argument(1);
Object* prototype;
if (prototype_value.is_null()) {
prototype = nullptr;
} else if (prototype_value.is_object()) {
prototype = &prototype_value.as_object();
} else {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {};
}
if (!object->set_prototype(prototype)) {
if (!vm.exception())
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse);
return {};
}
return object;
}
// 20.1.2.14 Object.isExtensible ( O ), https://tc39.es/ecma262/#sec-object.isextensible
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is_extensible)
{
auto argument = vm.argument(0);
if (!argument.is_object())
return Value(false);
return Value(argument.as_object().is_extensible());
}
// 20.1.2.15 Object.isFrozen ( O ), https://tc39.es/ecma262/#sec-object.isfrozen
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is_frozen)
{
auto argument = vm.argument(0);
if (!argument.is_object())
return Value(true);
return Value(argument.as_object().test_integrity_level(Object::IntegrityLevel::Frozen));
}
// 20.1.2.16 Object.isSealed ( O ), https://tc39.es/ecma262/#sec-object.issealed
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is_sealed)
{
auto argument = vm.argument(0);
if (!argument.is_object())
return Value(true);
return Value(argument.as_object().test_integrity_level(Object::IntegrityLevel::Sealed));
}
// 20.1.2.18 Object.preventExtensions ( O ), https://tc39.es/ecma262/#sec-object.preventextensions
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();
if (vm.exception())
return {};
if (!status) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse);
return {};
}
return argument;
}
// 20.1.2.6 Object.freeze ( O ), https://tc39.es/ecma262/#sec-object.freeze
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::freeze)
{
auto argument = vm.argument(0);
if (!argument.is_object())
return argument;
auto status = argument.as_object().set_integrity_level(Object::IntegrityLevel::Frozen);
if (vm.exception())
return {};
if (!status) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectFreezeFailed);
return {};
}
return argument;
}
// 20.1.2.7 Object.fromEntries ( iterable ), https://tc39.es/ecma262/#sec-object.fromentries
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::from_entries)
{
auto iterable = require_object_coercible(global_object, vm.argument(0));
if (vm.exception())
return {};
auto* object = Object::create(global_object, global_object.object_prototype());
get_iterator_values(global_object, iterable, [&](Value iterator_value) {
if (vm.exception())
return IterationDecision::Break;
if (!iterator_value.is_object()) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, String::formatted("Iterator value {}", iterator_value.to_string_without_side_effects()));
return IterationDecision::Break;
}
auto key = iterator_value.as_object().get(0).value_or(js_undefined());
if (vm.exception())
return IterationDecision::Break;
auto value = iterator_value.as_object().get(1).value_or(js_undefined());
if (vm.exception())
return IterationDecision::Break;
auto property_key = key.to_property_key(global_object);
if (vm.exception())
return IterationDecision::Break;
object->define_property(property_key, value);
if (vm.exception())
return IterationDecision::Break;
return IterationDecision::Continue;
});
if (vm.exception())
return {};
return object;
}
// 20.1.2.20 Object.seal ( O ), https://tc39.es/ecma262/#sec-object.seal
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::seal)
{
auto argument = vm.argument(0);
if (!argument.is_object())
return argument;
auto status = argument.as_object().set_integrity_level(Object::IntegrityLevel::Sealed);
if (vm.exception())
return {};
if (!status) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSealFailed);
return {};
}
return argument;
}
// 20.1.2.8 Object.getOwnPropertyDescriptor ( O, P ), https://tc39.es/ecma262/#sec-object.getownpropertydescriptor
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);
if (vm.exception())
return {};
return object->get_own_property_descriptor_object(property_key);
}
// 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");
return {};
}
auto property_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");
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());
}
}
return {};
}
return &object;
}
// 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()) {
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;
}
// 20.1.2.13 Object.is ( value1, value2 ), https://tc39.es/ecma262/#sec-object.is
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is)
{
return Value(same_value(vm.argument(0), vm.argument(1)));
}
// 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);
if (vm.exception())
return {};
return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::Key));
}
// 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);
if (vm.exception())
return {};
return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::Value));
}
// 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);
if (vm.exception())
return {};
return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::KeyAndValue));
}
// 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 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 {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {};
}
auto* object = Object::create(global_object, prototype);
if (!properties.is_undefined()) {
object->define_properties(properties);
if (vm.exception())
return {};
}
return object;
}
// 1 Object.hasOwn ( O, P ), https://tc39.es/proposal-accessible-object-hasownproperty/#sec-object.hasown
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::has_own)
{
auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
auto property_key = vm.argument(1).to_property_key(global_object);
if (vm.exception())
return {};
return Value(object->has_own_property(property_key));
}
// 20.1.2.1 Object.assign ( target, ...sources ), https://tc39.es/ecma262/#sec-object.assign
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::assign)
{
auto* to = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
if (vm.argument_count() == 1)
return to;
for (size_t i = 1; i < vm.argument_count(); ++i) {
auto next_source = vm.argument(i);
if (next_source.is_nullish())
continue;
auto from = next_source.to_object(global_object);
VERIFY(!vm.exception());
auto keys = from->get_own_properties(PropertyKind::Key);
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())
continue;
auto value = from->get(property_name);
if (vm.exception())
return {};
to->put(property_name, value);
if (vm.exception())
return {};
}
}
return to;
}
}