summaryrefslogtreecommitdiff
path: root/Libraries/LibJS
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-04-26 13:53:40 +0200
committerAndreas Kling <kling@serenityos.org>2020-04-26 15:51:07 +0200
commitf897c410927817050c9f98081d45a32e893e6d73 (patch)
tree1b45402c82e1785b649d29c3252f1df9bdceb677 /Libraries/LibJS
parent1617be1e6f1bb356ce1ae08d5443795a9c5f2f33 (diff)
downloadserenity-f897c410927817050c9f98081d45a32e893e6d73.zip
LibJS: Implement basic support for the "delete" operator
It turns out "delete" is actually a unary op :) This patch implements deletion of object properties, it doesn't yet work for casually deleting properties from the global object. When deleting a property from an object, we switch that object to having a unique shape, no longer sharing shapes with others. Once an object has a unique shape, it no longer needs to care about shape transitions.
Diffstat (limited to 'Libraries/LibJS')
-rw-r--r--Libraries/LibJS/AST.cpp20
-rw-r--r--Libraries/LibJS/AST.h1
-rw-r--r--Libraries/LibJS/Parser.cpp8
-rw-r--r--Libraries/LibJS/Runtime/Object.cpp51
-rw-r--r--Libraries/LibJS/Runtime/Object.h3
-rw-r--r--Libraries/LibJS/Runtime/ObjectConstructor.cpp3
-rw-r--r--Libraries/LibJS/Runtime/Shape.cpp44
-rw-r--r--Libraries/LibJS/Runtime/Shape.h8
-rw-r--r--Libraries/LibJS/Tests/delete-basic.js60
9 files changed, 190 insertions, 8 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp
index 8c27bf549c..2633cb6da4 100644
--- a/Libraries/LibJS/AST.cpp
+++ b/Libraries/LibJS/AST.cpp
@@ -378,6 +378,21 @@ Value LogicalExpression::execute(Interpreter& interpreter) const
Value UnaryExpression::execute(Interpreter& interpreter) const
{
+ if (m_op == UnaryOp::Delete) {
+ if (!m_lhs->is_member_expression())
+ return Value(true);
+ auto object_value = static_cast<const MemberExpression&>(*m_lhs).object().execute(interpreter);
+ if (interpreter.exception())
+ return {};
+ auto* object = object_value.to_object(interpreter.heap());
+ if (!object)
+ return {};
+ auto property_name = static_cast<const MemberExpression&>(*m_lhs).computed_property_name(interpreter);
+ if (!property_name.is_valid())
+ return {};
+ return object->delete_property(property_name);
+ }
+
auto lhs_result = m_lhs->execute(interpreter);
if (interpreter.exception())
return {};
@@ -416,6 +431,8 @@ Value UnaryExpression::execute(Interpreter& interpreter) const
}
case UnaryOp::Void:
return js_undefined();
+ case UnaryOp::Delete:
+ ASSERT_NOT_REACHED();
}
ASSERT_NOT_REACHED();
@@ -575,6 +592,9 @@ void UnaryExpression::dump(int indent) const
case UnaryOp::Void:
op_string = "void ";
break;
+ case UnaryOp::Delete:
+ op_string = "delete ";
+ break;
}
print_indent(indent);
diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h
index ba37df528a..bec3f2910a 100644
--- a/Libraries/LibJS/AST.h
+++ b/Libraries/LibJS/AST.h
@@ -400,6 +400,7 @@ enum class UnaryOp {
Minus,
Typeof,
Void,
+ Delete,
};
class UnaryExpression : public Expression {
diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp
index 7855501f30..caf0e7da75 100644
--- a/Libraries/LibJS/Parser.cpp
+++ b/Libraries/LibJS/Parser.cpp
@@ -187,6 +187,7 @@ Associativity Parser::operator_associativity(TokenType type) const
case TokenType::ExclamationMarkEqualsEquals:
case TokenType::Typeof:
case TokenType::Void:
+ case TokenType::Delete:
case TokenType::Ampersand:
case TokenType::Caret:
case TokenType::Pipe:
@@ -425,6 +426,9 @@ NonnullRefPtr<Expression> Parser::parse_unary_prefixed_expression()
case TokenType::Void:
consume();
return create_ast_node<UnaryExpression>(UnaryOp::Void, parse_expression(precedence, associativity));
+ case TokenType::Delete:
+ consume();
+ return create_ast_node<UnaryExpression>(UnaryOp::Delete, parse_expression(precedence, associativity));
default:
m_parser_state.m_has_errors = true;
expected("primary expression (missing switch case)");
@@ -1059,7 +1063,8 @@ bool Parser::match_unary_prefixed_expression() const
|| type == TokenType::Plus
|| type == TokenType::Minus
|| type == TokenType::Typeof
- || type == TokenType::Void;
+ || type == TokenType::Void
+ || type == TokenType::Delete;
}
bool Parser::match_secondary_expression() const
@@ -1114,7 +1119,6 @@ bool Parser::match_statement() const
|| type == TokenType::Return
|| type == TokenType::Let
|| type == TokenType::Class
- || type == TokenType::Delete
|| type == TokenType::Do
|| type == TokenType::If
|| type == TokenType::Throw
diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp
index 0950660999..e0d757395e 100644
--- a/Libraries/LibJS/Runtime/Object.cpp
+++ b/Libraries/LibJS/Runtime/Object.cpp
@@ -71,6 +71,10 @@ void Object::set_prototype(Object* new_prototype)
{
if (prototype() == new_prototype)
return;
+ if (shape().is_unique()) {
+ shape().set_prototype_without_transition(new_prototype);
+ return;
+ }
m_shape = m_shape->create_prototype_transition(new_prototype);
}
@@ -113,8 +117,11 @@ void Object::put_own_property(Object& this_object, const FlyString& property_nam
{
auto metadata = shape().lookup(property_name);
if (!metadata.has_value()) {
- auto* new_shape = m_shape->create_put_transition(property_name, attributes);
- set_shape(*new_shape);
+ if (m_shape->is_unique()) {
+ m_shape->add_property_to_unique_shape(property_name, attributes);
+ } else {
+ set_shape(*m_shape->create_put_transition(property_name, attributes));
+ }
metadata = shape().lookup(property_name);
ASSERT(metadata.has_value());
}
@@ -126,8 +133,11 @@ void Object::put_own_property(Object& this_object, const FlyString& property_nam
}
if (mode == PutOwnPropertyMode::DefineProperty && attributes != metadata.value().attributes) {
- auto* new_shape = m_shape->create_configure_transition(property_name, attributes);
- set_shape(*new_shape);
+ if (m_shape->is_unique()) {
+ m_shape->reconfigure_property_in_unique_shape(property_name, attributes);
+ } else {
+ set_shape(*m_shape->create_configure_transition(property_name, attributes));
+ }
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();
@@ -154,6 +164,39 @@ void Object::put_own_property(Object& this_object, const FlyString& property_nam
}
}
+Value Object::delete_property(PropertyName property_name)
+{
+ ASSERT(property_name.is_valid());
+ if (property_name.is_number()) {
+ if (property_name.as_number() < static_cast<i32>(elements().size())) {
+ elements()[property_name.as_number()] = {};
+ return Value(true);
+ }
+ return Value(true);
+ }
+ auto metadata = shape().lookup(property_name.as_string());
+ if (!metadata.has_value())
+ return Value(true);
+ if (!(metadata.value().attributes & Attribute::Configurable))
+ return Value(false);
+
+ size_t deleted_offset = metadata.value().offset;
+
+ ensure_shape_is_unique();
+
+ shape().remove_property_from_unique_shape(property_name.as_string(), deleted_offset);
+ m_storage.remove(deleted_offset);
+ return Value(true);
+}
+
+void Object::ensure_shape_is_unique()
+{
+ if (shape().is_unique())
+ return;
+
+ m_shape = m_shape->create_unique_clone();
+}
+
Value Object::get_by_index(i32 property_index) const
{
if (property_index < 0)
diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h
index 0b0ccc4fcc..5c945ea888 100644
--- a/Libraries/LibJS/Runtime/Object.h
+++ b/Libraries/LibJS/Runtime/Object.h
@@ -46,6 +46,8 @@ public:
Shape& shape() { return *m_shape; }
const Shape& shape() const { return *m_shape; }
+ Value delete_property(PropertyName);
+
virtual Value get_by_index(i32 property_index) const;
Value get(const FlyString& property_name) const;
Value get(PropertyName) const;
@@ -102,6 +104,7 @@ public:
private:
void set_shape(Shape&);
+ void ensure_shape_is_unique();
Shape* m_shape { nullptr };
Vector<Value> m_storage;
diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp
index 34fea44646..1fcbe4e8e2 100644
--- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp
+++ b/Libraries/LibJS/Runtime/ObjectConstructor.cpp
@@ -75,8 +75,9 @@ Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
result->elements().append(js_string(interpreter, String::number(i)));
}
- for (auto& it : object->shape().property_table())
+ for (auto& it : object->shape().property_table()) {
result->elements().append(js_string(interpreter, it.key));
+ }
return result;
}
diff --git a/Libraries/LibJS/Runtime/Shape.cpp b/Libraries/LibJS/Runtime/Shape.cpp
index cbc7649c81..d03ad74a36 100644
--- a/Libraries/LibJS/Runtime/Shape.cpp
+++ b/Libraries/LibJS/Runtime/Shape.cpp
@@ -29,6 +29,17 @@
namespace JS {
+Shape* Shape::create_unique_clone() const
+{
+ auto* new_shape = heap().allocate<Shape>();
+ new_shape->m_unique = true;
+ new_shape->m_prototype = m_prototype;
+ ensure_property_table();
+ new_shape->ensure_property_table();
+ (*new_shape->m_property_table) = *m_property_table;
+ return new_shape;
+}
+
Shape* Shape::create_put_transition(const FlyString& property_name, u8 attributes)
{
TransitionKey key { property_name, attributes };
@@ -89,7 +100,10 @@ void Shape::visit_children(Cell::Visitor& visitor)
Optional<PropertyMetadata> Shape::lookup(const FlyString& property_name) const
{
- return property_table().get(property_name);
+ auto property = property_table().get(property_name);
+ if (!property.has_value())
+ return {};
+ return property;
}
const HashMap<FlyString, PropertyMetadata>& Shape::property_table() const
@@ -134,4 +148,32 @@ void Shape::ensure_property_table() const
}
}
+void Shape::add_property_to_unique_shape(const FlyString& property_name, u8 attributes)
+{
+ ASSERT(is_unique());
+ ASSERT(m_property_table);
+ ASSERT(!m_property_table->contains(property_name));
+ m_property_table->set(property_name, { m_property_table->size(), attributes });
+}
+
+void Shape::reconfigure_property_in_unique_shape(const FlyString& property_name, u8 attributes)
+{
+ ASSERT(is_unique());
+ ASSERT(m_property_table);
+ ASSERT(m_property_table->contains(property_name));
+ m_property_table->set(property_name, { m_property_table->size(), attributes });
+}
+
+void Shape::remove_property_from_unique_shape(const FlyString& property_name, size_t offset)
+{
+ ASSERT(is_unique());
+ ASSERT(m_property_table);
+ m_property_table->remove(property_name);
+ for (auto& it : *m_property_table) {
+ ASSERT(it.value.offset != offset);
+ if (it.value.offset > offset)
+ --it.value.offset;
+ }
+}
+
}
diff --git a/Libraries/LibJS/Runtime/Shape.h b/Libraries/LibJS/Runtime/Shape.h
index 70c75fef78..26c8eb574b 100644
--- a/Libraries/LibJS/Runtime/Shape.h
+++ b/Libraries/LibJS/Runtime/Shape.h
@@ -78,6 +78,9 @@ public:
Shape* create_configure_transition(const FlyString& name, u8 attributes);
Shape* create_prototype_transition(Object* new_prototype);
+ bool is_unique() const { return m_unique; }
+ Shape* create_unique_clone() const;
+
Object* prototype() { return m_prototype; }
const Object* prototype() const { return m_prototype; }
@@ -87,6 +90,10 @@ public:
void set_prototype_without_transition(Object* new_prototype) { m_prototype = new_prototype; }
+ void remove_property_from_unique_shape(const FlyString&, size_t offset);
+ void add_property_to_unique_shape(const FlyString&, u8 attributes);
+ void reconfigure_property_in_unique_shape(const FlyString& property_name, u8 attributes);
+
private:
virtual const char* class_name() const override { return "Shape"; }
virtual void visit_children(Visitor&) override;
@@ -99,6 +106,7 @@ private:
Shape* m_previous { nullptr };
FlyString m_property_name;
u8 m_attributes { 0 };
+ bool m_unique { false };
Object* m_prototype { nullptr };
TransitionType m_transition_type { TransitionType::Invalid };
};
diff --git a/Libraries/LibJS/Tests/delete-basic.js b/Libraries/LibJS/Tests/delete-basic.js
new file mode 100644
index 0000000000..eda9741b6b
--- /dev/null
+++ b/Libraries/LibJS/Tests/delete-basic.js
@@ -0,0 +1,60 @@
+load("test-common.js");
+
+try {
+ o = {};
+ o.x = 1;
+ o.y = 2;
+ o.z = 3;
+ assert(Object.getOwnPropertyNames(o).length === 3);
+
+ assert(delete o.x === true);
+ assert(o.hasOwnProperty('x') === false);
+ assert(o.hasOwnProperty('y') === true);
+ assert(o.hasOwnProperty('z') === true);
+ assert(Object.getOwnPropertyNames(o).length === 2);
+
+ assert(delete o.y === true);
+ assert(o.hasOwnProperty('x') === false);
+ assert(o.hasOwnProperty('y') === false);
+ assert(o.hasOwnProperty('z') === true);
+ assert(Object.getOwnPropertyNames(o).length === 1);
+
+ assert(delete o.z === true);
+ assert(o.hasOwnProperty('x') === false);
+ assert(o.hasOwnProperty('y') === false);
+ assert(o.hasOwnProperty('z') === false);
+ assert(Object.getOwnPropertyNames(o).length === 0);
+
+ a = [ 3, 5, 7 ];
+
+ assert(Object.getOwnPropertyNames(a).length === 4);
+
+ assert(delete a[0] === true);
+ assert(a.hasOwnProperty(0) === false);
+ assert(a.hasOwnProperty(1) === true);
+ assert(a.hasOwnProperty(2) === true);
+ assert(Object.getOwnPropertyNames(a).length === 3);
+
+ assert(delete a[1] === true);
+ assert(a.hasOwnProperty(0) === false);
+ assert(a.hasOwnProperty(1) === false);
+ assert(a.hasOwnProperty(2) === true);
+ assert(Object.getOwnPropertyNames(a).length === 2);
+
+ assert(delete a[2] === true);
+ assert(a.hasOwnProperty(0) === false);
+ assert(a.hasOwnProperty(1) === false);
+ assert(a.hasOwnProperty(2) === false);
+ assert(Object.getOwnPropertyNames(a).length === 1);
+
+ q = {};
+ Object.defineProperty(q, "foo", { value: 1, writable: false, enumerable: false });
+ assert(q.foo === 1);
+
+ assert(delete q.foo === false);
+ assert(q.hasOwnProperty('foo') === true);
+
+ console.log("PASS");
+} catch (e) {
+ console.log("FAIL: " + e);
+}