diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-07-07 21:38:46 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-07-09 23:33:00 +0200 |
commit | 7a1d485b19a43b3538b3ac69040ebc2b3bb14d85 (patch) | |
tree | 911f871ab04920383b79d24ae22c9ea80976685c | |
parent | 9783a4936cd0677dc3fe12cbf9009eceb47c4d1f (diff) | |
download | serenity-7a1d485b19a43b3538b3ac69040ebc2b3bb14d85.zip |
LibJS: Integrate Symbols into objects as valid keys
This allows objects properties to be created for symbol keys in addition
to just plain strings/numbers
-rw-r--r-- | Libraries/LibJS/AST.cpp | 53 | ||||
-rw-r--r-- | Libraries/LibJS/MarkupGenerator.cpp | 2 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.cpp | 128 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Object.h | 37 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ObjectConstructor.cpp | 19 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/PropertyName.h | 75 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ProxyObject.cpp | 14 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ProxyObject.h | 12 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/ReflectObject.cpp | 12 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Shape.cpp | 25 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Shape.h | 28 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/StringOrSymbol.h | 152 | ||||
-rw-r--r-- | Libraries/LibJS/Runtime/Symbol.h | 6 | ||||
-rw-r--r-- | Userland/js.cpp | 15 |
14 files changed, 424 insertions, 154 deletions
diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index 8eb975359a..65832acdba 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -65,6 +65,15 @@ static void update_function_name(Value& value, const FlyString& name) } } +static String get_function_name(Interpreter& interpreter, Value value) +{ + if (value.is_symbol()) + return String::format("[%s]", value.as_symbol().description().characters()); + if (value.is_string()) + return value.as_string().string(); + return value.to_string(interpreter); +} + Value ScopeNode::execute(Interpreter& interpreter, GlobalObject& global_object) const { return interpreter.run(global_object, *this); @@ -106,7 +115,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete if (interpreter.exception()) return {}; if (is_super_property_lookup && (lookup_target.is_null() || lookup_target.is_undefined())) { - interpreter.throw_exception<TypeError>(ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, lookup_target.to_string_without_side_effects()); + interpreter.throw_exception<TypeError>(ErrorType::ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, lookup_target.to_string_without_side_effects().characters()); return {}; } @@ -374,7 +383,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj return {}; auto* object = rhs_result.to_object(interpreter, global_object); while (object) { - auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, true); + auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyReturnMode::Key, true); for (auto& property_name : property_names.as_object().indexed_properties()) { interpreter.set_variable(variable_name, property_name.value_and_attributes(object).value, global_object); if (interpreter.exception()) @@ -735,23 +744,21 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob auto& target = method.is_static() ? *class_constructor : class_prototype.as_object(); method_function.set_home_object(&target); - auto property_name = key.to_string(interpreter); - if (method.kind() == ClassMethod::Kind::Method) { - target.define_property(property_name, method_value); + target.define_property(StringOrSymbol::from_value(interpreter, key), method_value); } else { String accessor_name = [&] { switch (method.kind()) { case ClassMethod::Kind::Getter: - return String::format("get %s", property_name.characters()); + return String::format("get %s", get_function_name(interpreter, key).characters()); case ClassMethod::Kind::Setter: - return String::format("set %s", property_name.characters()); + return String::format("set %s", get_function_name(interpreter, key).characters()); default: ASSERT_NOT_REACHED(); } }(); update_function_name(method_value, accessor_name); - target.define_accessor(property_name, method_function, method.kind() == ClassMethod::Kind::Getter, Attribute::Configurable | Attribute::Enumerable); + target.define_accessor(StringOrSymbol::from_value(interpreter, key), method_function, method.kind() == ClassMethod::Kind::Getter, Attribute::Configurable | Attribute::Enumerable); } if (interpreter.exception()) return {}; @@ -1286,7 +1293,7 @@ Value AssignmentExpression::execute(Interpreter& interpreter, GlobalObject& glob if (reference.is_unresolvable()) return interpreter.throw_exception<ReferenceError>(ErrorType::InvalidLeftHandAssignment); - update_function_name(rhs_result, reference.name().as_string()); + update_function_name(rhs_result, get_function_name(interpreter, reference.name().to_value(interpreter))); reference.put(interpreter, global_object, rhs_result); if (interpreter.exception()) @@ -1488,20 +1495,20 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o { auto* object = Object::create_empty(interpreter, global_object); for (auto& property : m_properties) { - auto key_result = property.key().execute(interpreter, global_object); + auto key = property.key().execute(interpreter, global_object); if (interpreter.exception()) return {}; if (property.type() == ObjectProperty::Type::Spread) { - if (key_result.is_array()) { - auto& array_to_spread = static_cast<Array&>(key_result.as_object()); + if (key.is_array()) { + auto& array_to_spread = static_cast<Array&>(key.as_object()); for (auto& entry : array_to_spread.indexed_properties()) { object->indexed_properties().append(entry.value_and_attributes(&array_to_spread).value); if (interpreter.exception()) return {}; } - } else if (key_result.is_object()) { - auto& obj_to_spread = key_result.as_object(); + } else if (key.is_object()) { + auto& obj_to_spread = key.as_object(); for (auto& it : obj_to_spread.shape().property_table_ordered()) { if (it.value.attributes.is_enumerable()) { @@ -1510,8 +1517,8 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o return {}; } } - } else if (key_result.is_string()) { - auto& str_to_spread = key_result.as_string().string(); + } else if (key.is_string()) { + auto& str_to_spread = key.as_string().string(); for (size_t i = 0; i < str_to_spread.length(); i++) { object->define_property(i, js_string(interpreter, str_to_spread.substring(i, 1))); @@ -1523,7 +1530,6 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o continue; } - auto key = key_result.to_string(interpreter); if (interpreter.exception()) return {}; auto value = property.value().execute(interpreter, global_object); @@ -1533,22 +1539,22 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o if (value.is_function() && property.is_method()) value.as_function().set_home_object(object); - String name = key; + String name = get_function_name(interpreter, key); if (property.type() == ObjectProperty::Type::Getter) { - name = String::format("get %s", key.characters()); + name = String::format("get %s", name.characters()); } else if (property.type() == ObjectProperty::Type::Setter) { - name = String::format("set %s", key.characters()); + name = String::format("set %s", name.characters()); } update_function_name(value, name); if (property.type() == ObjectProperty::Type::Getter || property.type() == ObjectProperty::Type::Setter) { ASSERT(value.is_function()); - object->define_accessor(key, value.as_function(), property.type() == ObjectProperty::Type::Getter, Attribute::Configurable | Attribute::Enumerable); + object->define_accessor(PropertyName::from_value(interpreter, key), value.as_function(), property.type() == ObjectProperty::Type::Getter, Attribute::Configurable | Attribute::Enumerable); if (interpreter.exception()) return {}; } else { - object->define_property(key, value); + object->define_property(PropertyName::from_value(interpreter, key), value); if (interpreter.exception()) return {}; } @@ -1579,6 +1585,9 @@ PropertyName MemberExpression::computed_property_name(Interpreter& interpreter, if (index.is_integer() && index.as_i32() >= 0) return index.as_i32(); + if (index.is_symbol()) + return &index.as_symbol(); + auto index_string = index.to_string(interpreter); if (interpreter.exception()) return {}; diff --git a/Libraries/LibJS/MarkupGenerator.cpp b/Libraries/LibJS/MarkupGenerator.cpp index ddea7711d6..769e5b638b 100644 --- a/Libraries/LibJS/MarkupGenerator.cpp +++ b/Libraries/LibJS/MarkupGenerator.cpp @@ -146,7 +146,7 @@ void MarkupGenerator::object_to_html(const Object& object, StringBuilder& html_o size_t index = 0; for (auto& it : object.shape().property_table_ordered()) { - html_output.append(wrap_string_in_style(String::format("\"%s\"", it.key.characters()), StyleType::String)); + html_output.append(wrap_string_in_style(String::format("\"%s\"", it.key.to_display_string().characters()), StyleType::String)); html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); value_to_html(object.get_direct(it.value.offset), html_output, seen_objects); if (index != object.shape().property_count() - 1) diff --git a/Libraries/LibJS/Runtime/Object.cpp b/Libraries/LibJS/Runtime/Object.cpp index 09649fc074..72c270ae24 100644 --- a/Libraries/LibJS/Runtime/Object.cpp +++ b/Libraries/LibJS/Runtime/Object.cpp @@ -164,7 +164,7 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_ return {}; value_here = existing_property.value().value.value_or(js_undefined()); } else { - auto metadata = shape().lookup(property_name.as_string()); + auto metadata = shape().lookup(property_name.to_string_or_symbol()); if (!metadata.has_value()) return {}; value_here = m_storage[metadata.value().offset].value_or(js_undefined()); @@ -179,7 +179,7 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_ return value_here; } -Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode kind, bool only_enumerable_properties) const +Value Object::get_own_properties(const Object& this_object, GetOwnPropertyReturnMode kind, bool only_enumerable_properties, GetOwnPropertyReturnType return_type) const { auto* properties_array = Array::create(global_object()); @@ -188,9 +188,9 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k auto str = static_cast<const StringObject&>(this_object).primitive_string().string(); for (size_t i = 0; i < str.length(); ++i) { - if (kind == GetOwnPropertyMode::Key) { + if (kind == GetOwnPropertyReturnMode::Key) { properties_array->define_property(i, js_string(interpreter(), String::number(i))); - } else if (kind == GetOwnPropertyMode::Value) { + } else if (kind == GetOwnPropertyReturnMode::Value) { properties_array->define_property(i, js_string(interpreter(), String::format("%c", str[i]))); } else { auto* entry_array = Array::create(global_object()); @@ -215,9 +215,9 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable()) continue; - if (kind == GetOwnPropertyMode::Key) { + if (kind == GetOwnPropertyReturnMode::Key) { properties_array->define_property(property_index, js_string(interpreter(), String::number(entry.index()))); - } else if (kind == GetOwnPropertyMode::Value) { + } else if (kind == GetOwnPropertyReturnMode::Value) { properties_array->define_property(property_index, value_and_attributes.value); } else { auto* entry_array = Array::create(global_object()); @@ -239,30 +239,35 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k if (only_enumerable_properties && !it.value.attributes.is_enumerable()) continue; - size_t offset = it.value.offset + property_index; + if (return_type == GetOwnPropertyReturnType::StringOnly && it.key.is_symbol()) + continue; + if (return_type == GetOwnPropertyReturnType::SymbolOnly && it.key.is_string()) + continue; - if (kind == GetOwnPropertyMode::Key) { - properties_array->define_property(offset, js_string(interpreter(), it.key)); - } else if (kind == GetOwnPropertyMode::Value) { - properties_array->define_property(offset, this_object.get(it.key)); + if (kind == GetOwnPropertyReturnMode::Key) { + properties_array->define_property(property_index, it.key.to_value(interpreter())); + } else if (kind == GetOwnPropertyReturnMode::Value) { + properties_array->define_property(property_index, this_object.get(it.key)); } else { auto* entry_array = Array::create(global_object()); - entry_array->define_property(0, js_string(interpreter(), it.key)); + entry_array->define_property(0, it.key.to_value(interpreter())); if (interpreter().exception()) return {}; entry_array->define_property(1, this_object.get(it.key)); if (interpreter().exception()) return {}; - properties_array->define_property(offset, entry_array); + properties_array->define_property(property_index, entry_array); } if (interpreter().exception()) return {}; + + ++property_index; } return properties_array; } -Optional<PropertyDescriptor> Object::get_own_property_descriptor(PropertyName property_name) const +Optional<PropertyDescriptor> Object::get_own_property_descriptor(const PropertyName& property_name) const { Value value; PropertyAttributes attributes; @@ -275,7 +280,7 @@ Optional<PropertyDescriptor> Object::get_own_property_descriptor(PropertyName pr attributes = existing_value.value().attributes; attributes = default_attributes; } else { - auto metadata = shape().lookup(property_name.as_string()); + auto metadata = shape().lookup(property_name.to_string_or_symbol()); if (!metadata.has_value()) return {}; value = m_storage[metadata.value().offset]; @@ -301,7 +306,7 @@ Optional<PropertyDescriptor> Object::get_own_property_descriptor(PropertyName pr return descriptor; } -Value Object::get_own_property_descriptor_object(PropertyName property_name) const +Value Object::get_own_property_descriptor_object(const PropertyName& property_name) const { auto descriptor_opt = get_own_property_descriptor(property_name); if (!descriptor_opt.has_value()) @@ -343,7 +348,7 @@ void Object::set_shape(Shape& new_shape) m_shape = &new_shape; } -bool Object::define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions) +bool Object::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) { bool is_accessor_property = descriptor.has_property("get") || descriptor.has_property("set"); PropertyAttributes attributes; @@ -423,20 +428,22 @@ bool Object::define_property(const FlyString& property_name, const Object& descr return define_property(property_name, value, attributes, throw_exceptions); } -bool Object::define_property(PropertyName property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) +bool Object::define_property(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions) { if (property_name.is_number()) return put_own_property_by_index(*this, property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); - i32 property_index = property_name.as_string().to_int().value_or(-1); - if (property_index >= 0) - return put_own_property_by_index(*this, property_index, value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); - return put_own_property(*this, property_name.as_string(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); + if (property_name.is_string()) { + i32 property_index = property_name.as_string().to_int().value_or(-1); + if (property_index >= 0) + return put_own_property_by_index(*this, property_index, value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); + } + return put_own_property(*this, property_name.to_string_or_symbol(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions); } -bool Object::define_accessor(PropertyName property_name, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes, bool throw_exceptions) +bool Object::define_accessor(const PropertyName& property_name, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes, bool throw_exceptions) { Accessor* accessor { nullptr }; - auto property_metadata = shape().lookup(property_name.as_string()); + auto property_metadata = shape().lookup(property_name.to_string_or_symbol()); if (property_metadata.has_value()) { auto existing_property = get_direct(property_metadata.value().offset); if (existing_property.is_accessor()) @@ -458,7 +465,7 @@ bool Object::define_accessor(PropertyName property_name, Function& getter_or_set return true; } -bool Object::put_own_property(Object& this_object, const FlyString& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions) +bool Object::put_own_property(Object& this_object, const StringOrSymbol& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions) { ASSERT(!(mode == PutOwnPropertyMode::Put && value.is_accessor())); @@ -470,7 +477,7 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam dbg() << "Disallow define_property of non-extensible object"; #endif if (throw_exceptions && interpreter().in_strict_mode()) - interpreter().throw_exception<TypeError>(ErrorType::NonExtensibleDefine, property_name.characters()); + interpreter().throw_exception<TypeError>(ErrorType::NonExtensibleDefine, property_name.to_display_string().characters()); return false; } @@ -482,7 +489,6 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam attributes.set_has_setter(); } - if (new_property) { if (!m_shape->is_unique() && shape().property_count() > 100) { // If you add more than 100 properties to an object, let's stop doing @@ -505,7 +511,7 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam dbg() << "Disallow reconfig of non-configurable property"; #endif if (throw_exceptions) - interpreter().throw_exception<TypeError>(ErrorType::DescChangeNonConfigurable, property_name.characters()); + interpreter().throw_exception<TypeError>(ErrorType::DescChangeNonConfigurable, property_name.to_display_string().characters()); return false; } @@ -595,7 +601,7 @@ bool Object::put_own_property_by_index(Object& this_object, u32 property_index, return true; } -Value Object::delete_property(PropertyName property_name) +Value Object::delete_property(const PropertyName& property_name) { ASSERT(property_name.is_valid()); if (property_name.is_number()) @@ -604,7 +610,7 @@ Value Object::delete_property(PropertyName property_name) if (property_index >= 0) return Value(m_indexed_properties.remove(property_name.as_number())); - auto metadata = shape().lookup(property_name.as_string()); + auto metadata = shape().lookup(property_name.to_string_or_symbol()); if (!metadata.has_value()) return Value(true); if (!metadata.value().attributes.is_configurable()) @@ -614,7 +620,7 @@ Value Object::delete_property(PropertyName property_name) ensure_shape_is_unique(); - shape().remove_property_from_unique_shape(property_name.as_string(), deleted_offset); + shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), deleted_offset); m_storage.remove(deleted_offset); return Value(true); } @@ -652,15 +658,17 @@ Value Object::get_by_index(u32 property_index) const return {}; } -Value Object::get(PropertyName property_name, Value receiver) const +Value Object::get(const PropertyName& property_name, Value receiver) const { if (property_name.is_number()) return get_by_index(property_name.as_number()); - auto property_string = property_name.to_string(); - i32 property_index = property_string.to_int().value_or(-1); - if (property_index >= 0) - return get_by_index(property_index); + if (property_name.is_string()) { + auto property_string = property_name.to_string(); + i32 property_index = property_string.to_int().value_or(-1); + if (property_index >= 0) + return get_by_index(property_index); + } const Object* object = this; while (object) { @@ -705,23 +713,27 @@ bool Object::put_by_index(u32 property_index, Value value) return put_own_property_by_index(*this, property_index, value, default_attributes, PutOwnPropertyMode::Put); } -bool Object::put(PropertyName property_name, Value value, Value receiver) +bool Object::put(const PropertyName& property_name, Value value, Value receiver) { if (property_name.is_number()) return put_by_index(property_name.as_number(), value); ASSERT(!value.is_empty()); - auto property_string = property_name.to_string(); - i32 property_index = property_string.to_int().value_or(-1); - if (property_index >= 0) - return put_by_index(property_index, value); + if (property_name.is_string()) { + auto& property_string = property_name.as_string(); + i32 property_index = property_string.to_int().value_or(-1); + if (property_index >= 0) + return put_by_index(property_index, value); + } + + auto string_or_symbol = property_name.to_string_or_symbol(); // If there's a setter in the prototype chain, we go to the setter. // Otherwise, it goes in the own property storage. Object* object = this; while (object) { - auto metadata = object->shape().lookup(property_string); + auto metadata = object->shape().lookup(string_or_symbol); if (metadata.has_value()) { auto value_here = object->m_storage[metadata.value().offset]; if (value_here.is_accessor()) { @@ -739,22 +751,28 @@ bool Object::put(PropertyName property_name, Value value, Value receiver) if (interpreter().exception()) return {}; } - return put_own_property(*this, property_string, value, default_attributes, PutOwnPropertyMode::Put); + return put_own_property(*this, string_or_symbol, value, default_attributes, PutOwnPropertyMode::Put); } -bool Object::define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&, GlobalObject&)> native_function, i32 length, PropertyAttributes attribute) +bool Object::define_native_function(const StringOrSymbol& property_name, AK::Function<Value(Interpreter&, GlobalObject&)> native_function, i32 length, PropertyAttributes attribute) { - auto* function = NativeFunction::create(interpreter(), global_object(), property_name, move(native_function)); + String function_name; + if (property_name.is_string()) { + function_name = property_name.as_string(); + } else { + function_name = String::format("[%s]", property_name.as_symbol()->description().characters()); + } + auto* function = NativeFunction::create(interpreter(), global_object(), function_name, move(native_function)); function->define_property("length", Value(length), Attribute::Configurable); if (interpreter().exception()) return {}; - function->define_property("name", js_string(heap(), property_name), Attribute::Configurable); + function->define_property("name", js_string(heap(), function_name), Attribute::Configurable); if (interpreter().exception()) return {}; return define_property(property_name, function, attribute); } -bool Object::define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&, GlobalObject&)> getter, AK::Function<void(Interpreter&, GlobalObject&, Value)> setter, PropertyAttributes attribute) +bool Object::define_native_property(const StringOrSymbol& property_name, AK::Function<Value(Interpreter&, GlobalObject&)> getter, AK::Function<void(Interpreter&, GlobalObject&, Value)> setter, PropertyAttributes attribute) { return define_property(property_name, heap().allocate<NativeProperty>(global_object(), move(getter), move(setter)), attribute); } @@ -771,7 +789,7 @@ void Object::visit_children(Cell::Visitor& visitor) visitor.visit(value.value); } -bool Object::has_property(PropertyName property_name) const +bool Object::has_property(const PropertyName& property_name) const { const Object* object = this; while (object) { @@ -784,7 +802,7 @@ bool Object::has_property(PropertyName property_name) const return false; } -bool Object::has_own_property(PropertyName property_name) const +bool Object::has_own_property(const PropertyName& property_name) const { auto has_indexed_property = [&](u32 index) -> bool { if (is_string_object()) @@ -795,11 +813,13 @@ bool Object::has_own_property(PropertyName property_name) const if (property_name.is_number()) return has_indexed_property(property_name.as_number()); - i32 property_index = property_name.as_string().to_int().value_or(-1); - if (property_index >= 0) - return has_indexed_property(property_index); + if (property_name.is_string()) { + i32 property_index = property_name.as_string().to_int().value_or(-1); + if (property_index >= 0) + return has_indexed_property(property_index); + } - return shape().lookup(property_name.as_string()).has_value(); + return shape().lookup(property_name.to_string_or_symbol()).has_value(); } Value Object::to_primitive(Value::PreferredType preferred_type) const @@ -846,7 +866,7 @@ Value Object::to_string() const return js_string(heap(), String::format("[object %s]", class_name())); } -Value Object::invoke(const FlyString& property_name, Optional<MarkedValueList> arguments) +Value Object::invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments) { auto& interpreter = this->interpreter(); auto property = get(property_name).value_or(js_undefined()); diff --git a/Libraries/LibJS/Runtime/Object.h b/Libraries/LibJS/Runtime/Object.h index 3723560e27..082c5fff57 100644 --- a/Libraries/LibJS/Runtime/Object.h +++ b/Libraries/LibJS/Runtime/Object.h @@ -68,12 +68,17 @@ public: virtual bool inherits(const StringView& class_name) const { return class_name == this->class_name(); } - enum class GetOwnPropertyMode { + enum class GetOwnPropertyReturnMode { Key, Value, KeyAndValue, }; + enum class GetOwnPropertyReturnType { + StringOnly, + SymbolOnly, + }; + enum class PutOwnPropertyMode { Put, DefineProperty, @@ -84,26 +89,26 @@ public: GlobalObject& global_object() const { return shape().global_object(); } - virtual Value get(PropertyName, Value receiver = {}) const; + virtual Value get(const PropertyName&, Value receiver = {}) const; - virtual bool has_property(PropertyName) const; - bool has_own_property(PropertyName) const; + virtual bool has_property(const PropertyName&) const; + bool has_own_property(const PropertyName&) const; - virtual bool put(PropertyName, Value, Value receiver = {}); + virtual bool put(const PropertyName&, Value, Value receiver = {}); Value get_own_property(const Object& this_object, PropertyName, Value receiver) const; - Value get_own_properties(const Object& this_object, GetOwnPropertyMode, bool only_enumerable_properties = false) const; - virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const; - Value get_own_property_descriptor_object(PropertyName) const; + Value get_own_properties(const Object& this_object, GetOwnPropertyReturnMode, bool only_enumerable_properties = false, GetOwnPropertyReturnType = GetOwnPropertyReturnType::StringOnly) const; + virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const; + Value get_own_property_descriptor_object(const PropertyName&) const; - virtual bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true); - bool define_property(PropertyName, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); - bool define_accessor(PropertyName, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); + virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true); + bool define_property(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); + bool define_accessor(const PropertyName&, Function& getter_or_setter, bool is_getter, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); - bool define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes); - bool define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&, GlobalObject&)> getter, AK::Function<void(Interpreter&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes); + bool define_native_function(const StringOrSymbol& property_name, AK::Function<Value(Interpreter&, GlobalObject&)>, i32 length = 0, PropertyAttributes attributes = default_attributes); + bool define_native_property(const StringOrSymbol& property_name, AK::Function<Value(Interpreter&, GlobalObject&)> getter, AK::Function<void(Interpreter&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes); - virtual Value delete_property(PropertyName); + virtual Value delete_property(const PropertyName&); virtual bool is_array() const { return false; } virtual bool is_date() const { return false; } @@ -140,7 +145,7 @@ public: IndexedProperties& indexed_properties() { return m_indexed_properties; } void set_indexed_property_elements(Vector<Value>&& values) { m_indexed_properties = IndexedProperties(move(values)); } - Value invoke(const FlyString& property_name, Optional<MarkedValueList> arguments = {}); + Value invoke(const StringOrSymbol& property_name, Optional<MarkedValueList> arguments = {}); protected: enum class GlobalObjectTag { Tag }; @@ -151,7 +156,7 @@ protected: private: virtual Value get_by_index(u32 property_index) const; virtual bool put_by_index(u32 property_index, Value); - bool put_own_property(Object& this_object, const FlyString& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); + bool put_own_property(Object& this_object, const StringOrSymbol& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); bool put_own_property_by_index(Object& this_object, u32 property_index, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true); Value call_native_property_getter(Object* this_object, Value property) const; diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp index 7a0b4d246a..ca6de93616 100644 --- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/Libraries/LibJS/Runtime/ObjectConstructor.cpp @@ -84,8 +84,11 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names) auto* result = Array::create(global_object); for (auto& entry : object->indexed_properties()) result->indexed_properties().append(js_string(interpreter, String::number(entry.index()))); - for (auto& it : object->shape().property_table_ordered()) - result->indexed_properties().append(js_string(interpreter, it.key)); + for (auto& it : object->shape().property_table_ordered()) { + if (!it.key.is_string()) + continue; + result->indexed_properties().append(js_string(interpreter, it.key.as_string())); + } return result; } @@ -151,7 +154,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_descriptor) auto* object = interpreter.argument(0).to_object(interpreter, global_object); if (interpreter.exception()) return {}; - auto property_key = interpreter.argument(1).to_string(interpreter); + auto property_key = PropertyName::from_value(interpreter, interpreter.argument(1)); if (interpreter.exception()) return {}; return object->get_own_property_descriptor_object(property_key); @@ -164,7 +167,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_) if (!interpreter.argument(2).is_object()) return interpreter.throw_exception<TypeError>(ErrorType::NotAnObject, "Descriptor argument"); auto& object = interpreter.argument(0).as_object(); - auto property_key = interpreter.argument(1).to_string(interpreter); + auto property_key = StringOrSymbol::from_value(interpreter, interpreter.argument(1)); if (interpreter.exception()) return {}; auto& descriptor = interpreter.argument(2).as_object(); @@ -173,7 +176,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_) if (object.is_proxy_object()) { interpreter.throw_exception<TypeError>(ErrorType::ObjectDefinePropertyReturnedFalse); } else { - interpreter.throw_exception<TypeError>(ErrorType::NonExtensibleDefine, property_key.characters()); + interpreter.throw_exception<TypeError>(ErrorType::NonExtensibleDefine, property_key.to_display_string().characters()); } } return {}; @@ -195,7 +198,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::keys) if (interpreter.exception()) return {}; - return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Key, true); + return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyReturnMode::Key, true); } JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::values) @@ -207,7 +210,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::values) if (interpreter.exception()) return {}; - return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Value, true); + return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyReturnMode::Value, true); } JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries) @@ -219,7 +222,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries) if (interpreter.exception()) return {}; - return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::KeyAndValue, true); + return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyReturnMode::KeyAndValue, true); } } diff --git a/Libraries/LibJS/Runtime/PropertyName.h b/Libraries/LibJS/Runtime/PropertyName.h index eff04666bd..9627f2db88 100644 --- a/Libraries/LibJS/Runtime/PropertyName.h +++ b/Libraries/LibJS/Runtime/PropertyName.h @@ -27,6 +27,7 @@ #pragma once #include <AK/FlyString.h> +#include <LibJS/Runtime/StringOrSymbol.h> namespace JS { @@ -36,9 +37,21 @@ public: Invalid, Number, String, + Symbol, }; - PropertyName() {} + static PropertyName from_value(Interpreter& interpreter, Value value) + { + if (value.is_symbol()) + return &value.as_symbol(); + if (value.is_number()) + return value.as_i32(); + if (!value.is_empty()) + return value.to_string(interpreter); + return {}; + } + + PropertyName() { } PropertyName(i32 index) : m_type(Type::Number) @@ -65,23 +78,79 @@ public: { } + PropertyName(Symbol* symbol) + : m_type(Type::Symbol) + , m_symbol(symbol) + { + } + + PropertyName(const StringOrSymbol& string_or_symbol) + { + if (string_or_symbol.is_string()) { + m_string = string_or_symbol.as_string(); + m_type = Type::String; + } else if (string_or_symbol.is_symbol()) { + m_symbol = const_cast<Symbol*>(string_or_symbol.as_symbol()); + m_type = Type::Symbol; + } + } + bool is_valid() const { return m_type != Type::Invalid; } bool is_number() const { return m_type == Type::Number; } bool is_string() const { return m_type == Type::String; } + bool is_symbol() const { return m_type == Type::Symbol; } - i32 as_number() const { return m_number; } - const FlyString& as_string() const { return m_string; } + i32 as_number() const + { + ASSERT(is_number()); + return m_number; + } + + const FlyString& as_string() const + { + ASSERT(is_string()); + return m_string; + } + + const Symbol* as_symbol() const + { + ASSERT(is_symbol()); + return m_symbol; + } String to_string() const { + ASSERT(is_valid()); + ASSERT(!is_symbol()); if (is_string()) return as_string(); return String::number(as_number()); } + StringOrSymbol to_string_or_symbol() const + { + ASSERT(is_valid()); + ASSERT(!is_number()); + if (is_string()) + return StringOrSymbol(as_string()); + return StringOrSymbol(as_symbol()); + } + + Value to_value(Interpreter& interpreter) const + { + if (is_string()) + return js_string(interpreter, m_string); + if (is_number()) + return Value(m_number); + if (is_symbol()) + return m_symbol; + return js_undefined(); + } + private: Type m_type { Type::Invalid }; FlyString m_string; + Symbol* m_symbol { nullptr }; u32 m_number { 0 }; }; diff --git a/Libraries/LibJS/Runtime/ProxyObject.cpp b/Libraries/LibJS/Runtime/ProxyObject.cpp index decafe490f..6d532d03a8 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -213,7 +213,7 @@ bool ProxyObject::prevent_extensions() return trap_result; } -Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(PropertyName name) const +Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(const PropertyName& name) const { if (m_is_revoked) { interpreter().throw_exception<TypeError>(ErrorType::ProxyRevoked); @@ -270,7 +270,7 @@ Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(PropertyNa return result_desc; } -bool ProxyObject::define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions) +bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions) { if (m_is_revoked) { interpreter().throw_exception<TypeError>(ErrorType::ProxyRevoked); @@ -287,7 +287,7 @@ bool ProxyObject::define_property(const FlyString& property_name, const Object& } MarkedValueList arguments(interpreter().heap()); arguments.append(Value(&m_target)); - arguments.append(js_string(interpreter(), property_name)); + arguments.append(property_name.to_value(interpreter())); arguments.append(Value(const_cast<Object*>(&descriptor))); auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); if (interpreter().exception() || !trap_result) @@ -324,7 +324,7 @@ bool ProxyObject::define_property(const FlyString& property_name, const Object& return true; } -bool ProxyObject::has_property(PropertyName name) const +bool ProxyObject::has_property(const PropertyName& name) const { if (m_is_revoked) { interpreter().throw_exception<TypeError>(ErrorType::ProxyRevoked); @@ -364,7 +364,7 @@ bool ProxyObject::has_property(PropertyName name) const return trap_result; } -Value ProxyObject::get(PropertyName name, Value) const +Value ProxyObject::get(const PropertyName& name, Value) const { if (m_is_revoked) { interpreter().throw_exception<TypeError>(ErrorType::ProxyRevoked); @@ -396,7 +396,7 @@ Value ProxyObject::get(PropertyName name, Value) const return trap_result; } -bool ProxyObject::put(PropertyName name, Value value, Value) +bool ProxyObject::put(const PropertyName& name, Value value, Value) { if (m_is_revoked) { interpreter().throw_exception<TypeError>(ErrorType::ProxyRevoked); @@ -434,7 +434,7 @@ bool ProxyObject::put(PropertyName name, Value value, Value) return true; } -Value ProxyObject::delete_property(PropertyName name) +Value ProxyObject::delete_property(const PropertyName& name) { if (m_is_revoked) { interpreter().throw_exception<TypeError>(ErrorType::ProxyRevoked); diff --git a/Libraries/LibJS/Runtime/ProxyObject.h b/Libraries/LibJS/Runtime/ProxyObject.h index 9c8b271161..83451712eb 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Libraries/LibJS/Runtime/ProxyObject.h @@ -52,12 +52,12 @@ public: virtual bool set_prototype(Object* object) override; virtual bool is_extensible() const override; virtual bool prevent_extensions() override; - virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const override; - virtual bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true) override; - virtual bool has_property(PropertyName name) const override; - virtual Value get(PropertyName name, Value receiver) const override; - virtual bool put(PropertyName name, Value value, Value receiver) override; - virtual Value delete_property(PropertyName name) override; + virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const override; + virtual bool define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions = true) override; + virtual bool has_property(const PropertyName& name) const override; + virtual Value get(const PropertyName& name, Value receiver) const override; + virtual bool put(const PropertyName& name, Value value, Value receiver) override; + virtual Value delete_property(const PropertyName& name) override; void revoke() { m_is_revoked = true; } diff --git a/Libraries/LibJS/Runtime/ReflectObject.cpp b/Libraries/LibJS/Runtime/ReflectObject.cpp index 01a0936c8a..965a04fb0d 100644 --- a/Libraries/LibJS/Runtime/ReflectObject.cpp +++ b/Libraries/LibJS/Runtime/ReflectObject.cpp @@ -145,7 +145,7 @@ JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property) return {}; if (!interpreter.argument(2).is_object()) return interpreter.throw_exception<TypeError>(ErrorType::ReflectBadDescriptorArgument); - auto property_key = interpreter.argument(1).to_string(interpreter); + auto property_key = StringOrSymbol::from_value(interpreter, interpreter.argument(1)); if (interpreter.exception()) return {}; auto& descriptor = interpreter.argument(2).as_object(); @@ -162,7 +162,7 @@ JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property) return {}; auto property_key = interpreter.argument(1); - auto property_name = PropertyName(property_key.to_string(interpreter)); + auto property_name = PropertyName::from_value(interpreter, property_key); if (interpreter.exception()) return {}; auto property_key_number = property_key.to_number(interpreter); @@ -181,7 +181,7 @@ JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get) auto* target = get_target_object_from(interpreter, "get"); if (!target) return {}; - auto property_key = interpreter.argument(1).to_string(interpreter); + auto property_key = PropertyName::from_value(interpreter, interpreter.argument(1)); if (interpreter.exception()) return {}; Value receiver = {}; @@ -195,7 +195,7 @@ JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor) auto* target = get_target_object_from(interpreter, "getOwnPropertyDescriptor"); if (!target) return {}; - auto property_key = interpreter.argument(1).to_string(interpreter); + auto property_key = PropertyName::from_value(interpreter, interpreter.argument(1)); if (interpreter.exception()) return {}; return target->get_own_property_descriptor_object(property_key); @@ -214,7 +214,7 @@ JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has) auto* target = get_target_object_from(interpreter, "has"); if (!target) return {}; - auto property_key = interpreter.argument(1).to_string(interpreter); + auto property_key = PropertyName::from_value(interpreter, interpreter.argument(1)); if (interpreter.exception()) return {}; return Value(target->has_property(property_key)); @@ -233,7 +233,7 @@ JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys) auto* target = get_target_object_from(interpreter, "ownKeys"); if (!target) return {}; - return target->get_own_properties(*target, GetOwnPropertyMode::Key); + return target->get_own_properties(*target, GetOwnPropertyReturnMode::Key); } JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions) diff --git a/Libraries/LibJS/Runtime/Shape.cpp b/Libraries/LibJS/Runtime/Shape.cpp index e6e70e58ab..9d5e79eeaa 100644 --- a/Libraries/LibJS/Runtime/Shape.cpp +++ b/Libraries/LibJS/Runtime/Shape.cpp @@ -41,7 +41,7 @@ Shape* Shape::create_unique_clone() const return new_shape; } -Shape* Shape::create_put_transition(const FlyString& property_name, PropertyAttributes attributes) +Shape* Shape::create_put_transition(const StringOrSymbol& property_name, PropertyAttributes attributes) { TransitionKey key { property_name, attributes }; if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr)) @@ -51,7 +51,7 @@ Shape* Shape::create_put_transition(const FlyString& property_name, PropertyAttr return new_shape; } -Shape* Shape::create_configure_transition(const FlyString& property_name, PropertyAttributes attributes) +Shape* Shape::create_configure_transition(const StringOrSymbol& property_name, PropertyAttributes attributes) { TransitionKey key { property_name, attributes }; if (auto* existing_shape = m_forward_transitions.get(key).value_or(nullptr)) @@ -71,7 +71,7 @@ Shape::Shape(GlobalObject& global_object) { } -Shape::Shape(Shape& previous_shape, const FlyString& property_name, PropertyAttributes attributes, TransitionType transition_type) +Shape::Shape(Shape& previous_shape, const StringOrSymbol& property_name, PropertyAttributes attributes, TransitionType transition_type) : m_global_object(previous_shape.m_global_object) , m_previous(&previous_shape) , m_property_name(property_name) @@ -99,11 +99,16 @@ void Shape::visit_children(Cell::Visitor& visitor) visitor.visit(&m_global_object); visitor.visit(m_prototype); visitor.visit(m_previous); + m_property_name.visit_children(visitor); for (auto& it : m_forward_transitions) visitor.visit(it.value); + + ensure_property_table(); + for (auto& it : *m_property_table) + it.key.visit_children(visitor); } -Optional<PropertyMetadata> Shape::lookup(const FlyString& property_name) const +Optional<PropertyMetadata> Shape::lookup(const StringOrSymbol& property_name) const { auto property = property_table().get(property_name); if (!property.has_value()) @@ -111,7 +116,7 @@ Optional<PropertyMetadata> Shape::lookup(const FlyString& property_name) const return property; } -const HashMap<FlyString, PropertyMetadata>& Shape::property_table() const +const HashMap<StringOrSymbol, PropertyMetadata>& Shape::property_table() const { ensure_property_table(); return *m_property_table; @@ -138,7 +143,7 @@ void Shape::ensure_property_table() const { if (m_property_table) return; - m_property_table = make<HashMap<FlyString, PropertyMetadata>>(); + m_property_table = make<HashMap<StringOrSymbol, PropertyMetadata>>(); // FIXME: We need to make sure the GC doesn't collect the transition chain as we're building it. // Maybe some kind of RAII "prevent GC for a moment" helper thingy? @@ -151,7 +156,7 @@ void Shape::ensure_property_table() const u32 next_offset = 0; for (ssize_t i = transition_chain.size() - 1; i >= 0; --i) { auto* shape = transition_chain[i]; - if (shape->m_property_name.is_null()) { + if (!shape->m_property_name.is_valid()) { // Ignore prototype transitions as they don't affect the key map. continue; } @@ -165,7 +170,7 @@ void Shape::ensure_property_table() const } } -void Shape::add_property_to_unique_shape(const FlyString& property_name, PropertyAttributes attributes) +void Shape::add_property_to_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes) { ASSERT(is_unique()); ASSERT(m_property_table); @@ -173,7 +178,7 @@ void Shape::add_property_to_unique_shape(const FlyString& property_name, Propert m_property_table->set(property_name, { m_property_table->size(), attributes }); } -void Shape::reconfigure_property_in_unique_shape(const FlyString& property_name, PropertyAttributes attributes) +void Shape::reconfigure_property_in_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes) { ASSERT(is_unique()); ASSERT(m_property_table); @@ -181,7 +186,7 @@ void Shape::reconfigure_property_in_unique_shape(const FlyString& 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) +void Shape::remove_property_from_unique_shape(const StringOrSymbol& property_name, size_t offset) { ASSERT(is_unique()); ASSERT(m_property_table); diff --git a/Libraries/LibJS/Runtime/Shape.h b/Libraries/LibJS/Runtime/Shape.h index 17fd34a057..4e7f87391c 100644 --- a/Libraries/LibJS/Runtime/Shape.h +++ b/Libraries/LibJS/Runtime/Shape.h @@ -26,12 +26,12 @@ #pragma once -#include <AK/FlyString.h> #include <AK/HashMap.h> #include <AK/OwnPtr.h> #include <LibJS/Forward.h> #include <LibJS/Runtime/Cell.h> #include <LibJS/Runtime/PropertyAttributes.h> +#include <LibJS/Runtime/StringOrSymbol.h> #include <LibJS/Runtime/Value.h> namespace JS { @@ -42,7 +42,7 @@ struct PropertyMetadata { }; struct TransitionKey { - FlyString property_name; + StringOrSymbol property_name; PropertyAttributes attributes { 0 }; bool operator==(const TransitionKey& other) const @@ -63,11 +63,11 @@ public: }; explicit Shape(GlobalObject&); - Shape(Shape& previous_shape, const FlyString& property_name, PropertyAttributes attributes, TransitionType); + Shape(Shape& previous_shape, const StringOrSymbol& property_name, PropertyAttributes attributes, TransitionType); Shape(Shape& previous_shape, Object* new_prototype); - Shape* create_put_transition(const FlyString& name, PropertyAttributes attributes); - Shape* create_configure_transition(const FlyString& name, PropertyAttributes attributes); + Shape* create_put_transition(const StringOrSymbol&, PropertyAttributes attributes); + Shape* create_configure_transition(const StringOrSymbol&, PropertyAttributes attributes); Shape* create_prototype_transition(Object* new_prototype); bool is_unique() const { return m_unique; } @@ -78,12 +78,12 @@ public: Object* prototype() { return m_prototype; } const Object* prototype() const { return m_prototype; } - Optional<PropertyMetadata> lookup(const FlyString&) const; - const HashMap<FlyString, PropertyMetadata>& property_table() const; + Optional<PropertyMetadata> lookup(const StringOrSymbol&) const; + const HashMap<StringOrSymbol, PropertyMetadata>& property_table() const; size_t property_count() const; struct Property { - FlyString key; + StringOrSymbol key; PropertyMetadata value; }; @@ -91,9 +91,9 @@ 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&, PropertyAttributes attributes); - void reconfigure_property_in_unique_shape(const FlyString& property_name, PropertyAttributes attributes); + void remove_property_from_unique_shape(const StringOrSymbol&, size_t offset); + void add_property_to_unique_shape(const StringOrSymbol&, PropertyAttributes attributes); + void reconfigure_property_in_unique_shape(const StringOrSymbol& property_name, PropertyAttributes attributes); private: virtual const char* class_name() const override { return "Shape"; } @@ -103,11 +103,11 @@ private: GlobalObject& m_global_object; - mutable OwnPtr<HashMap<FlyString, PropertyMetadata>> m_property_table; + mutable OwnPtr<HashMap<StringOrSymbol, PropertyMetadata>> m_property_table; HashMap<TransitionKey, Shape*> m_forward_transitions; Shape* m_previous { nullptr }; - FlyString m_property_name; + StringOrSymbol m_property_name; PropertyAttributes m_attributes { 0 }; bool m_unique { false }; Object* m_prototype { nullptr }; @@ -120,6 +120,6 @@ template<> struct AK::Traits<JS::TransitionKey> : public GenericTraits<JS::TransitionKey> { static unsigned hash(const JS::TransitionKey& key) { - return pair_int_hash(key.attributes.bits(), key.property_name.hash()); + return pair_int_hash(key.attributes.bits(), Traits<JS::StringOrSymbol>::hash(key.property_name)); } }; diff --git a/Libraries/LibJS/Runtime/StringOrSymbol.h b/Libraries/LibJS/Runtime/StringOrSymbol.h new file mode 100644 index 0000000000..a0b7cacebf --- /dev/null +++ b/Libraries/LibJS/Runtime/StringOrSymbol.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/Symbol.h> +#include <LibJS/Runtime/Value.h> + +namespace JS { + +class StringOrSymbol { +public: + static StringOrSymbol from_value(Interpreter& interpreter, Value value) + { + if (value.is_symbol()) + return &value.as_symbol(); + if (!value.is_empty()) + return value.to_string(interpreter); + return {}; + } + + StringOrSymbol() = default; + + StringOrSymbol(const char* chars) + : m_ptr(StringImpl::create(chars).leak_ref()) + { + } + + StringOrSymbol(const String& string) + : m_ptr(StringImpl::create(string.characters(), string.length()).leak_ref()) + { + } + + StringOrSymbol(const Symbol* symbol) + : m_ptr(symbol) + { + set_symbol_flag(); + } + + StringOrSymbol(const StringOrSymbol& other) + { + m_ptr = other.m_ptr; + } + + ALWAYS_INLINE bool is_valid() const { return m_ptr != nullptr; } + ALWAYS_INLINE bool is_symbol() const { return is_valid() && (bits() & 1ul); } + ALWAYS_INLINE bool is_string() const { return is_valid() && !(bits() & 1ul); } + + ALWAYS_INLINE String as_string() const + { + ASSERT(is_string()); + return reinterpret_cast<const StringImpl*>(m_ptr); + } + + ALWAYS_INLINE const Symbol* as_symbol() const + { + ASSERT(is_symbol()); + return reinterpret_cast<const Symbol*>(bits() & ~1ul); + } + + String to_display_string() const + { + if (is_string()) + return as_string(); + if (is_symbol()) + return as_symbol()->to_string(); + ASSERT_NOT_REACHED(); + } + + Value to_value(Interpreter& interpreter) const + { + if (is_string()) + return js_string(interpreter, as_string()); + if (is_symbol()) + return const_cast<Symbol*>(as_symbol()); + return {}; + } + + void visit_children(Cell::Visitor& visitor) + { + if (is_symbol()) + visitor.visit(const_cast<Symbol*>(as_symbol())); + } + + ALWAYS_INLINE bool operator==(const StringOrSymbol& other) const + { + if (is_string()) + return other.is_string() && as_string() == other.as_string(); + if (is_symbol()) + return other.is_symbol() && as_symbol() == other.as_symbol(); + return true; + } + + StringOrSymbol& operator=(const StringOrSymbol& other) + { + if (this == &other) + return *this; + m_ptr = other.m_ptr; + return *this; + } + +private: + ALWAYS_INLINE u64 bits() const + { + return reinterpret_cast<uintptr_t>(m_ptr); + } + + ALWAYS_INLINE void set_symbol_flag() + { + m_ptr = reinterpret_cast<const void*>(bits() | 1ul); + } + + const void* m_ptr { nullptr }; +}; + +} + +template<> +struct AK::Traits<JS::StringOrSymbol> : public GenericTraits<JS::StringOrSymbol> { + static unsigned hash(const JS::StringOrSymbol& key) + { + if (key.is_string()) + return key.as_string().hash(); + if (key.is_symbol()) + return ptr_hash(key.as_symbol()); + return 0; + } +}; diff --git a/Libraries/LibJS/Runtime/Symbol.h b/Libraries/LibJS/Runtime/Symbol.h index 22218f7dcc..ce90502774 100644 --- a/Libraries/LibJS/Runtime/Symbol.h +++ b/Libraries/LibJS/Runtime/Symbol.h @@ -41,13 +41,13 @@ public: const String& description() const { return m_description; } bool is_global() const { return m_is_global; } - const String to_string() const { return String::format("Symbol(%s)", description().characters()); } + String to_string() const { return String::format("Symbol(%s)", description().characters()); } private: virtual const char* class_name() const override { return "Symbol"; } - const String m_description; - const bool m_is_global; + String m_description; + bool m_is_global; }; Symbol* js_symbol(Heap&, String description, bool is_global); diff --git a/Userland/js.cpp b/Userland/js.cpp index d18354bb4f..42ea3705cb 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -197,7 +197,11 @@ static void print_object(JS::Object& object, HashTable<JS::Object*>& seen_object size_t index = 0; for (auto& it : object.shape().property_table_ordered()) { - printf("\"\033[33;1m%s\033[0m\": ", it.key.characters()); + if (it.key.is_string()) { + printf("\"\033[33;1m%s\033[0m\": ", it.key.to_display_string().characters()); + } else { + printf("\033[33;1m%s\033[0m: ", it.key.to_display_string().characters()); + } print_value(object.get_direct(it.value.offset), seen_objects); if (index != object.shape().property_count() - 1) fputs(", ", stdout); @@ -792,10 +796,13 @@ int main(int argc, char** argv) Function<void(const JS::Shape&, const StringView&)> list_all_properties = [&results, &list_all_properties](const JS::Shape& shape, auto& property_pattern) { for (const auto& descriptor : shape.property_table()) { - if (descriptor.key.view().starts_with(property_pattern)) { - Line::CompletionSuggestion completion { descriptor.key, Line::CompletionSuggestion::ForSearch }; + if (!descriptor.key.is_string()) + continue; + auto key = descriptor.key.as_string(); + if (key.view().starts_with(property_pattern)) { + Line::CompletionSuggestion completion { key, Line::CompletionSuggestion::ForSearch }; if (!results.contains_slow(completion)) { // hide duplicates - results.append({ descriptor.key }); + results.append(key); } } } |