mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 07:07:45 +00:00
LibJS: Implement Object.freeze() and Object.seal()
This commit is contained in:
parent
1c3eef5317
commit
9af07c7803
8 changed files with 202 additions and 3 deletions
|
@ -118,6 +118,7 @@ namespace JS {
|
|||
P(flat) \
|
||||
P(floor) \
|
||||
P(forEach) \
|
||||
P(freeze) \
|
||||
P(from) \
|
||||
P(fromCharCode) \
|
||||
P(fround) \
|
||||
|
@ -206,6 +207,7 @@ namespace JS {
|
|||
P(resolve) \
|
||||
P(reverse) \
|
||||
P(round) \
|
||||
P(seal) \
|
||||
P(set) \
|
||||
P(setFullYear) \
|
||||
P(setHours) \
|
||||
|
|
|
@ -74,6 +74,8 @@
|
|||
M(NonExtensibleDefine, "Cannot define property {} on non-extensible object") \
|
||||
M(NumberIncompatibleThis, "Number.prototype.{} method called with incompatible this target") \
|
||||
M(ObjectDefinePropertyReturnedFalse, "Object's [[DefineProperty]] method returned false") \
|
||||
M(ObjectFreezeFailed, "Could not freeze object") \
|
||||
M(ObjectSealFailed, "Could not seal object") \
|
||||
M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \
|
||||
M(ObjectSetPrototypeOfTwoArgs, "Object.setPrototypeOf requires at least two arguments") \
|
||||
M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \
|
||||
|
|
|
@ -160,6 +160,54 @@ bool Object::prevent_extensions()
|
|||
return true;
|
||||
}
|
||||
|
||||
// 7.3.15 SetIntegrityLevel, 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& key, auto attributes) {
|
||||
auto property_name = PropertyName::from_value(global_object(), key);
|
||||
auto metadata = shape().lookup(property_name.to_string_or_symbol());
|
||||
VERIFY(metadata.has_value());
|
||||
auto value = get_direct(metadata->offset);
|
||||
define_property(property_name, value, metadata->attributes.bits() & attributes);
|
||||
};
|
||||
|
||||
auto& vm = this->vm();
|
||||
auto status = prevent_extensions();
|
||||
if (vm.exception())
|
||||
return false;
|
||||
if (!status)
|
||||
return false;
|
||||
auto keys = get_own_properties(PropertyKind::Key);
|
||||
if (vm.exception())
|
||||
return false;
|
||||
switch (level) {
|
||||
case IntegrityLevel::Sealed:
|
||||
for (auto& key : keys) {
|
||||
update_property(key, ~Attribute::Configurable);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
case IntegrityLevel::Frozen:
|
||||
for (auto& key : keys) {
|
||||
auto property_name = PropertyName::from_value(global_object(), key);
|
||||
auto property_descriptor = get_own_property_descriptor(property_name);
|
||||
VERIFY(property_descriptor.has_value());
|
||||
u8 attributes = property_descriptor->is_accessor_descriptor()
|
||||
? ~Attribute::Configurable
|
||||
: ~Attribute::Configurable & ~Attribute::Writable;
|
||||
update_property(key, attributes);
|
||||
if (vm.exception())
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Value Object::get_own_property(const PropertyName& property_name, Value receiver) const
|
||||
{
|
||||
VERIFY(property_name.is_valid());
|
||||
|
|
|
@ -84,6 +84,11 @@ public:
|
|||
DefineProperty,
|
||||
};
|
||||
|
||||
enum class IntegrityLevel {
|
||||
Sealed,
|
||||
Frozen,
|
||||
};
|
||||
|
||||
Shape& shape() { return *m_shape; }
|
||||
const Shape& shape() const { return *m_shape; }
|
||||
|
||||
|
@ -129,6 +134,8 @@ public:
|
|||
virtual bool is_extensible() const { return m_is_extensible; }
|
||||
virtual bool prevent_extensions();
|
||||
|
||||
bool set_integrity_level(IntegrityLevel);
|
||||
|
||||
virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
|
||||
virtual Value ordinary_to_primitive(Value::PreferredType preferred_type) const;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -55,6 +56,8 @@ void ObjectConstructor::initialize(GlobalObject& global_object)
|
|||
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.preventExtensions, prevent_extensions, 1, attr);
|
||||
define_native_function(vm.names.freeze, freeze, 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);
|
||||
|
@ -146,9 +149,43 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::prevent_extensions)
|
|||
auto argument = vm.argument(0);
|
||||
if (!argument.is_object())
|
||||
return argument;
|
||||
if (!argument.as_object().prevent_extensions()) {
|
||||
if (!vm.exception())
|
||||
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse);
|
||||
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, 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.20 Object.seal, 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;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
|
@ -52,6 +53,8 @@ private:
|
|||
JS_DECLARE_NATIVE_FUNCTION(set_prototype_of);
|
||||
JS_DECLARE_NATIVE_FUNCTION(is_extensible);
|
||||
JS_DECLARE_NATIVE_FUNCTION(prevent_extensions);
|
||||
JS_DECLARE_NATIVE_FUNCTION(seal);
|
||||
JS_DECLARE_NATIVE_FUNCTION(freeze);
|
||||
JS_DECLARE_NATIVE_FUNCTION(keys);
|
||||
JS_DECLARE_NATIVE_FUNCTION(values);
|
||||
JS_DECLARE_NATIVE_FUNCTION(entries);
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
test("length is 1", () => {
|
||||
expect(Object.freeze).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("returns given argument", () => {
|
||||
const o = {};
|
||||
expect(Object.freeze(42)).toBe(42);
|
||||
expect(Object.freeze("foobar")).toBe("foobar");
|
||||
expect(Object.freeze(o)).toBe(o);
|
||||
});
|
||||
|
||||
test("prevents addition of new properties", () => {
|
||||
const o = {};
|
||||
expect(o.foo).toBeUndefined();
|
||||
Object.freeze(o);
|
||||
o.foo = "bar";
|
||||
expect(o.foo).toBeUndefined();
|
||||
});
|
||||
|
||||
test("prevents deletion of existing properties", () => {
|
||||
const o = { foo: "bar" };
|
||||
expect(o.foo).toBe("bar");
|
||||
Object.freeze(o);
|
||||
delete o.foo;
|
||||
expect(o.foo).toBe("bar");
|
||||
});
|
||||
|
||||
test("prevents changing attributes of existing properties", () => {
|
||||
const o = { foo: "bar" };
|
||||
Object.freeze(o);
|
||||
// FIXME: These don't change anything and should not throw!
|
||||
// expect(Object.defineProperty(o, "foo", {})).toBe(o);
|
||||
// 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'"
|
||||
);
|
||||
});
|
||||
|
||||
test("prevents changing value of existing properties", () => {
|
||||
const o = { foo: "bar" };
|
||||
expect(o.foo).toBe("bar");
|
||||
Object.freeze(o);
|
||||
o.foo = "baz";
|
||||
expect(o.foo).toBe("bar");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
test("length is 1", () => {
|
||||
expect(Object.seal).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("returns given argument", () => {
|
||||
const o = {};
|
||||
expect(Object.seal(42)).toBe(42);
|
||||
expect(Object.seal("foobar")).toBe("foobar");
|
||||
expect(Object.seal(o)).toBe(o);
|
||||
});
|
||||
|
||||
test("prevents addition of new properties", () => {
|
||||
const o = {};
|
||||
expect(o.foo).toBeUndefined();
|
||||
Object.seal(o);
|
||||
o.foo = "bar";
|
||||
expect(o.foo).toBeUndefined();
|
||||
});
|
||||
|
||||
test("prevents deletion of existing properties", () => {
|
||||
const o = { foo: "bar" };
|
||||
expect(o.foo).toBe("bar");
|
||||
Object.seal(o);
|
||||
delete o.foo;
|
||||
expect(o.foo).toBe("bar");
|
||||
});
|
||||
|
||||
test("prevents changing attributes of existing properties", () => {
|
||||
const o = { foo: "bar" };
|
||||
Object.seal(o);
|
||||
// FIXME: These don't change anything and should not throw!
|
||||
// expect(Object.defineProperty(o, "foo", {})).toBe(o);
|
||||
// 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'"
|
||||
);
|
||||
});
|
||||
|
||||
test("doesn't prevent changing value of existing properties", () => {
|
||||
const o = { foo: "bar" };
|
||||
expect(o.foo).toBe("bar");
|
||||
Object.seal(o);
|
||||
o.foo = "baz";
|
||||
expect(o.foo).toBe("baz");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue