diff options
author | Andreas Kling <kling@serenityos.org> | 2020-04-09 22:55:17 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-04-10 00:36:06 +0200 |
commit | 8286f8b996be00b1a2c60dae4ceb870da0328dd9 (patch) | |
tree | 8ab1997148e5907beef324ee81ba3912d678846f /Libraries | |
parent | e6d920d87ddd7b02b4fbbca3d6d6e98b9c8107dd (diff) | |
download | serenity-8286f8b996be00b1a2c60dae4ceb870da0328dd9.zip |
LibJS: Add property configuration transitions
Object.defineProperty() can now change the attributes of a property
already on the object. Internally this becomes a shape transition with
the TransitionType::Configure. Such transitions don't expand the
property storage capacity, but rather simply keep attributes up to date
when generating a property table.
Diffstat (limited to 'Libraries')
-rw-r--r-- | Libraries/LibJS/Runtime/Object.cpp | 20 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ObjectConstructor.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Shape.cpp | 24 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Shape.h | 11 | ||||
-rw-r--r-- | Libraries/LibJS/Tests/Object.defineProperty.js | 8 |
5 files changed, 57 insertions, 8 deletions
diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index df4205fe72..15456d79c7 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -106,16 +106,30 @@ void Object::put_own_property(Object& this_object, const FlyString& property_nam set_shape(*new_shape); metadata = shape().lookup(property_name); ASSERT(metadata.has_value()); - } else if (!(metadata.value().attributes & Attribute::Writable)) { - dbg() << "Disallow write to non-writable property"; - return; } + if (mode == PutOwnPropertyMode::DefineProperty && !(metadata.value().attributes & Attribute::Configurable) && attributes != metadata.value().attributes) { dbg() << "Disallow reconfig of non-configurable property"; interpreter().throw_exception<Error>("TypeError", String::format("Cannot redefine property '%s'", property_name.characters())); return; } + if (mode == PutOwnPropertyMode::DefineProperty && attributes != metadata.value().attributes) { + auto* new_shape = m_shape->create_configure_transition(property_name, attributes); + set_shape(*new_shape); + metadata = shape().lookup(property_name); + + dbg() << "Reconfigured property " << property_name << ", new shape says offset is " << metadata.value().offset << " and my storage capacity is " << m_storage.size(); + } + + if (mode == PutOwnPropertyMode::Put && !(metadata.value().attributes & Attribute::Writable)) { + dbg() << "Disallow write to non-writable property"; + return; + } + + if (value.is_empty()) + return; + auto value_here = m_storage[metadata.value().offset]; if (value_here.is_object() && value_here.as_object().is_native_property()) { auto& native_property = static_cast<NativeProperty&>(value_here.as_object()); diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp index 5d3b54fdfb..0ed34c26b5 100644 --- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -127,7 +127,7 @@ Value ObjectConstructor::define_property(Interpreter& interpreter) auto& object = interpreter.argument(0).as_object(); auto& descriptor = interpreter.argument(2).as_object(); - Value value = descriptor.get("value").value_or(js_undefined()); + Value value = descriptor.get("value").value_or(Value()); u8 configurable = descriptor.get("configurable").value_or(Value(false)).to_boolean() * Attribute::Configurable; u8 enumerable = descriptor.get("enumerable").value_or(Value(false)).to_boolean() * Attribute::Enumerable; u8 writable = descriptor.get("writable").value_or(Value(false)).to_boolean() * Attribute::Writable; diff --git a/Libraries/LibJS/Runtime/Shape.cpp b/Libraries/LibJS/Runtime/Shape.cpp index 8dbbd34a2e..a5490a6a30 100644 --- a/Libraries/LibJS/Runtime/Shape.cpp +++ b/Libraries/LibJS/Runtime/Shape.cpp @@ -34,7 +34,17 @@ Shape* Shape::create_put_transition(const FlyString& property_name, u8 attribute auto* new_shape = m_forward_transitions.get(property_name).value_or(nullptr); if (new_shape && new_shape->m_attributes == attributes) return new_shape; - new_shape = heap().allocate<Shape>(this, property_name, attributes); + new_shape = heap().allocate<Shape>(this, property_name, attributes, TransitionType::Put); + m_forward_transitions.set(property_name, new_shape); + return new_shape; +} + +Shape* Shape::create_configure_transition(const FlyString& property_name, u8 attributes) +{ + auto* new_shape = m_forward_transitions.get(property_name).value_or(nullptr); + if (new_shape && new_shape->m_attributes == attributes) + return new_shape; + new_shape = heap().allocate<Shape>(this, property_name, attributes, TransitionType::Configure); m_forward_transitions.set(property_name, new_shape); return new_shape; } @@ -48,17 +58,19 @@ Shape::Shape() { } -Shape::Shape(Shape* previous_shape, const FlyString& property_name, u8 attributes) +Shape::Shape(Shape* previous_shape, const FlyString& property_name, u8 attributes, TransitionType transition_type) : m_previous(previous_shape) , m_property_name(property_name) , m_attributes(attributes) , m_prototype(previous_shape->m_prototype) + , m_transition_type(transition_type) { } Shape::Shape(Shape* previous_shape, Object* new_prototype) : m_previous(previous_shape) , m_prototype(new_prototype) + , m_transition_type(TransitionType::Prototype) { } @@ -114,7 +126,13 @@ void Shape::ensure_property_table() const // Ignore prototype transitions as they don't affect the key map. continue; } - m_property_table->set(shape->m_property_name, { next_offset++, shape->m_attributes }); + if (shape->m_transition_type == TransitionType::Put) { + m_property_table->set(shape->m_property_name, { next_offset++, shape->m_attributes }); + } else if (shape->m_transition_type == TransitionType::Configure) { + auto it = m_property_table->find(shape->m_property_name); + ASSERT(it != m_property_table->end()); + it->value.attributes = shape->m_attributes; + } } } diff --git a/Libraries/LibJS/Runtime/Shape.h b/Libraries/LibJS/Runtime/Shape.h index 6c98a86bf8..ff7999db54 100644 --- a/Libraries/LibJS/Runtime/Shape.h +++ b/Libraries/LibJS/Runtime/Shape.h @@ -52,11 +52,19 @@ class Shape final : public Cell { public: virtual ~Shape() override; + enum class TransitionType { + Invalid, + Put, + Configure, + Prototype, + }; + Shape(); - Shape(Shape* previous_shape, const FlyString& property_name, u8 attributes); + Shape(Shape* previous_shape, const FlyString& property_name, u8 attributes, TransitionType); Shape(Shape* previous_shape, Object* new_prototype); Shape* create_put_transition(const FlyString& name, u8 attributes); + Shape* create_configure_transition(const FlyString& name, u8 attributes); Shape* create_prototype_transition(Object* new_prototype); Object* prototype() { return m_prototype; } @@ -81,6 +89,7 @@ private: FlyString m_property_name; u8 m_attributes { 0 }; Object* m_prototype { nullptr }; + TransitionType m_transition_type { TransitionType::Invalid }; }; } diff --git a/Libraries/LibJS/Tests/Object.defineProperty.js b/Libraries/LibJS/Tests/Object.defineProperty.js index 4f3b7049a6..4351927c2d 100644 --- a/Libraries/LibJS/Tests/Object.defineProperty.js +++ b/Libraries/LibJS/Tests/Object.defineProperty.js @@ -33,6 +33,14 @@ try { assert(e.name === "TypeError"); } + Object.defineProperty(o, "baz", { value: 9, configurable: true, writable: false }); + Object.defineProperty(o, "baz", { configurable: true, writable: true }); + + d = Object.getOwnPropertyDescriptor(o, "baz"); + assert(d.configurable === true); + assert(d.writable === true); + assert(d.value === 9); + console.log("PASS"); } catch (e) { console.log(e) |