summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Applications/Spreadsheet/JSIntegration.cpp18
-rw-r--r--Userland/Applications/Spreadsheet/JSIntegration.h4
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp24
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h4
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp14
-rw-r--r--Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h2
-rw-r--r--Userland/Libraries/LibJS/AST.cpp30
-rw-r--r--Userland/Libraries/LibJS/Bytecode/Op.cpp4
-rw-r--r--Userland/Libraries/LibJS/CMakeLists.txt3
-rw-r--r--Userland/Libraries/LibJS/Forward.h5
-rw-r--r--Userland/Libraries/LibJS/MarkupGenerator.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp300
-rw-r--r--Userland/Libraries/LibJS/Runtime/AbstractOperations.h3
-rw-r--r--Userland/Libraries/LibJS/Runtime/Array.cpp23
-rw-r--r--Userland/Libraries/LibJS/Runtime/Array.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayBuffer.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp18
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp948
-rw-r--r--Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/BoundFunction.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/ErrorTypes.h24
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp79
-rw-r--r--Userland/Libraries/LibJS/Runtime/GlobalObject.cpp5
-rw-r--r--Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp95
-rw-r--r--Userland/Libraries/LibJS/Runtime/IndexedProperties.h14
-rw-r--r--Userland/Libraries/LibJS/Runtime/JSONObject.cpp12
-rw-r--r--Userland/Libraries/LibJS/Runtime/Object.cpp1594
-rw-r--r--Userland/Libraries/LibJS/Runtime/Object.h139
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp189
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp53
-rw-r--r--Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp59
-rw-r--r--Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp51
-rw-r--r--Userland/Libraries/LibJS/Runtime/PropertyAttributes.h76
-rw-r--r--Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h4
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp4
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyObject.cpp1010
-rw-r--r--Userland/Libraries/LibJS/Runtime/ProxyObject.h27
-rw-r--r--Userland/Libraries/LibJS/Runtime/Reference.cpp18
-rw-r--r--Userland/Libraries/LibJS/Runtime/ReflectObject.cpp267
-rw-r--r--Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp12
-rw-r--r--Userland/Libraries/LibJS/Runtime/Shape.cpp6
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringConstructor.cpp2
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringObject.cpp153
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringObject.h5
-rw-r--r--Userland/Libraries/LibJS/Runtime/StringPrototype.cpp16
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArray.cpp34
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArray.h440
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.cpp22
-rw-r--r--Userland/Libraries/LibJS/Runtime/Value.cpp9
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js15
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js4
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js2
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js14
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js7
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js2
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js2
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js2
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js5
-rw-r--r--Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js2
-rw-r--r--Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js6
-rw-r--r--Userland/Libraries/LibTest/JavaScriptTestRunner.h2
-rw-r--r--Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp8
-rw-r--r--Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp22
-rw-r--r--Userland/Libraries/LibWeb/Bindings/WindowObject.cpp3
-rw-r--r--Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl2
-rw-r--r--Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp20
-rw-r--r--Userland/Libraries/LibWeb/DOM/HTMLCollection.idl2
-rw-r--r--Userland/Utilities/js.cpp4
80 files changed, 3894 insertions, 2114 deletions
diff --git a/Userland/Applications/Spreadsheet/JSIntegration.cpp b/Userland/Applications/Spreadsheet/JSIntegration.cpp
index ab5a61a068..7fe9a2a77f 100644
--- a/Userland/Applications/Spreadsheet/JSIntegration.cpp
+++ b/Userland/Applications/Spreadsheet/JSIntegration.cpp
@@ -101,29 +101,29 @@ SheetGlobalObject::~SheetGlobalObject()
{
}
-JS::Value SheetGlobalObject::get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const
+JS::Value SheetGlobalObject::internal_get(const JS::PropertyName& property_name, JS::Value receiver) const
{
- if (name.is_string()) {
- if (name.as_string() == "value") {
+ if (property_name.is_string()) {
+ if (property_name.as_string() == "value") {
if (auto cell = m_sheet.current_evaluated_cell())
return cell->js_data();
return JS::js_undefined();
}
- if (auto pos = m_sheet.parse_cell_name(name.as_string()); pos.has_value()) {
+ if (auto pos = m_sheet.parse_cell_name(property_name.as_string()); pos.has_value()) {
auto& cell = m_sheet.ensure(pos.value());
cell.reference_from(m_sheet.current_evaluated_cell());
return cell.typed_js_data();
}
}
- return GlobalObject::get(name, receiver, allow_side_effects);
+ return Base::internal_get(property_name, receiver);
}
-bool SheetGlobalObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver)
+bool SheetGlobalObject::internal_set(const JS::PropertyName& property_name, JS::Value value, JS::Value receiver)
{
- if (name.is_string()) {
- if (auto pos = m_sheet.parse_cell_name(name.as_string()); pos.has_value()) {
+ if (property_name.is_string()) {
+ if (auto pos = m_sheet.parse_cell_name(property_name.as_string()); pos.has_value()) {
auto& cell = m_sheet.ensure(pos.value());
if (auto current = m_sheet.current_evaluated_cell())
current->reference_from(&cell);
@@ -133,7 +133,7 @@ bool SheetGlobalObject::put(const JS::PropertyName& name, JS::Value value, JS::V
}
}
- return GlobalObject::put(name, value, receiver);
+ return Base::internal_set(property_name, value, receiver);
}
void SheetGlobalObject::initialize_global_object()
diff --git a/Userland/Applications/Spreadsheet/JSIntegration.h b/Userland/Applications/Spreadsheet/JSIntegration.h
index b6a2bf322f..94c5e86049 100644
--- a/Userland/Applications/Spreadsheet/JSIntegration.h
+++ b/Userland/Applications/Spreadsheet/JSIntegration.h
@@ -26,8 +26,8 @@ public:
virtual ~SheetGlobalObject() override;
- virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override;
- virtual bool put(const JS::PropertyName&, JS::Value value, JS::Value receiver = {}) override;
+ virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override;
+ virtual bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override;
virtual void initialize_global_object() override;
JS_DECLARE_NATIVE_FUNCTION(get_real_cell_contents);
diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp
index 965c1d4e69..0223993c7b 100644
--- a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp
+++ b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp
@@ -21,40 +21,40 @@ DebuggerGlobalJSObject::DebuggerGlobalJSObject()
m_variables = lib->debug_info->get_variables_in_current_scope(regs);
}
-JS::Value DebuggerGlobalJSObject::get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const
+JS::Value DebuggerGlobalJSObject::internal_get(JS::PropertyName const& property_name, JS::Value receiver) const
{
- if (m_variables.is_empty() || !name.is_string())
- return JS::Object::get(name, receiver, allow_side_effects);
+ if (m_variables.is_empty() || !property_name.is_string())
+ return Base::internal_get(property_name, receiver);
auto it = m_variables.find_if([&](auto& variable) {
- return variable->name == name.as_string();
+ return variable->name == property_name.as_string();
});
if (it.is_end())
- return JS::Object::get(name, receiver, allow_side_effects);
+ return Base::internal_get(property_name, receiver);
auto& target_variable = **it;
auto js_value = debugger_to_js(target_variable);
if (js_value.has_value())
return js_value.value();
- auto error_string = String::formatted("Variable {} of type {} is not convertible to a JS Value", name.as_string(), target_variable.type_name);
+ auto error_string = String::formatted("Variable {} of type {} is not convertible to a JS Value", property_name.as_string(), target_variable.type_name);
vm().throw_exception<JS::TypeError>(const_cast<DebuggerGlobalJSObject&>(*this), error_string);
return {};
}
-bool DebuggerGlobalJSObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver)
+bool DebuggerGlobalJSObject::internal_set(JS::PropertyName const& property_name, JS::Value value, JS::Value receiver)
{
- if (m_variables.is_empty() || !name.is_string())
- return JS::Object::put(name, value, receiver);
+ if (m_variables.is_empty() || !property_name.is_string())
+ return Base::internal_set(property_name, value, receiver);
auto it = m_variables.find_if([&](auto& variable) {
- return variable->name == name.as_string();
+ return variable->name == property_name.as_string();
});
if (it.is_end())
- return JS::Object::put(name, value, receiver);
+ return Base::internal_set(property_name, value, receiver);
auto& target_variable = **it;
auto debugger_value = js_to_debugger(value, target_variable);
if (debugger_value.has_value())
return Debugger::the().session()->poke((u32*)target_variable.location_data.address, debugger_value.value());
- auto error_string = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name.as_string(), target_variable.type_name);
+ auto error_string = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), property_name.as_string(), target_variable.type_name);
vm().throw_exception<JS::TypeError>(const_cast<DebuggerGlobalJSObject&>(*this), error_string);
return {};
}
diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h
index df958b8ba6..255fb4fca6 100644
--- a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h
+++ b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h
@@ -20,8 +20,8 @@ class DebuggerGlobalJSObject final
public:
DebuggerGlobalJSObject();
- JS::Value get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override;
- bool put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) override;
+ virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override;
+ virtual bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override;
Optional<JS::Value> debugger_to_js(const Debug::DebugInfo::VariableInfo&) const;
Optional<u32> js_to_debugger(JS::Value value, const Debug::DebugInfo::VariableInfo&) const;
diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp
index ecf95de914..fa3a1e7d17 100644
--- a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp
+++ b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp
@@ -29,19 +29,19 @@ DebuggerVariableJSObject::~DebuggerVariableJSObject()
{
}
-bool DebuggerVariableJSObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver)
+bool DebuggerVariableJSObject::internal_set(const JS::PropertyName& property_name, JS::Value value, JS::Value receiver)
{
if (m_is_writing_properties)
- return JS::Object::put(name, value, receiver);
+ return Base::internal_set(property_name, value, receiver);
- if (!name.is_string()) {
- vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", name.to_string()));
+ if (!property_name.is_string()) {
+ vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", property_name.to_string()));
return false;
}
- auto property_name = name.as_string();
+ auto name = property_name.as_string();
auto it = m_variable_info.members.find_if([&](auto& variable) {
- return variable->name == property_name;
+ return variable->name == name;
});
if (it.is_end()) {
@@ -52,7 +52,7 @@ bool DebuggerVariableJSObject::put(const JS::PropertyName& name, JS::Value value
auto& member = **it;
auto new_value = debugger_object().js_to_debugger(value, member);
if (!new_value.has_value()) {
- auto string_error = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name.as_string(), member.type_name);
+ auto string_error = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name, member.type_name);
vm().throw_exception<JS::TypeError>(global_object(), string_error);
return false;
}
diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h
index 0b81676a53..61f5e06fd2 100644
--- a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h
+++ b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h
@@ -24,7 +24,7 @@ public:
virtual const char* class_name() const override { return m_variable_info.type_name.characters(); }
- virtual bool put(const JS::PropertyName& name, JS::Value value, JS::Value) override;
+ bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override;
void finish_writing_properties() { m_is_writing_properties = false; }
private:
diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp
index ffc7a2efb3..79d93b1bea 100644
--- a/Userland/Libraries/LibJS/AST.cpp
+++ b/Userland/Libraries/LibJS/AST.cpp
@@ -129,7 +129,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
auto property_name = member_expression.computed_property_name(interpreter, global_object);
if (!property_name.is_valid())
return {};
- auto reference = Reference { super_base, property_name, super_base };
+ auto reference = Reference { super_base, property_name, super_base, vm.in_strict_mode() };
callee = reference.get_value(global_object);
if (vm.exception())
return {};
@@ -559,7 +559,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
return {};
auto* object = rhs_result.to_object(global_object);
while (object) {
- auto property_names = object->get_enumerable_own_property_names(Object::PropertyKind::Key);
+ auto property_names = object->enumerable_own_property_names(Object::PropertyKind::Key);
for (auto& value : property_names) {
interpreter.vm().assign(target, value, global_object, has_declaration);
if (interpreter.exception())
@@ -578,7 +578,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
}
}
}
- object = object->prototype();
+ object = object->internal_get_prototype_of();
if (interpreter.exception())
return {};
}
@@ -781,6 +781,9 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject&
if (interpreter.exception())
return {};
+ // From here on equivalent to
+ // 13.3.4 EvaluatePropertyAccessWithIdentifierKey ( baseValue, identifierName, strict ), https://tc39.es/ecma262/#sec-evaluate-property-access-with-identifier-key
+
object_value = require_object_coercible(global_object, object_value);
if (interpreter.exception())
return {};
@@ -789,7 +792,8 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject&
if (!property_name.is_valid())
return Reference {};
- return Reference { object_value, property_name, {} };
+ auto strict = interpreter.vm().in_strict_mode();
+ return Reference { object_value, property_name, {}, strict };
}
Value UnaryExpression::execute(Interpreter& interpreter, GlobalObject& global_object) const
@@ -898,7 +902,7 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
class_constructor->define_property(vm.names.prototype, prototype, Attribute::Writable);
if (interpreter.exception())
return {};
- class_constructor->set_prototype(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object());
+ class_constructor->internal_set_prototype_of(super_constructor.is_null() ? global_object.function_prototype() : &super_constructor.as_object());
}
auto class_prototype = class_constructor->get(vm.names.prototype);
@@ -929,15 +933,15 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
switch (method.kind()) {
case ClassMethod::Kind::Method:
- target.define_property(property_key, method_value);
+ target.define_property_or_throw(property_key, { .value = method_value, .writable = true, .enumerable = false, .configurable = true });
break;
case ClassMethod::Kind::Getter:
update_function_name(method_value, String::formatted("get {}", get_function_name(global_object, key)));
- target.define_accessor(property_key, &method_function, nullptr, Attribute::Configurable | Attribute::Enumerable);
+ target.define_property_or_throw(property_key, { .get = &method_function, .enumerable = true, .configurable = true });
break;
case ClassMethod::Kind::Setter:
update_function_name(method_value, String::formatted("set {}", get_function_name(global_object, key)));
- target.define_accessor(property_key, nullptr, &method_function, Attribute::Configurable | Attribute::Enumerable);
+ target.define_property_or_throw(property_key, { .set = &method_function, .enumerable = true, .configurable = true });
break;
default:
VERIFY_NOT_REACHED();
@@ -1431,9 +1435,10 @@ Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object)
InterpreterNodeScope node_scope { interpreter, *this };
auto value = interpreter.vm().get_variable(string(), global_object);
+ if (interpreter.exception())
+ return {};
if (value.is_empty()) {
- if (!interpreter.exception())
- interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string());
+ interpreter.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, string());
return {};
}
return value;
@@ -1815,7 +1820,10 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o
if (key.is_object() && key.as_object().is_array()) {
auto& array_to_spread = static_cast<Array&>(key.as_object());
for (auto& entry : array_to_spread.indexed_properties()) {
- object->indexed_properties().put(object, entry.index(), entry.value_and_attributes(&array_to_spread).value);
+ auto value = array_to_spread.get(entry.index());
+ if (interpreter.exception())
+ return {};
+ object->indexed_properties().put(entry.index(), value);
if (interpreter.exception())
return {};
}
diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp
index 660f60c713..7b3a16de98 100644
--- a/Userland/Libraries/LibJS/Bytecode/Op.cpp
+++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp
@@ -193,7 +193,9 @@ void CopyObjectExcludingProperties::execute_impl(Bytecode::Interpreter& interpre
return;
}
- auto own_keys = from_object->get_own_properties(Object::PropertyKind::Key, true);
+ auto own_keys = from_object->internal_own_property_keys();
+ if (interpreter.vm().exception())
+ return;
for (auto& key : own_keys) {
if (!excluded_names.contains(key)) {
diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt
index 1d384b1685..78f363405a 100644
--- a/Userland/Libraries/LibJS/CMakeLists.txt
+++ b/Userland/Libraries/LibJS/CMakeLists.txt
@@ -97,7 +97,6 @@ set(SOURCES
Runtime/PromisePrototype.cpp
Runtime/PromiseReaction.cpp
Runtime/PromiseResolvingFunction.cpp
- Runtime/PropertyAttributes.cpp
Runtime/PropertyDescriptor.cpp
Runtime/ProxyConstructor.cpp
Runtime/ProxyObject.cpp
@@ -106,7 +105,7 @@ set(SOURCES
Runtime/RegExpConstructor.cpp
Runtime/RegExpObject.cpp
Runtime/RegExpPrototype.cpp
- Runtime/OrdinaryFunctionObject.cpp
+ Runtime/OrdinaryFunctionObject.cpp
Runtime/Set.cpp
Runtime/SetConstructor.cpp
Runtime/SetIterator.cpp
diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h
index ef249ac01a..1c39061d03 100644
--- a/Userland/Libraries/LibJS/Forward.h
+++ b/Userland/Libraries/LibJS/Forward.h
@@ -175,11 +175,6 @@ class TypedArrayPrototype;
// Tag type used to differentiate between u8 as used by Uint8Array and u8 as used by Uint8ClampedArray.
struct ClampedU8;
-enum class AllowSideEffects {
- Yes,
- No
-};
-
#define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName, ArrayType) \
class ClassName; \
class ConstructorName; \
diff --git a/Userland/Libraries/LibJS/MarkupGenerator.cpp b/Userland/Libraries/LibJS/MarkupGenerator.cpp
index 7a6fe8f550..cce056286f 100644
--- a/Userland/Libraries/LibJS/MarkupGenerator.cpp
+++ b/Userland/Libraries/LibJS/MarkupGenerator.cpp
@@ -98,7 +98,7 @@ void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_outp
html_output.append(wrap_string_in_style(", ", StyleType::Punctuation));
first = false;
// FIXME: Exception check
- value_to_html(it.value_and_attributes(const_cast<Array*>(&array)).value, html_output, seen_objects);
+ value_to_html(array.get(it.index()), html_output, seen_objects);
}
html_output.append(wrap_string_in_style(" ]", StyleType::Punctuation));
}
@@ -114,7 +114,7 @@ void MarkupGenerator::object_to_html(const Object& object, StringBuilder& html_o
html_output.append(wrap_string_in_style(String::number(entry.index()), StyleType::Number));
html_output.append(wrap_string_in_style(": ", StyleType::Punctuation));
// FIXME: Exception check
- value_to_html(entry.value_and_attributes(const_cast<Object*>(&object)).value, html_output, seen_objects);
+ value_to_html(object.get(entry.index()), html_output, seen_objects);
}
if (!object.indexed_properties().is_empty() && object.shape().property_count())
diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
index 11d4302885..c6176fd17d 100644
--- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
+++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp
@@ -6,14 +6,15 @@
*/
#include <AK/Function.h>
+#include <AK/Optional.h>
#include <AK/Result.h>
#include <AK/TemporaryChange.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/ArgumentsObject.h>
#include <LibJS/Runtime/Array.h>
-#include <LibJS/Runtime/ArrayPrototype.h>
#include <LibJS/Runtime/BoundFunction.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/ErrorTypes.h>
@@ -23,6 +24,7 @@
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/ObjectEnvironment.h>
+#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/ProxyObject.h>
#include <LibJS/Runtime/Reference.h>
@@ -111,27 +113,202 @@ GlobalObject* get_function_realm(GlobalObject& global_object, FunctionObject con
{
auto& vm = global_object.vm();
- if (function.realm())
+ // 1. Assert: ! IsCallable(obj) is true.
+
+ // 2. If obj has a [[Realm]] internal slot, then
+ if (function.realm()) {
+ // a. Return obj.[[Realm]].
return function.realm();
+ }
+
+ // 3. If obj is a bound function exotic object, then
if (is<BoundFunction>(function)) {
auto& bound_function = static_cast<BoundFunction const&>(function);
+
+ // a. Let target be obj.[[BoundTargetFunction]].
auto& target = bound_function.target_function();
+
+ // b. Return ? GetFunctionRealm(target).
return get_function_realm(global_object, target);
}
+
+ // 4. If obj is a Proxy exotic object, then
if (is<ProxyObject>(function)) {
auto& proxy = static_cast<ProxyObject const&>(function);
+
+ // a. If obj.[[ProxyHandler]] is null, throw a TypeError exception.
if (proxy.is_revoked()) {
vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return nullptr;
}
+
+ // b. Let proxyTarget be obj.[[ProxyTarget]].
auto& proxy_target = proxy.target();
+
+ // c. Return ? GetFunctionRealm(proxyTarget).
VERIFY(proxy_target.is_function());
return get_function_realm(global_object, static_cast<FunctionObject const&>(proxy_target));
}
+
// 5. Return the current Realm Record.
return &global_object;
}
+// 10.1.6.2 IsCompatiblePropertyDescriptor ( Extensible, Desc, Current ), https://tc39.es/ecma262/#sec-iscompatiblepropertydescriptor
+bool is_compatible_property_descriptor(bool extensible, PropertyDescriptor const& descriptor, Optional<PropertyDescriptor> const& current)
+{
+ // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
+ return validate_and_apply_property_descriptor(nullptr, {}, extensible, descriptor, current);
+}
+
+// 10.1.6.3 ValidateAndApplyPropertyDescriptor ( O, P, extensible, Desc, current ),
+bool validate_and_apply_property_descriptor(Object* object, PropertyName const& property_name, bool extensible, PropertyDescriptor const& descriptor, Optional<PropertyDescriptor> const& current)
+{
+ // 1. Assert: If O is not undefined, then IsPropertyKey(P) is true.
+ if (object)
+ VERIFY(property_name.is_valid());
+
+ // 2. If current is undefined, then
+ if (!current.has_value()) {
+ // a. If extensible is false, return false.
+ if (!extensible)
+ return false;
+
+ // b. Assert: extensible is true.
+ // c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
+ if (descriptor.is_generic_descriptor() || descriptor.is_data_descriptor()) {
+ // i. If O is not undefined, create an own data property named P of object O whose [[Value]], [[Writable]],
+ // [[Enumerable]], and [[Configurable]] attribute values are described by Desc.
+ // If the value of an attribute field of Desc is absent, the attribute of the newly created property is set
+ // to its default value.
+ if (object) {
+ auto value = descriptor.value.value_or(js_undefined());
+ object->storage_set(property_name, { value, descriptor.attributes() });
+ }
+ }
+ // d. Else,
+ else {
+ // i. Assert: ! IsAccessorDescriptor(Desc) is true.
+ VERIFY(descriptor.is_accessor_descriptor());
+
+ // ii. If O is not undefined, create an own accessor property named P of object O whose [[Get]], [[Set]],
+ // [[Enumerable]], and [[Configurable]] attribute values are described by Desc.
+ // If the value of an attribute field of Desc is absent, the attribute of the newly created property is set
+ // to its default value.
+ if (object) {
+ auto accessor = Accessor::create(object->vm(), descriptor.get.value_or(nullptr), descriptor.set.value_or(nullptr));
+ object->storage_set(property_name, { accessor, descriptor.attributes() });
+ }
+ }
+ // e. Return true.
+ return true;
+ }
+
+ // 3. If every field in Desc is absent, return true.
+ if (descriptor.is_empty())
+ return true;
+
+ // 4. If current.[[Configurable]] is false, then
+ if (!*current->configurable) {
+ // a. If Desc.[[Configurable]] is present and its value is true, return false.
+ if (descriptor.configurable.has_value() && *descriptor.configurable)
+ return false;
+
+ // b. If Desc.[[Enumerable]] is present and ! SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) is false, return false.
+ if (descriptor.enumerable.has_value() && *descriptor.enumerable != *current->enumerable)
+ return false;
+ }
+
+ // 5. If ! IsGenericDescriptor(Desc) is true, then
+ if (descriptor.is_generic_descriptor()) {
+ // a. NOTE: No further validation is required.
+ }
+ // 6. Else if ! SameValue(! IsDataDescriptor(current), ! IsDataDescriptor(Desc)) is false, then
+ else if (current->is_data_descriptor() != descriptor.is_data_descriptor()) {
+ // a. If current.[[Configurable]] is false, return false.
+ if (!*current->configurable)
+ return false;
+
+ // b. If IsDataDescriptor(current) is true, then
+ if (current->is_data_descriptor()) {
+ // If O is not undefined, convert the property named P of object O from a data property to an accessor property.
+ // Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and
+ // set the rest of the property's attributes to their default values.
+ if (object) {
+ auto accessor = Accessor::create(object->vm(), nullptr, nullptr);
+ object->storage_set(property_name, { accessor, current->attributes() });
+ }
+ }
+ // c. Else,
+ else {
+ // If O is not undefined, convert the property named P of object O from an accessor property to a data property.
+ // Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and
+ // set the rest of the property's attributes to their default values.
+ if (object) {
+ auto value = js_undefined();
+ object->storage_set(property_name, { value, current->attributes() });
+ }
+ }
+ }
+ // 7. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
+ else if (current->is_data_descriptor() && descriptor.is_data_descriptor()) {
+ // a. If current.[[Configurable]] is false and current.[[Writable]] is false, then
+ if (!*current->configurable && !*current->writable) {
+ // i. If Desc.[[Writable]] is present and Desc.[[Writable]] is true, return false.
+ if (descriptor.writable.has_value() && *descriptor.writable)
+ return false;
+
+ // ii. If Desc.[[Value]] is present and SameValue(Desc.[[Value]], current.[[Value]]) is false, return false.
+ if (descriptor.value.has_value() && !same_value(*descriptor.value, *current->value))
+ return false;
+
+ // iii. Return true.
+ return true;
+ }
+ }
+ // 8. Else,
+ else {
+ // a. Assert: ! IsAccessorDescriptor(current) and ! IsAccessorDescriptor(Desc) are both true.
+ VERIFY(current->is_accessor_descriptor());
+ VERIFY(descriptor.is_accessor_descriptor());
+
+ // b. If current.[[Configurable]] is false, then
+ if (!*current->configurable) {
+ // i. If Desc.[[Set]] is present and SameValue(Desc.[[Set]], current.[[Set]]) is false, return false.
+ if (descriptor.set.has_value() && *descriptor.set != *current->set)
+ return false;
+
+ // ii. If Desc.[[Get]] is present and SameValue(Desc.[[Get]], current.[[Get]]) is false, return false.
+ if (descriptor.get.has_value() && *descriptor.get != *current->get)
+ return false;
+
+ // iii. Return true.
+ return true;
+ }
+ }
+
+ // 9. If O is not undefined, then
+ if (object) {
+ // a. For each field of Desc that is present, set the corresponding attribute of the property named P of object O to the value of the field.
+ Value value;
+ if (descriptor.is_accessor_descriptor() || (current->is_accessor_descriptor() && !descriptor.is_data_descriptor())) {
+ auto* getter = descriptor.get.value_or(current->get.value_or(nullptr));
+ auto* setter = descriptor.set.value_or(current->set.value_or(nullptr));
+ value = Accessor::create(object->vm(), getter, setter);
+ } else {
+ value = descriptor.value.value_or(current->value.value_or({}));
+ }
+ PropertyAttributes attributes;
+ attributes.set_writable(descriptor.writable.value_or(current->writable.value_or(false)));
+ attributes.set_enumerable(descriptor.enumerable.value_or(current->enumerable.value_or(false)));
+ attributes.set_configurable(descriptor.configurable.value_or(current->configurable.value_or(false)));
+ object->storage_set(property_name, { value, attributes });
+ }
+
+ // 10. Return true.
+ return true;
+}
+
// 10.1.14 GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
Object* get_prototype_from_constructor(GlobalObject& global_object, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)())
{
@@ -177,7 +354,7 @@ Object* get_super_constructor(VM& vm)
{
auto& env = get_this_environment(vm);
auto& active_function = verify_cast<FunctionEnvironment>(env).function_object();
- auto* super_constructor = active_function.prototype();
+ auto* super_constructor = active_function.internal_get_prototype_of();
return super_constructor;
}
@@ -230,26 +407,45 @@ Value perform_eval(Value x, GlobalObject& caller_realm, CallerMode strict_caller
Object* create_unmapped_arguments_object(GlobalObject& global_object, Vector<Value> const& arguments)
{
auto& vm = global_object.vm();
- auto* object = Object::create(global_object, global_object.object_prototype());
- if (vm.exception())
- return nullptr;
- for (auto& argument : arguments)
- object->indexed_properties().append(argument);
+ // 1. Let len be the number of elements in argumentsList.
+ auto length = arguments.size();
+
+ // 2. Let obj be ! OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »).
+ // 3. Set obj.[[ParameterMap]] to undefined.
+ auto* object = Object::create(global_object, global_object.object_prototype());
// 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
- auto length = arguments.size();
- object->define_property(vm.names.length, Value(length), Attribute::Writable | Attribute::Configurable);
- if (vm.exception())
- return nullptr;
+ object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true });
+ VERIFY(!vm.exception());
- object->define_property(*vm.well_known_symbol_iterator(), global_object.array_prototype()->get(vm.names.values), Attribute::Writable | Attribute::Configurable);
+ // 5. Let index be 0.
+ // 6. Repeat, while index < len,
+ for (size_t index = 0; index < length; ++index) {
+ // a. Let val be argumentsList[index].
+ auto value = arguments[index];
- // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, [[Configurable]]: false }).
- object->define_accessor(vm.names.callee, global_object.throw_type_error_function(), global_object.throw_type_error_function(), 0);
+ // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
+ object->create_data_property_or_throw(index, value);
+ VERIFY(!vm.exception());
+
+ // c. Set index to index + 1.
+ }
+
+ // 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
+ // FIXME: This is not guaranteed to be %Array.prototype.values%!
+ auto array_prototype_values = global_object.array_prototype()->get(vm.names.values);
if (vm.exception())
- return nullptr;
+ return {};
+ object->define_property_or_throw(*vm.well_known_symbol_iterator(), { .value = array_prototype_values, .writable = true, .enumerable = false, .configurable = true });
+ VERIFY(!vm.exception());
+
+ // 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { [[Get]]: %ThrowTypeError%, [[Set]]: %ThrowTypeError%, [[Enumerable]]: false, [[Configurable]]: false }).
+ auto* throw_type_error = global_object.throw_type_error_function();
+ object->define_property_or_throw(vm.names.callee, { .get = throw_type_error, .set = throw_type_error, .enumerable = false, .configurable = false });
+ VERIFY(!vm.exception());
+ // 9. Return obj.
return object;
}
@@ -260,33 +456,75 @@ Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObje
(void)formals;
auto& vm = global_object.vm();
+
+ // 1. Assert: formals does not contain a rest parameter, any binding patterns, or any initializers. It may contain duplicate identifiers.
+
+ // 2. Let len be the number of elements in argumentsList.
+ auto length = arguments.size();
+
+ // 3. Let obj be ! MakeBasicObject(« [[Prototype]], [[Extensible]], [[ParameterMap]] »).
auto* object = vm.heap().allocate<ArgumentsObject>(global_object, global_object);
- if (vm.exception())
- return nullptr;
+ VERIFY(!vm.exception());
+
+ // 4. Set obj.[[GetOwnProperty]] as specified in 10.4.4.1.
+ // 5. Set obj.[[DefineOwnProperty]] as specified in 10.4.4.2.
+ // 6. Set obj.[[Get]] as specified in 10.4.4.3.
+ // 7. Set obj.[[Set]] as specified in 10.4.4.4.
+ // 8. Set obj.[[Delete]] as specified in 10.4.4.5.
+ // 9. Set obj.[[Prototype]] to %Object.prototype%.
// 14. Let index be 0.
// 15. Repeat, while index < len,
- // a. Let val be argumentsList[index].
- // b . Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
- // c. Set index to index + 1.
- for (auto& argument : arguments)
- object->indexed_properties().append(argument);
+ for (size_t index = 0; index < length; ++index) {
+ // a. Let val be argumentsList[index].
+ auto value = arguments[index];
+
+ // b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
+ object->create_data_property_or_throw(index, value);
+ VERIFY(!vm.exception());
+
+ // c. Set index to index + 1.
+ }
// 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
- auto length = arguments.size();
- object->define_property(vm.names.length, Value(length), Attribute::Writable | Attribute::Configurable);
- if (vm.exception())
- return nullptr;
+ object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true });
+ VERIFY(!vm.exception());
// 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
- object->define_property(*vm.well_known_symbol_iterator(), global_object.array_prototype()->get(vm.names.values), Attribute::Writable | Attribute::Configurable);
+ // FIXME: This is not guaranteed to be %Array.prototype.values%!
+ auto array_prototype_values = global_object.array_prototype()->get(vm.names.values);
+ if (vm.exception())
+ return {};
+ object->define_property_or_throw(*vm.well_known_symbol_iterator(), { .value = array_prototype_values, .writable = true, .enumerable = false, .configurable = true });
+ VERIFY(!vm.exception());
// 21. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor { [[Value]]: func, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
- object->define_property(vm.names.callee, Value(&function), Attribute::Writable | Attribute::Configurable);
- if (vm.exception())
- return nullptr;
+ object->define_property_or_throw(vm.names.callee, { .value = &function, .writable = true, .enumerable = false, .configurable = true });
+ VERIFY(!vm.exception());
+ // 22. Return obj.
return object;
}
+// 7.1.21 CanonicalNumericIndexString ( argument ), https://tc39.es/ecma262/#sec-canonicalnumericindexstring
+Value canonical_numeric_index_string(GlobalObject& global_object, Value argument)
+{
+ // 1. Assert: Type(argument) is String.
+ VERIFY(argument.is_string());
+
+ // 2. If argument is "-0", return -0𝔽.
+ if (argument.as_string().string() == "-0")
+ return Value(-0.0);
+
+ // 3. Let n be ! ToNumber(argument).
+ auto n = argument.to_number(global_object);
+
+ // 4. If SameValue(! ToString(n), argument) is false, return undefined.
+ if (!same_value(n.to_primitive_string(global_object), argument))
+ return js_undefined();
+
+ // 5. Return n.
+ return n;
+}
+
}
diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h
index 58f2aadee3..68e7c1355e 100644
--- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.h
+++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.h
@@ -24,9 +24,12 @@ size_t length_of_array_like(GlobalObject&, Object const&);
MarkedValueList create_list_from_array_like(GlobalObject&, Value, Function<Result<void, ErrorType>(Value)> = {});
FunctionObject* species_constructor(GlobalObject&, Object const&, FunctionObject& default_constructor);
GlobalObject* get_function_realm(GlobalObject&, FunctionObject const&);
+bool is_compatible_property_descriptor(bool extensible, PropertyDescriptor const&, Optional<PropertyDescriptor> const& current);
+bool validate_and_apply_property_descriptor(Object*, PropertyName const&, bool extensible, PropertyDescriptor const&, Optional<PropertyDescriptor> const& current);
Object* get_prototype_from_constructor(GlobalObject&, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)());
Object* create_unmapped_arguments_object(GlobalObject&, Vector<Value> const& arguments);
Object* create_mapped_arguments_object(GlobalObject&, FunctionObject&, Vector<FunctionNode::Parameter> const&, Vector<Value> const& arguments, Environment&);
+Value canonical_numeric_index_string(GlobalObject&, Value);
enum class CallerMode {
Strict,
diff --git a/Userland/Libraries/LibJS/Runtime/Array.cpp b/Userland/Libraries/LibJS/Runtime/Array.cpp
index 271b3b426f..50dbccef9d 100644
--- a/Userland/Libraries/LibJS/Runtime/Array.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Array.cpp
@@ -23,16 +23,27 @@ Array* Array::create(GlobalObject& global_object, size_t length, Object* prototy
if (!prototype)
prototype = global_object.array_prototype();
auto* array = global_object.heap().allocate<Array>(global_object, *prototype);
- array->put(vm.names.length, Value(length));
+ array->internal_define_own_property(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = false });
return array;
}
// 7.3.17 CreateArrayFromList ( elements ), https://tc39.es/ecma262/#sec-createarrayfromlist
-Array* Array::create_from(GlobalObject& global_object, const Vector<Value>& elements)
+Array* Array::create_from(GlobalObject& global_object, Vector<Value> const& elements)
{
+ // 1. Assert: elements is a List whose elements are all ECMAScript language values.
+
+ // 2. Let array be ! ArrayCreate(0).
auto* array = Array::create(global_object, 0);
- for (size_t i = 0; i < elements.size(); ++i)
- array->define_property(i, elements[i]);
+
+ // 3. Let n be 0.
+ // 4. For each element e of elements, do
+ for (u32 n = 0; n < elements.size(); ++n) {
+ // a. Perform ! CreateDataPropertyOrThrow(array, ! ToString(𝔽(n)), e).
+ array->create_data_property_or_throw(n, elements[n]);
+ // b. Set n to n + 1.
+ }
+
+ // 5. Return array.
return array;
}
@@ -72,10 +83,10 @@ JS_DEFINE_NATIVE_GETTER(Array::length_getter)
// TODO: could be incorrect if receiver/this_value is fixed or changed
if (!this_object->is_array()) {
- Value val = this_object->get_own_property(vm.names.length.to_string_or_symbol(), this_object);
+ auto value = this_object->internal_get(vm.names.length.to_string_or_symbol(), this_object);
if (vm.exception())
return {};
- return val;
+ return value;
}
return Value(this_object->indexed_properties().array_like_size());
diff --git a/Userland/Libraries/LibJS/Runtime/Array.h b/Userland/Libraries/LibJS/Runtime/Array.h
index c98f3853d7..077a2ad29b 100644
--- a/Userland/Libraries/LibJS/Runtime/Array.h
+++ b/Userland/Libraries/LibJS/Runtime/Array.h
@@ -15,7 +15,7 @@ class Array : public Object {
public:
static Array* create(GlobalObject&, size_t length, Object* prototype = nullptr);
- static Array* create_from(GlobalObject&, const Vector<Value>&);
+ static Array* create_from(GlobalObject&, Vector<Value> const&);
explicit Array(Object& prototype);
virtual void initialize(GlobalObject&) override;
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h
index 1e43e608bb..4d712e56f4 100644
--- a/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h
+++ b/Userland/Libraries/LibJS/Runtime/ArrayBuffer.h
@@ -102,7 +102,7 @@ static Value raw_bytes_to_numeric(GlobalObject& global_object, ByteBuffer raw_va
}
}
-// 25.1.2.10 GetValueFromBuffer ( arrayBuffer, byteIndex, type, isTypedArray, order [ , isLittleEndian ] ), https://tc39.es/ecma262/#sec-getvaluefrombuffer
+// Implementation for 25.1.2.10 GetValueFromBuffer, used in TypedArray<T>::get_value_from_buffer().
template<typename T>
Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_array, Order, bool is_little_endian)
{
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
index ab173340ad..1dcf312880 100644
--- a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
@@ -67,7 +67,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target)
auto* array = Array::create(global_object(), 0, proto);
size_t int_length;
if (!length.is_number()) {
- array->define_property(0, length);
+ array->create_data_property_or_throw(0, length);
int_length = 1;
} else {
int_length = length.to_u32(global_object());
@@ -76,7 +76,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target)
return {};
}
}
- array->put(vm.names.length, Value(int_length));
+ array->set(vm.names.length, Value(int_length), true);
return array;
}
@@ -85,7 +85,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target)
return {};
for (size_t k = 0; k < vm.argument_count(); ++k)
- array->define_property(k, vm.argument(k));
+ array->create_data_property_or_throw(k, vm.argument(k));
return array;
}
@@ -139,7 +139,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
return {};
if (!next) {
- array_object.put(vm.names.length, Value(k));
+ array_object.set(vm.names.length, Value(k), true);
if (vm.exception())
return {};
return array;
@@ -160,7 +160,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
mapped_value = next_value;
}
- array_object.define_property(k, mapped_value);
+ array_object.create_data_property_or_throw(k, mapped_value);
if (vm.exception()) {
iterator_close(*iterator);
return {};
@@ -201,10 +201,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
} else {
mapped_value = k_value;
}
- array_object.define_property(k, mapped_value);
+ array_object.create_data_property_or_throw(k, mapped_value);
}
- array_object.put(vm.names.length, Value(length));
+ array_object.set(vm.names.length, Value(length), true);
if (vm.exception())
return {};
@@ -236,11 +236,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of)
}
auto& array_object = array.as_object();
for (size_t k = 0; k < vm.argument_count(); ++k) {
- array_object.define_property(k, vm.argument(k));
+ array_object.create_data_property_or_throw(k, vm.argument(k));
if (vm.exception())
return {};
}
- array_object.put(vm.names.length, Value(vm.argument_count()));
+ array_object.set(vm.names.length, Value(vm.argument_count()), true);
if (vm.exception())
return {};
return array;
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
index 01167c8416..0f51bdf008 100644
--- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp
@@ -78,16 +78,16 @@ void ArrayPrototype::initialize(GlobalObject& global_object)
// 23.1.3.34 Array.prototype [ @@unscopables ], https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
auto* unscopable_list = Object::create(global_object, nullptr);
- unscopable_list->define_property(vm.names.copyWithin, Value(true));
- unscopable_list->define_property(vm.names.entries, Value(true));
- unscopable_list->define_property(vm.names.fill, Value(true));
- unscopable_list->define_property(vm.names.find, Value(true));
- unscopable_list->define_property(vm.names.findIndex, Value(true));
- unscopable_list->define_property(vm.names.flat, Value(true));
- unscopable_list->define_property(vm.names.flatMap, Value(true));
- unscopable_list->define_property(vm.names.includes, Value(true));
- unscopable_list->define_property(vm.names.keys, Value(true));
- unscopable_list->define_property(vm.names.values, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.copyWithin, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.entries, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.fill, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.find, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.findIndex, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.flat, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.flatMap, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.includes, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.keys, Value(true));
+ unscopable_list->create_data_property_or_throw(vm.names.values, Value(true));
define_property(*vm.well_known_symbol_unscopables(), unscopable_list, Attribute::Configurable);
}
@@ -96,56 +96,6 @@ ArrayPrototype::~ArrayPrototype()
{
}
-static FunctionObject* callback_from_args(GlobalObject& global_object, const String& name)
-{
- auto& vm = global_object.vm();
- if (vm.argument_count() < 1) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ArrayPrototypeOneArg, name);
- return nullptr;
- }
- auto callback = vm.argument(0);
- if (!callback.is_function()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects());
- return nullptr;
- }
- return &callback.as_function();
-}
-
-static void for_each_item(VM& vm, GlobalObject& global_object, const String& name, Function<IterationDecision(size_t index, Value value, Value callback_result)> callback, bool skip_empty = true)
-{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
- return;
-
- auto initial_length = length_of_array_like(global_object, *this_object);
- if (vm.exception())
- return;
-
- auto* callback_function = callback_from_args(global_object, name);
- if (!callback_function)
- return;
-
- auto this_value = vm.argument(1);
-
- for (size_t i = 0; i < initial_length; ++i) {
- auto value = this_object->get(i);
- if (vm.exception())
- return;
- if (value.is_empty()) {
- if (skip_empty)
- continue;
- value = js_undefined();
- }
-
- auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), this_object);
- if (vm.exception())
- return;
-
- if (callback(i, value, callback_result) == IterationDecision::Break)
- break;
- }
-}
-
// 10.4.2.3 ArraySpeciesCreate ( originalArray, length ), https://tc39.es/ecma262/#sec-arrayspeciescreate
static Object* array_species_create(GlobalObject& global_object, Object& original_array, size_t length)
{
@@ -203,54 +153,189 @@ static Object* array_species_create(GlobalObject& global_object, Object& origina
// 23.1.3.7 Array.prototype.filter ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.filter
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::filter)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
+ auto callback_function = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
return {};
- auto* new_array = array_species_create(global_object, *this_object, 0);
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
if (vm.exception())
return {};
- size_t to_index = 0;
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let A be ? ArraySpeciesCreate(O, 0).
+ auto* array = array_species_create(global_object, *object, 0);
+ if (vm.exception())
+ return {};
- for_each_item(vm, global_object, "filter", [&](auto, auto value, auto callback_result) {
- if (callback_result.to_boolean()) {
- new_array->define_property(to_index, value);
- ++to_index;
+ // 5. Let k be 0.
+ size_t k = 0;
+
+ // 6. Let to be 0.
+ size_t to = 0;
+
+ // 7. Repeat, while k < len,
+ for (; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(k);
+ if (vm.exception())
+ return {};
+
+ // ii. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
+ auto selected = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+
+ // iii. If selected is true, then
+ if (selected.to_boolean()) {
+ // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue).
+ array->create_data_property_or_throw(to, k_value);
+
+ // 2. Set to to to + 1.
+ ++to;
+ }
}
- return IterationDecision::Continue;
- });
- return Value(new_array);
+
+ // d. Set k to k + 1.
+ }
+
+ // 8. Return A.
+ return array;
}
// 23.1.3.12 Array.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.foreach
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each)
{
- for_each_item(vm, global_object, "forEach", [](auto, auto, auto) {
- return IterationDecision::Continue;
- });
+ auto callback_function = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
+ if (vm.exception())
+ return {};
+
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let k be 0.
+ // 5. Repeat, while k < len,
+ for (size_t k = 0; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
+ (void)vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+ }
+
+ // d. Set k to k + 1.
+ }
+
+ // 6. Return undefined.
return js_undefined();
}
// 23.1.3.18 Array.prototype.map ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.map
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::map)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
+ auto callback_function = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
return {};
- auto initial_length = length_of_array_like(global_object, *this_object);
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
if (vm.exception())
return {};
- auto* new_array = array_species_create(global_object, *this_object, initial_length);
+
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let A be ? ArraySpeciesCreate(O, len).
+ auto* array = array_species_create(global_object, *object, length);
if (vm.exception())
return {};
- for_each_item(vm, global_object, "map", [&](auto index, auto, auto callback_result) {
+
+ // 5. Let k be 0.
+ // 6. Repeat, while k < len,
+ for (size_t k = 0; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
if (vm.exception())
- return IterationDecision::Break;
- new_array->define_property(index, callback_result);
- return IterationDecision::Continue;
- });
- return Value(new_array);
+ return {};
+
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
+ auto mapped_value = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+
+ // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
+ array->create_data_property_or_throw(property_name, mapped_value);
+ if (vm.exception())
+ return {};
+ }
+
+ // d. Set k to k + 1.
+ }
+
+ // 7. Return A.
+ return array;
}
// 23.1.3.20 Array.prototype.push ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.push
@@ -318,7 +403,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::unshift)
if (vm.exception())
return {};
} else {
- this_object->delete_property(to, true);
+ this_object->delete_property_or_throw(to);
if (vm.exception())
return {};
}
@@ -360,7 +445,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::pop)
auto element = this_object->get(index).value_or(js_undefined());
if (vm.exception())
return {};
- this_object->delete_property(index, true);
+ this_object->delete_property_or_throw(index);
if (vm.exception())
return {};
this_object->put(vm.names.length, Value((i32)index));
@@ -402,13 +487,13 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift)
if (vm.exception())
return {};
} else {
- this_object->delete_property(to, true);
+ this_object->delete_property_or_throw(to);
if (vm.exception())
return {};
}
}
- this_object->delete_property(length - 1, true);
+ this_object->delete_property_or_throw(length - 1);
if (vm.exception())
return {};
@@ -571,10 +656,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat)
if (vm.exception())
return;
if (k_exists) {
- auto k_value = obj.get(k).value_or(js_undefined());
+ auto k_value = obj.get(k);
if (vm.exception())
return;
- new_array->define_property(n, k_value);
+ new_array->create_data_property_or_throw(n, k_value);
if (vm.exception())
return;
}
@@ -586,7 +671,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat)
vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize);
return;
}
- new_array->define_property(n, arg);
+ new_array->create_data_property_or_throw(n, arg);
if (vm.exception())
return;
++n;
@@ -667,11 +752,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice)
return {};
if (present) {
- auto value = this_object->get((u32)actual_start).value_or(js_undefined());
+ auto value = this_object->get((u32)actual_start);
if (vm.exception())
return {};
- new_array->define_property(index, value);
+ new_array->create_data_property_or_throw(index, value);
if (vm.exception())
return {};
}
@@ -690,140 +775,284 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice)
// 23.1.3.14 Array.prototype.indexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.indexof
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::index_of)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
+ auto search_element = vm.argument(0);
+ auto from_index = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
return {};
- i32 length = length_of_array_like(global_object, *this_object);
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
if (vm.exception())
return {};
+
+ // 3. If len is 0, return -1𝔽.
if (length == 0)
return Value(-1);
- i32 from_index = 0;
- if (vm.argument_count() >= 2) {
- from_index = vm.argument(1).to_i32(global_object);
- if (vm.exception())
- return {};
- if (from_index >= length)
- return Value(-1);
- if (from_index < 0)
- from_index = max(length + from_index, 0);
+
+ // 4. Let n be ? ToIntegerOrInfinity(fromIndex).
+ auto n = from_index.to_integer_or_infinity(global_object);
+ if (vm.exception())
+ return {};
+
+ // 5. Assert: If fromIndex is undefined, then n is 0.
+ if (from_index.is_undefined())
+ VERIFY(n == 0);
+
+ // 6. If n is +∞, return -1𝔽.
+ if (Value(n).is_positive_infinity())
+ return Value(-1);
+
+ // 7. Else if n is -∞, set n to 0.
+ if (Value(n).is_negative_infinity())
+ n = 0;
+
+ u32 k;
+
+ // 8. If n ≥ 0, then
+ if (n >= 0) {
+ // a. Let k be n.
+ k = (u32)n;
}
- auto search_element = vm.argument(0);
- for (i32 i = from_index; i < length; ++i) {
- auto element = this_object->get(i);
+ // 9. Else,
+ else {
+ // a. Let k be len + n.
+ // b. If k < 0, set k to 0.
+ k = max((i32)length + (i32)n, 0);
+ }
+
+ // 10. Repeat, while k < len,
+ for (; k < length; ++k) {
+ auto property_name = PropertyName { k };
+
+ // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))).
+ auto k_present = object->has_property(property_name);
if (vm.exception())
return {};
- if (strict_eq(element, search_element))
- return Value(i);
+
+ // b. If kPresent is true, then
+ if (k_present) {
+ // i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
+ auto element_k = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Let same be IsStrictlyEqual(searchElement, elementK).
+ auto same = strict_eq(search_element, element_k);
+
+ // iii. If same is true, return 𝔽(k).
+ if (same)
+ return Value(k);
+ }
+
+ // c. Set k to k + 1.
}
+
+ // 11. Return -1𝔽.
return Value(-1);
}
// 23.1.3.21 Array.prototype.reduce ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-array.prototype.reduce
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
+ auto callback_function = vm.argument(0);
+ auto initial_value = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
return {};
- auto initial_length = length_of_array_like(global_object, *this_object);
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
if (vm.exception())
return {};
- auto* callback_function = callback_from_args(global_object, "reduce");
- if (!callback_function)
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. If len = 0 and initialValue is not present, throw a TypeError exception.
+ if (length == 0 && vm.argument_count() <= 1) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial);
return {};
+ }
- size_t start = 0;
+ // 5. Let k be 0.
+ size_t k = 0;
+ // 6. Let accumulator be undefined.
auto accumulator = js_undefined();
+
+ // 7. If initialValue is present, then
if (vm.argument_count() > 1) {
- accumulator = vm.argument(1);
- } else {
- bool start_found = false;
- while (!start_found && start < initial_length) {
- auto value = this_object->get(start);
+ // a. Set accumulator to initialValue.
+ accumulator = initial_value;
+ }
+ // 8. Else,
+ else {
+ // a. Let kPresent be false.
+ bool k_present = false;
+
+ // b. Repeat, while kPresent is false and k < len,
+ for (; !k_present && k < length; ++k) {
+ // i. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // ii. Set kPresent to ? HasProperty(O, Pk).
+ k_present = object->has_property(property_name);
if (vm.exception())
return {};
- start_found = !value.is_empty();
- if (start_found)
- accumulator = value;
- start += 1;
+
+ // iii. If kPresent is true, then
+ if (k_present) {
+ // 1. Set accumulator to ? Get(O, Pk).
+ accumulator = object->get(property_name);
+ if (vm.exception())
+ return {};
+ }
+
+ // iv. Set k to k + 1.
}
- if (!start_found) {
+
+ // c. If kPresent is false, throw a TypeError exception.
+ if (!k_present) {
vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial);
return {};
}
}
- auto this_value = js_undefined();
+ // 9. Repeat, while k < len,
+ for (; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
- for (size_t i = start; i < initial_length; ++i) {
- auto value = this_object->get(i);
- if (vm.exception())
- return {};
- if (value.is_empty())
- continue;
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
- accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object);
- if (vm.exception())
- return {};
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »).
+ accumulator = vm.call(callback_function.as_function(), js_undefined(), accumulator, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+ }
+
+ // d. Set k to k + 1.
}
+ // 10. Return accumulator.
return accumulator;
}
// 23.1.3.22 Array.prototype.reduceRight ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-array.prototype.reduceright
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce_right)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
+ auto callback_function = vm.argument(0);
+ auto initial_value = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
return {};
- auto initial_length = length_of_array_like(global_object, *this_object);
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
if (vm.exception())
return {};
- auto* callback_function = callback_from_args(global_object, "reduceRight");
- if (!callback_function)
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
return {};
+ }
- int start = initial_length - 1;
+ // 4. If len = 0 and initialValue is not present, throw a TypeError exception.
+ if (length == 0 && vm.argument_count() <= 1) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial);
+ return {};
+ }
+ // 5. Let k be len - 1.
+ ssize_t k = length - 1;
+
+ // 6. Let accumulator be undefined.
auto accumulator = js_undefined();
+
+ // 7. If initialValue is present, then
if (vm.argument_count() > 1) {
- accumulator = vm.argument(1);
- } else {
- bool start_found = false;
- while (!start_found && start >= 0) {
- auto value = this_object->get(start);
+ // a. Set accumulator to initialValue.
+ accumulator = initial_value;
+ }
+ // 8. Else,
+ else {
+ // a. Let kPresent be false.
+ bool k_present = false;
+
+ // b. Repeat, while kPresent is false and k ≥ 0,
+ for (; !k_present && k >= 0; --k) {
+ // i. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // ii. Set kPresent to ? HasProperty(O, Pk).
+ k_present = object->has_property(property_name);
if (vm.exception())
return {};
- start_found = !value.is_empty();
- if (start_found)
- accumulator = value;
- start -= 1;
+
+ // iii. If kPresent is true, then
+ if (k_present) {
+ // 1. Set accumulator to ? Get(O, Pk).
+ accumulator = object->get(property_name);
+ if (vm.exception())
+ return {};
+ }
+
+ // iv. Set k to k - 1.
}
- if (!start_found) {
+
+ // c. If kPresent is false, throw a TypeError exception.
+ if (!k_present) {
vm.throw_exception<TypeError>(global_object, ErrorType::ReduceNoInitial);
return {};
}
}
- auto this_value = js_undefined();
+ // 9. Repeat, while k ≥ 0,
+ for (; k >= 0; --k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
- for (int i = start; i >= 0; --i) {
- auto value = this_object->get(i);
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
if (vm.exception())
return {};
- if (value.is_empty())
- continue;
- accumulator = vm.call(*callback_function, this_value, accumulator, value, Value((i32)i), this_object);
- if (vm.exception())
- return {};
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Set accumulator to ? Call(callbackfn, undefined, « accumulator, kValue, 𝔽(k), O »).
+ accumulator = vm.call(callback_function.as_function(), js_undefined(), accumulator, k_value, Value((i32)k), object);
+ if (vm.exception())
+ return {};
+ }
+
+ // d. Set k to k - 1.
}
+ // 10. Return accumulator.
return accumulator;
}
@@ -872,11 +1101,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reverse)
this_object->define_property(lower, upper_value);
if (vm.exception())
return {};
- this_object->delete_property(upper, true);
+ this_object->delete_property_or_throw(upper);
if (vm.exception())
return {};
} else if (lower_exists && !upper_exists) {
- this_object->delete_property(lower, true);
+ this_object->delete_property_or_throw(lower);
if (vm.exception())
return {};
this_object->define_property(upper, lower_value);
@@ -937,13 +1166,14 @@ static void array_merge_sort(VM& vm, GlobalObject& global_object, FunctionObject
if (vm.exception())
return;
- if (call_result.is_nan()) {
+ auto number = call_result.to_number(global_object);
+ if (vm.exception())
+ return;
+
+ if (number.is_nan())
comparison_result = 0;
- } else {
- comparison_result = call_result.to_double(global_object);
- if (vm.exception())
- return;
- }
+ else
+ comparison_result = number.as_double();
} else {
// FIXME: It would probably be much better to be smarter about this and implement
// the Abstract Relational Comparison in line once iterating over code points, rather
@@ -998,29 +1228,33 @@ static void array_merge_sort(VM& vm, GlobalObject& global_object, FunctionObject
// 23.1.3.27 Array.prototype.sort ( comparefn ), https://tc39.es/ecma262/#sec-array.prototype.sort
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort)
{
- auto* array = vm.this_value(global_object).to_object(global_object);
- if (vm.exception())
- return {};
-
auto callback = vm.argument(0);
if (!callback.is_undefined() && !callback.is_function()) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects());
return {};
}
- auto original_length = length_of_array_like(global_object, *array);
+ auto* object = vm.this_value(global_object).to_object(global_object);
if (vm.exception())
return {};
- MarkedValueList values_to_sort(vm.heap());
+ auto length = length_of_array_like(global_object, *object);
+ if (vm.exception())
+ return {};
- for (size_t i = 0; i < original_length; ++i) {
- auto element_val = array->get(i);
+ MarkedValueList items(vm.heap());
+ for (size_t k = 0; k < length; ++k) {
+ auto k_present = object->has_property(k);
if (vm.exception())
return {};
- if (!element_val.is_empty())
- values_to_sort.append(element_val);
+ if (k_present) {
+ auto k_value = object->get(k);
+ if (vm.exception())
+ return {};
+
+ items.append(k_value);
+ }
}
// Perform sorting by merge sort. This isn't as efficient compared to quick sort, but
@@ -1028,12 +1262,12 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort)
// to be stable. FIXME: when initially scanning through the array, maintain a flag
// for if an unstable sort would be indistinguishable from a stable sort (such as just
// just strings or numbers), and in that case use quick sort instead for better performance.
- array_merge_sort(vm, global_object, callback.is_undefined() ? nullptr : &callback.as_function(), values_to_sort);
+ array_merge_sort(vm, global_object, callback.is_undefined() ? nullptr : &callback.as_function(), items);
if (vm.exception())
return {};
- for (size_t i = 0; i < values_to_sort.size(); ++i) {
- array->put(i, values_to_sort[i]);
+ for (size_t j = 0; j < items.size(); ++j) {
+ object->set(j, items[j], true);
if (vm.exception())
return {};
}
@@ -1041,47 +1275,91 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort)
// The empty parts of the array are always sorted to the end, regardless of the
// compare function. FIXME: For performance, a similar process could be used
// for undefined, which are sorted to right before the empty values.
- for (size_t i = values_to_sort.size(); i < original_length; ++i) {
- array->delete_property(i, true);
+ for (size_t j = items.size(); j < length; ++j) {
+ object->delete_property_or_throw(j);
if (vm.exception())
return {};
}
- return array;
+ return object;
}
// 23.1.3.17 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.lastindexof
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::last_index_of)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
+ auto search_element = vm.argument(0);
+ auto from_index = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
return {};
- i32 length = length_of_array_like(global_object, *this_object);
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
if (vm.exception())
return {};
+
+ // 3. If len is 0, return -1𝔽.
if (length == 0)
return Value(-1);
- i32 from_index = length - 1;
+
+ double n;
+
+ // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1.
if (vm.argument_count() >= 2) {
- double from_argument = vm.argument(1).to_integer_or_infinity(global_object);
+ n = from_index.to_integer_or_infinity(global_object);
if (vm.exception())
return {};
- if (vm.argument(1).is_negative_infinity()) {
- return Value(-1);
- }
- if (from_argument >= 0)
- from_index = min(from_argument, length - 1.);
- else
- from_index = length + from_argument;
+ } else {
+ n = (double)length - 1;
}
- auto search_element = vm.argument(0);
- for (i32 i = from_index; i >= 0; --i) {
- auto element = this_object->get(i);
+
+ // 5. If n is -∞, return -1𝔽.
+ if (Value(n).is_negative_infinity())
+ return Value(-1);
+
+ i32 k;
+
+ // 6. If n ≥ 0, then
+ if (n >= 0) {
+ // a. Let k be min(n, len - 1).
+ k = min((i32)n, (i32)length - 1);
+ }
+ // 7. Else,
+ else {
+ // a. Let k be len + n.
+ k = (i32)length + (i32)n;
+ }
+
+ // 8. Repeat, while k ≥ 0,
+ for (; k >= 0; --k) {
+ auto property_name = PropertyName { k };
+
+ // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))).
+ auto k_present = object->has_property(property_name);
if (vm.exception())
return {};
- if (strict_eq(element, search_element))
- return Value(i);
+
+ // b. If kPresent is true, then
+ if (k_present) {
+ // i. Let elementK be ? Get(O, ! ToString(𝔽(k))).
+ auto element_k = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Let same be IsStrictlyEqual(searchElement, elementK).
+ auto same = strict_eq(search_element, element_k);
+
+ // iii. If same is true, return 𝔽(k).
+ if (same)
+ return Value(k);
+ }
+
+ // c. Set k to k - 1.
}
+
+ // 9. Return -1𝔽.
return Value(-1);
}
@@ -1120,61 +1398,213 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes)
// 23.1.3.8 Array.prototype.find ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.find
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find)
{
- auto result = js_undefined();
- for_each_item(
- vm, global_object, "find", [&](auto, auto value, auto callback_result) {
- if (callback_result.to_boolean()) {
- result = value;
- return IterationDecision::Break;
- }
- return IterationDecision::Continue;
- },
- false);
- return result;
+ auto predicate = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
+ if (vm.exception())
+ return {};
+
+ // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+ if (!predicate.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let k be 0.
+ // 5. Repeat, while k < len,
+ for (size_t k = 0; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
+ auto test_result = vm.call(predicate.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+
+ // d. If testResult is true, return kValue.
+ if (test_result.to_boolean())
+ return k_value;
+
+ // e. Set k to k + 1.
+ }
+
+ // 6. Return undefined.
+ return js_undefined();
}
// 23.1.3.9 Array.prototype.findIndex ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.findindex
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_index)
{
- auto result_index = -1;
- for_each_item(
- vm, global_object, "findIndex", [&](auto index, auto, auto callback_result) {
- if (callback_result.to_boolean()) {
- result_index = index;
- return IterationDecision::Break;
- }
- return IterationDecision::Continue;
- },
- false);
- return Value(result_index);
+ auto predicate = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
+ if (vm.exception())
+ return {};
+
+ // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+ if (!predicate.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let k be 0.
+ // 5. Repeat, while k < len,
+ for (size_t k = 0; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // c. Let testResult be ! ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)).
+ auto test_result = vm.call(predicate.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+
+ // d. If testResult is true, return 𝔽(k).
+ if (test_result.to_boolean())
+ return Value(k);
+
+ // e. Set k to k + 1.
+ }
+
+ // 6. Return -1𝔽.
+ return Value(-1);
}
// 23.1.3.26 Array.prototype.some ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.some
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::some)
{
- auto result = false;
- for_each_item(vm, global_object, "some", [&](auto, auto, auto callback_result) {
- if (callback_result.to_boolean()) {
- result = true;
- return IterationDecision::Break;
+ auto callback_function = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
+ if (vm.exception())
+ return {};
+
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let k be 0.
+ // 5. Repeat, while k < len,
+ for (size_t k = 0; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
+ auto test_result = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+
+ // iii. If testResult is true, return true.
+ if (test_result.to_boolean())
+ return Value(true);
}
- return IterationDecision::Continue;
- });
- return Value(result);
+
+ // d. Set k to k + 1.
+ }
+
+ // 6. Return false.
+ return Value(false);
}
// 23.1.3.5 Array.prototype.every ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.every
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::every)
{
- auto result = true;
- for_each_item(vm, global_object, "every", [&](auto, auto, auto callback_result) {
- if (!callback_result.to_boolean()) {
- result = false;
- return IterationDecision::Break;
+ auto callback_function = vm.argument(0);
+ auto this_arg = vm.argument(1);
+
+ // 1. Let O be ? ToObject(this value).
+ auto object = vm.this_value(global_object).to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ // 2. Let len be ? LengthOfArrayLike(O).
+ auto length = length_of_array_like(global_object, *object);
+ if (vm.exception())
+ return {};
+
+ // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
+ if (!callback_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects());
+ return {};
+ }
+
+ // 4. Let k be 0.
+ // 5. Repeat, while k < len,
+ for (size_t k = 0; k < length; ++k) {
+ // a. Let Pk be ! ToString(𝔽(k)).
+ auto property_name = PropertyName { k };
+
+ // b. Let kPresent be ? HasProperty(O, Pk).
+ auto k_present = object->has_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // c. If kPresent is true, then
+ if (k_present) {
+ // i. Let kValue be ? Get(O, Pk).
+ auto k_value = object->get(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. Let testResult be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
+ auto test_result = vm.call(callback_function.as_function(), this_arg, k_value, Value(k), object);
+ if (vm.exception())
+ return {};
+
+ // iii. If testResult is false, return false.
+ if (!test_result.to_boolean())
+ return Value(false);
}
- return IterationDecision::Continue;
- });
- return Value(result);
+
+ // d. Set k to k + 1.
+ }
+
+ // 6. Return true.
+ return Value(true);
}
// 23.1.3.28 Array.prototype.splice ( start, deleteCount, ...items ), https://tc39.es/ecma262#sec-array.prototype.splice
@@ -1235,7 +1665,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice)
if (vm.exception())
return {};
- removed_elements->define_property(i, from_value);
+ removed_elements->create_data_property_or_throw(i, from_value);
if (vm.exception())
return {};
}
@@ -1256,14 +1686,14 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice)
if (!from.is_empty()) {
this_object->define_property(to, from);
} else {
- this_object->delete_property(to, true);
+ this_object->delete_property_or_throw(to);
}
if (vm.exception())
return {};
}
for (size_t i = initial_length; i > new_length; --i) {
- this_object->delete_property(i - 1, true);
+ this_object->delete_property_or_throw(i - 1);
if (vm.exception())
return {};
}
@@ -1278,7 +1708,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice)
if (!from.is_empty()) {
this_object->define_property(to, from);
} else {
- this_object->delete_property(to, true);
+ this_object->delete_property_or_throw(to);
}
if (vm.exception())
return {};
@@ -1413,7 +1843,7 @@ static size_t flatten_into_array(GlobalObject& global_object, Object& new_array,
return {};
}
- new_array.define_property(target_index, value);
+ new_array.create_data_property_or_throw(target_index, value);
if (vm.exception())
return {};
@@ -1454,29 +1884,37 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat)
// 23.1.3.11 Array.prototype.flatMap ( mapperFunction [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.flatmap
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map)
{
- auto* this_object = vm.this_value(global_object).to_object(global_object);
- if (!this_object)
- return {};
+ auto mapper_function = vm.argument(0);
+ auto this_arg = vm.argument(1);
- auto length = length_of_array_like(global_object, *this_object);
+ // 1. Let O be ? ToObject(this value).
+ auto* object = vm.this_value(global_object).to_object(global_object);
if (vm.exception())
return {};
- auto* mapper_function = callback_from_args(global_object, "flatMap");
- if (!mapper_function)
+ // 2. Let sourceLen be ? LengthOfArrayLike(O).
+ auto source_length = length_of_array_like(global_object, *object);
+ if (vm.exception())
return {};
- auto this_argument = vm.argument(1);
+ // 3. If ! IsCallable(mapperFunction) is false, throw a TypeError exception.
+ if (!mapper_function.is_function()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, mapper_function.to_string_without_side_effects());
+ return {};
+ }
- auto* new_array = array_species_create(global_object, *this_object, 0);
+ // 4. Let A be ? ArraySpeciesCreate(O, 0).
+ auto* array = array_species_create(global_object, *object, 0);
if (vm.exception())
return {};
- flatten_into_array(global_object, *new_array, *this_object, length, 0, 1, mapper_function, this_argument);
+ // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg).
+ flatten_into_array(global_object, *array, *object, source_length, 0, 1, &mapper_function.as_function(), this_arg);
if (vm.exception())
return {};
- return new_array;
+ // 6. Return A.
+ return array;
}
// 23.1.3.3 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin
@@ -1550,7 +1988,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within)
if (vm.exception())
return {};
} else {
- this_object->delete_property(to_i, true);
+ this_object->delete_property_or_throw(to_i);
if (vm.exception())
return {};
}
diff --git a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp
index 81478d9987..a38c247c35 100644
--- a/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/BigIntPrototype.cpp
@@ -40,7 +40,7 @@ static Value this_bigint_value(GlobalObject& global_object, Value value)
if (value.is_bigint())
return value;
if (value.is_object() && is<BigIntObject>(value.as_object()))
- return static_cast<BigIntObject&>(value.as_object()).value_of();
+ return &static_cast<BigIntObject&>(value.as_object()).bigint();
auto& vm = global_object.vm();
vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "BigInt");
return {};
diff --git a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp
index f5c35e9200..18aadb3448 100644
--- a/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp
+++ b/Userland/Libraries/LibJS/Runtime/BoundFunction.cpp
@@ -37,7 +37,7 @@ Value BoundFunction::call()
Value BoundFunction::construct(FunctionObject& new_target)
{
if (auto this_value = vm().this_value(global_object()); m_constructor_prototype && this_value.is_object()) {
- this_value.as_object().set_prototype(m_constructor_prototype);
+ this_value.as_object().internal_set_prototype_of(m_constructor_prototype);
if (vm().exception())
return {};
}
diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
index 01a9d07d35..ef41a6deab 100644
--- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
+++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h
@@ -8,7 +8,6 @@
#define JS_ENUMERATE_ERROR_TYPES(M) \
M(ArrayMaxSize, "Maximum array size exceeded") \
- M(ArrayPrototypeOneArg, "Array.prototype.{}() requires at least one argument") \
M(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \
M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \
M(BigIntBadOperator, "Cannot use {} operator with BigInt") \
@@ -23,7 +22,6 @@
M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \
M(Convert, "Cannot convert {} to {}") \
M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \
- M(DescChangeNonConfigurable, "Cannot change attributes of non-configurable property '{}'") \
M(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \
M(DetachedArrayBuffer, "ArrayBuffer is detached") \
M(DivisionByZero, "Division by zero") \
@@ -60,9 +58,11 @@
M(NotASymbol, "{} is not a symbol") \
M(NotIterable, "{} is not iterable") \
M(NotObjectCoercible, "{} cannot be converted to an object") \
- M(ObjectDefinePropertyReturnedFalse, "Object's [[DefineProperty]] method returned false") \
+ M(ObjectDefineOwnPropertyReturnedFalse, "Object's [[DefineOwnProperty]] method returned false") \
+ M(ObjectDeleteReturnedFalse, "Object's [[Delete]] method returned false") \
M(ObjectFreezeFailed, "Could not freeze object") \
M(ObjectSealFailed, "Could not seal object") \
+ M(ObjectSetReturnedFalse, "Object's [[Set]] method returned false") \
M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \
M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \
M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \
@@ -81,11 +81,17 @@
M(ProxyDefinePropNonConfigurableNonExisting, "Proxy handler's defineProperty trap " \
"violates invariant: a property cannot be defined as non-configurable if it does not " \
"already exist on the target object") \
+ M(ProxyDefinePropNonWritable, "Proxy handler's defineProperty trap violates invariant: a non-configurable property cannot be " \
+ "non-writable, unless there exists a corresponding non-configurable, non-writable own property of " \
+ "the target object") \
M(ProxyDefinePropNonExtensible, "Proxy handler's defineProperty trap violates invariant: " \
"a property cannot be reported as being defined if the property does not exist on " \
"the target and the target is non-extensible") \
M(ProxyDeleteNonConfigurable, "Proxy handler's deleteProperty trap violates invariant: " \
"cannot report a non-configurable own property of the target as deleted") \
+ M(ProxyDeleteNonExtensible, "Proxy handler's deleteProperty trap violates invariant: " \
+ "a property cannot be reported as deleted, if it exists as an own property of the target object and " \
+ "the target object is non-extensible. ") \
M(ProxyGetImmutableDataProperty, "Proxy handler's get trap violates invariant: the " \
"returned value must match the value on the target if the property exists on the " \
"target as a non-writable, non-configurable own data property") \
@@ -103,6 +109,10 @@
M(ProxyGetOwnDescriptorNonConfigurable, "Proxy handler's getOwnPropertyDescriptor trap " \
"violates invariant: cannot return undefined for a property on the target which is " \
"a non-configurable property") \
+ M(ProxyGetOwnDescriptorNonConfigurableNonWritable, "Proxy handler's getOwnPropertyDescriptor trap " \
+ "violates invariant: cannot a property as both non-configurable and " \
+ "non-writable, unless it exists as a non-configurable, non-writable own " \
+ "property of the target object") \
M(ProxyGetOwnDescriptorReturn, "Proxy handler's getOwnPropertyDescriptor trap violates " \
"invariant: must return an object or undefined") \
M(ProxyGetOwnDescriptorUndefinedReturn, "Proxy handler's getOwnPropertyDescriptor trap " \
@@ -120,6 +130,8 @@
"non-extensible") \
M(ProxyIsExtensibleReturn, "Proxy handler's isExtensible trap violates invariant: " \
"return value must match the target's extensibility") \
+ M(ProxyOwnPropertyKeysNotStringOrSymbol, "Proxy handler's ownKeys trap violates invariant: " \
+ "the type of each result list element is either String or Symbol") \
M(ProxyPreventExtensionsReturn, "Proxy handler's preventExtensions trap violates " \
"invariant: cannot return true if the target object is extensible") \
M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \
@@ -138,11 +150,6 @@
M(ReferenceNullishSetProperty, "Cannot set property '{}' of {}") \
M(ReferencePrimitiveSetProperty, "Cannot set property '{}' of {} '{}'") \
M(ReferenceUnresolvable, "Unresolvable reference") \
- M(ReflectArgumentMustBeAConstructor, "First argument of Reflect.{}() must be a constructor") \
- M(ReflectArgumentMustBeAFunction, "First argument of Reflect.{}() must be a function") \
- M(ReflectArgumentMustBeAnObject, "First argument of Reflect.{}() must be an object") \
- M(ReflectBadNewTarget, "Optional third argument of Reflect.construct() must be a constructor") \
- M(ReflectBadDescriptorArgument, "Descriptor argument is not an object") \
M(RegExpCompileError, "RegExp compile error: {}") \
M(RegExpObjectBadFlag, "Invalid RegExp flag '{}'") \
M(RegExpObjectRepeatedFlag, "Repeated RegExp flag '{}'") \
@@ -151,6 +158,7 @@
M(SpeciesConstructorDidNotCreate, "Species constructor did not create {}") \
M(SpeciesConstructorReturned, "Species constructor returned {}") \
M(StringMatchAllNonGlobalRegExp, "RegExp argument is non-global") \
+ M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}") \
M(StringRepeatCountMustBe, "repeat count must be a {} number") \
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
M(ThisIsAlreadyInitialized, "|this| is already initialized") \
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp
index 722a5e55c7..62ebfb403c 100644
--- a/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp
+++ b/Userland/Libraries/LibJS/Runtime/FunctionEnvironment.cpp
@@ -35,7 +35,7 @@ Value FunctionEnvironment::get_super_base() const
auto home_object = m_function_object->home_object();
if (home_object.is_undefined())
return js_undefined();
- return home_object.as_object().prototype();
+ return home_object.as_object().internal_get_prototype_of();
}
// 9.1.1.3.2 HasThisBinding ( ), https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp
index 2e4a5c5393..732dec0c0e 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp
+++ b/Userland/Libraries/LibJS/Runtime/GlobalEnvironment.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -143,10 +144,14 @@ bool GlobalEnvironment::has_lexical_declaration(FlyString const& name) const
// 9.1.1.4.14 HasRestrictedGlobalProperty ( N ), https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
bool GlobalEnvironment::has_restricted_global_property(FlyString const& name) const
{
- auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name);
- if (!existing_prop.has_value() || existing_prop.value().value.is_undefined())
+ auto& vm = this->vm();
+ auto& global_object = m_object_record->binding_object();
+ auto existing_prop = global_object.internal_get_own_property(name);
+ if (vm.exception())
+ return {};
+ if (!existing_prop.has_value())
return false;
- if (existing_prop.value().attributes.is_configurable())
+ if (*existing_prop->configurable)
return false;
return true;
}
@@ -154,21 +159,29 @@ bool GlobalEnvironment::has_restricted_global_property(FlyString const& name) co
// 9.1.1.4.15 CanDeclareGlobalVar ( N ), https://tc39.es/ecma262/#sec-candeclareglobalvar
bool GlobalEnvironment::can_declare_global_var(FlyString const& name) const
{
- bool has_property = m_object_record->binding_object().has_own_property(name);
+ auto& vm = this->vm();
+ auto& global_object = m_object_record->binding_object();
+ bool has_property = global_object.has_own_property(name);
+ if (vm.exception())
+ return {};
if (has_property)
return true;
- return m_object_record->binding_object().is_extensible();
+ return global_object.is_extensible();
}
// 9.1.1.4.16 CanDeclareGlobalFunction ( N ), https://tc39.es/ecma262/#sec-candeclareglobalfunction
bool GlobalEnvironment::can_declare_global_function(FlyString const& name) const
{
- auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name);
- if (!existing_prop.has_value() || existing_prop.value().value.is_undefined())
- return m_object_record->binding_object().is_extensible();
- if (existing_prop.value().attributes.is_configurable())
+ auto& vm = this->vm();
+ auto& global_object = m_object_record->binding_object();
+ auto existing_prop = global_object.internal_get_own_property(name);
+ if (vm.exception())
+ return {};
+ if (!existing_prop.has_value())
+ return global_object.is_extensible();
+ if (*existing_prop->configurable)
return true;
- if (existing_prop.value().is_data_descriptor() && existing_prop.value().attributes.is_writable() && existing_prop.value().attributes.is_enumerable())
+ if (existing_prop->is_data_descriptor() && *existing_prop->writable && *existing_prop->enumerable)
return true;
return false;
}
@@ -176,11 +189,21 @@ bool GlobalEnvironment::can_declare_global_function(FlyString const& name) const
// 9.1.1.4.17 CreateGlobalVarBinding ( N, D ), https://tc39.es/ecma262/#sec-createglobalvarbinding
void GlobalEnvironment::create_global_var_binding(FlyString const& name, bool can_be_deleted)
{
- bool has_property = m_object_record->binding_object().has_own_property(name);
- bool extensible = m_object_record->binding_object().is_extensible();
+ auto& vm = this->vm();
+ auto& global_object = m_object_record->binding_object();
+ bool has_property = global_object.has_own_property(name);
+ if (vm.exception())
+ return;
+ bool extensible = global_object.is_extensible();
+ if (vm.exception())
+ return;
if (!has_property && extensible) {
- m_object_record->create_mutable_binding(static_cast<GlobalObject&>(m_object_record->binding_object()), name, can_be_deleted);
+ m_object_record->create_mutable_binding(m_object_record->global_object(), name, can_be_deleted);
+ if (vm.exception())
+ return;
m_object_record->initialize_binding(m_object_record->global_object(), name, js_undefined());
+ if (vm.exception())
+ return;
}
if (!m_var_names.contains_slow(name))
m_var_names.append(name);
@@ -189,23 +212,21 @@ void GlobalEnvironment::create_global_var_binding(FlyString const& name, bool ca
// 9.1.1.4.18 CreateGlobalFunctionBinding ( N, V, D ), https://tc39.es/ecma262/#sec-createglobalfunctionbinding
void GlobalEnvironment::create_global_function_binding(FlyString const& name, Value value, bool can_be_deleted)
{
- auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name);
+ auto& vm = this->vm();
+ auto& global_object = m_object_record->binding_object();
+ auto existing_prop = global_object.internal_get_own_property(name);
+ if (vm.exception())
+ return;
PropertyDescriptor desc;
- if (!existing_prop.has_value() || existing_prop.value().value.is_undefined() || existing_prop.value().attributes.is_configurable()) {
- desc.value = value;
- desc.attributes.set_has_writable();
- desc.attributes.set_writable();
- desc.attributes.set_has_enumerable();
- desc.attributes.set_enumerable();
- desc.attributes.set_has_configurable();
- if (can_be_deleted)
- desc.attributes.set_configurable();
- } else {
- desc.value = value;
- }
- // FIXME: This should be DefinePropertyOrThrow, followed by Set
- m_object_record->binding_object().define_property(name, value, desc.attributes);
- if (vm().exception())
+ if (!existing_prop.has_value() || *existing_prop->configurable)
+ desc = { .value = value, .writable = true, .enumerable = true, .configurable = can_be_deleted };
+ else
+ desc = { .value = value };
+ global_object.define_property_or_throw(name, desc);
+ if (vm.exception())
+ return;
+ global_object.set(name, value, false);
+ if (vm.exception())
return;
if (!m_var_names.contains_slow(name))
m_var_names.append(name);
diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
index 81c25365bd..4c30e70a1f 100644
--- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp
@@ -111,7 +111,8 @@ void GlobalObject::initialize_global_object()
static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this);
static_cast<ObjectPrototype*>(m_object_prototype)->initialize(*this);
- Object::set_prototype(m_object_prototype);
+ auto success = Object::internal_set_prototype_of(m_object_prototype);
+ VERIFY(success);
// This must be initialized before allocating AggregateErrorPrototype, which uses ErrorPrototype as its prototype.
m_error_prototype = heap().allocate<ErrorPrototype>(*this, *this);
@@ -147,9 +148,9 @@ void GlobalObject::initialize_global_object()
vm.throw_exception<TypeError>(global_object, ErrorType::RestrictedFunctionPropertiesAccess);
return Value();
});
- m_throw_type_error_function->prevent_extensions();
m_throw_type_error_function->define_property_without_transition(vm.names.length, Value(0), 0, false);
m_throw_type_error_function->define_property_without_transition(vm.names.name, js_string(vm, ""), 0, false);
+ m_throw_type_error_function->internal_prevent_extensions();
// 10.2.4 AddRestrictedFunctionProperties ( F, realm ), https://tc39.es/ecma262/#sec-addrestrictedfunctionproperties
m_function_prototype->define_accessor(vm.names.caller, throw_type_error_function(), throw_type_error_function(), Attribute::Configurable);
diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp
index ae492d600c..56ca07ec40 100644
--- a/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp
+++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.cpp
@@ -52,15 +52,8 @@ void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttribute
void SimpleIndexedPropertyStorage::remove(u32 index)
{
- if (index < m_array_size)
- m_packed_elements[index] = {};
-}
-
-void SimpleIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes)
-{
- VERIFY(attributes == default_attributes);
- m_array_size++;
- m_packed_elements.insert(index, value);
+ VERIFY(index < m_array_size);
+ m_packed_elements[index] = {};
}
ValueAndAttributes SimpleIndexedPropertyStorage::take_first()
@@ -87,7 +80,9 @@ GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedProper
{
m_array_size = storage.array_like_size();
for (size_t i = 0; i < storage.m_packed_elements.size(); ++i) {
- m_sparse_elements.set(i, { storage.m_packed_elements[i], default_attributes });
+ auto value = storage.m_packed_elements[i];
+ if (!value.is_empty())
+ m_sparse_elements.set(i, { value, default_attributes });
}
}
@@ -112,34 +107,10 @@ void GenericIndexedPropertyStorage::put(u32 index, Value value, PropertyAttribut
void GenericIndexedPropertyStorage::remove(u32 index)
{
- if (index >= m_array_size)
- return;
- if (index + 1 == m_array_size) {
- take_last();
- return;
- }
+ VERIFY(index < m_array_size);
m_sparse_elements.remove(index);
}
-void GenericIndexedPropertyStorage::insert(u32 index, Value value, PropertyAttributes attributes)
-{
- if (index >= m_array_size) {
- put(index, value, attributes);
- return;
- }
-
- m_array_size++;
-
- if (!m_sparse_elements.is_empty()) {
- HashMap<u32, ValueAndAttributes> new_sparse_elements;
- for (auto& entry : m_sparse_elements)
- new_sparse_elements.set(entry.key >= index ? entry.key + 1 : entry.key, entry.value);
- m_sparse_elements = move(new_sparse_elements);
- }
-
- m_sparse_elements.set(index, { value, attributes });
-}
-
ValueAndAttributes GenericIndexedPropertyStorage::take_first()
{
VERIFY(m_array_size > 0);
@@ -226,10 +197,10 @@ bool IndexedPropertyIterator::operator!=(const IndexedPropertyIterator& other) c
return m_index != other.m_index;
}
-ValueAndAttributes IndexedPropertyIterator::value_and_attributes(Object* this_object, AllowSideEffects allow_side_effects)
+ValueAndAttributes IndexedPropertyIterator::value_and_attributes()
{
if (m_index < m_indexed_properties.array_like_size())
- return m_indexed_properties.get(this_object, m_index, allow_side_effects).value_or({});
+ return m_indexed_properties.get(m_index).value_or({});
return {};
}
@@ -245,62 +216,24 @@ void IndexedPropertyIterator::skip_empty_indices()
m_index = m_indexed_properties.array_like_size();
}
-Optional<ValueAndAttributes> IndexedProperties::get(Object* this_object, u32 index, AllowSideEffects allow_side_effects) const
+Optional<ValueAndAttributes> IndexedProperties::get(u32 index) const
{
- auto result = m_storage->get(index);
- if (allow_side_effects == AllowSideEffects::No)
- return result;
- if (!result.has_value())
- return {};
- auto& value = result.value();
- if (value.value.is_accessor()) {
- VERIFY(this_object);
- auto& accessor = value.value.as_accessor();
- return ValueAndAttributes { accessor.call_getter(this_object), value.attributes };
- }
- return result;
+ return m_storage->get(index);
}
-void IndexedProperties::put(Object* this_object, u32 index, Value value, PropertyAttributes attributes, AllowSideEffects allow_side_effects)
+void IndexedProperties::put(u32 index, Value value, PropertyAttributes attributes)
{
if (m_storage->is_simple_storage() && (attributes != default_attributes || index > (array_like_size() + SPARSE_ARRAY_HOLE_THRESHOLD))) {
switch_to_generic_storage();
}
- if (m_storage->is_simple_storage() || allow_side_effects == AllowSideEffects::No) {
- m_storage->put(index, value, attributes);
- return;
- }
-
- auto value_here = m_storage->get(index);
- if (value_here.has_value() && value_here.value().value.is_accessor()) {
- VERIFY(this_object);
- value_here.value().value.as_accessor().call_setter(this_object, value);
- } else {
- m_storage->put(index, value, attributes);
- }
+ m_storage->put(index, value, attributes);
}
-bool IndexedProperties::remove(u32 index)
+void IndexedProperties::remove(u32 index)
{
- auto result = m_storage->get(index);
- if (!result.has_value())
- return true;
- if (!result.value().attributes.is_configurable())
- return false;
+ VERIFY(m_storage->has_index(index));
m_storage->remove(index);
- return true;
-}
-
-void IndexedProperties::insert(u32 index, Value value, PropertyAttributes attributes)
-{
- if (m_storage->is_simple_storage()) {
- if (attributes != default_attributes
- || index > (array_like_size() + SPARSE_ARRAY_HOLE_THRESHOLD)) {
- switch_to_generic_storage();
- }
- }
- m_storage->insert(index, value, attributes);
}
ValueAndAttributes IndexedProperties::take_first(Object* this_object)
diff --git a/Userland/Libraries/LibJS/Runtime/IndexedProperties.h b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h
index 88466fc3a2..67561e8092 100644
--- a/Userland/Libraries/LibJS/Runtime/IndexedProperties.h
+++ b/Userland/Libraries/LibJS/Runtime/IndexedProperties.h
@@ -30,7 +30,6 @@ public:
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
virtual void remove(u32 index) = 0;
- virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
virtual ValueAndAttributes take_first() = 0;
virtual ValueAndAttributes take_last() = 0;
@@ -51,7 +50,6 @@ public:
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
virtual void remove(u32 index) override;
- virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
virtual ValueAndAttributes take_first() override;
virtual ValueAndAttributes take_last() override;
@@ -80,7 +78,6 @@ public:
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
virtual void remove(u32 index) override;
- virtual void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
virtual ValueAndAttributes take_first() override;
virtual ValueAndAttributes take_last() override;
@@ -104,7 +101,7 @@ public:
bool operator!=(const IndexedPropertyIterator&) const;
u32 index() const { return m_index; };
- ValueAndAttributes value_and_attributes(Object* this_object, AllowSideEffects = AllowSideEffects::Yes);
+ ValueAndAttributes value_and_attributes();
private:
void skip_empty_indices();
@@ -124,15 +121,14 @@ public:
}
bool has_index(u32 index) const { return m_storage->has_index(index); }
- Optional<ValueAndAttributes> get(Object* this_object, u32 index, AllowSideEffects = AllowSideEffects::Yes) const;
- void put(Object* this_object, u32 index, Value value, PropertyAttributes attributes = default_attributes, AllowSideEffects allow_side_effects = AllowSideEffects::Yes);
- bool remove(u32 index);
+ Optional<ValueAndAttributes> get(u32 index) const;
+ void put(u32 index, Value value, PropertyAttributes attributes = default_attributes);
+ void remove(u32 index);
- void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes);
ValueAndAttributes take_first(Object* this_object);
ValueAndAttributes take_last(Object* this_object);
- void append(Value value, PropertyAttributes attributes = default_attributes) { put(nullptr, array_like_size(), value, attributes, AllowSideEffects::No); }
+ void append(Value value, PropertyAttributes attributes = default_attributes) { put(array_like_size(), value, attributes); }
IndexedPropertyIterator begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); };
IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); };
diff --git a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
index a737d84e3a..91616f805f 100644
--- a/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/JSONObject.cpp
@@ -117,7 +117,7 @@ String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Valu
}
auto* wrapper = Object::create(global_object, global_object.object_prototype());
- wrapper->define_property(String::empty(), value);
+ wrapper->create_data_property_or_throw(String::empty(), value);
auto result = serialize_json_property(global_object, state, String::empty(), wrapper);
if (vm.exception())
return {};
@@ -256,7 +256,7 @@ String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyS
return {};
}
} else {
- auto property_list = object.get_enumerable_own_property_names(PropertyKind::Key);
+ auto property_list = object.enumerable_own_property_names(PropertyKind::Key);
if (vm.exception())
return {};
for (auto& property : property_list) {
@@ -428,7 +428,7 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse)
if (reviver.is_function()) {
auto* root = Object::create(global_object, global_object.object_prototype());
auto root_name = String::empty();
- root->define_property(root_name, unfiltered);
+ root->create_data_property_or_throw(root_name, unfiltered);
auto result = internalize_json_property(global_object, root, root_name, reviver.as_function());
if (vm.exception())
return {};
@@ -493,9 +493,9 @@ Value JSONObject::internalize_json_property(GlobalObject& global_object, Object*
if (vm.exception())
return;
if (element.is_undefined())
- value_object.delete_property(key);
+ value_object.internal_delete(key);
else
- value_object.define_property(key, element, default_attributes);
+ value_object.create_data_property(key, element);
};
if (is_array) {
@@ -508,7 +508,7 @@ Value JSONObject::internalize_json_property(GlobalObject& global_object, Object*
return {};
}
} else {
- auto property_list = value_object.get_enumerable_own_property_names(Object::PropertyKind::Key);
+ auto property_list = value_object.enumerable_own_property_names(Object::PropertyKind::Key);
for (auto& property_name : property_list) {
process_property(property_name.as_string().string());
if (vm.exception())
diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp
index 46ef56ed1f..a641e6e6c9 100644
--- a/Userland/Libraries/LibJS/Runtime/Object.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Object.cpp
@@ -5,11 +5,11 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
-#include <AK/Debug.h>
#include <AK/String.h>
#include <AK/TemporaryChange.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Interpreter.h>
+#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
@@ -17,60 +17,14 @@
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/NativeProperty.h>
#include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/ProxyObject.h>
#include <LibJS/Runtime/Shape.h>
-#include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/TemporaryClearException.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
-PropertyDescriptor PropertyDescriptor::from_dictionary(VM& vm, const Object& object)
-{
- PropertyAttributes attributes;
- if (object.has_property(vm.names.configurable)) {
- attributes.set_has_configurable();
- if (object.get(vm.names.configurable).value_or(Value(false)).to_boolean())
- attributes.set_configurable();
- if (vm.exception())
- return {};
- }
- if (object.has_property(vm.names.enumerable)) {
- attributes.set_has_enumerable();
- if (object.get(vm.names.enumerable).value_or(Value(false)).to_boolean())
- attributes.set_enumerable();
- if (vm.exception())
- return {};
- }
- if (object.has_property(vm.names.writable)) {
- attributes.set_has_writable();
- if (object.get(vm.names.writable).value_or(Value(false)).to_boolean())
- attributes.set_writable();
- if (vm.exception())
- return {};
- }
- PropertyDescriptor descriptor { attributes, object.get(vm.names.value), nullptr, nullptr };
- if (vm.exception())
- return {};
- auto getter = object.get(vm.names.get);
- if (vm.exception())
- return {};
- if (getter.is_function())
- descriptor.getter = &getter.as_function();
- if (!getter.is_empty())
- descriptor.attributes.set_has_getter();
-
- auto setter = object.get(vm.names.set);
- if (vm.exception())
- return {};
- if (setter.is_function())
- descriptor.setter = &setter.as_function();
- if (!setter.is_empty())
- descriptor.attributes.set_has_setter();
-
- return descriptor;
-}
-
// 10.1.12 OrdinaryObjectCreate ( proto [ , additionalInternalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinaryobjectcreate
Object* Object::create(GlobalObject& global_object, Object* prototype)
{
@@ -96,7 +50,8 @@ Object::Object(ConstructWithoutPrototypeTag, GlobalObject& global_object)
Object::Object(Object& prototype)
{
m_shape = prototype.global_object().empty_object_shape();
- set_prototype(&prototype);
+ auto success = internal_set_prototype_of(&prototype);
+ VERIFY(success);
}
Object::Object(Shape& shape)
@@ -113,881 +68,1018 @@ Object::~Object()
{
}
-Object* Object::prototype()
+// 7.2 Testing and Comparison Operations, https://tc39.es/ecma262/#sec-testing-and-comparison-operations
+
+// 7.2.5 IsExtensible ( O ), https://tc39.es/ecma262/#sec-isextensible-o
+bool Object::is_extensible() const
{
- return shape().prototype();
+ return internal_is_extensible();
}
-const Object* Object::prototype() const
+// 7.3 Operations on Objects, https://tc39.es/ecma262/#sec-operations-on-objects
+
+// 7.3.2 Get ( O, P ), https://tc39.es/ecma262/#sec-get-o-p
+Value Object::get(PropertyName const& property_name) const
{
- return shape().prototype();
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Return ? O.[[Get]](P, O).
+ return internal_get(property_name, this);
}
-// 10.1.2.1 OrdinarySetPrototypeOf ( O, V ), https://tc39.es/ecma262/#sec-ordinarysetprototypeof
-bool Object::set_prototype(Object* new_prototype)
+// 7.3.3 GetV ( V, P ) is defined as Value::get().
+
+// 7.3.4 Set ( O, P, V, Throw ), https://tc39.es/ecma262/#sec-set-o-p-v-throw
+bool Object::set(PropertyName const& property_name, Value value, bool throw_exceptions)
{
- if (prototype() == new_prototype)
- return true;
- if (!m_is_extensible)
- return false;
- auto* prototype = new_prototype;
- while (prototype) {
- if (prototype == this)
- return false;
- // NOTE: This is a best-effort implementation of the following step:
- // "If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1,
- // set done to true."
- // We don't have a good way of detecting whether certain virtual Object methods have been
- // overridden by a given object, but as ProxyObject is the only one doing that, this check
- // does the trick.
- if (is<ProxyObject>(prototype))
- break;
- prototype = prototype->prototype();
+ VERIFY(!value.is_empty());
+ auto& vm = this->vm();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Assert: Type(Throw) is Boolean.
+
+ // 4. Let success be ? O.[[Set]](P, V, O).
+ auto success = internal_set(property_name, value, this);
+ if (vm.exception())
+ return {};
+
+ // 5. If success is false and Throw is true, throw a TypeError exception.
+ if (!success && throw_exceptions) {
+ // FIXME: Improve/contextualize error message
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectSetReturnedFalse);
+ return {};
}
- auto& shape = this->shape();
- if (shape.is_unique())
- shape.set_prototype_without_transition(new_prototype);
- else
- m_shape = shape.create_prototype_transition(new_prototype);
- return true;
+
+ // 6. Return success.
+ return success;
}
-bool Object::has_prototype(const Object* prototype) const
+// 7.3.5 CreateDataProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createdataproperty
+bool Object::create_data_property(PropertyName const& property_name, Value value)
{
- for (auto* object = this->prototype(); object; object = object->prototype()) {
- if (vm().exception())
- return false;
- if (object == prototype)
- return true;
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
+ auto new_descriptor = PropertyDescriptor {
+ .value = value,
+ .writable = true,
+ .enumerable = true,
+ .configurable = true,
+ };
+
+ // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
+ return internal_define_own_property(property_name, new_descriptor);
+}
+
+// 7.3.6 CreateMethodProperty ( O, P, V ), https://tc39.es/ecma262/#sec-createmethodproperty
+bool Object::create_method_property(PropertyName const& property_name, Value value)
+{
+ VERIFY(!value.is_empty());
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }.
+ auto new_descriptor = PropertyDescriptor {
+ .value = value,
+ .writable = true,
+ .enumerable = false,
+ .configurable = true,
+ };
+
+ // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
+ return internal_define_own_property(property_name, new_descriptor);
+}
+
+// 7.3.7 CreateDataPropertyOrThrow ( O, P, V ), https://tc39.es/ecma262/#sec-createdatapropertyorthrow
+bool Object::create_data_property_or_throw(PropertyName const& property_name, Value value)
+{
+ VERIFY(!value.is_empty());
+ auto& vm = this->vm();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Let success be ? CreateDataProperty(O, P, V).
+ auto success = create_data_property(property_name, value);
+ if (vm.exception())
+ return {};
+
+ // 4. If success is false, throw a TypeError exception.
+ if (!success) {
+ // FIXME: Improve/contextualize error message
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectDefineOwnPropertyReturnedFalse);
+ return {};
}
- return false;
+
+ // 5. Return success.
+ return success;
}
-bool Object::prevent_extensions()
+// 7.3.8 DefinePropertyOrThrow ( O, P, desc ), https://tc39.es/ecma262/#sec-definepropertyorthrow
+bool Object::define_property_or_throw(PropertyName const& property_name, PropertyDescriptor const& property_descriptor)
{
- m_is_extensible = false;
+ auto& vm = this->vm();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
+ auto success = internal_define_own_property(property_name, property_descriptor);
+ if (vm.exception())
+ return {};
+
+ // 4. If success is false, throw a TypeError exception.
+ if (!success) {
+ // FIXME: Improve/contextualize error message
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectDefineOwnPropertyReturnedFalse);
+ return {};
+ }
+
+ // 5. Return success.
+ return success;
+}
+
+// 7.3.9 DeletePropertyOrThrow ( O, P ), https://tc39.es/ecma262/#sec-deletepropertyorthrow
+bool Object::delete_property_or_throw(PropertyName const& property_name)
+{
+ auto& vm = this->vm();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Let success be ? O.[[Delete]](P).
+ auto success = internal_delete(property_name);
+ if (vm.exception())
+ return {};
+
+ // 4. If success is false, throw a TypeError exception.
+ if (!success) {
+ // FIXME: Improve/contextualize error message
+ vm.throw_exception<TypeError>(global_object(), ErrorType::ObjectDeleteReturnedFalse);
+ return {};
+ }
+
+ // 5. Return success.
+ return success;
+}
+
+// 7.3.11 HasProperty ( O, P ), https://tc39.es/ecma262/#sec-hasproperty
+bool Object::has_property(PropertyName const& property_name) const
+{
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Return ? O.[[HasProperty]](P).
+ return internal_has_property(property_name);
+}
+
+// 7.3.12 HasOwnProperty ( O, P ), https://tc39.es/ecma262/#sec-hasownproperty
+bool Object::has_own_property(PropertyName const& property_name) const
+{
+ auto& vm = this->vm();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. Let desc be ? O.[[GetOwnProperty]](P).
+ auto descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 4. If desc is undefined, return false.
+ if (!descriptor.has_value())
+ return false;
+
+ // 5. Return true.
return true;
}
// 7.3.15 SetIntegrityLevel ( O, level ), https://tc39.es/ecma262/#sec-setintegritylevel
bool Object::set_integrity_level(IntegrityLevel level)
{
- // FIXME: This feels clunky and should get nicer abstractions.
- auto update_property = [this](auto& property_name, auto new_attributes) {
- if (property_name.is_number()) {
- auto value_and_attributes = m_indexed_properties.get(nullptr, property_name.as_number(), AllowSideEffects::No).value();
- auto value = value_and_attributes.value;
- auto attributes = value_and_attributes.attributes.bits() & new_attributes;
- m_indexed_properties.put(nullptr, property_name.as_number(), value, attributes, AllowSideEffects::No);
- } else {
- auto metadata = shape().lookup(property_name.to_string_or_symbol()).value();
- auto attributes = metadata.attributes.bits() & new_attributes;
- if (m_shape->is_unique())
- m_shape->reconfigure_property_in_unique_shape(property_name.to_string_or_symbol(), attributes);
- else
- set_shape(*m_shape->create_configure_transition(property_name.to_string_or_symbol(), attributes));
- }
- };
-
auto& vm = this->vm();
- auto status = prevent_extensions();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: level is either sealed or frozen.
+ VERIFY(level == IntegrityLevel::Sealed || level == IntegrityLevel::Frozen);
+
+ // 3. Let status be ? O.[[PreventExtensions]]().
+ auto status = internal_prevent_extensions();
if (vm.exception())
- return false;
+ return {};
+
+ // 4. If status is false, return false.
if (!status)
return false;
- auto keys = get_own_properties(PropertyKind::Key);
+
+ // 5. Let keys be ? O.[[OwnPropertyKeys]]().
+ auto keys = internal_own_property_keys();
if (vm.exception())
- return false;
- switch (level) {
- case IntegrityLevel::Sealed:
+ return {};
+
+ // 6. If level is sealed, then
+ if (level == IntegrityLevel::Sealed) {
+ // a. For each element k of keys, do
for (auto& key : keys) {
- auto property_name = PropertyName::from_value(global_object(), key);
- update_property(property_name, ~Attribute::Configurable);
+ auto property_name = PropertyName::from_value(global_object, key);
+
+ // i. Perform ? DefinePropertyOrThrow(O, k, PropertyDescriptor { [[Configurable]]: false }).
+ define_property_or_throw(property_name, { .configurable = false });
if (vm.exception())
return {};
}
- break;
- case IntegrityLevel::Frozen:
+ }
+ // 7. Else,
+ else {
+ // a. Assert: level is frozen.
+
+ // b. For each element k of keys, do
for (auto& key : keys) {
- auto property_name = PropertyName::from_value(global_object(), key);
- auto property_descriptor = get_own_property_descriptor(property_name);
- if (!property_descriptor.has_value())
+ auto property_name = PropertyName::from_value(global_object, key);
+
+ // i. Let currentDesc be ? O.[[GetOwnProperty]](k).
+ auto current_descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // ii. If currentDesc is not undefined, then
+ if (!current_descriptor.has_value())
continue;
- u8 attributes = property_descriptor->is_accessor_descriptor()
- ? ~Attribute::Configurable
- : ~Attribute::Configurable & ~Attribute::Writable;
- update_property(property_name, attributes);
+
+ PropertyDescriptor descriptor;
+
+ // 1. If IsAccessorDescriptor(currentDesc) is true, then
+ if (current_descriptor->is_accessor_descriptor()) {
+ // a. Let desc be the PropertyDescriptor { [[Configurable]]: false }.
+ descriptor = { .configurable = false };
+ }
+ // 2. Else,
+ else {
+ // a. Let desc be the PropertyDescriptor { [[Configurable]]: false, [[Writable]]: false }.
+ descriptor = { .writable = false, .configurable = false };
+ }
+
+ // 3. Perform ? DefinePropertyOrThrow(O, k, desc).
+ define_property_or_throw(property_name, descriptor);
if (vm.exception())
return {};
}
- break;
- default:
- VERIFY_NOT_REACHED();
}
+
+ // 8. Return true.
return true;
}
// 7.3.16 TestIntegrityLevel ( O, level ), https://tc39.es/ecma262/#sec-testintegritylevel
-bool Object::test_integrity_level(IntegrityLevel level)
+bool Object::test_integrity_level(IntegrityLevel level) const
{
auto& vm = this->vm();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Assert: level is either sealed or frozen.
+ VERIFY(level == IntegrityLevel::Sealed || level == IntegrityLevel::Frozen);
+
+ // 3. Let extensible be ? IsExtensible(O).
auto extensible = is_extensible();
if (vm.exception())
- return false;
+ return {};
+
+ // 4. If extensible is true, return false.
+ // 5. NOTE: If the object is extensible, none of its properties are examined.
if (extensible)
return false;
- auto keys = get_own_properties(PropertyKind::Key);
+
+ // 6. Let keys be ? O.[[OwnPropertyKeys]]().
+ auto keys = internal_own_property_keys();
if (vm.exception())
- return false;
+ return {};
+
+ // 7. For each element k of keys, do
for (auto& key : keys) {
auto property_name = PropertyName::from_value(global_object(), key);
- auto property_descriptor = get_own_property_descriptor(property_name);
- if (!property_descriptor.has_value())
+
+ // a. Let currentDesc be ? O.[[GetOwnProperty]](k).
+ auto current_descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // b. If currentDesc is not undefined, then
+ if (!current_descriptor.has_value())
continue;
- if (property_descriptor->attributes.is_configurable())
+ // i. If currentDesc.[[Configurable]] is true, return false.
+ if (*current_descriptor->configurable)
return false;
- if (level == IntegrityLevel::Frozen && property_descriptor->is_data_descriptor()) {
- if (property_descriptor->attributes.is_writable())
+
+ // ii. If level is frozen and IsDataDescriptor(currentDesc) is true, then
+ if (level == IntegrityLevel::Frozen && current_descriptor->is_data_descriptor()) {
+ // 1. If currentDesc.[[Writable]] is true, return false.
+ if (*current_descriptor->writable)
return false;
}
}
+
+ // 8. Return true.
return true;
}
-Value Object::get_own_property(const PropertyName& property_name, Value receiver, AllowSideEffects allow_side_effects) const
+// 7.3.23 EnumerableOwnPropertyNames ( O, kind ), https://tc39.es/ecma262/#sec-enumerableownpropertynames
+MarkedValueList Object::enumerable_own_property_names(PropertyKind kind) const
{
- VERIFY(property_name.is_valid());
+ // NOTE: This has been flattened for readability, so some `else` branches in the
+ // spec text have been replaced with `continue`s in the loop below.
- Value value_here;
+ auto& vm = this->vm();
+ auto& global_object = this->global_object();
- if (property_name.is_number()) {
- auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), AllowSideEffects::No);
- if (!existing_property.has_value())
- return {};
- value_here = existing_property.value().value.value_or(js_undefined());
- } else {
- 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());
- }
+ // 1. Assert: Type(O) is Object.
- VERIFY(!value_here.is_empty());
- if (allow_side_effects == AllowSideEffects::Yes) {
- VERIFY(!receiver.is_empty());
- if (value_here.is_accessor())
- return value_here.as_accessor().call_getter(receiver);
- if (value_here.is_native_property())
- return call_native_property_getter(value_here.as_native_property(), receiver);
- }
- return value_here;
-}
+ // 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
+ auto own_keys = internal_own_property_keys();
+ if (vm.exception())
+ return MarkedValueList { heap() };
-MarkedValueList Object::get_own_properties(PropertyKind kind, bool only_enumerable_properties, GetOwnPropertyReturnType return_type) const
-{
- MarkedValueList properties(heap());
+ // 3. Let properties be a new empty List.
+ auto properties = MarkedValueList { heap() };
- // FIXME: Support generic iterables
- if (is<StringObject>(*this)) {
- auto str = static_cast<const StringObject&>(*this).primitive_string().string();
+ // 4. For each element key of ownKeys, do
+ for (auto& key : own_keys) {
+ // a. If Type(key) is String, then
+ if (!key.is_string())
+ continue;
+ auto property_name = PropertyName::from_value(global_object, key);
- for (size_t i = 0; i < str.length(); ++i) {
+ // i. Let desc be ? O.[[GetOwnProperty]](key).
+ auto descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return MarkedValueList { heap() };
+
+ // ii. If desc is not undefined and desc.[[Enumerable]] is true, then
+ if (descriptor.has_value() && *descriptor->enumerable) {
+ // 1. If kind is key, append key to properties.
if (kind == PropertyKind::Key) {
- properties.append(js_string(vm(), String::number(i)));
- } else if (kind == PropertyKind::Value) {
- properties.append(js_string(vm(), String::formatted("{:c}", str[i])));
- } else {
- auto* entry_array = Array::create(global_object(), 0);
- entry_array->define_property(0, js_string(vm(), String::number(i)));
- entry_array->define_property(1, js_string(vm(), String::formatted("{:c}", str[i])));
- properties.append(entry_array);
+ properties.append(key);
+ continue;
}
- if (vm().exception())
+ // 2. Else,
+
+ // a. Let value be ? Get(O, key).
+ auto value = get(property_name);
+ if (vm.exception())
return MarkedValueList { heap() };
- }
- }
- if (return_type != GetOwnPropertyReturnType::SymbolOnly) {
- for (auto& entry : m_indexed_properties) {
- auto value_and_attributes = entry.value_and_attributes(const_cast<Object*>(this));
- if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable())
+ // b. If kind is value, append value to properties.
+ if (kind == PropertyKind::Value) {
+ properties.append(value);
continue;
-
- if (kind == PropertyKind::Key) {
- properties.append(js_string(vm(), String::number(entry.index())));
- } else if (kind == PropertyKind::Value) {
- properties.append(value_and_attributes.value);
- } else {
- auto* entry_array = Array::create(global_object(), 0);
- entry_array->define_property(0, js_string(vm(), String::number(entry.index())));
- entry_array->define_property(1, value_and_attributes.value);
- properties.append(entry_array);
}
- if (vm().exception())
- return MarkedValueList { heap() };
- }
- }
+ // c. Else,
- auto add_property_to_results = [&](auto& property) {
- if (kind == PropertyKind::Key) {
- properties.append(property.key.to_value(vm()));
- } else if (kind == PropertyKind::Value) {
- Value v = get(property.key);
- // Value may just have been deleted
- if (!v.is_empty())
- properties.append(v);
- } else {
- Value val = get(property.key);
- if (val.is_empty())
- return;
-
- auto* entry_array = Array::create(global_object(), 0);
- entry_array->define_property(0, property.key.to_value(vm()));
- entry_array->define_property(1, val);
- properties.append(entry_array);
- }
- };
+ // i. Assert: kind is key+value.
+ VERIFY(kind == PropertyKind::KeyAndValue);
- // NOTE: Most things including for..in/of and Object.{keys,values,entries}() use StringOnly, and in those
- // cases we won't be iterating the ordered property table twice. We can certainly improve this though.
- if (return_type == GetOwnPropertyReturnType::All || return_type == GetOwnPropertyReturnType::StringOnly) {
- for (auto& it : shape().property_table_ordered()) {
- if (only_enumerable_properties && !it.value.attributes.is_enumerable())
- continue;
- if (it.key.is_symbol())
- continue;
- add_property_to_results(it);
- if (vm().exception())
- return MarkedValueList { heap() };
- }
- }
- if (return_type == GetOwnPropertyReturnType::All || return_type == GetOwnPropertyReturnType::SymbolOnly) {
- for (auto& it : shape().property_table_ordered()) {
- if (only_enumerable_properties && !it.value.attributes.is_enumerable())
- continue;
- if (it.key.is_string())
- continue;
- add_property_to_results(it);
- if (vm().exception())
- return MarkedValueList { heap() };
+ // ii. Let entry be ! CreateArrayFromList(« key, value »).
+ auto entry = Array::create_from(global_object, { key, value });
+
+ // iii. Append entry to properties.
+ properties.append(entry);
}
}
+ // 5. Return properties.
return properties;
}
-// 7.3.23 EnumerableOwnPropertyNames ( O, kind ), https://tc39.es/ecma262/#sec-enumerableownpropertynames
-MarkedValueList Object::get_enumerable_own_property_names(PropertyKind kind) const
+// 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
+
+// 10.1.1 [[GetPrototypeOf]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
+Object* Object::internal_get_prototype_of() const
{
- return get_own_properties(kind, true, Object::GetOwnPropertyReturnType::StringOnly);
+ // 1. Return O.[[Prototype]].
+ return const_cast<Object*>(prototype());
}
-Optional<PropertyDescriptor> Object::get_own_property_descriptor(const PropertyName& property_name) const
+// 10.1.2 [[SetPrototypeOf]] ( V ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v
+bool Object::internal_set_prototype_of(Object* new_prototype)
{
- VERIFY(property_name.is_valid());
+ // 1. Assert: Either Type(V) is Object or Type(V) is Null.
- Value value;
- PropertyAttributes attributes;
-
- if (property_name.is_number()) {
- auto existing_value = m_indexed_properties.get(nullptr, property_name.as_number(), AllowSideEffects::No);
- if (!existing_value.has_value())
- return {};
- value = existing_value.value().value;
- attributes = existing_value.value().attributes;
- } else {
- auto metadata = shape().lookup(property_name.to_string_or_symbol());
- if (!metadata.has_value())
- return {};
- value = m_storage[metadata.value().offset];
- attributes = metadata.value().attributes;
- }
+ // 2. Let current be O.[[Prototype]].
+ // 3. If SameValue(V, current) is true, return true.
+ if (prototype() == new_prototype)
+ return true;
- PropertyDescriptor descriptor { attributes, {}, nullptr, nullptr };
- if (value.is_native_property()) {
- auto result = call_native_property_getter(value.as_native_property(), const_cast<Object*>(this));
- descriptor.value = result.value_or(js_undefined());
- } else if (value.is_accessor()) {
- auto& pair = value.as_accessor();
- if (pair.getter())
- descriptor.getter = pair.getter();
- if (pair.setter())
- descriptor.setter = pair.setter();
- } else {
- descriptor.value = value.value_or(js_undefined());
- }
+ // 4. Let extensible be O.[[Extensible]].
+ // 5. If extensible is false, return false.
+ if (!m_is_extensible)
+ return false;
- return descriptor;
-}
+ // 6. Let p be V.
+ auto* prototype = new_prototype;
-// Equivalent to:
-// 6.2.5.4 FromPropertyDescriptor ( Desc ), https://tc39.es/ecma262/#sec-frompropertydescriptor
-Value Object::get_own_property_descriptor_object(const PropertyName& property_name) const
-{
- VERIFY(property_name.is_valid());
+ // 7. Let done be false.
+ // 8. Repeat, while done is false,
+ while (prototype) {
+ // a. If p is null, set done to true.
- auto& vm = this->vm();
- auto& global_object = this->global_object();
+ // b. Else if SameValue(p, O) is true, return false.
+ if (prototype == this)
+ return false;
+ // c. Else,
- auto descriptor_opt = get_own_property_descriptor(property_name);
- if (!descriptor_opt.has_value())
- return js_undefined();
- auto descriptor = descriptor_opt.value();
+ // i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1, set done to true.
+ // NOTE: This is a best-effort implementation; we don't have a good way of detecting whether certain virtual
+ // Object methods have been overridden by a given object, but as ProxyObject is the only one doing that for
+ // [[SetPrototypeOf]], this check does the trick.
+ if (is<ProxyObject>(prototype))
+ break;
- auto* descriptor_object = Object::create(global_object, global_object.object_prototype());
- if (descriptor.is_data_descriptor()) {
- descriptor_object->define_property(vm.names.value, descriptor.value.value_or(js_undefined()));
- descriptor_object->define_property(vm.names.writable, Value(descriptor.attributes.is_writable()));
+ // ii. Else, set p to p.[[Prototype]].
+ prototype = prototype->prototype();
}
- if (descriptor.is_accessor_descriptor()) {
- descriptor_object->define_property(vm.names.get, descriptor.getter ? Value(descriptor.getter) : js_undefined());
- descriptor_object->define_property(vm.names.set, descriptor.setter ? Value(descriptor.setter) : js_undefined());
- }
+ // 9. Set O.[[Prototype]] to V.
+ auto& shape = this->shape();
+ if (shape.is_unique())
+ shape.set_prototype_without_transition(new_prototype);
+ else
+ m_shape = shape.create_prototype_transition(new_prototype);
- descriptor_object->define_property(vm.names.enumerable, Value(descriptor.attributes.is_enumerable()));
- descriptor_object->define_property(vm.names.configurable, Value(descriptor.attributes.is_configurable()));
- return descriptor_object;
+ // 10. Return true.
+ return true;
}
-void Object::set_shape(Shape& new_shape)
+// 10.1.3 [[IsExtensible]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
+bool Object::internal_is_extensible() const
{
- m_storage.resize(new_shape.property_count());
- m_shape = &new_shape;
+ // 1. Return O.[[Extensible]].
+ return m_is_extensible;
}
-bool Object::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions)
+// 10.1.4 [[PreventExtensions]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
+bool Object::internal_prevent_extensions()
{
- auto& vm = this->vm();
- bool is_accessor_property = descriptor.has_property(vm.names.get) || descriptor.has_property(vm.names.set);
- PropertyAttributes attributes;
- if (descriptor.has_property(vm.names.configurable)) {
- attributes.set_has_configurable();
- if (descriptor.get(vm.names.configurable).value_or(Value(false)).to_boolean())
- attributes.set_configurable();
- if (vm.exception())
- return false;
- }
- if (descriptor.has_property(vm.names.enumerable)) {
- attributes.set_has_enumerable();
- if (descriptor.get(vm.names.enumerable).value_or(Value(false)).to_boolean())
- attributes.set_enumerable();
- if (vm.exception())
- return false;
- }
+ // 1. Set O.[[Extensible]] to false.
+ m_is_extensible = false;
- if (is_accessor_property) {
- if (descriptor.has_property(vm.names.value) || descriptor.has_property(vm.names.writable)) {
- if (throw_exceptions)
- vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorValueOrWritable);
- return false;
- }
+ // 2. Return true.
+ return true;
+}
- auto getter = descriptor.get(vm.names.get).value_or(js_undefined());
- if (vm.exception())
- return {};
- auto setter = descriptor.get(vm.names.set).value_or(js_undefined());
- if (vm.exception())
- return {};
+// 10.1.5 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p
+Optional<PropertyDescriptor> Object::internal_get_own_property(PropertyName const& property_name) const
+{
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
- FunctionObject* getter_function { nullptr };
- FunctionObject* setter_function { nullptr };
+ // 2. If O does not have an own property with key P, return undefined.
+ if (!storage_has(property_name))
+ return {};
- // We should only take previous getters for our own object not from any prototype
- auto existing_property = get_own_property(property_name, {}, AllowSideEffects::No).value_or(js_undefined());
+ // 3. Let D be a newly created Property Descriptor with no fields.
+ PropertyDescriptor descriptor;
- if (getter.is_function()) {
- getter_function = &getter.as_function();
- } else if (!getter.is_undefined()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "get");
- return false;
- } else if (existing_property.is_accessor()) {
- // FIXME: This is a hack, since we store Accessor as a getter & setter tuple value, instead of as separate entries in the property
- getter_function = existing_property.as_accessor().getter();
- }
+ // 4. Let X be O's own property whose key is P.
+ auto [value, attributes] = *storage_get(property_name);
- if (setter.is_function()) {
- setter_function = &setter.as_function();
- } else if (!setter.is_undefined()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::AccessorBadField, "set");
- return false;
- } else if (existing_property.is_accessor()) {
- // FIXME: See above
- setter_function = existing_property.as_accessor().setter();
- }
+ // 5. If X is a data property, then
+ if (!value.is_accessor()) {
+ // a. Set D.[[Value]] to the value of X's [[Value]] attribute.
+ descriptor.value = value.value_or(js_undefined());
+
+ // b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
+ descriptor.writable = attributes.is_writable();
+ }
+ // 6. Else,
+ else {
+ // a. Assert: X is an accessor property.
- dbgln_if(OBJECT_DEBUG, "Defining new property {} with accessor descriptor {{ attributes={}, getter={}, setter={} }}", property_name.to_display_string(), attributes, getter, setter);
+ // b. Set D.[[Get]] to the value of X's [[Get]] attribute.
+ descriptor.get = value.as_accessor().getter();
- return define_property(property_name, Accessor::create(vm, getter_function, setter_function), attributes, throw_exceptions);
+ // c. Set D.[[Set]] to the value of X's [[Set]] attribute.
+ descriptor.set = value.as_accessor().setter();
}
- auto value = descriptor.get(vm.names.value);
+ // 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
+ descriptor.enumerable = attributes.is_enumerable();
+
+ // 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
+ descriptor.configurable = attributes.is_configurable();
+
+ // 9. Return D.
+ return descriptor;
+}
+
+// 10.1.6 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc
+bool Object::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor)
+{
+ VERIFY(property_name.is_valid());
+ auto& vm = this->vm();
+
+ // 1. Let current be ? O.[[GetOwnProperty]](P).
+ auto current = internal_get_own_property(property_name);
if (vm.exception())
return {};
- if (descriptor.has_property(vm.names.writable)) {
- attributes.set_has_writable();
- if (descriptor.get(vm.names.writable).value_or(Value(false)).to_boolean())
- attributes.set_writable();
- if (vm.exception())
- return false;
- }
+
+ // 2. Let extensible be ? IsExtensible(O).
+ auto extensible = is_extensible();
if (vm.exception())
return {};
- dbgln_if(OBJECT_DEBUG, "Defining new property {} with data descriptor {{ attributes={}, value={} }}", property_name.to_display_string(), attributes, value);
-
- return define_property(property_name, value, attributes, throw_exceptions);
+ // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current).
+ return validate_and_apply_property_descriptor(this, property_name, extensible, property_descriptor, current);
}
-bool Object::define_property_without_transition(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions)
+// 10.1.7 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p
+bool Object::internal_has_property(PropertyName const& property_name) const
{
- TemporaryChange change(m_transitions_enabled, false);
- return define_property(property_name, value, attributes, throw_exceptions);
-}
+ auto& vm = this->vm();
-bool Object::define_property(const PropertyName& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions)
-{
+ // 1. Assert: IsPropertyKey(P) is true.
VERIFY(property_name.is_valid());
- if (property_name.is_number())
- return put_own_property_by_index(property_name.as_number(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
+ // 2. Let hasOwn be ? O.[[GetOwnProperty]](P).
+ auto has_own = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 3. If hasOwn is not undefined, return true.
+ if (has_own.has_value())
+ return true;
+
+ // 4. Let parent be ? O.[[GetPrototypeOf]]().
+ auto parent = internal_get_prototype_of();
+ if (vm.exception())
+ return {};
+
+ // 5. If parent is not null, then
+ if (parent) {
+ // a. Return ? parent.[[HasProperty]](P).
+ return parent->internal_has_property(property_name);
+ }
- return put_own_property(property_name.to_string_or_symbol(), value, attributes, PutOwnPropertyMode::DefineProperty, throw_exceptions);
+ // 6. Return false.
+ return false;
}
-bool Object::define_native_accessor(PropertyName const& property_name, Function<Value(VM&, GlobalObject&)> getter, Function<Value(VM&, GlobalObject&)> setter, PropertyAttributes attribute)
+// 10.1.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver
+Value Object::internal_get(PropertyName const& property_name, Value receiver) const
{
+ VERIFY(!receiver.is_empty());
auto& vm = this->vm();
- String formatted_property_name;
- if (property_name.is_string()) {
- formatted_property_name = property_name.as_string();
- } else {
- formatted_property_name = String::formatted("[{}]", property_name.as_symbol()->description());
- }
- FunctionObject* getter_function = nullptr;
- if (getter) {
- auto name = String::formatted("get {}", formatted_property_name);
- getter_function = NativeFunction::create(global_object(), name, move(getter));
- getter_function->define_property_without_transition(vm.names.length, Value(0), Attribute::Configurable);
- if (vm.exception())
- return {};
- getter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable);
- if (vm.exception())
- return {};
- }
- FunctionObject* setter_function = nullptr;
- if (setter) {
- auto name = String::formatted("set {}", formatted_property_name);
- setter_function = NativeFunction::create(global_object(), name, move(setter));
- setter_function->define_property_without_transition(vm.names.length, Value(1), Attribute::Configurable);
- if (vm.exception())
- return {};
- setter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable);
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let desc be ? O.[[GetOwnProperty]](P).
+ auto descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 3. If desc is undefined, then
+ if (!descriptor.has_value()) {
+ // a. Let parent be ? O.[[GetPrototypeOf]]().
+ auto parent = internal_get_prototype_of();
if (vm.exception())
return {};
+
+ // b. If parent is null, return undefined.
+ if (!parent)
+ return js_undefined();
+
+ // c. Return ? parent.[[Get]](P, Receiver).
+ return parent->internal_get(property_name, receiver);
}
- return define_accessor(property_name, getter_function, setter_function, attribute);
+
+ // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
+ if (descriptor->is_data_descriptor())
+ return *descriptor->value;
+
+ // 5. Assert: IsAccessorDescriptor(desc) is true.
+ VERIFY(descriptor->is_accessor_descriptor());
+
+ // 6. Let getter be desc.[[Get]].
+ auto* getter = *descriptor->get;
+
+ // 7. If getter is undefined, return undefined.
+ if (!getter)
+ return js_undefined();
+
+ // 8. Return ? Call(getter, Receiver).
+ return vm.call(*getter, receiver);
}
-bool Object::define_accessor(const PropertyName& property_name, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes, bool throw_exceptions)
+static bool ordinary_set_with_own_descriptor(Object&, PropertyName const&, Value, Value, Optional<PropertyDescriptor>);
+
+// 10.1.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
+bool Object::internal_set(PropertyName const& property_name, Value value, Value receiver)
{
+ VERIFY(!value.is_empty());
+ VERIFY(!receiver.is_empty());
+ auto& vm = this->vm();
+
+ // 1. Assert: IsPropertyKey(P) is true.
VERIFY(property_name.is_valid());
- auto existing_property = get_own_property(property_name, this, AllowSideEffects::No);
- auto* accessor = existing_property.is_accessor() ? &existing_property.as_accessor() : nullptr;
- if (!accessor) {
- accessor = Accessor::create(vm(), getter, setter);
- bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions);
- if (vm().exception())
- return {};
- if (!definition_success)
- return false;
- } else {
- if (getter)
- accessor->set_getter(getter);
- if (setter)
- accessor->set_setter(setter);
- }
- return true;
+ // 2. Let ownDesc be ? O.[[GetOwnProperty]](P).
+ auto own_descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 3. Return OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
+ return ordinary_set_with_own_descriptor(*this, property_name, value, receiver, own_descriptor);
}
-bool Object::put_own_property(const StringOrSymbol& property_name, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions)
+// 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ), https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor
+bool ordinary_set_with_own_descriptor(Object& object, PropertyName const& property_name, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor)
{
- VERIFY(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
-
- if (value.is_accessor()) {
- auto& accessor = value.as_accessor();
- if (accessor.getter())
- attributes.set_has_getter();
- if (accessor.setter())
- attributes.set_has_setter();
- }
+ auto& vm = object.vm();
- // NOTE: We disable transitions during initialize(), this makes building common runtime objects significantly faster.
- // Transitions are primarily interesting when scripts add properties to objects.
- if (!m_transitions_enabled && !m_shape->is_unique()) {
- m_shape->add_property_without_transition(property_name, attributes);
- m_storage.resize(m_shape->property_count());
- m_storage[m_shape->property_count() - 1] = value;
- return true;
- }
-
- auto metadata = shape().lookup(property_name);
- bool new_property = !metadata.has_value();
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
- if (!is_extensible() && new_property) {
- dbgln_if(OBJECT_DEBUG, "Disallow define_property of non-extensible object");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_name.to_display_string());
- return false;
- }
+ // 2. If ownDesc is undefined, then
+ if (!own_descriptor.has_value()) {
+ // a. Let parent be ? O.[[GetPrototypeOf]]().
+ auto parent = object.internal_get_prototype_of();
+ if (vm.exception())
+ return {};
- 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
- // transitions to avoid filling up the heap with shapes.
- ensure_shape_is_unique();
+ // b. If parent is not null, then
+ if (parent) {
+ // i. Return ? parent.[[Set]](P, V, Receiver).
+ return parent->internal_set(property_name, value, receiver);
}
-
- if (m_shape->is_unique()) {
- m_shape->add_property_to_unique_shape(property_name, attributes);
- m_storage.resize(m_shape->property_count());
- } else if (m_transitions_enabled) {
- set_shape(*m_shape->create_put_transition(property_name, attributes));
- } else {
- m_shape->add_property_without_transition(property_name, attributes);
- m_storage.resize(m_shape->property_count());
+ // c. Else,
+ else {
+ // i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
+ own_descriptor = PropertyDescriptor {
+ .value = js_undefined(),
+ .writable = true,
+ .enumerable = true,
+ .configurable = true,
+ };
}
- metadata = shape().lookup(property_name);
- VERIFY(metadata.has_value());
}
- auto value_here = m_storage[metadata.value().offset];
- if (!new_property && mode == PutOwnPropertyMode::DefineProperty && !metadata.value().attributes.is_configurable()) {
- if ((attributes.has_configurable() && attributes.is_configurable()) || (attributes.has_enumerable() && attributes.is_enumerable() != metadata.value().attributes.is_enumerable())) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string());
+ // 3. If IsDataDescriptor(ownDesc) is true, then
+ if (own_descriptor->is_data_descriptor()) {
+ // a. If ownDesc.[[Writable]] is false, return false.
+ if (!*own_descriptor->writable)
return false;
- }
- if (value_here.is_accessor() != value.is_accessor()) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string());
+ // b. If Type(Receiver) is not Object, return false.
+ if (!receiver.is_object())
return false;
- }
- if (!value_here.is_accessor() && !metadata.value().attributes.is_writable() && ((attributes.has_writable() && attributes.is_writable()) || (!value.is_empty() && !same_value(value, value_here)))) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string());
- return false;
- }
+ // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
+ auto existing_descriptor = receiver.as_object().internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
- if (value_here.is_accessor() && ((attributes.has_setter() && value.as_accessor().setter() != value_here.as_accessor().setter()) || (attributes.has_getter() && value.as_accessor().getter() != value_here.as_accessor().getter()))) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_display_string());
- return false;
- }
- }
+ // d. If existingDescriptor is not undefined, then
+ if (existing_descriptor.has_value()) {
+ // i. If IsAccessorDescriptor(existingDescriptor) is true, return false.
+ if (existing_descriptor->is_accessor_descriptor())
+ return false;
- // FIXME: Instead of adding back existing attributes we should just stop overwriting them
- if (!attributes.has_configurable() && metadata.value().attributes.is_configurable()) {
- attributes.set_has_configurable();
- attributes.set_configurable();
- }
- if (!attributes.has_enumerable() && metadata.value().attributes.is_enumerable()) {
- attributes.set_has_enumerable();
- attributes.set_enumerable();
- }
- if (!value.is_accessor() && !attributes.has_writable() && metadata.value().attributes.is_writable()) {
- attributes.set_has_writable();
- attributes.set_writable();
- }
+ // ii. If existingDescriptor.[[Writable]] is false, return false.
+ if (!*existing_descriptor->writable)
+ return false;
- if (mode == PutOwnPropertyMode::DefineProperty && attributes != metadata.value().attributes) {
- 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));
+ // iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
+ auto value_descriptor = PropertyDescriptor { .value = value };
+
+ // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
+ return receiver.as_object().internal_define_own_property(property_name, value_descriptor);
}
- metadata = shape().lookup(property_name);
+ // e. Else,
+ else {
+ // i. Assert: Receiver does not currently have a property P.
+ VERIFY(!receiver.as_object().storage_has(property_name));
- dbgln_if(OBJECT_DEBUG, "Reconfigured property {}, new shape says offset is {} and my storage capacity is {}", property_name.to_display_string(), metadata.value().offset, m_storage.size());
+ // ii. Return ? CreateDataProperty(Receiver, P, V).
+ return receiver.as_object().create_data_property(property_name, value);
+ }
}
- if (!new_property && mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !metadata.value().attributes.is_writable()) {
- dbgln_if(OBJECT_DEBUG, "Disallow write to non-writable property");
- if (throw_exceptions && vm().in_strict_mode())
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescWriteNonWritable, property_name.to_display_string());
+ // 4. Assert: IsAccessorDescriptor(ownDesc) is true.
+ VERIFY(own_descriptor->is_accessor_descriptor());
+
+ // 5. Let setter be ownDesc.[[Set]].
+ auto* setter = *own_descriptor->set;
+
+ // 6. If setter is undefined, return false.
+ if (!setter)
return false;
- }
- if (value.is_empty())
- return true;
+ // 7. Perform ? Call(setter, Receiver, « V »).
+ (void)vm.call(*setter, receiver, value);
- if (value_here.is_native_property()) {
- call_native_property_setter(value_here.as_native_property(), this, value);
- } else {
- m_storage[metadata.value().offset] = value;
- }
+ // 8. Return true.
return true;
}
-bool Object::put_own_property_by_index(u32 property_index, Value value, PropertyAttributes attributes, PutOwnPropertyMode mode, bool throw_exceptions)
+// 10.1.10 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p
+bool Object::internal_delete(PropertyName const& property_name)
{
- VERIFY(!(mode == PutOwnPropertyMode::Put && value.is_accessor()));
-
- auto existing_property = m_indexed_properties.get(nullptr, property_index, AllowSideEffects::No);
- auto new_property = !existing_property.has_value();
+ auto& vm = this->vm();
- if (!is_extensible() && new_property) {
- dbgln_if(OBJECT_DEBUG, "Disallow define_property of non-extensible object");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_index);
- return false;
- }
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
- if (value.is_accessor()) {
- auto& accessor = value.as_accessor();
- if (accessor.getter())
- attributes.set_has_getter();
- if (accessor.setter())
- attributes.set_has_setter();
- }
+ // 2. Let desc be ? O.[[GetOwnProperty]](P).
+ auto descriptor = internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
- if (new_property) {
- if (!is_extensible()) {
- dbgln_if(OBJECT_DEBUG, "Disallow define_property of non-extensible object");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::NonExtensibleDefine, property_index);
- return false;
- }
+ // 3. If desc is undefined, return true.
+ if (!descriptor.has_value())
+ return true;
- m_indexed_properties.put(this, property_index, value, attributes, mode == PutOwnPropertyMode::Put ? AllowSideEffects::Yes : AllowSideEffects::No);
+ // 4. If desc.[[Configurable]] is true, then
+ if (*descriptor->configurable) {
+ // a. Remove the own property with name P from O.
+ storage_delete(property_name);
+ // b. Return true.
return true;
}
- if (attributes == 0 && value.is_empty())
- return true;
-
- PropertyAttributes existing_attributes = existing_property.value().attributes;
- auto value_here = existing_property.value().value;
+ // 5. Return false.
+ return false;
+}
- if (mode == PutOwnPropertyMode::DefineProperty && !existing_attributes.is_configurable()) {
- if ((attributes.has_configurable() && attributes.is_configurable()) || (attributes.has_enumerable() && attributes.is_enumerable() != existing_attributes.is_enumerable())) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index);
- return false;
- }
+// 10.1.11 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
+MarkedValueList Object::internal_own_property_keys() const
+{
+ auto& vm = this->vm();
- if (value_here.is_accessor() != value.is_accessor()) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index);
- return false;
- }
+ // 1. Let keys be a new empty List.
+ MarkedValueList keys { heap() };
- if (!value_here.is_accessor() && !existing_attributes.is_writable() && ((attributes.has_writable() && attributes.is_writable()) || (!value.is_empty() && !same_value(value, value_here)))) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index);
- return false;
- }
+ // 2. For each own property key P of O such that P is an array index, in ascending numeric index order, do
+ for (auto& entry : m_indexed_properties) {
+ // a. Add P as the last element of keys.
+ keys.append(js_string(vm, String::number(entry.index())));
+ }
- if (value_here.is_accessor() && ((attributes.has_setter() && value.as_accessor().setter() != value_here.as_accessor().setter()) || (attributes.has_getter() && value.as_accessor().getter() != value_here.as_accessor().getter()))) {
- dbgln_if(OBJECT_DEBUG, "Disallow reconfig of non-configurable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_index);
- return false;
+ // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
+ for (auto& it : shape().property_table_ordered()) {
+ if (it.key.is_string()) {
+ // a. Add P as the last element of keys.
+ keys.append(it.key.to_value(vm));
}
}
- if (mode == PutOwnPropertyMode::Put && !value_here.is_accessor() && !existing_attributes.is_writable()) {
- dbgln_if(OBJECT_DEBUG, "Disallow write to non-writable property");
- if (throw_exceptions)
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescWriteNonWritable, property_index);
- return false;
+ // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
+ for (auto& it : shape().property_table_ordered()) {
+ if (it.key.is_symbol()) {
+ // a. Add P as the last element of keys.
+ keys.append(it.key.to_value(vm));
+ }
}
- PropertyAttributes combined_attributes = existing_attributes.overwrite(attributes);
+ // 5. Return keys.
+ return keys;
+}
- if (value.is_empty()) {
- if (combined_attributes == existing_attributes) {
- return true;
- }
- value = value_here.value_or(js_undefined());
- }
+Optional<ValueAndAttributes> Object::storage_get(PropertyName const& property_name, CallNativeProperty call_native_property) const
+{
+ VERIFY(property_name.is_valid());
- attributes = combined_attributes;
+ Value value;
+ PropertyAttributes attributes;
- if (value_here.is_native_property()) {
- call_native_property_setter(value_here.as_native_property(), this, value);
+ if (property_name.is_number()) {
+ auto value_and_attributes = m_indexed_properties.get(property_name.as_number());
+ if (!value_and_attributes.has_value())
+ return {};
+ value = value_and_attributes->value;
+ attributes = value_and_attributes->attributes;
} else {
- m_indexed_properties.put(this, property_index, value, attributes, mode == PutOwnPropertyMode::Put ? AllowSideEffects::Yes : AllowSideEffects::No);
+ auto metadata = shape().lookup(property_name.to_string_or_symbol());
+ if (!metadata.has_value())
+ return {};
+ value = m_storage[metadata->offset];
+ attributes = metadata->attributes;
}
- return true;
+ if (value.is_native_property() && call_native_property == CallNativeProperty::Yes)
+ value = call_native_property_getter(value.as_native_property(), this);
+ return ValueAndAttributes { .value = value, .attributes = attributes };
+}
+
+bool Object::storage_has(PropertyName const& property_name) const
+{
+ VERIFY(property_name.is_valid());
+ if (property_name.is_number())
+ return m_indexed_properties.has_index(property_name.as_number());
+ return shape().lookup(property_name.to_string_or_symbol()).has_value();
}
-bool Object::delete_property(PropertyName const& property_name, bool force_throw_exception)
+void Object::storage_set(PropertyName const& property_name, ValueAndAttributes const& value_and_attributes)
{
VERIFY(property_name.is_valid());
+ auto [value, attributes] = value_and_attributes;
+
if (property_name.is_number()) {
- if (!m_indexed_properties.remove(property_name.as_number())) {
- if (force_throw_exception || vm().in_strict_mode())
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.as_number());
- return false;
+ auto index = property_name.as_number();
+ if (value.is_native_property()) {
+ m_indexed_properties.put(index, value, attributes);
+ } else {
+ auto existing_value = m_indexed_properties.get(index);
+ if (existing_value.has_value() && existing_value->value.is_native_property())
+ call_native_property_setter(existing_value->value.as_native_property(), this, value);
+ else
+ m_indexed_properties.put(index, value, attributes);
}
- return true;
+ return;
}
- auto metadata = shape().lookup(property_name.to_string_or_symbol());
- if (!metadata.has_value())
- return true;
- if (!metadata.value().attributes.is_configurable()) {
- if (force_throw_exception || vm().in_strict_mode())
- vm().throw_exception<TypeError>(global_object(), ErrorType::DescChangeNonConfigurable, property_name.to_string_or_symbol().to_display_string());
- return false;
+ // NOTE: We disable transitions during initialize(), this makes building common runtime objects significantly faster.
+ // Transitions are primarily interesting when scripts add properties to objects.
+ if (!m_transitions_enabled && !m_shape->is_unique()) {
+ m_shape->add_property_without_transition(property_name, attributes);
+ m_storage.resize(m_shape->property_count());
+ m_storage[m_shape->property_count() - 1] = value;
+ return;
}
- size_t deleted_offset = metadata.value().offset;
-
- ensure_shape_is_unique();
+ auto property_name_string_or_symbol = property_name.to_string_or_symbol();
+ auto metadata = shape().lookup(property_name_string_or_symbol);
- shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), deleted_offset);
- m_storage.remove(deleted_offset);
- return true;
-}
-
-void Object::ensure_shape_is_unique()
-{
- if (shape().is_unique())
- return;
+ if (!metadata.has_value()) {
+ if (!m_shape->is_unique() && shape().property_count() > 100) {
+ // If you add more than 100 properties to an object, let's stop doing
+ // transitions to avoid filling up the heap with shapes.
+ ensure_shape_is_unique();
+ }
- m_shape = m_shape->create_unique_clone();
-}
+ if (m_shape->is_unique()) {
+ m_shape->add_property_to_unique_shape(property_name_string_or_symbol, attributes);
+ m_storage.resize(m_shape->property_count());
+ } else if (m_transitions_enabled) {
+ set_shape(*m_shape->create_put_transition(property_name_string_or_symbol, attributes));
+ } else {
+ m_shape->add_property_without_transition(property_name, attributes);
+ m_storage.resize(m_shape->property_count());
+ }
+ metadata = shape().lookup(property_name_string_or_symbol);
+ VERIFY(metadata.has_value());
+ }
-Value Object::get_by_index(u32 property_index, AllowSideEffects allow_side_effects) const
-{
- const Object* object = this;
- while (object) {
- if (is<StringObject>(*object)) {
- auto& string = static_cast<const StringObject&>(*object).primitive_string().string();
- if (property_index < string.length())
- return js_string(heap(), string.substring(property_index, 1));
- } else if (static_cast<size_t>(property_index) < object->m_indexed_properties.array_like_size()) {
- auto result = object->m_indexed_properties.get(const_cast<Object*>(this), property_index, allow_side_effects);
- if (vm().exception())
- return {};
- if (result.has_value() && !result.value().value.is_empty())
- return result.value().value;
+ if (attributes != metadata->attributes) {
+ if (m_shape->is_unique()) {
+ m_shape->reconfigure_property_in_unique_shape(property_name_string_or_symbol, attributes);
+ } else {
+ set_shape(*m_shape->create_configure_transition(property_name_string_or_symbol, attributes));
}
- object = object->prototype();
- if (vm().exception())
- return {};
+ metadata = shape().lookup(property_name_string_or_symbol);
+ VERIFY(metadata.has_value());
+ }
+
+ if (value.is_native_property()) {
+ m_storage[metadata->offset] = value;
+ } else {
+ auto existing_value = m_storage[metadata->offset];
+ if (existing_value.is_native_property())
+ call_native_property_setter(existing_value.as_native_property(), this, value);
+ else
+ m_storage[metadata->offset] = value;
}
- return {};
}
-Value Object::get(const PropertyName& property_name, Value receiver, AllowSideEffects allow_side_effects) const
+void Object::storage_delete(PropertyName const& property_name)
{
VERIFY(property_name.is_valid());
+ VERIFY(storage_has(property_name));
if (property_name.is_number())
- return get_by_index(property_name.as_number(), allow_side_effects);
+ return m_indexed_properties.remove(property_name.as_number());
- if (receiver.is_empty())
- receiver = Value(this);
+ auto metadata = shape().lookup(property_name.to_string_or_symbol());
+ VERIFY(metadata.has_value());
- const Object* object = this;
- while (object) {
- auto value = object->get_own_property(property_name, receiver, allow_side_effects);
- if (vm().exception())
+ ensure_shape_is_unique();
+
+ shape().remove_property_from_unique_shape(property_name.to_string_or_symbol(), metadata->offset);
+ m_storage.remove(metadata->offset);
+}
+
+void Object::set_shape(Shape& new_shape)
+{
+ m_storage.resize(new_shape.property_count());
+ m_shape = &new_shape;
+}
+
+bool Object::define_native_accessor(PropertyName const& property_name, Function<Value(VM&, GlobalObject&)> getter, Function<Value(VM&, GlobalObject&)> setter, PropertyAttributes attribute)
+{
+ auto& vm = this->vm();
+ String formatted_property_name;
+ if (property_name.is_string()) {
+ formatted_property_name = property_name.as_string();
+ } else {
+ formatted_property_name = String::formatted("[{}]", property_name.as_symbol()->description());
+ }
+ FunctionObject* getter_function = nullptr;
+ if (getter) {
+ auto name = String::formatted("get {}", formatted_property_name);
+ getter_function = NativeFunction::create(global_object(), name, move(getter));
+ getter_function->define_property_without_transition(vm.names.length, Value(0), Attribute::Configurable);
+ if (vm.exception())
return {};
- if (!value.is_empty())
- return value;
- object = object->prototype();
- if (vm().exception())
+ getter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable);
+ if (vm.exception())
return {};
}
- return {};
+ FunctionObject* setter_function = nullptr;
+ if (setter) {
+ auto name = String::formatted("set {}", formatted_property_name);
+ setter_function = NativeFunction::create(global_object(), name, move(setter));
+ setter_function->define_property_without_transition(vm.names.length, Value(1), Attribute::Configurable);
+ if (vm.exception())
+ return {};
+ setter_function->define_property_without_transition(vm.names.name, js_string(vm.heap(), name), Attribute::Configurable);
+ if (vm.exception())
+ return {};
+ }
+ return define_accessor(property_name, getter_function, setter_function, attribute);
}
-Value Object::get_without_side_effects(const PropertyName& property_name) const
+bool Object::define_property_without_transition(PropertyName const& property_name, Value value, PropertyAttributes attributes, bool throw_exceptions)
{
- TemporaryClearException clear_exception(vm());
- return get(property_name, {}, AllowSideEffects::No);
+ TemporaryChange change(m_transitions_enabled, false);
+ return define_property(property_name, value, attributes, throw_exceptions);
}
-bool Object::put_by_index(u32 property_index, Value value)
+bool Object::define_accessor(PropertyName const& property_name, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes, bool throw_exceptions)
{
- VERIFY(!value.is_empty());
+ VERIFY(property_name.is_valid());
- // 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 existing_value = object->m_indexed_properties.get(nullptr, property_index, AllowSideEffects::No);
- if (existing_value.has_value()) {
- auto value_here = existing_value.value();
- if (value_here.value.is_accessor()) {
- value_here.value.as_accessor().call_setter(object, value);
- return true;
- }
- if (value_here.value.is_native_property()) {
- // FIXME: Why doesn't put_by_index() receive the receiver value from put()?!
- auto receiver = this;
- call_native_property_setter(value_here.value.as_native_property(), receiver, value);
- return true;
- }
- }
- object = object->prototype();
+ auto existing_property = storage_get(property_name).value_or({}).value;
+ auto* accessor = existing_property.is_accessor() ? &existing_property.as_accessor() : nullptr;
+ if (!accessor) {
+ accessor = Accessor::create(vm(), getter, setter);
+ bool definition_success = define_property(property_name, accessor, attributes, throw_exceptions);
if (vm().exception())
return {};
+ if (!definition_success)
+ return false;
+ } else {
+ if (getter)
+ accessor->set_getter(getter);
+ if (setter)
+ accessor->set_setter(setter);
}
- return put_own_property_by_index(property_index, value, default_attributes, PutOwnPropertyMode::Put, vm().in_strict_mode());
+ return true;
}
-bool Object::put(const PropertyName& property_name, Value value, Value receiver)
+void Object::ensure_shape_is_unique()
{
- VERIFY(property_name.is_valid());
-
- if (property_name.is_number())
- return put_by_index(property_name.as_number(), value);
-
- VERIFY(!value.is_empty());
-
- auto string_or_symbol = property_name.to_string_or_symbol();
+ if (shape().is_unique())
+ return;
- if (receiver.is_empty())
- receiver = Value(this);
+ m_shape = m_shape->create_unique_clone();
+}
- // 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;
+// Simple side-effect free property lookup, following the prototype chain. Non-standard.
+Value Object::get_without_side_effects(const PropertyName& property_name) const
+{
+ auto* object = this;
while (object) {
- 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()) {
- value_here.as_accessor().call_setter(receiver, value);
- return true;
- }
- if (value_here.is_native_property()) {
- call_native_property_setter(value_here.as_native_property(), receiver, value);
- return true;
- }
- }
+ auto value_and_attributes = object->storage_get(property_name, CallNativeProperty::No);
+ if (value_and_attributes.has_value())
+ return value_and_attributes->value;
object = object->prototype();
- if (vm().exception())
- return false;
}
- return put_own_property(string_or_symbol, value, default_attributes, PutOwnPropertyMode::Put, vm().in_strict_mode());
+ return {};
}
bool Object::define_native_function(PropertyName const& property_name, Function<Value(VM&, GlobalObject&)> native_function, i32 length, PropertyAttributes attribute)
@@ -1015,46 +1107,70 @@ bool Object::define_native_property(PropertyName const& property_name, Function<
}
// 20.1.2.3.1 ObjectDefineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-objectdefineproperties
-void Object::define_properties(Value properties)
+Object* Object::define_properties(Value properties)
{
auto& vm = this->vm();
- auto* props = properties.to_object(global_object());
- if (!props)
- return;
- auto keys = props->get_own_properties(PropertyKind::Key);
+ auto& global_object = this->global_object();
+
+ // 1. Assert: Type(O) is Object.
+
+ // 2. Let props be ? ToObject(Properties).
+ auto* props = properties.to_object(global_object);
if (vm.exception())
- return;
+ return {};
+
+ // 3. Let keys be ? props.[[OwnPropertyKeys]]().
+ auto keys = props->internal_own_property_keys();
+ if (vm.exception())
+ return {};
+
struct NameAndDescriptor {
PropertyName name;
PropertyDescriptor descriptor;
};
+
+ // 4. Let descriptors be a new empty List.
Vector<NameAndDescriptor> descriptors;
- for (auto& key : keys) {
- auto property_name = PropertyName::from_value(global_object(), key);
- auto property_descriptor = props->get_own_property_descriptor(property_name);
- if (property_descriptor.has_value() && property_descriptor->attributes.is_enumerable()) {
+
+ // 5. For each element nextKey of keys, do
+ for (auto& next_key : keys) {
+ auto property_name = PropertyName::from_value(global_object, next_key);
+
+ // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
+ auto property_descriptor = props->internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
+ if (property_descriptor.has_value() && *property_descriptor->enumerable) {
+ // i. Let descObj be ? Get(props, nextKey).
auto descriptor_object = props->get(property_name);
if (vm.exception())
- return;
- if (!descriptor_object.is_object()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::NotAnObject, descriptor_object.to_string_without_side_effects());
- return;
- }
- auto descriptor = PropertyDescriptor::from_dictionary(vm, descriptor_object.as_object());
+ return {};
+
+ // ii. Let desc be ? ToPropertyDescriptor(descObj).
+ auto descriptor = to_property_descriptor(global_object, descriptor_object);
if (vm.exception())
- return;
+ return {};
+
+ // iii. Append the pair (a two element List) consisting of nextKey and desc to the end of descriptors.
descriptors.append({ property_name, descriptor });
}
}
+
+ // 6. For each element pair of descriptors, do
for (auto& [name, descriptor] : descriptors) {
- // FIXME: The spec has both of this handled by DefinePropertyOrThrow(O, P, desc).
- // We should invest some time in improving object property handling, it not being
- // super close to the spec makes this and other things unnecessarily complicated.
- if (descriptor.is_accessor_descriptor())
- define_accessor(name, descriptor.getter, descriptor.setter, descriptor.attributes);
- else
- define_property(name, descriptor.value, descriptor.attributes);
+ // a. Let P be the first element of pair.
+ // b. Let desc be the second element of pair.
+
+ // c. Perform ? DefinePropertyOrThrow(O, P, desc).
+ define_property_or_throw(name, descriptor);
+ if (vm.exception())
+ return {};
}
+
+ // 7. Return O.
+ return this;
}
void Object::visit_edges(Cell::Visitor& visitor)
@@ -1070,46 +1186,18 @@ void Object::visit_edges(Cell::Visitor& visitor)
});
}
-bool Object::has_property(const PropertyName& property_name) const
-{
- const Object* object = this;
- while (object) {
- if (object->has_own_property(property_name))
- return true;
- object = object->prototype();
- if (vm().exception())
- return false;
- }
- return false;
-}
-
-bool Object::has_own_property(const PropertyName& property_name) const
-{
- VERIFY(property_name.is_valid());
-
- auto has_indexed_property = [&](u32 index) -> bool {
- if (is<StringObject>(*this))
- return index < static_cast<const StringObject*>(this)->primitive_string().string().length();
- return m_indexed_properties.has_index(index);
- };
-
- if (property_name.is_number())
- return has_indexed_property(property_name.as_number());
-
- return shape().lookup(property_name.to_string_or_symbol()).has_value();
-}
-
+// 7.1.1.1 OrdinaryToPrimitive ( O, hint ), https://tc39.es/ecma262/#sec-ordinarytoprimitive
Value Object::ordinary_to_primitive(Value::PreferredType preferred_type) const
{
VERIFY(preferred_type == Value::PreferredType::String || preferred_type == Value::PreferredType::Number);
auto& vm = this->vm();
- Vector<FlyString, 2> method_names;
+ AK::Array<PropertyName, 2> method_names;
if (preferred_type == Value::PreferredType::String)
- method_names = { vm.names.toString.as_string(), vm.names.valueOf.as_string() };
+ method_names = { vm.names.toString, vm.names.valueOf };
else
- method_names = { vm.names.valueOf.as_string(), vm.names.toString.as_string() };
+ method_names = { vm.names.valueOf, vm.names.toString };
for (auto& method_name : method_names) {
auto method = get(method_name);
diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h
index c959f92cb0..6659a886f1 100644
--- a/Userland/Libraries/LibJS/Runtime/Object.h
+++ b/Userland/Libraries/LibJS/Runtime/Object.h
@@ -14,6 +14,7 @@
#include <LibJS/Runtime/IndexedProperties.h>
#include <LibJS/Runtime/MarkedValueList.h>
#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/Value.h>
@@ -25,19 +26,6 @@ public: \
using Base = base_class; \
virtual const char* class_name() const override { return #class_; }
-struct PropertyDescriptor {
- PropertyAttributes attributes;
- Value value;
- FunctionObject* getter { nullptr };
- FunctionObject* setter { nullptr };
-
- static PropertyDescriptor from_dictionary(VM&, const Object&);
-
- bool is_accessor_descriptor() const { return getter || setter; }
- bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); }
- bool is_generic_descriptor() const { return !is_accessor_descriptor() && !is_data_descriptor(); }
-};
-
class Object : public Cell {
public:
static Object* create(GlobalObject&, Object* prototype);
@@ -53,12 +41,6 @@ public:
KeyAndValue,
};
- enum class GetOwnPropertyReturnType {
- All,
- StringOnly,
- SymbolOnly,
- };
-
enum class PutOwnPropertyMode {
Put,
DefineProperty,
@@ -69,27 +51,84 @@ public:
Frozen,
};
- Shape& shape() { return *m_shape; }
- const Shape& shape() const { return *m_shape; }
+ // Please DO NOT make up your own non-standard methods unless you
+ // have a very good reason to do so. If any object abstract
+ // operation from the spec is missing, add it instead.
+ // Functionality for implementation details like shapes and
+ // property storage are obviously exempt from this rule :^)
+ //
+ // Methods named [[Foo]]() in the spec are named internal_foo()
+ // here, as they are "The [[Foo]] internal method of a ... object".
+ // They must be virtual and may be overridden. All other methods
+ // follow the the regular PascalCase name converted to camel_case
+ // naming convention and must not be virtual.
+
+ // 7.1 Type Conversion, https://tc39.es/ecma262/#sec-type-conversion
+
+ Value ordinary_to_primitive(Value::PreferredType preferred_type) const;
+
+ // 7.2 Testing and Comparison Operations, https://tc39.es/ecma262/#sec-testing-and-comparison-operations
+
+ bool is_extensible() const;
+
+ // 7.3 Operations on Objects, https://tc39.es/ecma262/#sec-operations-on-objects
+
+ Value get(PropertyName const&) const;
+ bool set(PropertyName const&, Value, bool throw_exceptions);
+ bool create_data_property(PropertyName const&, Value);
+ bool create_method_property(PropertyName const&, Value);
+ bool create_data_property_or_throw(PropertyName const&, Value);
+ bool define_property_or_throw(PropertyName const&, PropertyDescriptor const&);
+ bool delete_property_or_throw(PropertyName const&);
+ bool has_property(PropertyName const&) const;
+ bool has_own_property(PropertyName const&) const;
+ bool set_integrity_level(IntegrityLevel);
+ bool test_integrity_level(IntegrityLevel) const;
+ MarkedValueList enumerable_own_property_names(PropertyKind kind) const;
- GlobalObject& global_object() const { return *shape().global_object(); }
+ // 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
- virtual Value get(const PropertyName&, Value receiver = {}, AllowSideEffects = AllowSideEffects::Yes) const;
- Value get_without_side_effects(const PropertyName&) const;
+ virtual Object* internal_get_prototype_of() const;
+ virtual bool internal_set_prototype_of(Object* prototype);
+ virtual bool internal_is_extensible() const;
+ virtual bool internal_prevent_extensions();
+ virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const&) const;
+ virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor const&);
+ virtual bool internal_has_property(PropertyName const&) const;
+ virtual Value internal_get(PropertyName const&, Value receiver) const;
+ virtual bool internal_set(PropertyName const&, Value value, Value receiver);
+ virtual bool internal_delete(PropertyName const&);
+ virtual MarkedValueList internal_own_property_keys() const;
+
+ // 20.1 Object Objects, https://tc39.es/ecma262/#sec-object-objects
+
+ Object* define_properties(Value properties);
+
+ // Implementation-specific storage abstractions
- virtual bool has_property(const PropertyName&) const;
- bool has_own_property(const PropertyName&) const;
+ enum class CallNativeProperty {
+ Yes,
+ No,
+ };
+
+ Optional<ValueAndAttributes> storage_get(PropertyName const&, CallNativeProperty = CallNativeProperty::Yes) const;
+ bool storage_has(PropertyName const&) const;
+ void storage_set(PropertyName const&, ValueAndAttributes const&);
+ void storage_delete(PropertyName const&);
- virtual bool put(const PropertyName&, Value, Value receiver = {});
+ // Non-standard methods
+
+ // - Helpers using old, non-standard names but wrapping the standard methods.
+ // FIXME: Update all the code relying on these and remove them.
+ bool put(PropertyName const& property_name, Value value, Value receiver = {}) { return internal_set(property_name, value, receiver.value_or(this)); }
+ Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName const& property_name) const { return internal_get_own_property(property_name); }
+ bool define_property(PropertyName const& property_name, Value value, PropertyAttributes attributes = default_attributes, bool = true)
+ {
+ return internal_define_own_property(property_name, { .value = value, .writable = attributes.is_writable(), .enumerable = attributes.is_enumerable(), .configurable = attributes.is_configurable() });
+ };
- Value get_own_property(const PropertyName&, Value receiver, AllowSideEffects = AllowSideEffects::Yes) const;
- MarkedValueList get_own_properties(PropertyKind, bool only_enumerable_properties = false, GetOwnPropertyReturnType = GetOwnPropertyReturnType::All) const;
- MarkedValueList get_enumerable_own_property_names(PropertyKind) const;
- virtual Optional<PropertyDescriptor> get_own_property_descriptor(const PropertyName&) const;
- Value get_own_property_descriptor_object(const PropertyName&) const;
+ Value get_without_side_effects(const PropertyName&) const;
- 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_property_without_transition(const PropertyName&, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true);
bool define_accessor(const PropertyName&, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true);
@@ -97,10 +136,6 @@ public:
bool define_native_property(PropertyName const&, Function<Value(VM&, GlobalObject&)> getter, Function<void(VM&, GlobalObject&, Value)> setter, PropertyAttributes attributes = default_attributes);
bool define_native_accessor(PropertyName const&, Function<Value(VM&, GlobalObject&)> getter, Function<Value(VM&, GlobalObject&)> setter, PropertyAttributes attributes = default_attributes);
- void define_properties(Value properties);
-
- virtual bool delete_property(PropertyName const&, bool force_throw_exception = false);
-
virtual bool is_array() const { return false; }
virtual bool is_function() const { return false; }
virtual bool is_typed_array() const { return false; }
@@ -115,20 +150,7 @@ public:
virtual const char* class_name() const override { return "Object"; }
virtual void visit_edges(Cell::Visitor&) override;
-
- virtual Object* prototype();
- virtual const Object* prototype() const;
- virtual bool set_prototype(Object* prototype);
- bool has_prototype(const Object* prototype) const;
-
- virtual bool is_extensible() const { return m_is_extensible; }
- virtual bool prevent_extensions();
-
- bool set_integrity_level(IntegrityLevel);
- bool test_integrity_level(IntegrityLevel);
-
virtual Value value_of() const { return Value(const_cast<Object*>(this)); }
- virtual Value ordinary_to_primitive(Value::PreferredType preferred_type) const;
Value get_direct(size_t index) const { return m_storage[index]; }
@@ -150,6 +172,11 @@ public:
return invoke(property_name);
}
+ Shape& shape() { return *m_shape; }
+ Shape const& shape() const { return *m_shape; }
+
+ GlobalObject& global_object() const { return *shape().global_object(); }
+
void ensure_shape_is_unique();
void enable_transitions() { m_transitions_enabled = true; }
@@ -164,19 +191,17 @@ protected:
explicit Object(GlobalObjectTag);
Object(ConstructWithoutPrototypeTag, GlobalObject&);
- virtual Value get_by_index(u32 property_index, AllowSideEffects = AllowSideEffects::Yes) const;
- virtual bool put_by_index(u32 property_index, Value);
+ bool m_is_extensible { true };
private:
- bool put_own_property(const StringOrSymbol& property_name, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
- bool put_own_property_by_index(u32 property_index, Value, PropertyAttributes attributes, PutOwnPropertyMode = PutOwnPropertyMode::Put, bool throw_exceptions = true);
-
Value call_native_property_getter(NativeProperty& property, Value this_value) const;
void call_native_property_setter(NativeProperty& property, Value this_value, Value) const;
void set_shape(Shape&);
- bool m_is_extensible { true };
+ Object* prototype() { return shape().prototype(); }
+ Object const* prototype() const { return shape().prototype(); }
+
bool m_transitions_enabled { true };
Shape* m_shape { nullptr };
Vector<Value> m_storage;
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp
index df1bc62b19..70f28e4c19 100644
--- a/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ObjectConstructor.cpp
@@ -80,55 +80,102 @@ Value ObjectConstructor::construct(FunctionObject& new_target)
return value.to_object(global_object);
}
-// 20.1.2.10 Object.getOwnPropertyNames ( O ), https://tc39.es/ecma262/#sec-object.getownpropertynames
-JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names)
+enum class GetOwnPropertyKeysType {
+ String,
+ Symbol,
+};
+
+// 20.1.2.11.1 GetOwnPropertyKeys ( O, type ), https://tc39.es/ecma262/#sec-getownpropertykeys
+static Array* get_own_property_keys(GlobalObject& global_object, Value value, GetOwnPropertyKeysType type)
{
- auto* object = vm.argument(0).to_object(global_object);
+ auto& vm = global_object.vm();
+
+ // 1. Let obj be ? ToObject(O).
+ auto* object = value.to_object(global_object);
+ if (vm.exception())
+ return {};
+
+ // 2. Let keys be ? obj.[[OwnPropertyKeys]]().
+ auto keys = object->internal_own_property_keys();
if (vm.exception())
return {};
- return Array::create_from(global_object, object->get_own_properties(PropertyKind::Key, false, GetOwnPropertyReturnType::StringOnly));
+
+ // 3. Let nameList be a new empty List.
+ auto name_list = MarkedValueList { vm.heap() };
+
+ // 4. For each element nextKey of keys, do
+ for (auto& next_key : keys) {
+ // a. If Type(nextKey) is Symbol and type is symbol or Type(nextKey) is String and type is string, then
+ if ((next_key.is_symbol() && type == GetOwnPropertyKeysType::Symbol) || (next_key.is_string() && type == GetOwnPropertyKeysType::String)) {
+ // i. Append nextKey as the last element of nameList.
+ name_list.append(next_key);
+ }
+ }
+
+ // 5. Return CreateArrayFromList(nameList).
+ return Array::create_from(global_object, name_list);
+}
+
+// 20.1.2.10 Object.getOwnPropertyNames ( O ), https://tc39.es/ecma262/#sec-object.getownpropertynames
+JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names)
+{
+ // 1. Return ? GetOwnPropertyKeys(O, string).
+ return get_own_property_keys(global_object, vm.argument(0), GetOwnPropertyKeysType::String);
}
// 20.1.2.11 Object.getOwnPropertySymbols ( O ), https://tc39.es/ecma262/#sec-object.getownpropertysymbols
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_symbols)
{
- auto* object = vm.argument(0).to_object(global_object);
- if (vm.exception())
- return {};
- return Array::create_from(global_object, object->get_own_properties(PropertyKind::Key, false, GetOwnPropertyReturnType::SymbolOnly));
+ // 1. Return ? GetOwnPropertyKeys(O, symbol).
+ return get_own_property_keys(global_object, vm.argument(0), GetOwnPropertyKeysType::Symbol);
}
// 20.1.2.12 Object.getPrototypeOf ( O ), https://tc39.es/ecma262/#sec-object.getprototypeof
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of)
{
+ // 1. Let obj be ? ToObject(O).
auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
- return object->prototype();
+
+ // 2. Return ? obj.[[GetPrototypeOf]]().
+ return object->internal_get_prototype_of();
}
// 20.1.2.21 Object.setPrototypeOf ( O, proto ), https://tc39.es/ecma262/#sec-object.setprototypeof
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::set_prototype_of)
{
- auto argument = require_object_coercible(global_object, vm.argument(0));
+ auto proto = vm.argument(1);
+
+ // 1. Set O to ? RequireObjectCoercible(O).
+ auto object = require_object_coercible(global_object, vm.argument(0));
if (vm.exception())
return {};
- auto prototype_value = vm.argument(1);
- if (!prototype_value.is_object() && !prototype_value.is_null()) {
+
+ // 2. If Type(proto) is neither Object nor Null, throw a TypeError exception.
+ if (!proto.is_object() && !proto.is_null()) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {};
}
- if (!argument.is_object())
- return argument;
- auto* prototype = prototype_value.is_null() ? nullptr : &prototype_value.as_object();
- auto status = argument.as_object().set_prototype(prototype);
+
+ // 3. If Type(O) is not Object, return O.
+ if (!object.is_object())
+ return object;
+
+ // 4. Let status be ? O.[[SetPrototypeOf]](proto).
+ auto status = object.as_object().internal_set_prototype_of(proto.is_null() ? nullptr : &proto.as_object());
if (vm.exception())
return {};
+
+ // 5. If status is false, throw a TypeError exception.
if (!status) {
+ // FIXME: Improve/contextualize error message
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse);
return {};
}
- return argument;
+
+ // 6. Return O.
+ return object;
}
// 20.1.2.14 Object.isExtensible ( O ), https://tc39.es/ecma262/#sec-object.isextensible
@@ -164,10 +211,11 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::prevent_extensions)
auto argument = vm.argument(0);
if (!argument.is_object())
return argument;
- auto status = argument.as_object().prevent_extensions();
+ auto status = argument.as_object().internal_prevent_extensions();
if (vm.exception())
return {};
if (!status) {
+ // FIXME: Improve/contextualize error message
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse);
return {};
}
@@ -247,54 +295,48 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_descriptor)
auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ auto key = vm.argument(1).to_property_key(global_object);
+ if (vm.exception())
+ return {};
+ auto descriptor = object->internal_get_own_property(key);
if (vm.exception())
return {};
- return object->get_own_property_descriptor_object(property_key);
+ return from_property_descriptor(global_object, descriptor);
}
// 20.1.2.4 Object.defineProperty ( O, P, Attributes ), https://tc39.es/ecma262/#sec-object.defineproperty
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_)
{
if (!vm.argument(0).is_object()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument");
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, vm.argument(0).to_string_without_side_effects());
return {};
}
- auto property_key = vm.argument(1).to_property_key(global_object);
+ auto key = vm.argument(1).to_property_key(global_object);
if (vm.exception())
return {};
- if (!vm.argument(2).is_object()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Descriptor argument");
+ auto descriptor = to_property_descriptor(global_object, vm.argument(2));
+ if (vm.exception())
return {};
- }
- auto& object = vm.argument(0).as_object();
- auto& descriptor = vm.argument(2).as_object();
- if (!object.define_property(property_key, descriptor)) {
- if (!vm.exception()) {
- if (AK::is<ProxyObject>(object)) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse);
- } else {
- vm.throw_exception<TypeError>(global_object, ErrorType::NonExtensibleDefine, property_key.to_display_string());
- }
- }
+ vm.argument(0).as_object().define_property_or_throw(key, descriptor);
+ if (vm.exception())
return {};
- }
- return &object;
+ return vm.argument(0);
}
// 20.1.2.3 Object.defineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-object.defineproperties
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_properties)
{
- if (!vm.argument(0).is_object()) {
+ auto object = vm.argument(0);
+ auto properties = vm.argument(1);
+
+ // 1. If Type(O) is not Object, throw a TypeError exception.
+ if (!object.is_object()) {
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument");
return {};
}
- auto& object = vm.argument(0).as_object();
- auto properties = vm.argument(1);
- object.define_properties(properties);
- if (vm.exception())
- return {};
- return &object;
+
+ // 2. Return ? ObjectDefineProperties(O, Properties).
+ return object.as_object().define_properties(properties);
}
// 20.1.2.13 Object.is ( value1, value2 ), https://tc39.es/ecma262/#sec-object.is
@@ -306,56 +348,61 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::is)
// 20.1.2.17 Object.keys ( O ), https://tc39.es/ecma262/#sec-object.keys
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::keys)
{
- auto* obj_arg = vm.argument(0).to_object(global_object);
+ auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
-
- return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::Key));
+ auto name_list = object->enumerable_own_property_names(PropertyKind::Key);
+ if (vm.exception())
+ return {};
+ return Array::create_from(global_object, name_list);
}
// 20.1.2.22 Object.values ( O ), https://tc39.es/ecma262/#sec-object.values
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::values)
{
- auto* obj_arg = vm.argument(0).to_object(global_object);
+ auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
-
- return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::Value));
+ auto name_list = object->enumerable_own_property_names(PropertyKind::Value);
+ if (vm.exception())
+ return {};
+ return Array::create_from(global_object, name_list);
}
// 20.1.2.5 Object.entries ( O ), https://tc39.es/ecma262/#sec-object.entries
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::entries)
{
- auto* obj_arg = vm.argument(0).to_object(global_object);
+ auto* object = vm.argument(0).to_object(global_object);
if (vm.exception())
return {};
-
- return Array::create_from(global_object, obj_arg->get_enumerable_own_property_names(PropertyKind::KeyAndValue));
+ auto name_list = object->enumerable_own_property_names(PropertyKind::KeyAndValue);
+ if (vm.exception())
+ return {};
+ return Array::create_from(global_object, name_list);
}
// 20.1.2.2 Object.create ( O, Properties ), https://tc39.es/ecma262/#sec-object.create
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::create)
{
- auto prototype_value = vm.argument(0);
+ auto proto = vm.argument(0);
auto properties = vm.argument(1);
- Object* prototype;
- if (prototype_value.is_null()) {
- prototype = nullptr;
- } else if (prototype_value.is_object()) {
- prototype = &prototype_value.as_object();
- } else {
+ // 1. If Type(O) is neither Object nor Null, throw a TypeError exception.
+ if (!proto.is_object() && !proto.is_null()) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {};
}
- auto* object = Object::create(global_object, prototype);
+ // 2. Let obj be ! OrdinaryObjectCreate(O).
+ auto* object = Object::create(global_object, proto.is_null() ? nullptr : &proto.as_object());
+ // 3. If Properties is not undefined, then
if (!properties.is_undefined()) {
- object->define_properties(properties);
- if (vm.exception())
- return {};
+ // a. Return ? ObjectDefineProperties(obj, Properties).
+ return object->define_properties(properties);
}
+
+ // 4. Return obj.
return object;
}
@@ -385,18 +432,18 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::assign)
continue;
auto from = next_source.to_object(global_object);
VERIFY(!vm.exception());
- auto keys = from->get_own_properties(PropertyKind::Key);
+ auto keys = from->internal_own_property_keys();
if (vm.exception())
return {};
- for (auto& key : keys) {
- auto property_name = PropertyName::from_value(global_object, key);
- auto property_descriptor = from->get_own_property_descriptor(property_name);
- if (!property_descriptor.has_value() || !property_descriptor->attributes.is_enumerable())
+ for (auto& next_key : keys) {
+ auto property_name = PropertyName::from_value(global_object, next_key);
+ auto property_descriptor = from->internal_get_own_property(property_name);
+ if (!property_descriptor.has_value() || !*property_descriptor->enumerable)
continue;
- auto value = from->get(property_name);
+ auto prop_value = from->get(property_name);
if (vm.exception())
return {};
- to->put(property_name, value);
+ to->set(property_name, prop_value, true);
if (vm.exception())
return {};
}
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp
index 7e02959898..5a30b09c1e 100644
--- a/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ObjectEnvironment.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -25,9 +26,9 @@ void ObjectEnvironment::visit_edges(Cell::Visitor& visitor)
Optional<Variable> ObjectEnvironment::get_from_environment(FlyString const& name) const
{
- auto value = m_binding_object.get(name);
- if (value.is_empty())
+ if (!m_binding_object.storage_has(name))
return {};
+ auto value = m_binding_object.get(name);
return Variable { value, DeclarationKind::Var };
}
@@ -38,33 +39,39 @@ bool ObjectEnvironment::put_into_environment(FlyString const& name, Variable var
bool ObjectEnvironment::delete_from_environment(FlyString const& name)
{
- return m_binding_object.delete_property(name);
+ return m_binding_object.internal_delete(name);
}
// 9.1.1.2.1 HasBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-hasbinding-n
bool ObjectEnvironment::has_binding(FlyString const& name) const
{
+ auto& vm = this->vm();
bool found_binding = m_binding_object.has_property(name);
+ if (vm.exception())
+ return {};
if (!found_binding)
return false;
-
- // FIXME: Implement the rest of this operation.
-
+ if (!m_with_environment)
+ return true;
+ auto unscopables = m_binding_object.get(*vm.well_known_symbol_unscopables());
+ if (vm.exception())
+ return {};
+ if (unscopables.is_object()) {
+ auto blocked = unscopables.as_object().get(name);
+ if (vm.exception())
+ return {};
+ if (blocked.to_boolean())
+ return false;
+ }
return true;
}
// 9.1.1.2.2 CreateMutableBinding ( N, D ), https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d
void ObjectEnvironment::create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted)
{
- PropertyAttributes attributes;
- attributes.set_enumerable();
- attributes.set_has_enumerable();
- attributes.set_writable();
- attributes.set_has_writable();
- attributes.set_has_configurable();
- if (can_be_deleted)
- attributes.set_configurable();
- m_binding_object.define_property(name, js_undefined(), attributes, true);
+ // 1. Let bindingObject be envRec.[[BindingObject]].
+ // 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }).
+ m_binding_object.define_property_or_throw(name, { .value = js_undefined(), .writable = true, .enumerable = true, .configurable = can_be_deleted });
}
// 9.1.1.2.3 CreateImmutableBinding ( N, S ), https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s
@@ -83,34 +90,38 @@ void ObjectEnvironment::initialize_binding(GlobalObject& global_object, FlyStrin
// 9.1.1.2.5 SetMutableBinding ( N, V, S ), https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s
void ObjectEnvironment::set_mutable_binding(GlobalObject& global_object, FlyString const& name, Value value, bool strict)
{
+ auto& vm = this->vm();
bool still_exists = m_binding_object.has_property(name);
+ if (vm.exception())
+ return;
if (!still_exists && strict) {
global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name);
return;
}
- // FIXME: This should use the Set abstract operation.
- // FIXME: Set returns a bool, so this may need to return a bool as well.
- m_binding_object.put(name, value);
+ m_binding_object.set(name, value, strict);
}
// 9.1.1.2.6 GetBindingValue ( N, S ), https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s
Value ObjectEnvironment::get_binding_value(GlobalObject& global_object, FlyString const& name, bool strict)
{
- if (!m_binding_object.has_property(name)) {
+ auto& vm = this->vm();
+ auto value = m_binding_object.has_property(name);
+ if (vm.exception())
+ return {};
+ if (!value) {
if (!strict)
return js_undefined();
global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name);
return {};
}
- // FIXME: This should use the Get abstract operation.
return m_binding_object.get(name);
}
// 9.1.1.2.7 DeleteBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n
bool ObjectEnvironment::delete_binding(GlobalObject&, FlyString const& name)
{
- return m_binding_object.delete_property(name);
+ return m_binding_object.internal_delete(name);
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp
index 863315cf9f..367ed5d815 100644
--- a/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ObjectPrototype.cpp
@@ -130,7 +130,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::property_is_enumerable)
auto property_descriptor = this_object->get_own_property_descriptor(property_key);
if (!property_descriptor.has_value())
return Value(false);
- return Value(property_descriptor.value().attributes.is_enumerable());
+ return Value(*property_descriptor->enumerable);
}
// 20.1.3.3 Object.prototype.isPrototypeOf ( V ), https://tc39.es/ecma262/#sec-object.prototype.isprototypeof
@@ -145,7 +145,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::is_prototype_of)
return {};
for (;;) {
- object = object->prototype();
+ object = object->internal_get_prototype_of();
if (!object)
return Value(false);
if (same_value(this_object, object))
@@ -166,22 +166,16 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_getter)
return {};
}
+ auto descriptor = PropertyDescriptor { .get = &getter.as_function(), .enumerable = true, .configurable = true };
+
auto key = vm.argument(0).to_property_key(global_object);
if (vm.exception())
return {};
- auto descriptor = Object::create(global_object, global_object.object_prototype());
- descriptor->define_property(vm.names.get, getter);
- descriptor->define_property(vm.names.enumerable, Value(true));
- descriptor->define_property(vm.names.configurable, Value(true));
-
- auto success = object->define_property(key, *descriptor);
+ object->define_property_or_throw(key, descriptor);
if (vm.exception())
return {};
- if (!success) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse);
- return {};
- }
+
return js_undefined();
}
@@ -198,22 +192,16 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_setter)
return {};
}
+ auto descriptor = PropertyDescriptor { .set = &setter.as_function(), .enumerable = true, .configurable = true };
+
auto key = vm.argument(0).to_property_key(global_object);
if (vm.exception())
return {};
- auto descriptor = Object::create(global_object, global_object.object_prototype());
- descriptor->define_property(vm.names.set, setter);
- descriptor->define_property(vm.names.enumerable, Value(true));
- descriptor->define_property(vm.names.configurable, Value(true));
-
- auto success = object->define_property(key, *descriptor);
+ object->define_property_or_throw(key, descriptor);
if (vm.exception())
return {};
- if (!success) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse);
- return {};
- }
+
return js_undefined();
}
@@ -229,12 +217,15 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::lookup_getter)
return {};
while (object) {
- auto desc = object->get_own_property_descriptor(key);
+ auto desc = object->internal_get_own_property(key);
if (vm.exception())
return {};
- if (desc.has_value())
- return desc->getter ?: js_undefined();
- object = object->prototype();
+ if (desc.has_value()) {
+ if (desc->is_accessor_descriptor())
+ return *desc->get ?: js_undefined();
+ return js_undefined();
+ }
+ object = object->internal_get_prototype_of();
if (vm.exception())
return {};
}
@@ -254,12 +245,15 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::lookup_setter)
return {};
while (object) {
- auto desc = object->get_own_property_descriptor(key);
+ auto desc = object->internal_get_own_property(key);
if (vm.exception())
return {};
- if (desc.has_value())
- return desc->setter ?: js_undefined();
- object = object->prototype();
+ if (desc.has_value()) {
+ if (desc->is_accessor_descriptor())
+ return *desc->set ?: js_undefined();
+ return js_undefined();
+ }
+ object = object->internal_get_prototype_of();
if (vm.exception())
return {};
}
@@ -273,7 +267,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::proto_getter)
auto object = vm.this_value(global_object).to_object(global_object);
if (vm.exception())
return {};
- auto proto = object->prototype();
+ auto proto = object->internal_get_prototype_of();
if (vm.exception())
return {};
return proto;
@@ -293,10 +287,11 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::proto_setter)
if (!object.is_object())
return js_undefined();
- auto status = object.as_object().set_prototype(proto.is_object() ? &proto.as_object() : nullptr);
+ auto status = object.as_object().internal_set_prototype_of(proto.is_object() ? &proto.as_object() : nullptr);
if (vm.exception())
return {};
if (!status) {
+ // FIXME: Improve/contextualize error message
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse);
return {};
}
diff --git a/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp
index 1f0d1cdcd4..02a5c06df2 100644
--- a/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/OrdinaryFunctionObject.cpp
@@ -94,7 +94,7 @@ void OrdinaryFunctionObject::initialize(GlobalObject& global_object)
break;
case FunctionKind::Generator:
// prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
- prototype->set_prototype(global_object.generator_object_prototype());
+ prototype->internal_set_prototype_of(global_object.generator_object_prototype());
break;
}
define_property(vm.names.prototype, prototype, Attribute::Writable);
diff --git a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp
deleted file mode 100644
index 5a68b567d3..0000000000
--- a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2021, David Tuin <david.tuin@gmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include "PropertyAttributes.h"
-
-namespace JS {
-
-PropertyAttributes PropertyAttributes::overwrite(PropertyAttributes attr) const
-{
- PropertyAttributes combined = m_bits;
- if (attr.has_configurable()) {
- if (attr.is_configurable()) {
- combined.set_configurable();
- } else {
- combined.m_bits &= ~Attribute::Configurable;
- }
- combined.set_has_configurable();
- }
-
- if (attr.has_enumerable()) {
- if (attr.is_enumerable()) {
- combined.set_enumerable();
- } else {
- combined.m_bits &= ~Attribute::Enumerable;
- }
- combined.set_has_configurable();
- }
-
- if (attr.has_writable()) {
- if (attr.is_writable()) {
- combined.set_writable();
- } else {
- combined.m_bits &= ~Attribute::Writable;
- }
- combined.set_has_writable();
- }
-
- if (attr.has_getter()) {
- combined.set_has_getter();
- }
-
- if (attr.has_setter()) {
- combined.set_has_setter();
- }
- return combined;
-}
-
-}
diff --git a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h
index 44792ee9a7..e0313d6a16 100644
--- a/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h
+++ b/Userland/Libraries/LibJS/Runtime/PropertyAttributes.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -13,73 +14,72 @@ namespace JS {
struct Attribute {
enum {
- Configurable = 1 << 0,
+ Writable = 1 << 0,
Enumerable = 1 << 1,
- Writable = 1 << 2,
- HasGetter = 1 << 3,
- HasSetter = 1 << 4,
- HasConfigurable = 1 << 5,
- HasEnumerable = 1 << 6,
- HasWritable = 1 << 7,
+ Configurable = 1 << 2,
};
};
+// 6.1.7.1 Property Attributes, https://tc39.es/ecma262/#sec-property-attributes
class PropertyAttributes {
public:
PropertyAttributes(u8 bits = 0)
+ : m_bits(bits)
{
- m_bits = bits;
- if (bits & Attribute::Configurable)
- m_bits |= Attribute::HasConfigurable;
- if (bits & Attribute::Enumerable)
- m_bits |= Attribute::HasEnumerable;
- if (bits & Attribute::Writable)
- m_bits |= Attribute::HasWritable;
}
- bool is_empty() const { return !m_bits; }
+ [[nodiscard]] bool is_writable() const { return m_bits & Attribute::Writable; }
+ [[nodiscard]] bool is_enumerable() const { return m_bits & Attribute::Enumerable; }
+ [[nodiscard]] bool is_configurable() const { return m_bits & Attribute::Configurable; }
- bool has_configurable() const { return m_bits & Attribute::HasConfigurable; }
- bool has_enumerable() const { return m_bits & Attribute::HasEnumerable; }
- bool has_writable() const { return m_bits & Attribute::HasWritable; }
- bool has_getter() const { return m_bits & Attribute::HasGetter; }
- bool has_setter() const { return m_bits & Attribute::HasSetter; }
+ void set_writable(bool writable = true)
+ {
+ if (writable)
+ m_bits |= Attribute::Writable;
+ else
+ m_bits &= ~Attribute::Writable;
+ }
- bool is_configurable() const { return m_bits & Attribute::Configurable; }
- bool is_enumerable() const { return m_bits & Attribute::Enumerable; }
- bool is_writable() const { return m_bits & Attribute::Writable; }
+ void set_enumerable(bool enumerable = true)
+ {
+ if (enumerable)
+ m_bits |= Attribute::Enumerable;
+ else
+ m_bits &= ~Attribute::Enumerable;
+ }
- void set_has_configurable() { m_bits |= Attribute::HasConfigurable; }
- void set_has_enumerable() { m_bits |= Attribute::HasEnumerable; }
- void set_has_writable() { m_bits |= Attribute::HasWritable; }
- void set_configurable() { m_bits |= Attribute::Configurable; }
- void set_enumerable() { m_bits |= Attribute::Enumerable; }
- void set_writable() { m_bits |= Attribute::Writable; }
- void set_has_getter() { m_bits |= Attribute::HasGetter; }
- void set_has_setter() { m_bits |= Attribute::HasSetter; }
+ void set_configurable(bool configurable = true)
+ {
+ if (configurable)
+ m_bits |= Attribute::Configurable;
+ else
+ m_bits &= ~Attribute::Configurable;
+ }
bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; }
bool operator!=(const PropertyAttributes& other) const { return m_bits != other.m_bits; }
- PropertyAttributes overwrite(PropertyAttributes attr) const;
-
- u8 bits() const { return m_bits; }
+ [[nodiscard]] u8 bits() const { return m_bits; }
private:
u8 m_bits;
};
-const PropertyAttributes default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable;
+PropertyAttributes const default_attributes = Attribute::Configurable | Attribute::Writable | Attribute::Enumerable;
}
namespace AK {
template<>
-struct Formatter<JS::PropertyAttributes> : Formatter<u8> {
- void format(FormatBuilder& builder, const JS::PropertyAttributes& attributes)
+struct Formatter<JS::PropertyAttributes> : Formatter<StringView> {
+ void format(FormatBuilder& builder, JS::PropertyAttributes const& property_attributes)
{
- Formatter<u8>::format(builder, attributes.bits());
+ Vector<String> parts;
+ parts.append(String::formatted("[[Writable]]: {}", property_attributes.is_writable()));
+ parts.append(String::formatted("[[Enumerable]]: {}", property_attributes.is_enumerable()));
+ parts.append(String::formatted("[[Configurable]]: {}", property_attributes.is_configurable()));
+ Formatter<StringView>::format(builder, String::formatted("PropertyAttributes {{ {} }}", String::join(", ", parts)));
}
};
diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp
index ef69b75a7e..dfdaeae9cc 100644
--- a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.cpp
@@ -13,9 +13,6 @@
namespace JS {
-// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit,
-// but the names conflict - this is the easiest solution. :^)
-#if 0
// 6.2.5.1 IsAccessorDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isaccessordescriptor
bool PropertyDescriptor::is_accessor_descriptor() const
{
@@ -190,6 +187,5 @@ PropertyAttributes PropertyDescriptor::attributes() const
attributes |= Attribute::Configurable;
return { attributes };
}
-#endif
}
diff --git a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h
index f505b94a21..7ff5447ee6 100644
--- a/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h
+++ b/Userland/Libraries/LibJS/Runtime/PropertyDescriptor.h
@@ -12,9 +12,6 @@
namespace JS {
-// Disabled until PropertyDescriptor in Object.h is gone. I want to introduce this thing in a separate commit,
-// but the names conflict - this is the easiest solution. :^)
-#if 0
// 6.2.5 The Property Descriptor Specification Type, https://tc39.es/ecma262/#sec-property-descriptor-specification-type
Value from_property_descriptor(GlobalObject&, Optional<PropertyDescriptor> const&);
@@ -68,6 +65,5 @@ struct Formatter<JS::PropertyDescriptor> : Formatter<StringView> {
Formatter<StringView>::format(builder, String::formatted("PropertyDescriptor {{ {} }}", String::join(", ", parts)));
}
};
-#endif
}
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp
index 13d6b76882..540a691a21 100644
--- a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp
@@ -82,8 +82,8 @@ JS_DEFINE_NATIVE_FUNCTION(ProxyConstructor::revocable)
revoker->define_property(vm.names.length, Value(0), Attribute::Configurable);
auto* result = Object::create(global_object, global_object.object_prototype());
- result->define_property(vm.names.proxy, proxy);
- result->define_property(vm.names.revoke, revoker);
+ result->create_data_property_or_throw(vm.names.proxy, proxy);
+ result->create_data_property_or_throw(vm.names.revoke, revoker);
return result;
}
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp
index 1288dbf402..38e852bd3f 100644
--- a/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.cpp
@@ -10,35 +10,11 @@
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/ProxyObject.h>
namespace JS {
-bool static is_compatible_property_descriptor(bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional)
-{
- if (!current_descriptor_optional.has_value())
- return is_extensible;
- auto current_descriptor = current_descriptor_optional.value();
- if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter)
- return true;
- if (!current_descriptor.attributes.is_configurable()) {
- if (new_descriptor.attributes.is_configurable())
- return false;
- if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable())
- return false;
- }
- if (new_descriptor.is_generic_descriptor())
- return true;
- if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable())
- return false;
- if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) {
- if (new_descriptor.attributes.is_writable())
- return false;
- return new_descriptor.value.is_empty() && same_value(new_descriptor.value, current_descriptor.value);
- }
- return true;
-}
-
ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler)
{
return global_object.heap().allocate<ProxyObject>(global_object, target, handler, *global_object.object_prototype());
@@ -55,423 +31,961 @@ ProxyObject::~ProxyObject()
{
}
-Object* ProxyObject::prototype()
+// 10.5.1 [[GetPrototypeOf]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
+Object* ProxyObject::internal_get_prototype_of() const
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Let handler be O.[[ProxyHandler]].
+
+ // 2. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return nullptr;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.getPrototypeOf);
- if (vm.exception())
- return nullptr;
- if (!trap)
- return m_target.prototype();
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target));
+
+ // 3. Assert: Type(handler) is Object.
+ // 4. Let target be O.[[ProxyTarget]].
+
+ // 5. Let trap be ? GetMethod(handler, "getPrototypeOf").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.getPrototypeOf);
if (vm.exception())
- return nullptr;
- if (!trap_result.is_object() && !trap_result.is_null()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfReturn);
- return nullptr;
+ return {};
+
+ // 6. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[GetPrototypeOf]]().
+ return m_target.internal_get_prototype_of();
}
- if (m_target.is_extensible()) {
- if (vm.exception())
- return nullptr;
- if (trap_result.is_null())
- return nullptr;
- return &trap_result.as_object();
+
+ // 7. Let handlerProto be ? Call(trap, handler, « target »).
+ auto handler_proto = vm.call(*trap, &m_handler, &m_target);
+ if (vm.exception())
+ return {};
+
+ // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception.
+ if (!handler_proto.is_object() && !handler_proto.is_null()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetPrototypeOfReturn);
+ return {};
}
- auto target_proto = m_target.prototype();
+
+ // 9. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
if (vm.exception())
- return nullptr;
- if (!same_value(trap_result, Value(target_proto))) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetPrototypeOfNonExtensible);
- return nullptr;
+ return {};
+
+ // 10. If extensibleTarget is true, return handlerProto.
+ if (extensible_target)
+ return handler_proto.is_null() ? nullptr : &handler_proto.as_object();
+
+ // 11. Let targetProto be ? target.[[GetPrototypeOf]]().
+ auto target_proto = m_target.internal_get_prototype_of();
+ if (vm.exception())
+ return {};
+
+ // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception.
+ if (!same_value(handler_proto, target_proto)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetPrototypeOfNonExtensible);
+ return {};
}
- return &trap_result.as_object();
+
+ // 13. Return handlerProto.
+ return handler_proto.is_null() ? nullptr : &handler_proto.as_object();
}
-const Object* ProxyObject::prototype() const
+// 10.5.2 [[SetPrototypeOf]] ( V ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v
+bool ProxyObject::internal_set_prototype_of(Object* prototype)
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: Either Type(V) is Object or Type(V) is Null.
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return nullptr;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype());
-}
-bool ProxyObject::set_prototype(Object* object)
-{
- auto& vm = this->vm();
- if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "setPrototypeOf").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.setPrototypeOf);
+ if (vm.exception())
+ return {};
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[SetPrototypeOf]](V).
+ return m_target.internal_set_prototype_of(prototype);
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.setPrototypeOf);
+
+ // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, V »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, prototype);
if (vm.exception())
+ return {};
+
+ // 9. If booleanTrapResult is false, return false.
+ if (!trap_result.to_boolean())
return false;
- if (!trap)
- return m_target.set_prototype(object);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), Value(object));
- if (vm.exception() || !trap_result.to_boolean())
- return false;
- if (m_target.is_extensible())
+
+ // 10. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // 11. If extensibleTarget is true, return true.
+ if (extensible_target)
return true;
- auto* target_proto = m_target.prototype();
+
+ // 12. Let targetProto be ? target.[[GetPrototypeOf]]().
+ auto* target_proto = m_target.internal_get_prototype_of();
if (vm.exception())
- return false;
- if (!same_value(Value(object), Value(target_proto))) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetPrototypeOfNonExtensible);
- return false;
+ return {};
+
+ // 13. If SameValue(V, targetProto) is false, throw a TypeError exception.
+ if (!same_value(prototype, target_proto)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxySetPrototypeOfNonExtensible);
+ return {};
}
+
+ // 14. Return true.
return true;
}
-bool ProxyObject::is_extensible() const
+// 10.5.3 [[IsExtensible]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible
+bool ProxyObject::internal_is_extensible() const
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Let handler be O.[[ProxyHandler]].
+
+ // 2. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.isExtensible);
+
+ // 3. Assert: Type(handler) is Object.
+ // 4. Let target be O.[[ProxyTarget]].
+
+ // 5. Let trap be ? GetMethod(handler, "isExtensible").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.isExtensible);
if (vm.exception())
- return false;
- if (!trap)
+ return {};
+
+ // 6. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? IsExtensible(target).
return m_target.is_extensible();
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target));
+ }
+
+ // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target);
if (vm.exception())
- return false;
- if (trap_result.to_boolean() != m_target.is_extensible()) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyIsExtensibleReturn);
- return false;
+ return {};
+
+ // 8. Let targetResult be ? IsExtensible(target).
+ auto target_result = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception.
+ if (trap_result.to_boolean() != target_result) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyIsExtensibleReturn);
+ return {};
}
+
+ // 10. Return booleanTrapResult.
return trap_result.to_boolean();
}
-bool ProxyObject::prevent_extensions()
+// 10.5.4 [[PreventExtensions]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions
+bool ProxyObject::internal_prevent_extensions()
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Let handler be O.[[ProxyHandler]].
+
+ // 2. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.preventExtensions);
+
+ // 3. Assert: Type(handler) is Object.
+ // 4. Let target be O.[[ProxyTarget]].
+
+ // 5. Let trap be ? GetMethod(handler, "preventExtensions").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.preventExtensions);
if (vm.exception())
- return false;
- if (!trap)
- return m_target.prevent_extensions();
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target));
+ return {};
+
+ // 6. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[PreventExtensions]]().
+ return m_target.internal_prevent_extensions();
+ }
+
+ // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target);
if (vm.exception())
- return false;
- if (trap_result.to_boolean() && m_target.is_extensible()) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyPreventExtensionsReturn);
- return false;
+ return {};
+
+ // 8. If booleanTrapResult is true, then
+ if (trap_result.to_boolean()) {
+ // a. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // b. If extensibleTarget is true, throw a TypeError exception.
+ if (extensible_target) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyPreventExtensionsReturn);
+ return {};
+ }
}
+
+ // 9. Return booleanTrapResult.
return trap_result.to_boolean();
}
-Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(const PropertyName& name) const
+// 10.5.5 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p
+Optional<PropertyDescriptor> ProxyObject::internal_get_own_property(const PropertyName& property_name) const
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.getOwnPropertyDescriptor);
+
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.getOwnPropertyDescriptor);
if (vm.exception())
return {};
- if (!trap)
- return m_target.get_own_property_descriptor(name);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm));
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[GetOwnProperty]](P).
+ return m_target.internal_get_own_property(property_name);
+ }
+
+ // 8. Let trapResultObj be ? Call(trap, handler, « target, P »).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm));
if (vm.exception())
return {};
+
+ // 9. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception.
if (!trap_result.is_object() && !trap_result.is_undefined()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorReturn);
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorReturn);
return {};
}
- auto target_desc = m_target.get_own_property_descriptor(name);
+
+ // 10. Let targetDesc be ? target.[[GetOwnProperty]](P).
+ auto target_descriptor = m_target.internal_get_own_property(property_name);
if (vm.exception())
return {};
+
+ // 11. If trapResultObj is undefined, then
if (trap_result.is_undefined()) {
- if (!target_desc.has_value())
+ // a. If targetDesc is undefined, return undefined.
+ if (!target_descriptor.has_value())
return {};
- if (!target_desc.value().attributes.is_configurable()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorNonConfigurable);
+
+ // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
+ if (!*target_descriptor->configurable) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorNonConfigurable);
return {};
}
- if (!m_target.is_extensible()) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorUndefinedReturn);
+
+ // c. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // d. If extensibleTarget is false, throw a TypeError exception.
+ if (!extensible_target) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorUndefinedReturn);
return {};
}
+
+ // e. Return undefined.
return {};
}
- auto result_desc = PropertyDescriptor::from_dictionary(vm, trap_result.as_object());
+
+ // 12. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // 13. Let resultDesc be ? ToPropertyDescriptor(trapResultObj).
+ auto result_desc = to_property_descriptor(global_object, trap_result);
if (vm.exception())
return {};
- if (!is_compatible_property_descriptor(m_target.is_extensible(), result_desc, target_desc)) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidDescriptor);
+
+ // 14. Call CompletePropertyDescriptor(resultDesc).
+ result_desc.complete();
+
+ // 15. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc).
+ auto valid = is_compatible_property_descriptor(extensible_target, result_desc, target_descriptor);
+
+ // 16. If valid is false, throw a TypeError exception.
+ if (!valid) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorInvalidDescriptor);
return {};
}
- if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetOwnDescriptorInvalidNonConfig);
- return {};
+
+ // 17. If resultDesc.[[Configurable]] is false, then
+ if (!*result_desc.configurable) {
+ // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then
+ if (!target_descriptor.has_value() || *target_descriptor->configurable) {
+ // i. Throw a TypeError exception.
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorInvalidNonConfig);
+ return {};
+ }
+ // b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then
+ if (result_desc.writable.has_value() && !*result_desc.writable) {
+ // i. If targetDesc.[[Writable]] is true, throw a TypeError exception.
+ if (*target_descriptor->writable) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetOwnDescriptorNonConfigurableNonWritable);
+ return {};
+ }
+ }
}
+
+ // 18. Return resultDesc.
return result_desc;
}
-bool ProxyObject::define_property(const StringOrSymbol& property_name, const Object& descriptor, bool throw_exceptions)
+// 10.5.6 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc
+bool ProxyObject::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor)
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.defineProperty);
+
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "defineProperty").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.defineProperty);
if (vm.exception())
- return false;
- if (!trap)
- return m_target.define_property(property_name, descriptor, throw_exceptions);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), property_name.to_value(vm), Value(const_cast<Object*>(&descriptor)));
- if (vm.exception() || !trap_result.to_boolean())
- return false;
- auto target_desc = m_target.get_own_property_descriptor(property_name);
+ return {};
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[DefineOwnProperty]](P, Desc).
+ return m_target.internal_define_own_property(property_name, property_descriptor);
+ }
+
+ // 8. Let descObj be FromPropertyDescriptor(Desc).
+ auto descriptor_object = from_property_descriptor(global_object, property_descriptor);
+
+ // 9. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm), descriptor_object);
if (vm.exception())
+ return {};
+
+ // 10. If booleanTrapResult is false, return false.
+ if (!trap_result.to_boolean())
return false;
+
+ // 11. Let targetDesc be ? target.[[GetOwnProperty]](P).
+ auto target_descriptor = m_target.internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 12. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // 14. Else, let settingConfigFalse be false.
bool setting_config_false = false;
- if (descriptor.has_property(vm.names.configurable) && !descriptor.get(vm.names.configurable).to_boolean())
+
+ // 13. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then
+ if (property_descriptor.configurable.has_value() && !*property_descriptor.configurable) {
+ // a. Let settingConfigFalse be true.
setting_config_false = true;
- if (vm.exception())
- return false;
- if (!target_desc.has_value()) {
- if (!m_target.is_extensible()) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonExtensible);
- return false;
+ }
+
+ // 15. If targetDesc is undefined, then
+ if (!target_descriptor.has_value()) {
+ // a. If extensibleTarget is false, throw a TypeError exception.
+ if (!extensible_target) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropNonExtensible);
+ return {};
}
+ // b. If settingConfigFalse is true, throw a TypeError exception.
if (setting_config_false) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropNonConfigurableNonExisting);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropNonConfigurableNonExisting);
+ return {};
}
- } else {
- if (!is_compatible_property_descriptor(m_target.is_extensible(), PropertyDescriptor::from_dictionary(vm, descriptor), target_desc)) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropIncompatibleDescriptor);
- return false;
+ }
+ // 16. Else,
+ else {
+ // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception.
+ if (!is_compatible_property_descriptor(extensible_target, property_descriptor, target_descriptor)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropIncompatibleDescriptor);
+ return {};
}
- if (setting_config_false && target_desc.value().attributes.is_configurable()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDefinePropExistingConfigurable);
- return false;
+ // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception.
+ if (setting_config_false && *target_descriptor->configurable) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropExistingConfigurable);
+ return {};
+ }
+ // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then
+ if (target_descriptor->is_data_descriptor() && !*target_descriptor->configurable && *target_descriptor->writable) {
+ // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception.
+ if (property_descriptor.writable.has_value() && !*property_descriptor.writable) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDefinePropNonWritable);
+ return {};
+ }
}
}
+
+ // 17. Return true.
return true;
}
-bool ProxyObject::has_property(const PropertyName& name) const
+// 10.5.7 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p
+bool ProxyObject::internal_has_property(PropertyName const& property_name) const
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.has);
+
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "has").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.has);
if (vm.exception())
- return false;
- if (!trap)
- return m_target.has_property(name);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm));
+ return {};
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[HasProperty]](P).
+ return m_target.internal_has_property(property_name);
+ }
+
+ // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm));
if (vm.exception())
- return false;
+ return {};
+
+ // 9. If booleanTrapResult is false, then
if (!trap_result.to_boolean()) {
- auto target_desc = m_target.get_own_property_descriptor(name);
+ // a. Let targetDesc be ? target.[[GetOwnProperty]](P).
+ auto target_descriptor = m_target.internal_get_own_property(property_name);
if (vm.exception())
- return false;
- if (target_desc.has_value()) {
- if (!target_desc.value().attributes.is_configurable()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonConfigurable);
- return false;
+ return {};
+
+ // b. If targetDesc is not undefined, then
+ if (target_descriptor.has_value()) {
+ // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
+ if (!*target_descriptor->configurable) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyHasExistingNonConfigurable);
+ return {};
}
- if (!m_target.is_extensible()) {
- if (!vm.exception())
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyHasExistingNonExtensible);
+
+ // ii. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // iii. If extensibleTarget is false, throw a TypeError exception.
+ if (!extensible_target) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyHasExistingNonExtensible);
return false;
}
}
}
+
+ // 10. Return booleanTrapResult.
return trap_result.to_boolean();
}
-Value ProxyObject::get(const PropertyName& name, Value receiver, AllowSideEffects allow_side_effects) const
+// 10.5.8 [[Get]] ( P, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver
+Value ProxyObject::internal_get(PropertyName const& property_name, Value receiver) const
{
+ VERIFY(!receiver.is_empty());
+
auto& vm = this->vm();
- if (allow_side_effects == AllowSideEffects::No) {
- // Sorry, we're not going to call anything on this proxy.
- return js_string(vm, "<ProxyObject>");
- }
+ auto& global_object = this->global_object();
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return {};
}
- if (receiver.is_empty())
- receiver = Value(const_cast<ProxyObject*>(this));
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.get);
+
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "get").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.get);
if (vm.exception())
return {};
- if (!trap)
- return m_target.get(name, receiver);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm), receiver);
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[Get]](P, Receiver).
+ return m_target.internal_get(property_name, receiver);
+ }
+
+ // 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm), receiver);
if (vm.exception())
return {};
- auto target_desc = m_target.get_own_property_descriptor(name);
- if (target_desc.has_value()) {
- if (vm.exception())
- return {};
- if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(trap_result, target_desc.value().value)) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetImmutableDataProperty);
- return {};
+
+ // 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
+ auto target_descriptor = m_target.internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then
+ if (target_descriptor.has_value() && !*target_descriptor->configurable) {
+ // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then
+ if (target_descriptor->is_data_descriptor() && !*target_descriptor->writable) {
+ // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception.
+ if (!same_value(trap_result, *target_descriptor->value)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetImmutableDataProperty);
+ return {};
+ }
}
- if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyGetNonConfigurableAccessor);
- return {};
+ // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] is undefined, then
+ if (target_descriptor->is_accessor_descriptor() && !*target_descriptor->get) {
+ // i. If trapResult is not undefined, throw a TypeError exception.
+ if (!trap_result.is_undefined()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyGetNonConfigurableAccessor);
+ return {};
+ }
}
}
+
+ // 11. Return trapResult.
return trap_result;
}
-bool ProxyObject::put(const PropertyName& name, Value value, Value receiver)
+// 10.5.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
+bool ProxyObject::internal_set(PropertyName const& property_name, Value value, Value receiver)
{
+ VERIFY(!value.is_empty());
+ VERIFY(!receiver.is_empty());
+
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- if (receiver.is_empty())
- receiver = Value(const_cast<ProxyObject*>(this));
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.set);
+
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "set").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.set);
if (vm.exception())
- return false;
- if (!trap)
- return m_target.put(name, value, receiver);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm), value, receiver);
- if (vm.exception() || !trap_result.to_boolean())
- return false;
- auto target_desc = m_target.get_own_property_descriptor(name);
+ return {};
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[Set]](P, V, Receiver).
+ return m_target.internal_set(property_name, value, receiver);
+ }
+
+ // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, V, Receiver »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm), value, receiver);
if (vm.exception())
+ return {};
+
+ // 9. If booleanTrapResult is false, return false.
+ if (!trap_result.to_boolean())
return false;
- if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) {
- if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(value, target_desc.value().value)) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetImmutableDataProperty);
- return false;
+
+ // 10. Let targetDesc be ? target.[[GetOwnProperty]](P).
+ auto target_descriptor = m_target.internal_get_own_property(property_name);
+ if (vm.exception())
+ return {};
+
+ // 11. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then
+ if (target_descriptor.has_value() && !*target_descriptor->configurable) {
+ // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then
+ if (target_descriptor->is_data_descriptor() && !*target_descriptor->writable) {
+ // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception.
+ if (!same_value(value, *target_descriptor->value)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxySetImmutableDataProperty);
+ return {};
+ }
}
- if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxySetNonConfigurableAccessor);
+ // b. If IsAccessorDescriptor(targetDesc) is true, then
+ if (target_descriptor->is_accessor_descriptor()) {
+ // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception.
+ if (!*target_descriptor->set) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxySetNonConfigurableAccessor);
+ return {};
+ }
}
}
+
+ // 12. Return true.
return true;
}
-bool ProxyObject::delete_property(PropertyName const& name, bool force_throw_exception)
+// 10.5.10 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p
+bool ProxyObject::internal_delete(PropertyName const& property_name)
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let handler be O.[[ProxyHandler]].
+
+ // 3. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
- return false;
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.deleteProperty);
+
+ // 4. Assert: Type(handler) is Object.
+ // 5. Let target be O.[[ProxyTarget]].
+
+ // 6. Let trap be ? GetMethod(handler, "deleteProperty").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.deleteProperty);
if (vm.exception())
- return false;
- if (!trap)
- return m_target.delete_property(name, force_throw_exception);
- auto trap_result = vm.call(*trap, Value(&m_handler), Value(&m_target), name.to_value(vm));
+ return {};
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[Delete]](P).
+ return m_target.internal_delete(property_name);
+ }
+
+ // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)).
+ auto trap_result = vm.call(*trap, &m_handler, &m_target, property_name.to_value(vm));
if (vm.exception())
- return false;
+ return {};
+
+ // 9. If booleanTrapResult is false, return false.
if (!trap_result.to_boolean())
return false;
- auto target_desc = m_target.get_own_property_descriptor(name);
+
+ // 10. Let targetDesc be ? target.[[GetOwnProperty]](P).
+ auto target_descriptor = m_target.internal_get_own_property(property_name);
if (vm.exception())
- return false;
- if (!target_desc.has_value())
+ return {};
+
+ // 11. If targetDesc is undefined, return true.
+ if (!target_descriptor.has_value())
return true;
- if (!target_desc.value().attributes.is_configurable()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyDeleteNonConfigurable);
- return false;
+
+ // 12. If targetDesc.[[Configurable]] is false, throw a TypeError exception.
+ if (!*target_descriptor->configurable) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDeleteNonConfigurable);
+ return {};
+ }
+
+ // 13. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return {};
+
+ // 14. If extensibleTarget is false, throw a TypeError exception.
+ if (!extensible_target) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyDeleteNonExtensible);
+ return {};
}
+
+ // 15. Return true.
return true;
}
-void ProxyObject::visit_edges(Cell::Visitor& visitor)
+// 10.5.11 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
+MarkedValueList ProxyObject::internal_own_property_keys() const
{
- Base::visit_edges(visitor);
- visitor.visit(&m_target);
- visitor.visit(&m_handler);
+ auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // 1. Let handler be O.[[ProxyHandler]].
+
+ // 2. If handler is null, throw a TypeError exception.
+ if (m_is_revoked) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
+ return MarkedValueList { heap() };
+ }
+
+ // 3. Assert: Type(handler) is Object.
+ // 4. Let target be O.[[ProxyTarget]].
+
+ // 5. Let trap be ? GetMethod(handler, "ownKeys").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.ownKeys);
+ if (vm.exception())
+ return MarkedValueList { heap() };
+
+ // 6. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? target.[[OwnPropertyKeys]]().
+ return Object::internal_own_property_keys();
+ }
+
+ // 7. Let trapResultArray be ? Call(trap, handler, « target »).
+ auto trap_result_array = vm.call(*trap, &m_handler, &m_target);
+ if (vm.exception())
+ return MarkedValueList { heap() };
+
+ // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, Symbol »).
+ auto trap_result = create_list_from_array_like(global_object, trap_result_array, [](auto value) -> Result<void, ErrorType> {
+ if (!value.is_string() && !value.is_symbol())
+ return ErrorType::ProxyOwnPropertyKeysNotStringOrSymbol;
+ return {};
+ });
+
+ // FIXME: 9. If trapResult contains any duplicate entries, throw a TypeError exception.
+
+ // 10. Let extensibleTarget be ? IsExtensible(target).
+ auto extensible_target = m_target.is_extensible();
+ if (vm.exception())
+ return MarkedValueList { heap() };
+
+ // 11. Let targetKeys be ? target.[[OwnPropertyKeys]]().
+ auto target_keys = m_target.internal_own_property_keys();
+ if (vm.exception())
+ return MarkedValueList { heap() };
+
+ // 12. Assert: targetKeys is a List whose elements are only String and Symbol values.
+ // 13. Assert: targetKeys contains no duplicate entries.
+
+ // 14. Let targetConfigurableKeys be a new empty List.
+ auto target_configurable_keys = MarkedValueList { heap() };
+
+ // 15. Let targetNonconfigurableKeys be a new empty List.
+ auto target_nonconfigurable_keys = MarkedValueList { heap() };
+
+ // 16. For each element key of targetKeys, do
+ for (auto& key : target_keys) {
+ // a. Let desc be ? target.[[GetOwnProperty]](key).
+ auto descriptor = m_target.internal_get_own_property(PropertyName::from_value(global_object, key));
+
+ // b. If desc is not undefined and desc.[[Configurable]] is false, then
+ if (descriptor.has_value() && !*descriptor->configurable) {
+ // i. Append key as an element of targetNonconfigurableKeys.
+ target_nonconfigurable_keys.append(key);
+ }
+ // c. Else,
+ else {
+ // i. Append key as an element of targetConfigurableKeys.
+ target_configurable_keys.append(key);
+ }
+ }
+
+ // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, then
+ if (extensible_target && target_nonconfigurable_keys.is_empty()) {
+ // a. Return trapResult.
+ return trap_result;
+ }
+
+ // 18. Let uncheckedResultKeys be a List whose elements are the elements of trapResult.
+ auto unchecked_result_keys = MarkedValueList { heap() };
+ unchecked_result_keys.extend(trap_result);
+
+ // 19. For each element key of targetNonconfigurableKeys, do
+ for (auto& key : target_nonconfigurable_keys) {
+ // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception.
+ if (!unchecked_result_keys.contains_slow(key)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
+ return MarkedValueList { heap() };
+ }
+
+ // b. Remove key from uncheckedResultKeys.
+ unchecked_result_keys.remove_first_matching([&](auto& value) {
+ return same_value(value, key);
+ });
+ }
+
+ // 20. If extensibleTarget is true, return trapResult.
+ if (extensible_target)
+ return trap_result;
+
+ // 21. For each element key of targetConfigurableKeys, do
+ for (auto& key : target_configurable_keys) {
+ // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception.
+ if (!unchecked_result_keys.contains_slow(key)) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
+ return MarkedValueList { heap() };
+ }
+
+ // b. Remove key from uncheckedResultKeys.
+ unchecked_result_keys.remove_first_matching([&](auto& value) {
+ return same_value(value, key);
+ });
+ }
+
+ // 22. If uncheckedResultKeys is not empty, throw a TypeError exception.
+ if (!unchecked_result_keys.is_empty()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::FixmeAddAnErrorString);
+ return MarkedValueList { heap() };
+ }
+
+ // 23. Return trapResult.
+ return trap_result;
}
+// 10.5.12 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
Value ProxyObject::call()
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // A Proxy exotic object only has a [[Call]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Call]] internal method.
if (!is_function()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::NotAFunction, Value(this).to_string_without_side_effects());
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, Value(this).to_string_without_side_effects());
return {};
}
+
+ // 1. Let handler be O.[[ProxyHandler]].
+
+ // 2. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.apply);
+
+ // 3. Assert: Type(handler) is Object.
+ // 4. Let target be O.[[ProxyTarget]].
+
+ // 5. Let trap be ? GetMethod(handler, "apply").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.apply);
if (vm.exception())
return {};
- if (!trap)
+
+ // 6. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? Call(target, thisArgument, argumentsList).
return static_cast<FunctionObject&>(m_target).call();
- MarkedValueList arguments(heap());
- arguments.append(Value(&m_target));
- arguments.append(Value(&m_handler));
- // FIXME: Pass global object
- auto arguments_array = Array::create(global_object(), 0);
+ }
+
+ // 7. Let argArray be ! CreateArrayFromList(argumentsList).
+ auto arguments_array = Array::create(global_object, 0);
vm.for_each_argument([&](auto& argument) {
arguments_array->indexed_properties().append(argument);
});
- arguments.append(arguments_array);
- return vm.call(*trap, Value(&m_handler), move(arguments));
+ // 8. Return ? Call(trap, handler, « target, thisArgument, argArray »).
+ return vm.call(*trap, &m_handler, &m_target, &m_handler, arguments_array);
}
+// 10.5.13 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget
Value ProxyObject::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
+ auto& global_object = this->global_object();
+
+ // A Proxy exotic object only has a [[Construct]] internal method if the initial value of its [[ProxyTarget]] internal slot is an object that has a [[Construct]] internal method.
if (!is_function()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::NotAConstructor, Value(this).to_string_without_side_effects());
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, Value(this).to_string_without_side_effects());
return {};
}
+
+ // 1. Let handler be O.[[ProxyHandler]].
+
+ // 2. If handler is null, throw a TypeError exception.
if (m_is_revoked) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyRevoked);
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return {};
}
- auto trap = Value(&m_handler).get_method(global_object(), vm.names.construct);
+
+ // 3. Assert: Type(handler) is Object.
+ // 4. Let target be O.[[ProxyTarget]].
+ // 5. Assert: IsConstructor(target) is true.
+
+ // 6. Let trap be ? GetMethod(handler, "construct").
+ auto trap = Value(&m_handler).get_method(global_object, vm.names.construct);
if (vm.exception())
return {};
- if (!trap)
+
+ // 7. If trap is undefined, then
+ if (!trap) {
+ // a. Return ? Construct(target, argumentsList, newTarget).
return static_cast<FunctionObject&>(m_target).construct(new_target);
- MarkedValueList arguments(vm.heap());
- arguments.append(Value(&m_target));
- auto arguments_array = Array::create(global_object(), 0);
+ }
+
+ // 8. Let argArray be ! CreateArrayFromList(argumentsList).
+ auto arguments_array = Array::create(global_object, 0);
vm.for_each_argument([&](auto& argument) {
arguments_array->indexed_properties().append(argument);
});
- arguments.append(arguments_array);
- arguments.append(Value(&new_target));
- auto result = vm.call(*trap, Value(&m_handler), move(arguments));
+
+ // 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »).
+ auto result = vm.call(*trap, &m_handler, &m_target, arguments_array, &new_target);
+
+ // 10. If Type(newObj) is not Object, throw a TypeError exception.
if (!result.is_object()) {
- vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructBadReturnType);
+ vm.throw_exception<TypeError>(global_object, ErrorType::ProxyConstructBadReturnType);
return {};
}
+
+ // 11. Return newObj.
return result;
}
+void ProxyObject::visit_edges(Cell::Visitor& visitor)
+{
+ FunctionObject::visit_edges(visitor);
+ visitor.visit(&m_target);
+ visitor.visit(&m_handler);
+}
+
const FlyString& ProxyObject::name() const
{
VERIFY(is_function());
diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.h b/Userland/Libraries/LibJS/Runtime/ProxyObject.h
index da89558591..d96b5f2822 100644
--- a/Userland/Libraries/LibJS/Runtime/ProxyObject.h
+++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.h
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -27,21 +28,23 @@ public:
const Object& target() const { return m_target; }
const Object& handler() const { return m_handler; }
- virtual Object* prototype() override;
- virtual const Object* prototype() const override;
- 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(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, AllowSideEffects = AllowSideEffects::Yes) const override;
- virtual bool put(const PropertyName& name, Value value, Value receiver) override;
- virtual bool delete_property(PropertyName const& name, bool force_throw_exception = false) override;
-
bool is_revoked() const { return m_is_revoked; }
void revoke() { m_is_revoked = true; }
+ // 10.5 Proxy Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots
+
+ virtual Object* internal_get_prototype_of() const override;
+ virtual bool internal_set_prototype_of(Object* prototype) override;
+ virtual bool internal_is_extensible() const override;
+ virtual bool internal_prevent_extensions() override;
+ virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const&) const override;
+ virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor const&) override;
+ virtual bool internal_has_property(PropertyName const&) const override;
+ virtual Value internal_get(PropertyName const&, Value receiver) const override;
+ virtual bool internal_set(PropertyName const&, Value value, Value receiver) override;
+ virtual bool internal_delete(PropertyName const&) override;
+ virtual MarkedValueList internal_own_property_keys() const override;
+
private:
virtual void visit_edges(Visitor&) override;
diff --git a/Userland/Libraries/LibJS/Runtime/Reference.cpp b/Userland/Libraries/LibJS/Runtime/Reference.cpp
index 8b0311ea29..ac3152631e 100644
--- a/Userland/Libraries/LibJS/Runtime/Reference.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Reference.cpp
@@ -21,7 +21,7 @@ void Reference::put_value(GlobalObject& global_object, Value value)
throw_reference_error(global_object);
return;
}
- global_object.put(m_name, value);
+ global_object.set(m_name, value, false);
return;
}
@@ -39,7 +39,9 @@ void Reference::put_value(GlobalObject& global_object, Value value)
if (!base_obj)
return;
- bool succeeded = base_obj->put(m_name, value);
+ bool succeeded = base_obj->internal_set(m_name, value, get_this_value());
+ if (vm.exception())
+ return;
if (!succeeded && m_strict) {
vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishSetProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects());
return;
@@ -108,6 +110,7 @@ Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined)
return value->value;
}
+// 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation
bool Reference::delete_(GlobalObject& global_object)
{
// 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation
@@ -136,21 +139,22 @@ bool Reference::delete_(GlobalObject& global_object)
// b. If IsSuperReference(ref) is true, throw a ReferenceError exception.
if (is_super_reference()) {
vm.throw_exception<ReferenceError>(global_object, ErrorType::UnsupportedDeleteSuperProperty);
- return false;
+ return {};
}
// c. Let baseObj be ! ToObject(ref.[[Base]]).
auto* base_obj = m_base_value.to_object(global_object);
- if (!base_obj)
- return false;
+ VERIFY(base_obj);
// d. Let deleteStatus be ? baseObj.[[Delete]](ref.[[ReferencedName]]).
- bool delete_status = base_obj->delete_property(m_name);
+ bool delete_status = base_obj->internal_delete(m_name);
+ if (vm.exception())
+ return {};
// e. If deleteStatus is false and ref.[[Strict]] is true, throw a TypeError exception.
if (!delete_status && m_strict) {
vm.throw_exception<TypeError>(global_object, ErrorType::ReferenceNullishDeleteProperty, m_name.to_value(vm).to_string_without_side_effects(), m_base_value.to_string_without_side_effects());
- return false;
+ return {};
}
// f. Return deleteStatus.
diff --git a/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp
index 4294aa40e2..0c0d642b68 100644
--- a/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ReflectObject.cpp
@@ -15,17 +15,6 @@
namespace JS {
-static Object* get_target_object_from(GlobalObject& global_object, const String& name)
-{
- auto& vm = global_object.vm();
- auto target = vm.argument(0);
- if (!target.is_object()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAnObject, name);
- return nullptr;
- }
- return static_cast<Object*>(&target.as_object());
-}
-
ReflectObject::ReflectObject(GlobalObject& global_object)
: Object(*global_object.object_prototype())
{
@@ -62,182 +51,294 @@ ReflectObject::~ReflectObject()
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::apply)
{
auto target = vm.argument(0);
+ auto this_argument = vm.argument(1);
+ auto arguments_list = vm.argument(2);
+
+ // 1. If IsCallable(target) is false, throw a TypeError exception.
if (!target.is_function()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAFunction, vm.names.apply);
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, target.to_string_without_side_effects());
return {};
}
- auto this_arg = vm.argument(1);
- auto arguments = create_list_from_array_like(global_object, vm.argument(2));
+ // 2. Let args be ? CreateListFromArrayLike(argumentsList).
+ auto args = create_list_from_array_like(global_object, arguments_list);
if (vm.exception())
return {};
- return vm.call(target.as_function(), this_arg, move(arguments));
+
+ // 3. Perform PrepareForTailCall().
+ // 4. Return ? Call(target, thisArgument, args).
+ return vm.call(target.as_function(), this_argument, move(args));
}
// 28.1.2 Reflect.construct ( target, argumentsList [ , newTarget ] ), https://tc39.es/ecma262/#sec-reflect.construct
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::construct)
{
auto target = vm.argument(0);
+ auto arguments_list = vm.argument(1);
+ auto new_target = vm.argument(2);
+
+ // 1. If IsConstructor(target) is false, throw a TypeError exception.
if (!target.is_constructor()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ReflectArgumentMustBeAConstructor, vm.names.construct);
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, target.to_string_without_side_effects());
return {};
}
- auto* new_target = &target.as_function();
- if (vm.argument_count() > 2) {
- auto new_target_value = vm.argument(2);
- if (!new_target_value.is_constructor()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadNewTarget);
- return {};
- }
- new_target = &new_target_value.as_function();
+ // 2. If newTarget is not present, set newTarget to target.
+ if (vm.argument_count() < 3) {
+ new_target = target;
+ }
+ // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
+ else if (!new_target.is_constructor()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, new_target.to_string_without_side_effects());
+ return {};
}
- auto arguments = create_list_from_array_like(global_object, vm.argument(1));
+ // 4. Let args be ? CreateListFromArrayLike(argumentsList).
+ auto args = create_list_from_array_like(global_object, arguments_list);
if (vm.exception())
return {};
- return vm.construct(target.as_function(), *new_target, move(arguments));
+ // 5. Return ? Construct(target, args, newTarget).
+ return vm.construct(target.as_function(), new_target.as_function(), move(args));
}
// 28.1.3 Reflect.defineProperty ( target, propertyKey, attributes ), https://tc39.es/ecma262/#sec-reflect.defineproperty
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property)
{
- auto* target = get_target_object_from(global_object, "defineProperty");
- if (!target)
+ auto target = vm.argument(0);
+ auto property_key = vm.argument(1);
+ auto attributes = vm.argument(2);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ }
+
+ // 2. Let key be ? ToPropertyKey(propertyKey).
+ auto key = property_key.to_property_key(global_object);
if (vm.exception())
return {};
- if (!vm.argument(2).is_object()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadDescriptorArgument);
- return {};
- }
- auto& descriptor = vm.argument(2).as_object();
- auto success = target->define_property(property_key, descriptor, false);
+
+ // 3. Let desc be ? ToPropertyDescriptor(attributes).
+ auto descriptor = to_property_descriptor(global_object, attributes);
if (vm.exception())
return {};
- return Value(success);
+
+ // 4. Return ? target.[[DefineOwnProperty]](key, desc).
+ return Value(target.as_object().internal_define_own_property(key, descriptor));
}
// 28.1.4 Reflect.deleteProperty ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.deleteproperty
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property)
{
- auto* target = get_target_object_from(global_object, "deleteProperty");
- if (!target)
+ auto target = vm.argument(0);
+ auto property_key = vm.argument(1);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ }
+
+ // 2. Let key be ? ToPropertyKey(propertyKey).
+ auto key = property_key.to_property_key(global_object);
if (vm.exception())
return {};
- return Value(target->delete_property(property_key));
+
+ // 3. Return ? target.[[Delete]](key).
+ return Value(target.as_object().internal_delete(key));
}
// 28.1.5 Reflect.get ( target, propertyKey [ , receiver ] ), https://tc39.es/ecma262/#sec-reflect.get
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get)
{
- auto* target = get_target_object_from(global_object, "get");
- if (!target)
+ auto target = vm.argument(0);
+ auto property_key = vm.argument(1);
+ auto receiver = vm.argument(2);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ }
+
+ // 2. Let key be ? ToPropertyKey(propertyKey).
+ auto key = property_key.to_property_key(global_object);
if (vm.exception())
return {};
- Value receiver = {};
- if (vm.argument_count() > 2)
- receiver = vm.argument(2);
- return target->get(property_key, receiver).value_or(js_undefined());
+
+ // 3. If receiver is not present, then
+ if (vm.argument_count() < 3) {
+ // a. Set receiver to target.
+ receiver = target;
+ }
+
+ // 4. Return ? target.[[Get]](key, receiver).
+ return target.as_object().internal_get(key, receiver);
}
// 28.1.6 Reflect.getOwnPropertyDescriptor ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor)
{
- auto* target = get_target_object_from(global_object, "getOwnPropertyDescriptor");
- if (!target)
+ auto target = vm.argument(0);
+ auto property_key = vm.argument(1);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ }
+
+ // 2. Let key be ? ToPropertyKey(propertyKey).
+ auto key = property_key.to_property_key(global_object);
if (vm.exception())
return {};
- return target->get_own_property_descriptor_object(property_key);
+
+ // 3. Let desc be ? target.[[GetOwnProperty]](key).
+ auto descriptor = target.as_object().internal_get_own_property(key);
+ if (vm.exception())
+ return {};
+
+ // 4. Return FromPropertyDescriptor(desc).
+ return from_property_descriptor(global_object, descriptor);
}
// 28.1.7 Reflect.getPrototypeOf ( target ), https://tc39.es/ecma262/#sec-reflect.getprototypeof
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_prototype_of)
{
- auto* target = get_target_object_from(global_object, "getPrototypeOf");
- if (!target)
+ auto target = vm.argument(0);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- return target->prototype();
+ }
+
+ // 2. Return ? target.[[GetPrototypeOf]]().
+ return target.as_object().internal_get_prototype_of();
}
// 28.1.8 Reflect.has ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.has
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has)
{
- auto* target = get_target_object_from(global_object, "has");
- if (!target)
+ auto target = vm.argument(0);
+ auto property_key = vm.argument(1);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ }
+
+ // 2. Let key be ? ToPropertyKey(propertyKey).
+ auto key = property_key.to_property_key(global_object);
if (vm.exception())
return {};
- return Value(target->has_property(property_key));
+
+ // 3. Return ? target.[[HasProperty]](key).
+ return Value(target.as_object().internal_has_property(key));
}
// 28.1.9 Reflect.isExtensible ( target ), https://tc39.es/ecma262/#sec-reflect.isextensible
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::is_extensible)
{
- auto* target = get_target_object_from(global_object, "isExtensible");
- if (!target)
+ auto target = vm.argument(0);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- return Value(target->is_extensible());
+ }
+
+ // 2. Return ? target.[[IsExtensible]]().
+ return Value(target.as_object().internal_is_extensible());
}
// 28.1.10 Reflect.ownKeys ( target ), https://tc39.es/ecma262/#sec-reflect.ownkeys
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys)
{
- auto* target = get_target_object_from(global_object, "ownKeys");
- if (!target)
+ auto target = vm.argument(0);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
+ return {};
+ }
+
+ // 2. Let keys be ? target.[[OwnPropertyKeys]]().
+ auto keys = target.as_object().internal_own_property_keys();
+ if (vm.exception())
return {};
- return Array::create_from(global_object, target->get_own_properties(PropertyKind::Key));
+
+ // 3. Return CreateArrayFromList(keys).
+ return Array::create_from(global_object, keys);
}
// 28.1.11 Reflect.preventExtensions ( target ), https://tc39.es/ecma262/#sec-reflect.preventextensions
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions)
{
- auto* target = get_target_object_from(global_object, "preventExtensions");
- if (!target)
+ auto target = vm.argument(0);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- return Value(target->prevent_extensions());
+ }
+
+ // 2. Return ? target.[[PreventExtensions]]().
+ return Value(target.as_object().internal_prevent_extensions());
}
// 28.1.12 Reflect.set ( target, propertyKey, V [ , receiver ] ), https://tc39.es/ecma262/#sec-reflect.set
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set)
{
- auto* target = get_target_object_from(global_object, "set");
- if (!target)
+ auto target = vm.argument(0);
+ auto property_key = vm.argument(1);
+ auto value = vm.argument(2);
+ auto receiver = vm.argument(3);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto property_key = vm.argument(1).to_property_key(global_object);
+ }
+
+ // 2. Let key be ? ToPropertyKey(propertyKey).
+ auto key = property_key.to_property_key(global_object);
if (vm.exception())
return {};
- auto value = vm.argument(2);
- Value receiver = {};
- if (vm.argument_count() > 3)
- receiver = vm.argument(3);
- return Value(target->put(property_key, value, receiver));
+
+ // 3. If receiver is not present, then
+ if (vm.argument_count() < 4) {
+ // a. Set receiver to target.
+ receiver = target;
+ }
+
+ // 4. Return ? target.[[Set]](key, V, receiver).
+ return Value(target.as_object().internal_set(key, value, receiver));
}
// 28.1.13 Reflect.setPrototypeOf ( target, proto ), https://tc39.es/ecma262/#sec-reflect.setprototypeof
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set_prototype_of)
{
- auto* target = get_target_object_from(global_object, "setPrototypeOf");
- if (!target)
+ auto target = vm.argument(0);
+ auto proto = vm.argument(1);
+
+ // 1. If Type(target) is not Object, throw a TypeError exception.
+ if (!target.is_object()) {
+ vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
return {};
- auto prototype_value = vm.argument(1);
- if (!prototype_value.is_object() && !prototype_value.is_null()) {
+ }
+
+ // 2. If Type(proto) is not Object and proto is not null, throw a TypeError exception.
+ if (!proto.is_object() && !proto.is_null()) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {};
}
- Object* prototype = nullptr;
- if (!prototype_value.is_null())
- prototype = const_cast<Object*>(&prototype_value.as_object());
- return Value(target->set_prototype(prototype));
+
+ // 3. Return ? target.[[SetPrototypeOf]](proto).
+ return Value(target.as_object().internal_set_prototype_of(proto.is_null() ? nullptr : &proto.as_object()));
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp
index a8ec9db24a..29cd6f1b99 100644
--- a/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/RegExpPrototype.cpp
@@ -178,27 +178,27 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec)
auto* array = Array::create(global_object, result.n_capture_groups + 1);
if (vm.exception())
return {};
- array->define_property(vm.names.index, Value((i32)match.global_offset));
- array->define_property(vm.names.input, js_string(vm, str));
- array->indexed_properties().put(array, 0, js_string(vm, match.view.to_string()));
+ array->create_data_property_or_throw(vm.names.index, Value((i32)match.global_offset));
+ array->create_data_property_or_throw(vm.names.input, js_string(vm, str));
+ array->create_data_property_or_throw(0, js_string(vm, match.view.to_string()));
for (size_t i = 0; i < result.n_capture_groups; ++i) {
auto capture_value = js_undefined();
auto& capture = result.capture_group_matches[0][i + 1];
if (!capture.view.is_null())
capture_value = js_string(vm, capture.view.to_string());
- array->indexed_properties().put(array, i + 1, capture_value);
+ array->create_data_property_or_throw(i + 1, capture_value);
}
Value groups = js_undefined();
if (result.n_named_capture_groups > 0) {
auto groups_object = Object::create(global_object, nullptr);
for (auto& entry : result.named_capture_group_matches[0])
- groups_object->define_property(entry.key, js_string(vm, entry.value.view.to_string()));
+ groups_object->create_data_property_or_throw(entry.key, js_string(vm, entry.value.view.to_string()));
groups = move(groups_object);
}
- array->define_property(vm.names.groups, groups);
+ array->create_data_property_or_throw(vm.names.groups, groups);
return array;
}
diff --git a/Userland/Libraries/LibJS/Runtime/Shape.cpp b/Userland/Libraries/LibJS/Runtime/Shape.cpp
index e0cf827d35..3ea10d6da5 100644
--- a/Userland/Libraries/LibJS/Runtime/Shape.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Shape.cpp
@@ -207,8 +207,9 @@ void Shape::remove_property_from_unique_shape(const StringOrSymbol& property_nam
}
}
-void Shape::add_property_without_transition(const StringOrSymbol& property_name, PropertyAttributes attributes)
+void Shape::add_property_without_transition(StringOrSymbol const& property_name, PropertyAttributes attributes)
{
+ VERIFY(property_name.is_valid());
ensure_property_table();
if (m_property_table->set(property_name, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry)
++m_property_count;
@@ -216,7 +217,8 @@ void Shape::add_property_without_transition(const StringOrSymbol& property_name,
FLATTEN void Shape::add_property_without_transition(PropertyName const& property_name, PropertyAttributes attributes)
{
- add_property_without_transition(StringOrSymbol(property_name.as_string()), attributes);
+ VERIFY(property_name.is_valid());
+ add_property_without_transition(property_name.to_string_or_symbol(), attributes);
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp
index 52c300121b..36cdb96c78 100644
--- a/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/StringConstructor.cpp
@@ -98,7 +98,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
StringBuilder builder;
for (size_t i = 0; i < literal_segments; ++i) {
auto next_key = String::number(i);
- auto next_segment_value = raw->get(next_key).value_or(js_undefined());
+ auto next_segment_value = raw->get(next_key);
if (vm.exception())
return {};
auto next_segment = next_segment_value.to_string(global_object);
diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.cpp b/Userland/Libraries/LibJS/Runtime/StringObject.cpp
index cef42a85d6..046c14335d 100644
--- a/Userland/Libraries/LibJS/Runtime/StringObject.cpp
+++ b/Userland/Libraries/LibJS/Runtime/StringObject.cpp
@@ -5,8 +5,10 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/StringObject.h>
namespace JS {
@@ -40,18 +42,149 @@ void StringObject::visit_edges(Cell::Visitor& visitor)
visitor.visit(&m_string);
}
-Optional<PropertyDescriptor> StringObject::get_own_property_descriptor(PropertyName const& property_name) const
+// 10.4.3.5 StringGetOwnProperty ( S, P ),https://tc39.es/ecma262/#sec-stringgetownproperty
+static Optional<PropertyDescriptor> string_get_own_property(GlobalObject& global_object, StringObject const& string, PropertyName const& property_name)
{
- if (!property_name.is_number() || property_name.as_number() >= m_string.string().length())
- return Base::get_own_property_descriptor(property_name);
+ auto& vm = global_object.vm();
- PropertyDescriptor descriptor;
- descriptor.value = js_string(heap(), m_string.string().substring(property_name.as_number(), 1));
- descriptor.attributes.set_has_configurable();
- descriptor.attributes.set_has_enumerable();
- descriptor.attributes.set_has_writable();
- descriptor.attributes.set_enumerable();
- return descriptor;
+ // 1. Assert: S is an Object that has a [[StringData]] internal slot.
+ // 2. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 3. If Type(P) is not String, return undefined.
+ // NOTE: The spec only uses string and symbol keys, and later coerces to numbers -
+ // this is not the case for PropertyName, so '!property_name.is_string()' would be wrong.
+ if (property_name.is_symbol())
+ return {};
+
+ // 4. Let index be ! CanonicalNumericIndexString(P).
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+ Value index;
+ if (property_name.is_string())
+ index = canonical_numeric_index_string(global_object, property_name.to_value(vm));
+ else
+ index = Value(property_name.as_number());
+ // 5. If index is undefined, return undefined.
+ if (index.is_undefined())
+ return {};
+ // 6. If IsIntegralNumber(index) is false, return undefined.
+ if (!index.is_integral_number())
+ return {};
+ // 7. If index is -0𝔽, return undefined.
+ if (index.is_negative_zero())
+ return {};
+
+ // 8. Let str be S.[[StringData]].
+ // 9. Assert: Type(str) is String.
+ auto& str = string.primitive_string().string();
+
+ // 10. Let len be the length of str.
+ auto length = str.length();
+
+ // 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined.
+ if (index.as_double() < 0 || length <= index.as_double())
+ return {};
+
+ // 12. Let resultStr be the String value of length 1, containing one code unit from str, specifically the code unit at index ℝ(index).
+ auto result_str = js_string(string.vm(), str.substring(index.as_double(), 1));
+
+ // 13. Return the PropertyDescriptor { [[Value]]: resultStr, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }.
+ return PropertyDescriptor {
+ .value = result_str,
+ .writable = false,
+ .enumerable = true,
+ .configurable = false,
+ };
+}
+
+// 10.4.3.1 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-string-exotic-objects-getownproperty-p
+Optional<PropertyDescriptor> StringObject::internal_get_own_property(PropertyName const& property_name) const
+{
+ // Assert: IsPropertyKey(P) is true.
+
+ // 2. Let desc be OrdinaryGetOwnProperty(S, P).
+ auto descriptor = Object::internal_get_own_property(property_name);
+
+ // 3. If desc is not undefined, return desc.
+ if (descriptor.has_value())
+ return descriptor;
+
+ // 4. Return ! StringGetOwnProperty(S, P).
+ return string_get_own_property(global_object(), *this, property_name);
+}
+
+// 10.4.3.2 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-string-exotic-objects-defineownproperty-p-desc
+bool StringObject::internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor)
+{
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Let stringDesc be ! StringGetOwnProperty(S, P).
+ auto string_descriptor = string_get_own_property(global_object(), *this, property_name);
+
+ // 3. If stringDesc is not undefined, then
+ if (string_descriptor.has_value()) {
+ // a. Let extensible be S.[[Extensible]].
+ auto extensible = m_is_extensible;
+
+ // b. Return ! IsCompatiblePropertyDescriptor(extensible, Desc, stringDesc).
+ return is_compatible_property_descriptor(extensible, property_descriptor, string_descriptor);
+ }
+
+ // 4. Return ! OrdinaryDefineOwnProperty(S, P, Desc).
+ return Object::internal_define_own_property(property_name, property_descriptor);
+}
+
+// 10.4.3.3 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-string-exotic-objects-ownpropertykeys
+MarkedValueList StringObject::internal_own_property_keys() const
+{
+ auto& vm = this->vm();
+
+ // 1. Let keys be a new empty List.
+ auto keys = MarkedValueList { heap() };
+
+ // 2. Let str be O.[[StringData]].
+ auto& str = m_string.string();
+
+ // 3. Assert: Type(str) is String.
+
+ // 4. Let len be the length of str.
+ auto length = str.length();
+
+ // 5. For each integer i starting with 0 such that i < len, in ascending order, do
+ for (size_t i = 0; i < length; ++i) {
+ // a. Add ! ToString(𝔽(i)) as the last element of keys.
+ keys.append(js_string(vm, String::number(i)));
+ }
+
+ // 6. For each own property key P of O such that P is an array index and ! ToIntegerOrInfinity(P) ≥ len, in ascending numeric index order, do
+ for (auto& entry : indexed_properties()) {
+ if (entry.index() >= length) {
+ // a. Add P as the last element of keys.
+ keys.append(js_string(vm, String::number(entry.index())));
+ }
+ }
+
+ // 7. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do
+ for (auto& it : shape().property_table_ordered()) {
+ if (it.key.is_string()) {
+ // a. Add P as the last element of keys.
+ keys.append(it.key.to_value(vm));
+ }
+ }
+
+ // 8. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
+ for (auto& it : shape().property_table_ordered()) {
+ if (it.key.is_symbol()) {
+ // a. Add P as the last element of keys.
+ keys.append(it.key.to_value(vm));
+ }
+ }
+
+ // 9. Return keys.
+ return keys;
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/StringObject.h b/Userland/Libraries/LibJS/Runtime/StringObject.h
index d05d171c7e..2fd5fb77e6 100644
--- a/Userland/Libraries/LibJS/Runtime/StringObject.h
+++ b/Userland/Libraries/LibJS/Runtime/StringObject.h
@@ -27,9 +27,12 @@ public:
}
private:
+ virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const&) const override;
+ virtual bool internal_define_own_property(PropertyName const&, PropertyDescriptor const&) override;
+ virtual MarkedValueList internal_own_property_keys() const override;
+
virtual bool is_string_object() const final { return true; }
virtual void visit_edges(Visitor&) override;
- virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName const&) const override;
PrimitiveString& m_string;
};
diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp
index dbd76c07d3..4e443d02cb 100644
--- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp
@@ -596,7 +596,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
size_t result_len = 0;
auto limit = NumericLimits<u32>::max();
- if (!vm.argument(1).is_undefined()) {
+ if (!limit_argument.is_undefined()) {
limit = limit_argument.to_u32(global_object);
if (vm.exception())
return {};
@@ -609,8 +609,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
if (limit == 0)
return result;
- if (vm.argument(0).is_undefined()) {
- result->define_property(0, js_string(vm, string));
+ if (separator_argument.is_undefined()) {
+ result->create_data_property_or_throw(0, js_string(vm, string));
return result;
}
@@ -618,7 +618,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
auto separator_len = separator.length();
if (len == 0) {
if (separator_len > 0)
- result->define_property(0, js_string(vm, string));
+ result->create_data_property_or_throw(0, js_string(vm, string));
return result;
}
@@ -638,7 +638,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
}
auto segment = string.substring_view(start, pos - start);
- result->define_property(result_len, js_string(vm, segment));
+ result->create_data_property_or_throw(result_len, js_string(vm, segment));
result_len++;
if (result_len == limit)
return result;
@@ -647,7 +647,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
}
auto rest = string.substring(start, len - start);
- result->define_property(result_len, js_string(vm, rest));
+ result->create_data_property_or_throw(result_len, js_string(vm, rest));
return result;
}
@@ -731,6 +731,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match)
if (!regexp.is_nullish()) {
if (auto* matcher = regexp.get_method(global_object, *vm.well_known_symbol_match()))
return vm.call(*matcher, regexp, this_object);
+ if (vm.exception())
+ return {};
}
auto s = this_object.to_string(global_object);
if (vm.exception())
@@ -793,6 +795,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
if (!search_value.is_nullish()) {
if (auto* replacer = search_value.get_method(global_object, *vm.well_known_symbol_replace()))
return vm.call(*replacer, search_value, this_object, replace_value);
+ if (vm.exception())
+ return {};
}
auto string = this_object.to_string(global_object);
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
index 61154f7362..efd5a145e7 100644
--- a/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
+++ b/Userland/Libraries/LibJS/Runtime/TypedArray.cpp
@@ -96,6 +96,8 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object,
}
auto element_length = src_array.array_length();
+ auto src_element_size = src_array.element_size();
+ auto src_byte_offset = src_array.byte_offset();
auto element_size = dest_array.element_size();
Checked<size_t> byte_length = element_size;
byte_length *= element_length;
@@ -104,6 +106,7 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object,
return;
}
+ // FIXME: Determine and use bufferConstructor
auto data = ArrayBuffer::create(global_object, byte_length.value());
if (src_data->is_detached()) {
@@ -116,26 +119,19 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object,
return;
}
+ u64 src_byte_index = src_byte_offset;
+ u64 target_byte_index = 0;
+ for (u32 i = 0; i < element_length; ++i) {
+ auto value = src_array.get_value_from_buffer(src_byte_index, ArrayBuffer::Order::Unordered);
+ data->template set_value<T>(target_byte_index, value, true, ArrayBuffer::Order::Unordered);
+ src_byte_index += src_element_size;
+ target_byte_index += element_size;
+ }
+
dest_array.set_viewed_array_buffer(data);
dest_array.set_byte_length(byte_length.value());
dest_array.set_byte_offset(0);
dest_array.set_array_length(element_length);
-
- for (u32 i = 0; i < element_length; i++) {
- Value v;
-#undef __JS_ENUMERATE
-#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \
- if (is<JS::ClassName>(src_array)) { \
- auto& src = static_cast<JS::ClassName&>(src_array); \
- v = src.get_by_index(i); \
- }
- JS_ENUMERATE_TYPED_ARRAYS
-#undef __JS_ENUMERATE
-
- VERIFY(!v.is_empty());
-
- dest_array.put_by_index(i, v);
- }
}
// 23.2.5.1.5 InitializeTypedArrayFromArrayLike, https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike
@@ -160,10 +156,10 @@ static void initialize_typed_array_from_array_like(GlobalObject& global_object,
typed_array.set_array_length(length);
for (size_t k = 0; k < length; k++) {
- auto value = array_like.get(k).value_or(js_undefined());
+ auto value = array_like.get(k);
if (vm.exception())
return;
- typed_array.put_by_index(k, value);
+ typed_array.set(k, value, true);
if (vm.exception())
return;
}
@@ -188,7 +184,7 @@ static void initialize_typed_array_from_list(GlobalObject& global_object, TypedA
auto& vm = global_object.vm();
for (size_t k = 0; k < list.size(); k++) {
auto value = list[k];
- typed_array.put_by_index(k, value);
+ typed_array.set(k, value, true);
if (vm.exception())
return;
}
diff --git a/Userland/Libraries/LibJS/Runtime/TypedArray.h b/Userland/Libraries/LibJS/Runtime/TypedArray.h
index c5ef9a28e7..c0db745a3e 100644
--- a/Userland/Libraries/LibJS/Runtime/TypedArray.h
+++ b/Userland/Libraries/LibJS/Runtime/TypedArray.h
@@ -1,13 +1,17 @@
/*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
+#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PropertyDescriptor.h>
+#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/TypedArrayConstructor.h>
#include <LibJS/Runtime/VM.h>
@@ -57,6 +61,114 @@ private:
virtual void visit_edges(Visitor&) override;
};
+// 10.4.5.9 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex
+inline bool is_valid_integer_index(TypedArrayBase const& typed_array, Value property_index)
+{
+ if (typed_array.viewed_array_buffer()->is_detached())
+ return false;
+
+ // TODO: This can be optimized by skipping the following 3 out of 4 checks if property_index
+ // came from a number-type PropertyName instead of a canonicalized string-type PropertyName
+
+ // If ! IsIntegralNumber(index) is false, return false.
+ if (!property_index.is_integral_number())
+ return false;
+ // If index is -0𝔽, return false.
+ if (property_index.is_negative_zero())
+ return false;
+
+ // If ℝ(index) < 0 or ℝ(index) ≥ O.[[ArrayLength]], return false.
+ if (property_index.as_double() < 0 || property_index.as_double() >= typed_array.array_length())
+ return false;
+
+ return true;
+}
+
+// 10.4.5.10 IntegerIndexedElementGet ( O, index ), https://tc39.es/ecma262/#sec-integerindexedelementget
+template<typename T>
+inline Value integer_indexed_element_get(TypedArrayBase const& typed_array, Value property_index)
+{
+ // 1. Assert: O is an Integer-Indexed exotic object.
+
+ // 2. If ! IsValidIntegerIndex(O, index) is false, return undefined.
+ if (!is_valid_integer_index(typed_array, property_index))
+ return js_undefined();
+
+ // 3. Let offset be O.[[ByteOffset]].
+ auto offset = typed_array.byte_offset();
+
+ // 4. Let arrayTypeName be the String value of O.[[TypedArrayName]].
+ // 5. Let elementSize be the Element Size value specified in Table 64 for arrayTypeName.
+ // 6. Let indexedPosition be (ℝ(index) × elementSize) + offset.
+ Checked<size_t> indexed_position = (i64)property_index.as_double();
+ indexed_position *= typed_array.element_size();
+ indexed_position += offset;
+ // FIXME: Not exactly sure what we should do when overflow occurs.
+ // Just return as if it's an invalid index for now.
+ if (indexed_position.has_overflow()) {
+ dbgln("integer_indexed_element_get(): indexed_position overflowed, returning as if it's an invalid index.");
+ return js_undefined();
+ }
+
+ // 7. Let elementType be the Element Type value in Table 64 for arrayTypeName.
+ // 8. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered).
+ return typed_array.viewed_array_buffer()->template get_value<T>(indexed_position.value(), true, ArrayBuffer::Order::Unordered);
+}
+
+// 10.4.5.11 IntegerIndexedElementSet ( O, index, value ), https://tc39.es/ecma262/#sec-integerindexedelementset
+// NOTE: In error cases, the function will return as if it succeeded.
+template<typename T>
+inline void integer_indexed_element_set(TypedArrayBase& typed_array, Value property_index, Value value)
+{
+ VERIFY(!value.is_empty());
+ auto& vm = typed_array.vm();
+ auto& global_object = typed_array.global_object();
+
+ // 1. Assert: O is an Integer-Indexed exotic object.
+
+ Value num_value;
+
+ // 2. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value).
+ if (typed_array.content_type() == TypedArrayBase::ContentType::BigInt) {
+ num_value = value.to_bigint(global_object);
+ if (vm.exception())
+ return;
+ }
+ // 3. Otherwise, let numValue be ? ToNumber(value).
+ else {
+ num_value = value.to_number(global_object);
+ if (vm.exception())
+ return;
+ }
+
+ // 4. If ! IsValidIntegerIndex(O, index) is true, then
+ // NOTE: Inverted for flattened logic.
+ if (!is_valid_integer_index(typed_array, property_index))
+ return;
+
+ // a. Let offset be O.[[ByteOffset]].
+ auto offset = typed_array.byte_offset();
+
+ // b. Let arrayTypeName be the String value of O.[[TypedArrayName]].
+ // c. Let elementSize be the Element Size value specified in Table 64 for arrayTypeName.
+ // d. Let indexedPosition be (ℝ(index) × elementSize) + offset.
+ Checked<size_t> indexed_position = (i64)property_index.as_double();
+ indexed_position *= typed_array.element_size();
+ indexed_position += offset;
+ // FIXME: Not exactly sure what we should do when overflow occurs.
+ // Just return as if it succeeded for now.
+ if (indexed_position.has_overflow()) {
+ dbgln("integer_indexed_element_set(): indexed_position overflowed, returning as if succeeded.");
+ return;
+ }
+
+ // e. Let elementType be the Element Type value in Table 64 for arrayTypeName.
+ // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered).
+ typed_array.viewed_array_buffer()->template set_value<T>(indexed_position.value(), num_value, true, ArrayBuffer::Order::Unordered);
+
+ // 5. Return NormalCompletion(undefined).
+}
+
template<typename T>
class TypedArray : public TypedArrayBase {
JS_OBJECT(TypedArray, TypedArrayBase);
@@ -64,72 +176,286 @@ class TypedArray : public TypedArrayBase {
using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>;
public:
- // 10.4.5.11 IntegerIndexedElementSet ( O, index, value ), https://tc39.es/ecma262/#sec-integerindexedelementset
- // NOTE: In error cases, the function will return as if it succeeded.
- virtual bool put_by_index(u32 property_index, Value value) override
+ // 10.4.5.1 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p
+ virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const& property_name) const override
{
- auto& vm = this->vm();
- auto& global_object = this->global_object();
-
- Value num_value;
- if (content_type() == TypedArrayBase::ContentType::BigInt) {
- num_value = value.to_bigint(global_object);
- if (vm.exception())
- return {};
- } else {
- num_value = value.to_number(global_object);
- if (vm.exception())
- return {};
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Assert: O is an Integer-Indexed exotic object.
+
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+
+ // 3. If Type(P) is String, then
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ if (property_name.is_string() || property_name.is_number()) {
+ // a. Let numericIndex be ! CanonicalNumericIndexString(P).
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ Value numeric_index;
+ if (property_name.is_string())
+ numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm()));
+ else
+ numeric_index = Value(property_name.as_number());
+ // b. If numericIndex is not undefined, then
+ if (!numeric_index.is_undefined()) {
+ // i. Let value be ! IntegerIndexedElementGet(O, numericIndex).
+ auto value = integer_indexed_element_get<T>(*this, numeric_index);
+
+ // ii. If value is undefined, return undefined.
+ if (value.is_undefined())
+ return {};
+
+ // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
+ return PropertyDescriptor {
+ .value = value,
+ .writable = true,
+ .enumerable = true,
+ .configurable = true,
+ };
+ }
}
- if (!is_valid_integer_index(property_index))
- return true;
+ // 4. Return OrdinaryGetOwnProperty(O, P).
+ return Object::internal_get_own_property(property_name);
+ }
- auto offset = byte_offset();
+ // 10.4.5.2 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p
+ virtual bool internal_has_property(PropertyName const& property_name) const override
+ {
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Assert: O is an Integer-Indexed exotic object.
+
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+
+ // 3. If Type(P) is String, then
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ if (property_name.is_string() || property_name.is_number()) {
+ // a. Let numericIndex be ! CanonicalNumericIndexString(P).
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ Value numeric_index;
+ if (property_name.is_string())
+ numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm()));
+ else
+ numeric_index = Value(property_name.as_number());
+ // b. If numericIndex is not undefined, return ! IsValidIntegerIndex(O, numericIndex).
+ if (!numeric_index.is_undefined())
+ return is_valid_integer_index(*this, numeric_index);
+ }
- // FIXME: Not exactly sure what we should do when overflow occurs.
- // Just return as if it succeeded for now.
- Checked<size_t> indexed_position = property_index;
- indexed_position *= sizeof(UnderlyingBufferDataType);
- indexed_position += offset;
- if (indexed_position.has_overflow()) {
- dbgln("TypedArray::put_by_index: indexed_position overflowed, returning as if succeeded.");
- return true;
+ // 4. Return ? OrdinaryHasProperty(O, P).
+ return Object::internal_has_property(property_name);
+ }
+
+ // 10.4.5.3 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc
+ virtual bool internal_define_own_property(PropertyName const& property_name, PropertyDescriptor const& property_descriptor) override
+ {
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Assert: O is an Integer-Indexed exotic object.
+
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+
+ // 3. If Type(P) is String, then
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ if (property_name.is_string() || property_name.is_number()) {
+ // a. Let numericIndex be ! CanonicalNumericIndexString(P).
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ Value numeric_index;
+ if (property_name.is_string())
+ numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm()));
+ else
+ numeric_index = Value(property_name.as_number());
+ // b. If numericIndex is not undefined, then
+ if (!numeric_index.is_undefined()) {
+ // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return false.
+ if (!is_valid_integer_index(*this, numeric_index))
+ return false;
+
+ // ii. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, return false.
+ if (property_descriptor.configurable.has_value() && !*property_descriptor.configurable)
+ return false;
+
+ // iii. If Desc has an [[Enumerable]] field and if Desc.[[Enumerable]] is false, return false.
+ if (property_descriptor.enumerable.has_value() && !*property_descriptor.enumerable)
+ return false;
+
+ // iv. If ! IsAccessorDescriptor(Desc) is true, return false.
+ if (property_descriptor.is_accessor_descriptor())
+ return false;
+
+ // v. If Desc has a [[Writable]] field and if Desc.[[Writable]] is false, return false.
+ if (property_descriptor.writable.has_value() && !*property_descriptor.writable)
+ return false;
+
+ // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]).
+ if (property_descriptor.value.has_value()) {
+ integer_indexed_element_set<T>(*this, numeric_index, *property_descriptor.value);
+ if (vm().exception())
+ return {};
+ }
+
+ // vii. Return true.
+ return true;
+ }
}
- viewed_array_buffer()->template set_value<T>(indexed_position.value(), num_value, true, ArrayBuffer::Order::Unordered);
+ // 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc).
+ return Object::internal_define_own_property(property_name, property_descriptor);
+ }
- return true;
+ // 10.4.5.4 [[Get]] ( P, Receiver ), 10.4.5.4 [[Get]] ( P, Receiver )
+ virtual Value internal_get(PropertyName const& property_name, Value receiver) const override
+ {
+ VERIFY(!receiver.is_empty());
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+
+ // 2. If Type(P) is String, then
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ if (property_name.is_string() || property_name.is_number()) {
+ // a. Let numericIndex be ! CanonicalNumericIndexString(P).
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ Value numeric_index;
+ if (property_name.is_string())
+ numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm()));
+ else
+ numeric_index = Value(property_name.as_number());
+ // b. If numericIndex is not undefined, then
+ if (!numeric_index.is_undefined()) {
+ // i. Return ! IntegerIndexedElementGet(O, numericIndex).
+ return integer_indexed_element_get<T>(*this, numeric_index);
+ }
+ }
+
+ // 3. Return ? OrdinaryGet(O, P, Receiver).
+ return Object::internal_get(property_name, receiver);
}
- // 10.4.5.10 IntegerIndexedElementGet ( O, index ), https://tc39.es/ecma262/#sec-integerindexedelementget
- virtual Value get_by_index(u32 property_index, AllowSideEffects = AllowSideEffects::Yes) const override
+ // 10.4.5.5 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
+ virtual bool internal_set(PropertyName const& property_name, Value value, Value receiver) override
{
- if (!is_valid_integer_index(property_index))
- return js_undefined();
-
- auto offset = byte_offset();
-
- // FIXME: Not exactly sure what we should do when overflow occurs.
- // Just return as if it's an invalid index for now.
- Checked<size_t> indexed_position = property_index;
- indexed_position *= sizeof(UnderlyingBufferDataType);
- indexed_position += offset;
- if (indexed_position.has_overflow()) {
- dbgln("TypedArray::get_by_index: indexed_position overflowed, returning as if it's an invalid index.");
- return js_undefined();
+ VERIFY(!value.is_empty());
+ VERIFY(!receiver.is_empty());
+
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+
+ // 2. If Type(P) is String, then
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ if (property_name.is_string() || property_name.is_number()) {
+ // a. Let numericIndex be ! CanonicalNumericIndexString(P).
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ Value numeric_index;
+ if (property_name.is_string())
+ numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm()));
+ else
+ numeric_index = Value(property_name.as_number());
+ // b. If numericIndex is not undefined, then
+ if (!numeric_index.is_undefined()) {
+ // i. Perform ? IntegerIndexedElementSet(O, numericIndex, V).
+ integer_indexed_element_set<T>(*this, numeric_index, value);
+ if (vm().exception())
+ return {};
+
+ // ii. Return true.
+ return true;
+ }
}
- return viewed_array_buffer()->template get_value<T>(indexed_position.value(), true, ArrayBuffer::Order::Unordered);
+ // 3. Return ? OrdinarySet(O, P, V, Receiver).
+ return Object::internal_set(property_name, value, receiver);
}
- // 10.4.5.2 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p
- bool has_property(const PropertyName& name) const override
+ // 10.4.5.6 [[Delete]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p
+ virtual bool internal_delete(PropertyName const& property_name) override
{
- if (name.is_number()) {
- return is_valid_integer_index(name.as_number());
+ // 1. Assert: IsPropertyKey(P) is true.
+ VERIFY(property_name.is_valid());
+
+ // 2. Assert: O is an Integer-Indexed exotic object.
+
+ // NOTE: If the property name is a number type (An implementation-defined optimized
+ // property key type), it can be treated as a string property that has already been
+ // converted successfully into a canonical numeric index.
+
+ // 3. If Type(P) is String, then
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ if (property_name.is_string() || property_name.is_number()) {
+ // a. Let numericIndex be ! CanonicalNumericIndexString(P).
+ // NOTE: This includes an implementation-defined optimization, see note above!
+ Value numeric_index;
+ if (property_name.is_string())
+ numeric_index = canonical_numeric_index_string(global_object(), property_name.to_value(vm()));
+ else
+ numeric_index = Value(property_name.as_number());
+ // b. If numericIndex is not undefined, then
+ if (!numeric_index.is_undefined()) {
+ // i. If ! IsValidIntegerIndex(O, numericIndex) is false, return true; else return false.
+ if (!is_valid_integer_index(*this, numeric_index))
+ return true;
+ return false;
+ }
+ }
+
+ // 4. Return ? OrdinaryDelete(O, P).
+ return Object::internal_delete(property_name);
+ }
+
+ // 10.4.5.7 [[OwnPropertyKeys]] ( ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys
+ virtual MarkedValueList internal_own_property_keys() const override
+ {
+ auto& vm = this->vm();
+
+ // 1. Let keys be a new empty List.
+ auto keys = MarkedValueList { heap() };
+
+ // 2. Assert: O is an Integer-Indexed exotic object.
+
+ // 3. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then
+ if (!m_viewed_array_buffer->is_detached()) {
+ // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do
+ for (size_t i = 0; i < m_array_length; ++i) {
+ // i. Add ! ToString(𝔽(i)) as the last element of keys.
+ keys.append(js_string(vm, String::number(i)));
+ }
+ }
+
+ // 4. For each own property key P of O such that Type(P) is String and P is not an integer index, in ascending chronological order of property creation, do
+ for (auto& it : shape().property_table_ordered()) {
+ if (it.key.is_string()) {
+ // a. Add P as the last element of keys.
+ keys.append(it.key.to_value(vm));
+ }
}
- return Object::has_property(name);
+
+ // 5. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do
+ for (auto& it : shape().property_table_ordered()) {
+ if (it.key.is_symbol()) {
+ // a. Add P as the last element of keys.
+ keys.append(it.key.to_value(vm));
+ }
+ }
+
+ // 6. Return keys.
+ return keys;
}
Span<const UnderlyingBufferDataType> data() const
@@ -160,22 +486,6 @@ protected:
private:
virtual bool is_typed_array() const final { return true; }
-
- // 10.4.5.9 IsValidIntegerIndex ( O, index ), https://tc39.es/ecma262/#sec-isvalidintegerindex
- bool is_valid_integer_index(u32 property_index) const
- {
- if (viewed_array_buffer()->is_detached())
- return false;
-
- // FIXME: If ! IsIntegralNumber(index) is false, return false.
-
- // FIXME: If index is -0𝔽, return false.
-
- if (property_index >= m_array_length /* FIXME: or less than 0 (index is currently unsigned) */)
- return false;
-
- return true;
- }
};
#define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \
diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp
index 0ceae521d0..0ba42530a6 100644
--- a/Userland/Libraries/LibJS/Runtime/VM.cpp
+++ b/Userland/Libraries/LibJS/Runtime/VM.cpp
@@ -294,7 +294,7 @@ void VM::assign(const NonnullRefPtr<BindingPattern>& target, Value value, Global
auto* rest_object = Object::create(global_object, global_object.object_prototype());
for (auto& object_property : object->shape().property_table()) {
- if (!object_property.value.attributes.has_enumerable())
+ if (!object_property.value.attributes.is_enumerable())
continue;
if (seen_names.contains(object_property.key.to_display_string()))
continue;
@@ -386,10 +386,13 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
return possible_match.value().value;
}
}
- auto value = global_object.get(name);
- if (m_underscore_is_last_value && name == "_" && value.is_empty())
- return m_last_value;
- return value;
+
+ if (!global_object.storage_has(name)) {
+ if (m_underscore_is_last_value && name == "_")
+ return m_last_value;
+ return {};
+ }
+ return global_object.get(name);
}
// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference
@@ -472,9 +475,6 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
callee_context.this_value = this_argument;
auto result = function.construct(new_target);
- Value this_value = this_argument;
- if (auto* environment = callee_context.lexical_environment)
- this_value = environment->get_this_binding(global_object);
pop_execution_context();
pop_guard.disarm();
@@ -487,7 +487,7 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
if (exception())
return {};
if (prototype.is_object()) {
- result.as_object().set_prototype(&prototype.as_object());
+ result.as_object().internal_set_prototype_of(&prototype.as_object());
if (exception())
return {};
}
@@ -500,7 +500,9 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
if (result.is_object())
return result;
- return this_value;
+ if (auto* environment = callee_context.lexical_environment)
+ return environment->get_this_binding(global_object);
+ return this_argument;
}
void VM::throw_exception(Exception& exception)
diff --git a/Userland/Libraries/LibJS/Runtime/Value.cpp b/Userland/Libraries/LibJS/Runtime/Value.cpp
index a622beb72c..e626eb401a 100644
--- a/Userland/Libraries/LibJS/Runtime/Value.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Value.cpp
@@ -258,7 +258,7 @@ bool Value::is_regexp(GlobalObject& global_object) const
auto matcher = as_object().get(*vm.well_known_symbol_match());
if (vm.exception())
return false;
- if (!matcher.is_empty() && !matcher.is_undefined())
+ if (!matcher.is_undefined())
return matcher.to_boolean();
return is<RegExpObject>(as_object());
@@ -807,7 +807,7 @@ Value Value::get(GlobalObject& global_object, PropertyName const& property_name)
return {};
// 3. Return ? O.[[Get]](P, V).
- return object->get(property_name, *this);
+ return object->internal_get(property_name, *this);
}
// 7.3.10 GetMethod ( V, P ), https://tc39.es/ecma262/#sec-getmethod
@@ -819,7 +819,7 @@ FunctionObject* Value::get_method(GlobalObject& global_object, PropertyName cons
VERIFY(property_name.is_valid());
// 2. Let func be ? GetV(V, P).
- auto function = get(global_object, property_name).value_or(js_undefined());
+ auto function = get(global_object, property_name);
if (vm.exception())
return nullptr;
@@ -1252,6 +1252,7 @@ Value instance_of(GlobalObject& global_object, Value lhs, Value rhs)
return ordinary_has_instance(global_object, lhs, rhs);
}
+// 7.3.21 OrdinaryHasInstance ( C, O ), https://tc39.es/ecma262/#sec-ordinaryhasinstance
Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs)
{
auto& vm = global_object.vm();
@@ -1277,7 +1278,7 @@ Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs)
return {};
}
while (true) {
- lhs_object = lhs_object->prototype();
+ lhs_object = lhs_object->internal_get_prototype_of();
if (vm.exception())
return {};
if (!lhs_object)
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js
index 644091c90c..84259b9258 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.defineProperty.js
@@ -167,10 +167,7 @@ describe("errors", () => {
expect(() => {
Object.defineProperty(o, "foo", { value: 2, writable: true, enumerable: false });
- }).toThrowWithMessage(
- TypeError,
- "Cannot change attributes of non-configurable property 'foo'"
- );
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
});
test("redefine non-configurable symbol property", () => {
@@ -180,10 +177,7 @@ describe("errors", () => {
expect(() => {
Object.defineProperty(o, s, { value: 2, writable: true, enumerable: false });
- }).toThrowWithMessage(
- TypeError,
- "Cannot change attributes of non-configurable property 'Symbol(foo)'"
- );
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
});
test("cannot define 'value' and 'get' in the same descriptor", () => {
@@ -234,9 +228,6 @@ describe("errors", () => {
return this.secret_foo + 2;
},
});
- }).toThrowWithMessage(
- TypeError,
- "Cannot change attributes of non-configurable property 'foo'"
- );
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js
index c523614873..f8b7a89f69 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.freeze.js
@@ -34,10 +34,7 @@ describe("normal behavior", () => {
// expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o);
expect(() => {
Object.defineProperty(o, "foo", { configurable: true });
- }).toThrowWithMessage(
- TypeError,
- "Cannot change attributes of non-configurable property 'foo'"
- );
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
});
test("prevents changing value of existing properties", () => {
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js
index 292c3f5999..165e86f414 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.preventExtensions.js
@@ -47,7 +47,7 @@ describe("errors", () => {
expect(() => {
Object.defineProperty(o, "baz", { value: "baz" });
- }).toThrowWithMessage(TypeError, "Cannot define property baz on non-extensible object");
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
expect(o.baz).toBeUndefined();
});
@@ -59,7 +59,7 @@ describe("errors", () => {
expect(() => {
"use strict";
o.foo = "foo";
- }).toThrowWithMessage(TypeError, "Cannot define property foo on non-extensible object");
+ }).toThrowWithMessage(TypeError, "Cannot set property 'foo' of [object Object]");
expect((o.foo = "foo")).toBe("foo");
expect(o.foo).toBeUndefined();
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js
index 1df167766d..d48b0bd9f6 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Object/Object.seal.js
@@ -34,10 +34,7 @@ describe("normal behavior", () => {
// expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o);
expect(() => {
Object.defineProperty(o, "foo", { configurable: true });
- }).toThrowWithMessage(
- TypeError,
- "Cannot change attributes of non-configurable property 'foo'"
- );
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
});
test("doesn't prevent changing value of existing properties", () => {
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js
index 2044ea8084..f933566593 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.handler-defineProperty.js
@@ -77,7 +77,7 @@ describe("[[DefineProperty]] invariants", () => {
expect(() => {
Object.defineProperty(p, "foo", {});
- }).toThrowWithMessage(TypeError, "Object's [[DefineProperty]] method returned false");
+ }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
});
test("trap cannot return true for a non-extensible target if the property does not exist", () => {
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js
index 504a95bba0..d8787b9b6a 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.apply.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
expect(() => {
Reflect.apply(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.apply() must be a function"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not a function`);
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js
index 27529f296d..c0313392bb 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.construct.js
@@ -3,14 +3,11 @@ test("length is 2", () => {
});
describe("errors", () => {
- test("target must be a function", () => {
+ test("target must be a constructor", () => {
[null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
expect(() => {
Reflect.construct(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.construct() must be a constructor"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not a constructor`);
});
});
@@ -22,14 +19,11 @@ describe("errors", () => {
});
});
- test("new target must be a function", () => {
+ test("new target must be a constructor", () => {
[null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
expect(() => {
Reflect.construct(() => {}, [], value);
- }).toThrowWithMessage(
- TypeError,
- "Optional third argument of Reflect.construct() must be a constructor"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not a constructor`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js
index 786d4c0178..c81da19678 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.defineProperty.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.defineProperty(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.defineProperty() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
@@ -18,7 +15,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.defineProperty({}, "foo", value);
- }).toThrowWithMessage(TypeError, "Descriptor argument is not an object");
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js
index a0dcbbada7..beb95eff0e 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.deleteProperty.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.deleteProperty(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.deleteProperty() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js
index b26e9fe888..0b649a3d25 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.get.js
@@ -7,7 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.get(value);
- }).toThrowWithMessage(TypeError, "First argument of Reflect.get() must be an object");
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js
index e343a3f6fc..bb611edecb 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getOwnPropertyDescriptor.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.getOwnPropertyDescriptor(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.getOwnPropertyDescriptor() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js
index 87c9a63e87..ddc15d2c96 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.getPrototypeOf.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.getPrototypeOf(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.getPrototypeOf() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js
index f343daeafa..72f617b4fb 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.has.js
@@ -7,7 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.has(value);
- }).toThrowWithMessage(TypeError, "First argument of Reflect.has() must be an object");
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js
index 2f873da66c..c4baf5f552 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.isExtensible.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.isExtensible(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.isExtensible() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js
index 53ca14796f..7839584391 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.ownKeys.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.ownKeys(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.ownKeys() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js
index 6b0bfdc1bc..dab332e122 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.preventExtensions.js
@@ -3,10 +3,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.preventExtensions(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.preventExtensions() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js
index 854fb23888..50635f33d3 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.set.js
@@ -7,7 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.set(value);
- }).toThrowWithMessage(TypeError, "First argument of Reflect.set() must be an object");
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
});
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js
index 70bbdf9d9c..75424daf39 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Reflect/Reflect.setPrototypeOf.js
@@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => {
Reflect.setPrototypeOf(value);
- }).toThrowWithMessage(
- TypeError,
- "First argument of Reflect.setPrototypeOf() must be an object"
- );
+ }).toThrowWithMessage(TypeError, `${value} is not an object`);
});
});
diff --git a/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js b/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js
index ece57fe404..bc8dbe3d8c 100644
--- a/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js
+++ b/Userland/Libraries/LibJS/Tests/classes/class-advanced-extends.js
@@ -13,7 +13,7 @@ test("extending null", () => {
expect(() => {
new A();
- }).toThrowWithMessage(ReferenceError, "|this| has not been initialized");
+ }).toThrowWithMessage(TypeError, "Super constructor is not a constructor");
});
test("extending String", () => {
diff --git a/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js b/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js
index 2540beaf68..c0808ce0fc 100644
--- a/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js
+++ b/Userland/Libraries/LibJS/Tests/custom-@@hasInstance.js
@@ -1,8 +1,8 @@
test("basic functionality", () => {
function Foo() {}
- Foo[Symbol.hasInstance] = value => {
- return value === 2;
- };
+ Object.defineProperty(Foo, Symbol.hasInstance, {
+ value: instance => instance === 2,
+ });
expect(new Foo() instanceof Foo).toBeFalse();
expect(2 instanceof Foo).toBeTrue();
diff --git a/Userland/Libraries/LibTest/JavaScriptTestRunner.h b/Userland/Libraries/LibTest/JavaScriptTestRunner.h
index 940ad41e54..0c04276d7b 100644
--- a/Userland/Libraries/LibTest/JavaScriptTestRunner.h
+++ b/Userland/Libraries/LibTest/JavaScriptTestRunner.h
@@ -357,7 +357,7 @@ inline JSFileResult TestRunner::run_file_test(const String& test_path)
// Collect logged messages
auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array();
for (auto& entry : arr.indexed_properties()) {
- auto message = entry.value_and_attributes(&interpreter->global_object()).value;
+ auto message = arr.get(entry.index());
file_result.logged_messages.append(message.to_string_without_side_effects());
}
diff --git a/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp b/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp
index 8de111ab1b..c265cce00a 100644
--- a/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp
+++ b/Userland/Libraries/LibWeb/Bindings/CSSStyleDeclarationWrapperCustom.cpp
@@ -11,12 +11,12 @@
namespace Web::Bindings {
-JS::Value CSSStyleDeclarationWrapper::get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const
+JS::Value CSSStyleDeclarationWrapper::internal_get(const JS::PropertyName& name, JS::Value receiver) const
{
// FIXME: These should actually use camelCase versions of the property names!
auto property_id = CSS::property_id_from_string(name.to_string());
if (property_id == CSS::PropertyID::Invalid)
- return Base::get(name, receiver, allow_side_effects);
+ return Base::internal_get(name, receiver);
for (auto& property : impl().properties()) {
if (property.property_id == property_id)
return js_string(vm(), property.value->to_string());
@@ -24,12 +24,12 @@ JS::Value CSSStyleDeclarationWrapper::get(const JS::PropertyName& name, JS::Valu
return js_string(vm(), String::empty());
}
-bool CSSStyleDeclarationWrapper::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver)
+bool CSSStyleDeclarationWrapper::internal_set(const JS::PropertyName& name, JS::Value value, JS::Value receiver)
{
// FIXME: These should actually use camelCase versions of the property names!
auto property_id = CSS::property_id_from_string(name.to_string());
if (property_id == CSS::PropertyID::Invalid)
- return Base::put(name, value, receiver);
+ return Base::internal_set(name, value, receiver);
auto css_text = value.to_string(global_object());
if (vm().exception())
diff --git a/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp b/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp
index 950ddb1d06..5c51e06203 100644
--- a/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp
+++ b/Userland/Libraries/LibWeb/Bindings/HTMLCollectionWrapperCustom.cpp
@@ -12,21 +12,17 @@
namespace Web::Bindings {
-JS::Value HTMLCollectionWrapper::get(JS::PropertyName const& name, JS::Value receiver, JS::AllowSideEffects allow_side_effects) const
+JS::Value HTMLCollectionWrapper::internal_get(JS::PropertyName const& property_name, JS::Value receiver) const
{
- if (!name.is_string())
- return Base::get(name, receiver, allow_side_effects);
- auto* item = const_cast<DOM::HTMLCollection&>(impl()).named_item(name.to_string());
+ if (property_name.is_symbol())
+ return Base::internal_get(property_name, receiver);
+ DOM::Element* item = nullptr;
+ if (property_name.is_string())
+ item = const_cast<DOM::HTMLCollection&>(impl()).named_item(property_name.to_string());
+ else if (property_name.is_number())
+ item = const_cast<DOM::HTMLCollection&>(impl()).item(property_name.as_number());
if (!item)
- return Base::get(name, receiver, allow_side_effects);
- return JS::Value { wrap(global_object(), *item) };
-}
-
-JS::Value HTMLCollectionWrapper::get_by_index(u32 property_index, JS::AllowSideEffects allow_side_effects) const
-{
- auto* item = const_cast<DOM::HTMLCollection&>(impl()).item(property_index);
- if (!item)
- return Base::get_by_index(property_index, allow_side_effects);
+ return Base::internal_get(property_name, receiver);
return wrap(global_object(), *item);
}
diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
index b5c54ef232..5029fd2cdf 100644
--- a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
+++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp
@@ -43,7 +43,8 @@ void WindowObject::initialize_global_object()
{
Base::initialize_global_object();
- Object::set_prototype(&ensure_web_prototype<EventTargetPrototype>("EventTarget"));
+ auto success = Object::internal_set_prototype_of(&ensure_web_prototype<EventTargetPrototype>("EventTarget"));
+ VERIFY(success);
define_property("window", this, JS::Attribute::Enumerable);
define_property("frames", this, JS::Attribute::Enumerable);
diff --git a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl
index 6b5ad0ffce..d4f425e9b4 100644
--- a/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl
+++ b/Userland/Libraries/LibWeb/CSS/CSSStyleDeclaration.idl
@@ -1,4 +1,4 @@
-[CustomGet,CustomPut]
+[CustomGet,CustomSet]
interface CSSStyleDeclaration {
readonly attribute unsigned long length;
diff --git a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
index e32366e33d..381c369007 100644
--- a/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
+++ b/Userland/Libraries/LibWeb/CodeGenerators/WrapperGenerator.cpp
@@ -786,17 +786,12 @@ public:
if (interface.extended_attributes.contains("CustomGet")) {
generator.append(R"~~~(
- virtual JS::Value get(const JS::PropertyName&, JS::Value receiver = {}, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override;
+ virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override;
)~~~");
}
- if (interface.extended_attributes.contains("CustomGetByIndex")) {
+ if (interface.extended_attributes.contains("CustomSet")) {
generator.append(R"~~~(
- virtual JS::Value get_by_index(u32 property_index, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override;
-)~~~");
- }
- if (interface.extended_attributes.contains("CustomPut")) {
- generator.append(R"~~~(
- virtual bool put(const JS::PropertyName&, JS::Value, JS::Value receiver = {}) override;
+ virtual bool internal_set(const JS::PropertyName&, JS::Value, JS::Value receiver) override;
)~~~");
}
@@ -911,7 +906,8 @@ namespace Web::Bindings {
@wrapper_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
: @wrapper_base_class@(global_object, impl)
{
- set_prototype(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@"));
+ auto success = internal_set_prototype_of(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_class@>("@name@"));
+ VERIFY(success);
}
)~~~");
}
@@ -1277,11 +1273,13 @@ namespace Web::Bindings {
// https://heycam.github.io/webidl/#es-DOMException-specialness
// Object.getPrototypeOf(DOMException.prototype) === Error.prototype
generator.append(R"~~~(
- set_prototype(global_object.error_prototype());
+ auto success = internal_set_prototype_of(global_object.error_prototype());
+ VERIFY(success);
)~~~");
} else if (!interface.parent_name.is_empty()) {
generator.append(R"~~~(
- set_prototype(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_base_class@>("@parent_name@"));
+ auto success = internal_set_prototype_of(&static_cast<WindowObject&>(global_object).ensure_web_prototype<@prototype_base_class@>("@parent_name@"));
+ VERIFY(success);
)~~~");
}
diff --git a/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl b/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl
index 89cc874c8a..73bcf070f1 100644
--- a/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl
+++ b/Userland/Libraries/LibWeb/DOM/HTMLCollection.idl
@@ -1,4 +1,4 @@
-[CustomGet,CustomGetByIndex]
+[CustomGet]
interface HTMLCollection {
readonly attribute unsigned long length;
diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp
index 603550da79..730e2cf12b 100644
--- a/Userland/Utilities/js.cpp
+++ b/Userland/Utilities/js.cpp
@@ -192,7 +192,7 @@ static void print_array(JS::Array& array, HashTable<JS::Object*>& seen_objects)
bool first = true;
for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) {
print_separator(first);
- auto value = it.value_and_attributes(&array).value;
+ auto value = array.get(it.index());
// The V8 repl doesn't throw an exception here, and instead just
// prints 'undefined'. We may choose to replicate that behavior in
// the future, but for now lets just catch the error
@@ -212,7 +212,7 @@ static void print_object(JS::Object& object, HashTable<JS::Object*>& seen_object
for (auto& entry : object.indexed_properties()) {
print_separator(first);
out("\"\033[33;1m{}\033[0m\": ", entry.index());
- auto value = entry.value_and_attributes(&object).value;
+ auto value = object.get(entry.index());
// The V8 repl doesn't throw an exception here, and instead just
// prints 'undefined'. We may choose to replicate that behavior in
// the future, but for now lets just catch the error