summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Libraries/LibJS/Runtime/Object.cpp20
-rw-r--r--Libraries/LibJS/Runtime/Object.h4
-rw-r--r--Libraries/LibJS/Runtime/ObjectConstructor.cpp22
-rw-r--r--Libraries/LibJS/Runtime/ObjectConstructor.h2
-rw-r--r--Libraries/LibJS/Tests/Object.isExtensible.js22
-rw-r--r--Libraries/LibJS/Tests/Object.preventExtensions.js48
6 files changed, 118 insertions, 0 deletions
diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp
index accaeed2bf..e028047a7f 100644
--- a/Libraries/LibJS/Runtime/Object.cpp
+++ b/Libraries/LibJS/Runtime/Object.cpp
@@ -89,6 +89,12 @@ bool Object::has_prototype(const Object* prototype) const
return false;
}
+bool Object::prevent_extensions()
+{
+ m_is_extensible = false;
+ return true;
+}
+
Value Object::get_own_property(const Object& this_object, PropertyName property_name) const
{
Value value_here;
@@ -308,6 +314,13 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam
{
ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
+ if (!is_extensible()) {
+ dbg() << "Disallow define_property of non-extensible object";
+ if (throw_exceptions && interpreter().in_strict_mode())
+ interpreter().throw_exception<TypeError>(String::format("Cannot define property %s on non-extensible object", property_name.characters()));
+ return false;
+ }
+
if (value.is_accessor()) {
auto& accessor = value.as_accessor();
if (accessor.getter())
@@ -375,6 +388,13 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index,
{
ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
+ if (!is_extensible()) {
+ dbg() << "Disallow define_property of non-extensible object";
+ if (throw_exceptions && interpreter().in_strict_mode())
+ interpreter().throw_exception<TypeError>(String::format("Cannot define property %d on non-extensible object", property_index));
+ return false;
+ }
+
if (value.is_accessor()) {
auto& accessor = value.as_accessor();
if (accessor.getter())
diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h
index dea8eefb8e..0500eb0a26 100644
--- a/Libraries/LibJS/Runtime/Object.h
+++ b/Libraries/LibJS/Runtime/Object.h
@@ -98,6 +98,9 @@ public:
void set_prototype(Object*);
bool has_prototype(const Object* prototype) const;
+ bool is_extensible() const { return m_is_extensible; }
+ bool prevent_extensions();
+
virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
virtual Value to_primitive(Value::PreferredType preferred_type = Value::PreferredType::Default) const;
virtual Value to_string() const;
@@ -122,6 +125,7 @@ private:
void set_shape(Shape&);
void ensure_shape_is_unique();
+ bool m_is_extensible { true };
Shape* m_shape { nullptr };
Vector<Value> m_storage;
IndexedProperties m_indexed_properties;
diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp
index 828e0db2ea..1c56ce254f 100644
--- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp
+++ b/Libraries/LibJS/Runtime/ObjectConstructor.cpp
@@ -48,6 +48,8 @@ ObjectConstructor::ObjectConstructor()
define_native_function("getOwnPropertyNames", get_own_property_names, 1, attr);
define_native_function("getPrototypeOf", get_prototype_of, 1, attr);
define_native_function("setPrototypeOf", set_prototype_of, 2, attr);
+ define_native_function("isExtensible", is_extensible, 1, attr);
+ define_native_function("preventExtensions", prevent_extensions, 1, attr);
define_native_function("keys", keys, 1, attr);
define_native_function("values", values, 1, attr);
define_native_function("entries", entries, 1, attr);
@@ -104,6 +106,26 @@ Value ObjectConstructor::set_prototype_of(Interpreter& interpreter)
return {};
}
+Value ObjectConstructor::is_extensible(Interpreter& interpreter)
+{
+ auto argument = interpreter.argument(0);
+ if (!argument.is_object())
+ return Value(false);
+ return Value(argument.as_object().is_extensible());
+}
+
+Value ObjectConstructor::prevent_extensions(Interpreter& interpreter)
+{
+ auto argument = interpreter.argument(0);
+ if (!argument.is_object())
+ return argument;
+ if (!argument.as_object().prevent_extensions()) {
+ interpreter.throw_exception<TypeError>("Proxy preventExtensions handler returned false");
+ return {};
+ }
+ return argument;
+}
+
Value ObjectConstructor::get_own_property_descriptor(Interpreter& interpreter)
{
auto* object = interpreter.argument(0).to_object(interpreter);
diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.h b/Libraries/LibJS/Runtime/ObjectConstructor.h
index 2a7376b46f..13ad1a57b7 100644
--- a/Libraries/LibJS/Runtime/ObjectConstructor.h
+++ b/Libraries/LibJS/Runtime/ObjectConstructor.h
@@ -48,6 +48,8 @@ private:
static Value get_own_property_names(Interpreter&);
static Value get_prototype_of(Interpreter&);
static Value set_prototype_of(Interpreter&);
+ static Value is_extensible(Interpreter&);
+ static Value prevent_extensions(Interpreter&);
static Value keys(Interpreter&);
static Value values(Interpreter&);
static Value entries(Interpreter&);
diff --git a/Libraries/LibJS/Tests/Object.isExtensible.js b/Libraries/LibJS/Tests/Object.isExtensible.js
new file mode 100644
index 0000000000..a8870da53d
--- /dev/null
+++ b/Libraries/LibJS/Tests/Object.isExtensible.js
@@ -0,0 +1,22 @@
+load("test-common.js");
+
+try {
+ assert(Object.isExtensible() === false);
+ assert(Object.isExtensible(undefined) === false);
+ assert(Object.isExtensible(null) === false);
+ assert(Object.isExtensible(true) === false);
+ assert(Object.isExtensible(6) === false);
+ assert(Object.isExtensible("test") === false);
+
+ let s = Symbol();
+ assert(Object.isExtensible(s) === false);
+
+ let o = { foo: "foo" };
+ assert(Object.isExtensible(o) === true);
+ Object.preventExtensions(o);
+ assert(Object.isExtensible(o) === false);
+
+ console.log("PASS");
+} catch (e) {
+ console.log("FAIL: " + e);
+}
diff --git a/Libraries/LibJS/Tests/Object.preventExtensions.js b/Libraries/LibJS/Tests/Object.preventExtensions.js
new file mode 100644
index 0000000000..7aabc91960
--- /dev/null
+++ b/Libraries/LibJS/Tests/Object.preventExtensions.js
@@ -0,0 +1,48 @@
+load("test-common.js");
+
+try {
+ assert(Object.preventExtensions() === undefined);
+ assert(Object.preventExtensions(undefined) === undefined);
+ assert(Object.preventExtensions(null) === null);
+ assert(Object.preventExtensions(true) === true);
+ assert(Object.preventExtensions(6) === 6);
+ assert(Object.preventExtensions("test") === "test");
+
+ let s = Symbol();
+ assert(Object.preventExtensions(s) === s);
+
+ let o = { foo: "foo" };
+ assert(o.foo === "foo");
+ o.bar = "bar";
+ assert(o.bar === "bar");
+
+ assert(Object.preventExtensions(o) === o);
+ assert(o.foo === "foo");
+ assert(o.bar === "bar");
+
+ o.baz = "baz";
+ assert(o.baz === undefined);
+
+ Object.defineProperty(o, "baz", { value: "baz" });
+ assert(o.baz === undefined);
+
+ assertThrowsError(() => {
+ "use strict";
+ o.baz = "baz";
+ }, {
+ error: TypeError,
+ message: "Cannot define property baz on non-extensible object",
+ });
+
+ assertThrowsError(() => {
+ "use strict";
+ Object.defineProperty(o, "baz", { value: "baz" });
+ }, {
+ error: TypeError,
+ message: "Cannot define property baz on non-extensible object",
+ });
+
+ console.log("PASS");
+} catch (e) {
+ console.log("FAIL: " + e);
+}