diff options
-rw-r--r-- | Libraries/LibJS/Runtime/Object.cpp | 20 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.h | 4 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ObjectConstructor.cpp | 22 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ObjectConstructor.h | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/Object.isExtensible.js | 22 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/Object.preventExtensions.js | 48 |
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); +} |