LibJS: Rewrite most of Object for spec compliance :^)

This is a huge patch, I know. In hindsight this perhaps could've been
done slightly more incremental, but I started and then fixed everything
until it worked, and here we are. I tried splitting of some completely
unrelated changes into separate commits, however. Anyway.

This is a rewrite of most of Object, and by extension large parts of
Array, Proxy, Reflect, String, TypedArray, and some other things.

What we already had worked fine for about 90% of things, but getting the
last 10% right proved to be increasingly difficult with the current code
that sort of grew organically and is only very loosely based on the
spec - this became especially obvious when we started fixing a large
number of test262 failures.

Key changes include:

- 1:1 matching function names and parameters of all object-related
  functions, to avoid ambiguity. Previously we had things like put(),
  which the spec doesn't have - as a result it wasn't always clear which
  need to be used.
- Better separation between object abstract operations and internal
  methods - the former are always the same, the latter can be overridden
  (and are therefore virtual). The internal methods (i.e. [[Foo]] in the
  spec) are now prefixed with 'internal_' for clarity - again, it was
  previously not always clear which AO a certain method represents,
  get() could've been both Get and [[Get]] (I don't know which one it
  was closer to right now).
  Note that some of the old names have been kept until all code relying
  on them is updated, but they are now simple wrappers around the
  closest matching standard abstract operation.
- Simplifications of the storage layer: functions that write values to
  storage are now prefixed with 'storage_' to make their purpose clear,
  and as they are not part of the spec they should not contain any steps
  specified by it. Much functionality is now covered by the layers above
  it and was removed (e.g. handling of accessors, attribute checks).
- PropertyAttributes has been greatly simplified, and is being replaced
  by PropertyDescriptor - a concept similar to the current
  implementation, but more aligned with the actual spec. See the commit
  message of the previous commit where it was introduced for details.
- As a bonus, and since I had to look at the spec a whole lot anyway, I
  introduced more inline comments with the exact steps from the spec -
  this makes it super easy to verify correctness.
- East-const all the things.

As a result of all of this, things are much more correct but a bit
slower now. Retaining speed wasn't a consideration at all, I have done
no profiling of the new code - there might be low hanging fruits, which
we can then harvest separately.

Special thanks to Idan for helping me with this by tracking down bugs,
updating everything outside of LibJS to work with these changes (LibWeb,
Spreadsheet, HackStudio), as well as providing countless patches to fix
regressions I introduced - there still are very few (we got it down to
5), but we also get many new passing test262 tests in return. :^)

Co-authored-by: Idan Horowitz <idan.horowitz@gmail.com>
This commit is contained in:
Linus Groh 2021-07-04 18:14:16 +01:00
parent 4e5362b7cb
commit 09bd5f8772
Notes: sideshowbarker 2024-07-18 10:26:33 +09:00
80 changed files with 4019 additions and 2239 deletions

View file

@ -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 (property_name.is_string()) {
if (name.as_string() == "value") { if (property_name.as_string() == "value") {
if (auto cell = m_sheet.current_evaluated_cell()) if (auto cell = m_sheet.current_evaluated_cell())
return cell->js_data(); return cell->js_data();
return JS::js_undefined(); 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()); auto& cell = m_sheet.ensure(pos.value());
cell.reference_from(m_sheet.current_evaluated_cell()); cell.reference_from(m_sheet.current_evaluated_cell());
return cell.typed_js_data(); 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 (property_name.is_string()) {
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()); auto& cell = m_sheet.ensure(pos.value());
if (auto current = m_sheet.current_evaluated_cell()) if (auto current = m_sheet.current_evaluated_cell())
current->reference_from(&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() void SheetGlobalObject::initialize_global_object()

View file

@ -26,8 +26,8 @@ public:
virtual ~SheetGlobalObject() override; virtual ~SheetGlobalObject() override;
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;
virtual bool put(const JS::PropertyName&, JS::Value value, JS::Value receiver = {}) override; virtual bool internal_set(JS::PropertyName const&, JS::Value value, JS::Value receiver) override;
virtual void initialize_global_object() override; virtual void initialize_global_object() override;
JS_DECLARE_NATIVE_FUNCTION(get_real_cell_contents); JS_DECLARE_NATIVE_FUNCTION(get_real_cell_contents);

View file

@ -21,40 +21,40 @@ DebuggerGlobalJSObject::DebuggerGlobalJSObject()
m_variables = lib->debug_info->get_variables_in_current_scope(regs); 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()) if (m_variables.is_empty() || !property_name.is_string())
return JS::Object::get(name, receiver, allow_side_effects); return Base::internal_get(property_name, receiver);
auto it = m_variables.find_if([&](auto& variable) { 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()) 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& target_variable = **it;
auto js_value = debugger_to_js(target_variable); auto js_value = debugger_to_js(target_variable);
if (js_value.has_value()) if (js_value.has_value())
return js_value.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); vm().throw_exception<JS::TypeError>(const_cast<DebuggerGlobalJSObject&>(*this), error_string);
return {}; 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()) if (m_variables.is_empty() || !property_name.is_string())
return JS::Object::put(name, value, receiver); return Base::internal_set(property_name, value, receiver);
auto it = m_variables.find_if([&](auto& variable) { 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()) if (it.is_end())
return JS::Object::put(name, value, receiver); return Base::internal_set(property_name, value, receiver);
auto& target_variable = **it; auto& target_variable = **it;
auto debugger_value = js_to_debugger(value, target_variable); auto debugger_value = js_to_debugger(value, target_variable);
if (debugger_value.has_value()) if (debugger_value.has_value())
return Debugger::the().session()->poke((u32*)target_variable.location_data.address, debugger_value.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); vm().throw_exception<JS::TypeError>(const_cast<DebuggerGlobalJSObject&>(*this), error_string);
return {}; return {};
} }

View file

@ -20,8 +20,8 @@ class DebuggerGlobalJSObject final
public: public:
DebuggerGlobalJSObject(); DebuggerGlobalJSObject();
JS::Value get(const JS::PropertyName& name, JS::Value receiver, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override; virtual JS::Value internal_get(JS::PropertyName const&, JS::Value receiver) const override;
bool put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) 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<JS::Value> debugger_to_js(const Debug::DebugInfo::VariableInfo&) const;
Optional<u32> js_to_debugger(JS::Value value, const Debug::DebugInfo::VariableInfo&) const; Optional<u32> js_to_debugger(JS::Value value, const Debug::DebugInfo::VariableInfo&) const;

View file

@ -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) if (m_is_writing_properties)
return JS::Object::put(name, value, receiver); return Base::internal_set(property_name, value, receiver);
if (!name.is_string()) { if (!property_name.is_string()) {
vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", name.to_string())); vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", property_name.to_string()));
return false; return false;
} }
auto property_name = name.as_string(); auto name = property_name.as_string();
auto it = m_variable_info.members.find_if([&](auto& variable) { auto it = m_variable_info.members.find_if([&](auto& variable) {
return variable->name == property_name; return variable->name == name;
}); });
if (it.is_end()) { if (it.is_end()) {
@ -52,7 +52,7 @@ bool DebuggerVariableJSObject::put(const JS::PropertyName& name, JS::Value value
auto& member = **it; auto& member = **it;
auto new_value = debugger_object().js_to_debugger(value, member); auto new_value = debugger_object().js_to_debugger(value, member);
if (!new_value.has_value()) { 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); vm().throw_exception<JS::TypeError>(global_object(), string_error);
return false; return false;
} }

View file

@ -24,7 +24,7 @@ public:
virtual const char* class_name() const override { return m_variable_info.type_name.characters(); } 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; } void finish_writing_properties() { m_is_writing_properties = false; }
private: private:

View file

@ -129,7 +129,7 @@ CallExpression::ThisAndCallee CallExpression::compute_this_and_callee(Interprete
auto property_name = member_expression.computed_property_name(interpreter, global_object); auto property_name = member_expression.computed_property_name(interpreter, global_object);
if (!property_name.is_valid()) if (!property_name.is_valid())
return {}; 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); callee = reference.get_value(global_object);
if (vm.exception()) if (vm.exception())
return {}; return {};
@ -559,7 +559,7 @@ Value ForInStatement::execute(Interpreter& interpreter, GlobalObject& global_obj
return {}; return {};
auto* object = rhs_result.to_object(global_object); auto* object = rhs_result.to_object(global_object);
while (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) { for (auto& value : property_names) {
interpreter.vm().assign(target, value, global_object, has_declaration); interpreter.vm().assign(target, value, global_object, has_declaration);
if (interpreter.exception()) 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()) if (interpreter.exception())
return {}; return {};
} }
@ -781,6 +781,9 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject&
if (interpreter.exception()) if (interpreter.exception())
return {}; 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); object_value = require_object_coercible(global_object, object_value);
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
@ -789,7 +792,8 @@ Reference MemberExpression::to_reference(Interpreter& interpreter, GlobalObject&
if (!property_name.is_valid()) if (!property_name.is_valid())
return Reference {}; 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 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); class_constructor->define_property(vm.names.prototype, prototype, Attribute::Writable);
if (interpreter.exception()) if (interpreter.exception())
return {}; 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); auto class_prototype = class_constructor->get(vm.names.prototype);
@ -929,15 +933,15 @@ Value ClassExpression::execute(Interpreter& interpreter, GlobalObject& global_ob
switch (method.kind()) { switch (method.kind()) {
case ClassMethod::Kind::Method: 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; break;
case ClassMethod::Kind::Getter: case ClassMethod::Kind::Getter:
update_function_name(method_value, String::formatted("get {}", get_function_name(global_object, key))); 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; break;
case ClassMethod::Kind::Setter: case ClassMethod::Kind::Setter:
update_function_name(method_value, String::formatted("set {}", get_function_name(global_object, key))); 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; break;
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
@ -1431,9 +1435,10 @@ Value Identifier::execute(Interpreter& interpreter, GlobalObject& global_object)
InterpreterNodeScope node_scope { interpreter, *this }; InterpreterNodeScope node_scope { interpreter, *this };
auto value = interpreter.vm().get_variable(string(), global_object); auto value = interpreter.vm().get_variable(string(), global_object);
if (interpreter.exception())
return {};
if (value.is_empty()) { 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 {};
} }
return value; return value;
@ -1815,7 +1820,10 @@ Value ObjectExpression::execute(Interpreter& interpreter, GlobalObject& global_o
if (key.is_object() && key.as_object().is_array()) { if (key.is_object() && key.as_object().is_array()) {
auto& array_to_spread = static_cast<Array&>(key.as_object()); auto& array_to_spread = static_cast<Array&>(key.as_object());
for (auto& entry : array_to_spread.indexed_properties()) { 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()) if (interpreter.exception())
return {}; return {};
} }

View file

@ -193,7 +193,9 @@ void CopyObjectExcludingProperties::execute_impl(Bytecode::Interpreter& interpre
return; 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) { for (auto& key : own_keys) {
if (!excluded_names.contains(key)) { if (!excluded_names.contains(key)) {

View file

@ -97,7 +97,6 @@ set(SOURCES
Runtime/PromisePrototype.cpp Runtime/PromisePrototype.cpp
Runtime/PromiseReaction.cpp Runtime/PromiseReaction.cpp
Runtime/PromiseResolvingFunction.cpp Runtime/PromiseResolvingFunction.cpp
Runtime/PropertyAttributes.cpp
Runtime/PropertyDescriptor.cpp Runtime/PropertyDescriptor.cpp
Runtime/ProxyConstructor.cpp Runtime/ProxyConstructor.cpp
Runtime/ProxyObject.cpp Runtime/ProxyObject.cpp
@ -106,7 +105,7 @@ set(SOURCES
Runtime/RegExpConstructor.cpp Runtime/RegExpConstructor.cpp
Runtime/RegExpObject.cpp Runtime/RegExpObject.cpp
Runtime/RegExpPrototype.cpp Runtime/RegExpPrototype.cpp
Runtime/OrdinaryFunctionObject.cpp Runtime/OrdinaryFunctionObject.cpp
Runtime/Set.cpp Runtime/Set.cpp
Runtime/SetConstructor.cpp Runtime/SetConstructor.cpp
Runtime/SetIterator.cpp Runtime/SetIterator.cpp

View file

@ -175,11 +175,6 @@ class TypedArrayPrototype;
// Tag type used to differentiate between u8 as used by Uint8Array and u8 as used by Uint8ClampedArray. // Tag type used to differentiate between u8 as used by Uint8Array and u8 as used by Uint8ClampedArray.
struct ClampedU8; struct ClampedU8;
enum class AllowSideEffects {
Yes,
No
};
#define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName, ArrayType) \ #define __JS_ENUMERATE(ClassName, snake_name, ConstructorName, PrototypeName, ArrayType) \
class ClassName; \ class ClassName; \
class ConstructorName; \ class ConstructorName; \

View file

@ -98,7 +98,7 @@ void MarkupGenerator::array_to_html(const Array& array, StringBuilder& html_outp
html_output.append(wrap_string_in_style(", ", StyleType::Punctuation)); html_output.append(wrap_string_in_style(", ", StyleType::Punctuation));
first = false; first = false;
// FIXME: Exception check // 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)); 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(String::number(entry.index()), StyleType::Number));
html_output.append(wrap_string_in_style(": ", StyleType::Punctuation)); html_output.append(wrap_string_in_style(": ", StyleType::Punctuation));
// FIXME: Exception check // 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()) if (!object.indexed_properties().is_empty() && object.shape().property_count())

View file

@ -6,14 +6,15 @@
*/ */
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/Optional.h>
#include <AK/Result.h> #include <AK/Result.h>
#include <AK/TemporaryChange.h> #include <AK/TemporaryChange.h>
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Parser.h> #include <LibJS/Parser.h>
#include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/ArgumentsObject.h> #include <LibJS/Runtime/ArgumentsObject.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayPrototype.h>
#include <LibJS/Runtime/BoundFunction.h> #include <LibJS/Runtime/BoundFunction.h>
#include <LibJS/Runtime/DeclarativeEnvironment.h> #include <LibJS/Runtime/DeclarativeEnvironment.h>
#include <LibJS/Runtime/ErrorTypes.h> #include <LibJS/Runtime/ErrorTypes.h>
@ -23,6 +24,7 @@
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/ObjectEnvironment.h> #include <LibJS/Runtime/ObjectEnvironment.h>
#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyName.h> #include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/ProxyObject.h> #include <LibJS/Runtime/ProxyObject.h>
#include <LibJS/Runtime/Reference.h> #include <LibJS/Runtime/Reference.h>
@ -111,27 +113,202 @@ GlobalObject* get_function_realm(GlobalObject& global_object, FunctionObject con
{ {
auto& vm = global_object.vm(); 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(); return function.realm();
}
// 3. If obj is a bound function exotic object, then
if (is<BoundFunction>(function)) { if (is<BoundFunction>(function)) {
auto& bound_function = static_cast<BoundFunction const&>(function); auto& bound_function = static_cast<BoundFunction const&>(function);
// a. Let target be obj.[[BoundTargetFunction]].
auto& target = bound_function.target_function(); auto& target = bound_function.target_function();
// b. Return ? GetFunctionRealm(target).
return get_function_realm(global_object, target); return get_function_realm(global_object, target);
} }
// 4. If obj is a Proxy exotic object, then
if (is<ProxyObject>(function)) { if (is<ProxyObject>(function)) {
auto& proxy = static_cast<ProxyObject const&>(function); auto& proxy = static_cast<ProxyObject const&>(function);
// a. If obj.[[ProxyHandler]] is null, throw a TypeError exception.
if (proxy.is_revoked()) { if (proxy.is_revoked()) {
vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked); vm.throw_exception<TypeError>(global_object, ErrorType::ProxyRevoked);
return nullptr; return nullptr;
} }
// b. Let proxyTarget be obj.[[ProxyTarget]].
auto& proxy_target = proxy.target(); auto& proxy_target = proxy.target();
// c. Return ? GetFunctionRealm(proxyTarget).
VERIFY(proxy_target.is_function()); VERIFY(proxy_target.is_function());
return get_function_realm(global_object, static_cast<FunctionObject const&>(proxy_target)); return get_function_realm(global_object, static_cast<FunctionObject const&>(proxy_target));
} }
// 5. Return the current Realm Record. // 5. Return the current Realm Record.
return &global_object; 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 ) // 10.1.14 GetPrototypeFromConstructor ( constructor, intrinsicDefaultProto )
Object* get_prototype_from_constructor(GlobalObject& global_object, FunctionObject const& constructor, Object* (GlobalObject::*intrinsic_default_prototype)()) 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& env = get_this_environment(vm);
auto& active_function = verify_cast<FunctionEnvironment>(env).function_object(); 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; 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) Object* create_unmapped_arguments_object(GlobalObject& global_object, Vector<Value> const& arguments)
{ {
auto& vm = global_object.vm(); auto& vm = global_object.vm();
auto* object = Object::create(global_object, global_object.object_prototype());
if (vm.exception())
return nullptr;
for (auto& argument : arguments) // 1. Let len be the number of elements in argumentsList.
object->indexed_properties().append(argument); 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 }). // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
auto length = arguments.size(); object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true });
object->define_property(vm.names.length, Value(length), Attribute::Writable | Attribute::Configurable); VERIFY(!vm.exception());
if (vm.exception())
return nullptr;
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];
// 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 {};
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 }). // 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); auto* throw_type_error = global_object.throw_type_error_function();
if (vm.exception()) object->define_property_or_throw(vm.names.callee, { .get = throw_type_error, .set = throw_type_error, .enumerable = false, .configurable = false });
return nullptr; VERIFY(!vm.exception());
// 9. Return obj.
return object; return object;
} }
@ -260,33 +456,75 @@ Object* create_mapped_arguments_object(GlobalObject& global_object, FunctionObje
(void)formals; (void)formals;
auto& vm = global_object.vm(); 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); auto* object = vm.heap().allocate<ArgumentsObject>(global_object, global_object);
if (vm.exception()) VERIFY(!vm.exception());
return nullptr;
// 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. // 14. Let index be 0.
// 15. Repeat, while index < len, // 15. Repeat, while index < len,
// a. Let val be argumentsList[index]. for (size_t index = 0; index < length; ++index) {
// b . Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val). // a. Let val be argumentsList[index].
// c. Set index to index + 1. auto value = arguments[index];
for (auto& argument : arguments)
object->indexed_properties().append(argument); // 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 }). // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }).
auto length = arguments.size(); object->define_property_or_throw(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = true });
object->define_property(vm.names.length, Value(length), Attribute::Writable | Attribute::Configurable); VERIFY(!vm.exception());
if (vm.exception())
return nullptr;
// 20. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor { [[Value]]: %Array.prototype.values%, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). // 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 }). // 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); object->define_property_or_throw(vm.names.callee, { .value = &function, .writable = true, .enumerable = false, .configurable = true });
if (vm.exception()) VERIFY(!vm.exception());
return nullptr;
// 22. Return obj.
return object; 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;
}
} }

View file

@ -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)> = {}); MarkedValueList create_list_from_array_like(GlobalObject&, Value, Function<Result<void, ErrorType>(Value)> = {});
FunctionObject* species_constructor(GlobalObject&, Object const&, FunctionObject& default_constructor); FunctionObject* species_constructor(GlobalObject&, Object const&, FunctionObject& default_constructor);
GlobalObject* get_function_realm(GlobalObject&, FunctionObject const&); 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* 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_unmapped_arguments_object(GlobalObject&, Vector<Value> const& arguments);
Object* create_mapped_arguments_object(GlobalObject&, FunctionObject&, Vector<FunctionNode::Parameter> const&, Vector<Value> const& arguments, Environment&); 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 { enum class CallerMode {
Strict, Strict,

View file

@ -23,16 +23,27 @@ Array* Array::create(GlobalObject& global_object, size_t length, Object* prototy
if (!prototype) if (!prototype)
prototype = global_object.array_prototype(); prototype = global_object.array_prototype();
auto* array = global_object.heap().allocate<Array>(global_object, *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; return array;
} }
// 7.3.17 CreateArrayFromList ( elements ), https://tc39.es/ecma262/#sec-createarrayfromlist // 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); 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; 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 // TODO: could be incorrect if receiver/this_value is fixed or changed
if (!this_object->is_array()) { 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()) if (vm.exception())
return {}; return {};
return val; return value;
} }
return Value(this_object->indexed_properties().array_like_size()); return Value(this_object->indexed_properties().array_like_size());

View file

@ -15,7 +15,7 @@ class Array : public Object {
public: public:
static Array* create(GlobalObject&, size_t length, Object* prototype = nullptr); 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); explicit Array(Object& prototype);
virtual void initialize(GlobalObject&) override; virtual void initialize(GlobalObject&) override;

View file

@ -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> template<typename T>
Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_array, Order, bool is_little_endian) Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_array, Order, bool is_little_endian)
{ {

View file

@ -67,7 +67,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target)
auto* array = Array::create(global_object(), 0, proto); auto* array = Array::create(global_object(), 0, proto);
size_t int_length; size_t int_length;
if (!length.is_number()) { if (!length.is_number()) {
array->define_property(0, length); array->create_data_property_or_throw(0, length);
int_length = 1; int_length = 1;
} else { } else {
int_length = length.to_u32(global_object()); int_length = length.to_u32(global_object());
@ -76,7 +76,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target)
return {}; return {};
} }
} }
array->put(vm.names.length, Value(int_length)); array->set(vm.names.length, Value(int_length), true);
return array; return array;
} }
@ -85,7 +85,7 @@ Value ArrayConstructor::construct(FunctionObject& new_target)
return {}; return {};
for (size_t k = 0; k < vm.argument_count(); ++k) 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; return array;
} }
@ -139,7 +139,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
return {}; return {};
if (!next) { if (!next) {
array_object.put(vm.names.length, Value(k)); array_object.set(vm.names.length, Value(k), true);
if (vm.exception()) if (vm.exception())
return {}; return {};
return array; return array;
@ -160,7 +160,7 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
mapped_value = next_value; mapped_value = next_value;
} }
array_object.define_property(k, mapped_value); array_object.create_data_property_or_throw(k, mapped_value);
if (vm.exception()) { if (vm.exception()) {
iterator_close(*iterator); iterator_close(*iterator);
return {}; return {};
@ -201,10 +201,10 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::from)
} else { } else {
mapped_value = k_value; 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()) if (vm.exception())
return {}; return {};
@ -236,11 +236,11 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayConstructor::of)
} }
auto& array_object = array.as_object(); auto& array_object = array.as_object();
for (size_t k = 0; k < vm.argument_count(); ++k) { 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()) if (vm.exception())
return {}; 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()) if (vm.exception())
return {}; return {};
return array; return array;

File diff suppressed because it is too large Load diff

View file

@ -40,7 +40,7 @@ static Value this_bigint_value(GlobalObject& global_object, Value value)
if (value.is_bigint()) if (value.is_bigint())
return value; return value;
if (value.is_object() && is<BigIntObject>(value.as_object())) 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(); auto& vm = global_object.vm();
vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "BigInt"); vm.throw_exception<TypeError>(global_object, ErrorType::NotA, "BigInt");
return {}; return {};

View file

@ -37,7 +37,7 @@ Value BoundFunction::call()
Value BoundFunction::construct(FunctionObject& new_target) Value BoundFunction::construct(FunctionObject& new_target)
{ {
if (auto this_value = vm().this_value(global_object()); m_constructor_prototype && this_value.is_object()) { 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()) if (vm().exception())
return {}; return {};
} }

View file

@ -8,7 +8,6 @@
#define JS_ENUMERATE_ERROR_TYPES(M) \ #define JS_ENUMERATE_ERROR_TYPES(M) \
M(ArrayMaxSize, "Maximum array size exceeded") \ 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(AccessorBadField, "Accessor descriptor's '{}' field must be a function or undefined") \
M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \ M(AccessorValueOrWritable, "Accessor property descriptor cannot specify a value or writable key") \
M(BigIntBadOperator, "Cannot use {} operator with BigInt") \ M(BigIntBadOperator, "Cannot use {} operator with BigInt") \
@ -23,7 +22,6 @@
M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \ M(ConstructorWithoutNew, "{} constructor must be called with 'new'") \
M(Convert, "Cannot convert {} to {}") \ M(Convert, "Cannot convert {} to {}") \
M(DataViewOutOfRangeByteOffset, "Data view byte offset {} is out of range for buffer with length {}") \ 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(DescWriteNonWritable, "Cannot write to non-writable property '{}'") \
M(DetachedArrayBuffer, "ArrayBuffer is detached") \ M(DetachedArrayBuffer, "ArrayBuffer is detached") \
M(DivisionByZero, "Division by zero") \ M(DivisionByZero, "Division by zero") \
@ -60,9 +58,11 @@
M(NotASymbol, "{} is not a symbol") \ M(NotASymbol, "{} is not a symbol") \
M(NotIterable, "{} is not iterable") \ M(NotIterable, "{} is not iterable") \
M(NotObjectCoercible, "{} cannot be converted to an object") \ 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(ObjectFreezeFailed, "Could not freeze object") \
M(ObjectSealFailed, "Could not seal object") \ M(ObjectSealFailed, "Could not seal object") \
M(ObjectSetReturnedFalse, "Object's [[Set]] method returned false") \
M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \ M(ObjectSetPrototypeOfReturnedFalse, "Object's [[SetPrototypeOf]] method returned false") \
M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \ M(ObjectPreventExtensionsReturnedFalse, "Object's [[PreventExtensions]] method returned false") \
M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \ M(ObjectPrototypeNullOrUndefinedOnSuperPropertyAccess, \
@ -81,11 +81,17 @@
M(ProxyDefinePropNonConfigurableNonExisting, "Proxy handler's defineProperty trap " \ M(ProxyDefinePropNonConfigurableNonExisting, "Proxy handler's defineProperty trap " \
"violates invariant: a property cannot be defined as non-configurable if it does not " \ "violates invariant: a property cannot be defined as non-configurable if it does not " \
"already exist on the target object") \ "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: " \ M(ProxyDefinePropNonExtensible, "Proxy handler's defineProperty trap violates invariant: " \
"a property cannot be reported as being defined if the property does not exist on " \ "a property cannot be reported as being defined if the property does not exist on " \
"the target and the target is non-extensible") \ "the target and the target is non-extensible") \
M(ProxyDeleteNonConfigurable, "Proxy handler's deleteProperty trap violates invariant: " \ M(ProxyDeleteNonConfigurable, "Proxy handler's deleteProperty trap violates invariant: " \
"cannot report a non-configurable own property of the target as deleted") \ "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 " \ 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 " \ "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") \ "target as a non-writable, non-configurable own data property") \
@ -103,6 +109,10 @@
M(ProxyGetOwnDescriptorNonConfigurable, "Proxy handler's getOwnPropertyDescriptor trap " \ M(ProxyGetOwnDescriptorNonConfigurable, "Proxy handler's getOwnPropertyDescriptor trap " \
"violates invariant: cannot return undefined for a property on the target which is " \ "violates invariant: cannot return undefined for a property on the target which is " \
"a non-configurable property") \ "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 " \ M(ProxyGetOwnDescriptorReturn, "Proxy handler's getOwnPropertyDescriptor trap violates " \
"invariant: must return an object or undefined") \ "invariant: must return an object or undefined") \
M(ProxyGetOwnDescriptorUndefinedReturn, "Proxy handler's getOwnPropertyDescriptor trap " \ M(ProxyGetOwnDescriptorUndefinedReturn, "Proxy handler's getOwnPropertyDescriptor trap " \
@ -120,6 +130,8 @@
"non-extensible") \ "non-extensible") \
M(ProxyIsExtensibleReturn, "Proxy handler's isExtensible trap violates invariant: " \ M(ProxyIsExtensibleReturn, "Proxy handler's isExtensible trap violates invariant: " \
"return value must match the target's extensibility") \ "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 " \ M(ProxyPreventExtensionsReturn, "Proxy handler's preventExtensions trap violates " \
"invariant: cannot return true if the target object is extensible") \ "invariant: cannot return true if the target object is extensible") \
M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \ M(ProxyRevoked, "An operation was performed on a revoked Proxy object") \
@ -138,11 +150,6 @@
M(ReferenceNullishSetProperty, "Cannot set property '{}' of {}") \ M(ReferenceNullishSetProperty, "Cannot set property '{}' of {}") \
M(ReferencePrimitiveSetProperty, "Cannot set property '{}' of {} '{}'") \ M(ReferencePrimitiveSetProperty, "Cannot set property '{}' of {} '{}'") \
M(ReferenceUnresolvable, "Unresolvable reference") \ 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(RegExpCompileError, "RegExp compile error: {}") \
M(RegExpObjectBadFlag, "Invalid RegExp flag '{}'") \ M(RegExpObjectBadFlag, "Invalid RegExp flag '{}'") \
M(RegExpObjectRepeatedFlag, "Repeated RegExp flag '{}'") \ M(RegExpObjectRepeatedFlag, "Repeated RegExp flag '{}'") \
@ -151,6 +158,7 @@
M(SpeciesConstructorDidNotCreate, "Species constructor did not create {}") \ M(SpeciesConstructorDidNotCreate, "Species constructor did not create {}") \
M(SpeciesConstructorReturned, "Species constructor returned {}") \ M(SpeciesConstructorReturned, "Species constructor returned {}") \
M(StringMatchAllNonGlobalRegExp, "RegExp argument is non-global") \ 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(StringRepeatCountMustBe, "repeat count must be a {} number") \
M(ThisHasNotBeenInitialized, "|this| has not been initialized") \ M(ThisHasNotBeenInitialized, "|this| has not been initialized") \
M(ThisIsAlreadyInitialized, "|this| is already initialized") \ M(ThisIsAlreadyInitialized, "|this| is already initialized") \

View file

@ -35,7 +35,7 @@ Value FunctionEnvironment::get_super_base() const
auto home_object = m_function_object->home_object(); auto home_object = m_function_object->home_object();
if (home_object.is_undefined()) if (home_object.is_undefined())
return js_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 // 9.1.1.3.2 HasThisBinding ( ), https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * 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 // 9.1.1.4.14 HasRestrictedGlobalProperty ( N ), https://tc39.es/ecma262/#sec-hasrestrictedglobalproperty
bool GlobalEnvironment::has_restricted_global_property(FlyString const& name) const bool GlobalEnvironment::has_restricted_global_property(FlyString const& name) const
{ {
auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name); auto& vm = this->vm();
if (!existing_prop.has_value() || existing_prop.value().value.is_undefined()) 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; return false;
if (existing_prop.value().attributes.is_configurable()) if (*existing_prop->configurable)
return false; return false;
return true; 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 // 9.1.1.4.15 CanDeclareGlobalVar ( N ), https://tc39.es/ecma262/#sec-candeclareglobalvar
bool GlobalEnvironment::can_declare_global_var(FlyString const& name) const 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) if (has_property)
return true; 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 // 9.1.1.4.16 CanDeclareGlobalFunction ( N ), https://tc39.es/ecma262/#sec-candeclareglobalfunction
bool GlobalEnvironment::can_declare_global_function(FlyString const& name) const bool GlobalEnvironment::can_declare_global_function(FlyString const& name) const
{ {
auto existing_prop = m_object_record->binding_object().get_own_property_descriptor(name); auto& vm = this->vm();
if (!existing_prop.has_value() || existing_prop.value().value.is_undefined()) auto& global_object = m_object_record->binding_object();
return m_object_record->binding_object().is_extensible(); auto existing_prop = global_object.internal_get_own_property(name);
if (existing_prop.value().attributes.is_configurable()) if (vm.exception())
return {};
if (!existing_prop.has_value())
return global_object.is_extensible();
if (*existing_prop->configurable)
return true; 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 true;
return false; 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 // 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) 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); auto& vm = this->vm();
bool extensible = m_object_record->binding_object().is_extensible(); 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) { 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()); m_object_record->initialize_binding(m_object_record->global_object(), name, js_undefined());
if (vm.exception())
return;
} }
if (!m_var_names.contains_slow(name)) if (!m_var_names.contains_slow(name))
m_var_names.append(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 // 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) 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; PropertyDescriptor desc;
if (!existing_prop.has_value() || existing_prop.value().value.is_undefined() || existing_prop.value().attributes.is_configurable()) { if (!existing_prop.has_value() || *existing_prop->configurable)
desc.value = value; desc = { .value = value, .writable = true, .enumerable = true, .configurable = can_be_deleted };
desc.attributes.set_has_writable(); else
desc.attributes.set_writable(); desc = { .value = value };
desc.attributes.set_has_enumerable(); global_object.define_property_or_throw(name, desc);
desc.attributes.set_enumerable(); if (vm.exception())
desc.attributes.set_has_configurable(); return;
if (can_be_deleted) global_object.set(name, value, false);
desc.attributes.set_configurable(); if (vm.exception())
} 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())
return; return;
if (!m_var_names.contains_slow(name)) if (!m_var_names.contains_slow(name))
m_var_names.append(name); m_var_names.append(name);

View file

@ -111,7 +111,8 @@ void GlobalObject::initialize_global_object()
static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this); static_cast<FunctionPrototype*>(m_function_prototype)->initialize(*this);
static_cast<ObjectPrototype*>(m_object_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. // This must be initialized before allocating AggregateErrorPrototype, which uses ErrorPrototype as its prototype.
m_error_prototype = heap().allocate<ErrorPrototype>(*this, *this); 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); vm.throw_exception<TypeError>(global_object, ErrorType::RestrictedFunctionPropertiesAccess);
return Value(); 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.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->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 // 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); m_function_prototype->define_accessor(vm.names.caller, throw_type_error_function(), throw_type_error_function(), Attribute::Configurable);

View file

@ -52,15 +52,8 @@ void SimpleIndexedPropertyStorage::put(u32 index, Value value, PropertyAttribute
void SimpleIndexedPropertyStorage::remove(u32 index) void SimpleIndexedPropertyStorage::remove(u32 index)
{ {
if (index < m_array_size) VERIFY(index < m_array_size);
m_packed_elements[index] = {}; 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);
} }
ValueAndAttributes SimpleIndexedPropertyStorage::take_first() ValueAndAttributes SimpleIndexedPropertyStorage::take_first()
@ -87,7 +80,9 @@ GenericIndexedPropertyStorage::GenericIndexedPropertyStorage(SimpleIndexedProper
{ {
m_array_size = storage.array_like_size(); m_array_size = storage.array_like_size();
for (size_t i = 0; i < storage.m_packed_elements.size(); ++i) { 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) void GenericIndexedPropertyStorage::remove(u32 index)
{ {
if (index >= m_array_size) VERIFY(index < m_array_size);
return;
if (index + 1 == m_array_size) {
take_last();
return;
}
m_sparse_elements.remove(index); 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() ValueAndAttributes GenericIndexedPropertyStorage::take_first()
{ {
VERIFY(m_array_size > 0); VERIFY(m_array_size > 0);
@ -226,10 +197,10 @@ bool IndexedPropertyIterator::operator!=(const IndexedPropertyIterator& other) c
return m_index != other.m_index; 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()) 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 {}; return {};
} }
@ -245,62 +216,24 @@ void IndexedPropertyIterator::skip_empty_indices()
m_index = m_indexed_properties.array_like_size(); 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); return 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;
} }
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))) { if (m_storage->is_simple_storage() && (attributes != default_attributes || index > (array_like_size() + SPARSE_ARRAY_HOLE_THRESHOLD))) {
switch_to_generic_storage(); switch_to_generic_storage();
} }
if (m_storage->is_simple_storage() || allow_side_effects == AllowSideEffects::No) { m_storage->put(index, value, attributes);
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);
}
} }
bool IndexedProperties::remove(u32 index) void IndexedProperties::remove(u32 index)
{ {
auto result = m_storage->get(index); VERIFY(m_storage->has_index(index));
if (!result.has_value())
return true;
if (!result.value().attributes.is_configurable())
return false;
m_storage->remove(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) ValueAndAttributes IndexedProperties::take_first(Object* this_object)

View file

@ -30,7 +30,6 @@ public:
virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0; virtual void put(u32 index, Value value, PropertyAttributes attributes = default_attributes) = 0;
virtual void remove(u32 index) = 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_first() = 0;
virtual ValueAndAttributes take_last() = 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 put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
virtual void remove(u32 index) 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_first() override;
virtual ValueAndAttributes take_last() 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 put(u32 index, Value value, PropertyAttributes attributes = default_attributes) override;
virtual void remove(u32 index) 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_first() override;
virtual ValueAndAttributes take_last() override; virtual ValueAndAttributes take_last() override;
@ -104,7 +101,7 @@ public:
bool operator!=(const IndexedPropertyIterator&) const; bool operator!=(const IndexedPropertyIterator&) const;
u32 index() const { return m_index; }; u32 index() const { return m_index; };
ValueAndAttributes value_and_attributes(Object* this_object, AllowSideEffects = AllowSideEffects::Yes); ValueAndAttributes value_and_attributes();
private: private:
void skip_empty_indices(); void skip_empty_indices();
@ -124,15 +121,14 @@ public:
} }
bool has_index(u32 index) const { return m_storage->has_index(index); } bool has_index(u32 index) const { return m_storage->has_index(index); }
Optional<ValueAndAttributes> get(Object* this_object, u32 index, AllowSideEffects = AllowSideEffects::Yes) const; Optional<ValueAndAttributes> get(u32 index) const;
void put(Object* this_object, u32 index, Value value, PropertyAttributes attributes = default_attributes, AllowSideEffects allow_side_effects = AllowSideEffects::Yes); void put(u32 index, Value value, PropertyAttributes attributes = default_attributes);
bool remove(u32 index); void remove(u32 index);
void insert(u32 index, Value value, PropertyAttributes attributes = default_attributes);
ValueAndAttributes take_first(Object* this_object); ValueAndAttributes take_first(Object* this_object);
ValueAndAttributes take_last(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 begin(bool skip_empty = true) const { return IndexedPropertyIterator(*this, 0, skip_empty); };
IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); }; IndexedPropertyIterator end() const { return IndexedPropertyIterator(*this, array_like_size(), false); };

View file

@ -117,7 +117,7 @@ String JSONObject::stringify_impl(GlobalObject& global_object, Value value, Valu
} }
auto* wrapper = Object::create(global_object, global_object.object_prototype()); 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); auto result = serialize_json_property(global_object, state, String::empty(), wrapper);
if (vm.exception()) if (vm.exception())
return {}; return {};
@ -256,7 +256,7 @@ String JSONObject::serialize_json_object(GlobalObject& global_object, StringifyS
return {}; return {};
} }
} else { } 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()) if (vm.exception())
return {}; return {};
for (auto& property : property_list) { for (auto& property : property_list) {
@ -428,7 +428,7 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse)
if (reviver.is_function()) { if (reviver.is_function()) {
auto* root = Object::create(global_object, global_object.object_prototype()); auto* root = Object::create(global_object, global_object.object_prototype());
auto root_name = String::empty(); 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()); auto result = internalize_json_property(global_object, root, root_name, reviver.as_function());
if (vm.exception()) if (vm.exception())
return {}; return {};
@ -493,9 +493,9 @@ Value JSONObject::internalize_json_property(GlobalObject& global_object, Object*
if (vm.exception()) if (vm.exception())
return; return;
if (element.is_undefined()) if (element.is_undefined())
value_object.delete_property(key); value_object.internal_delete(key);
else else
value_object.define_property(key, element, default_attributes); value_object.create_data_property(key, element);
}; };
if (is_array) { if (is_array) {
@ -508,7 +508,7 @@ Value JSONObject::internalize_json_property(GlobalObject& global_object, Object*
return {}; return {};
} }
} else { } 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) { for (auto& property_name : property_list) {
process_property(property_name.as_string().string()); process_property(property_name.as_string().string());
if (vm.exception()) if (vm.exception())

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@
#include <LibJS/Runtime/IndexedProperties.h> #include <LibJS/Runtime/IndexedProperties.h>
#include <LibJS/Runtime/MarkedValueList.h> #include <LibJS/Runtime/MarkedValueList.h>
#include <LibJS/Runtime/PrimitiveString.h> #include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyName.h> #include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/Shape.h> #include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
@ -25,19 +26,6 @@ public: \
using Base = base_class; \ using Base = base_class; \
virtual const char* class_name() const override { return #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 { class Object : public Cell {
public: public:
static Object* create(GlobalObject&, Object* prototype); static Object* create(GlobalObject&, Object* prototype);
@ -53,12 +41,6 @@ public:
KeyAndValue, KeyAndValue,
}; };
enum class GetOwnPropertyReturnType {
All,
StringOnly,
SymbolOnly,
};
enum class PutOwnPropertyMode { enum class PutOwnPropertyMode {
Put, Put,
DefineProperty, DefineProperty,
@ -69,27 +51,84 @@ public:
Frozen, Frozen,
}; };
Shape& shape() { return *m_shape; } // Please DO NOT make up your own non-standard methods unless you
const Shape& shape() const { return *m_shape; } // 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.
GlobalObject& global_object() const { return *shape().global_object(); } // 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;
// 10.1 Ordinary Object Internal Methods and Internal Slots, https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
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
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&);
// 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() });
};
virtual Value get(const PropertyName&, Value receiver = {}, AllowSideEffects = AllowSideEffects::Yes) const;
Value get_without_side_effects(const PropertyName&) const; Value get_without_side_effects(const PropertyName&) const;
virtual bool has_property(const PropertyName&) const;
bool has_own_property(const PropertyName&) const;
virtual bool put(const PropertyName&, Value, Value receiver = {});
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;
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_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); 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_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); 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_array() const { return false; }
virtual bool is_function() const { return false; } virtual bool is_function() const { return false; }
virtual bool is_typed_array() 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 const char* class_name() const override { return "Object"; }
virtual void visit_edges(Cell::Visitor&) override; 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 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]; } Value get_direct(size_t index) const { return m_storage[index]; }
@ -150,6 +172,11 @@ public:
return invoke(property_name); 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 ensure_shape_is_unique();
void enable_transitions() { m_transitions_enabled = true; } void enable_transitions() { m_transitions_enabled = true; }
@ -164,19 +191,17 @@ protected:
explicit Object(GlobalObjectTag); explicit Object(GlobalObjectTag);
Object(ConstructWithoutPrototypeTag, GlobalObject&); Object(ConstructWithoutPrototypeTag, GlobalObject&);
virtual Value get_by_index(u32 property_index, AllowSideEffects = AllowSideEffects::Yes) const; bool m_is_extensible { true };
virtual bool put_by_index(u32 property_index, Value);
private: 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; Value call_native_property_getter(NativeProperty& property, Value this_value) const;
void call_native_property_setter(NativeProperty& property, Value this_value, Value) const; void call_native_property_setter(NativeProperty& property, Value this_value, Value) const;
void set_shape(Shape&); 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 }; bool m_transitions_enabled { true };
Shape* m_shape { nullptr }; Shape* m_shape { nullptr };
Vector<Value> m_storage; Vector<Value> m_storage;

View file

@ -80,55 +80,102 @@ Value ObjectConstructor::construct(FunctionObject& new_target)
return value.to_object(global_object); return value.to_object(global_object);
} }
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& 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 {};
// 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 // 20.1.2.10 Object.getOwnPropertyNames ( O ), https://tc39.es/ecma262/#sec-object.getownpropertynames
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names) JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_names)
{ {
auto* object = vm.argument(0).to_object(global_object); // 1. Return ? GetOwnPropertyKeys(O, string).
if (vm.exception()) return get_own_property_keys(global_object, vm.argument(0), GetOwnPropertyKeysType::String);
return {};
return Array::create_from(global_object, object->get_own_properties(PropertyKind::Key, false, GetOwnPropertyReturnType::StringOnly));
} }
// 20.1.2.11 Object.getOwnPropertySymbols ( O ), https://tc39.es/ecma262/#sec-object.getownpropertysymbols // 20.1.2.11 Object.getOwnPropertySymbols ( O ), https://tc39.es/ecma262/#sec-object.getownpropertysymbols
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_symbols) JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_symbols)
{ {
auto* object = vm.argument(0).to_object(global_object); // 1. Return ? GetOwnPropertyKeys(O, symbol).
if (vm.exception()) return get_own_property_keys(global_object, vm.argument(0), GetOwnPropertyKeysType::Symbol);
return {};
return Array::create_from(global_object, object->get_own_properties(PropertyKind::Key, false, GetOwnPropertyReturnType::SymbolOnly));
} }
// 20.1.2.12 Object.getPrototypeOf ( O ), https://tc39.es/ecma262/#sec-object.getprototypeof // 20.1.2.12 Object.getPrototypeOf ( O ), https://tc39.es/ecma262/#sec-object.getprototypeof
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of) JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_prototype_of)
{ {
// 1. Let obj be ? ToObject(O).
auto* object = vm.argument(0).to_object(global_object); auto* object = vm.argument(0).to_object(global_object);
if (vm.exception()) if (vm.exception())
return {}; 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 // 20.1.2.21 Object.setPrototypeOf ( O, proto ), https://tc39.es/ecma262/#sec-object.setprototypeof
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::set_prototype_of) 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()) if (vm.exception())
return {}; 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); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {}; return {};
} }
if (!argument.is_object())
return argument; // 3. If Type(O) is not Object, return O.
auto* prototype = prototype_value.is_null() ? nullptr : &prototype_value.as_object(); if (!object.is_object())
auto status = argument.as_object().set_prototype(prototype); 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()) if (vm.exception())
return {}; return {};
// 5. If status is false, throw a TypeError exception.
if (!status) { if (!status) {
// FIXME: Improve/contextualize error message
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse);
return {}; return {};
} }
return argument;
// 6. Return O.
return object;
} }
// 20.1.2.14 Object.isExtensible ( O ), https://tc39.es/ecma262/#sec-object.isextensible // 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); auto argument = vm.argument(0);
if (!argument.is_object()) if (!argument.is_object())
return argument; return argument;
auto status = argument.as_object().prevent_extensions(); auto status = argument.as_object().internal_prevent_extensions();
if (vm.exception()) if (vm.exception())
return {}; return {};
if (!status) { if (!status) {
// FIXME: Improve/contextualize error message
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPreventExtensionsReturnedFalse);
return {}; return {};
} }
@ -247,54 +295,48 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::get_own_property_descriptor)
auto* object = vm.argument(0).to_object(global_object); auto* object = vm.argument(0).to_object(global_object);
if (vm.exception()) if (vm.exception())
return {}; 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()) if (vm.exception())
return {}; return {};
return object->get_own_property_descriptor_object(property_key); auto descriptor = object->internal_get_own_property(key);
if (vm.exception())
return {};
return from_property_descriptor(global_object, descriptor);
} }
// 20.1.2.4 Object.defineProperty ( O, P, Attributes ), https://tc39.es/ecma262/#sec-object.defineproperty // 20.1.2.4 Object.defineProperty ( O, P, Attributes ), https://tc39.es/ecma262/#sec-object.defineproperty
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_) JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_property_)
{ {
if (!vm.argument(0).is_object()) { 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 {}; 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()) if (vm.exception())
return {}; return {};
if (!vm.argument(2).is_object()) { auto descriptor = to_property_descriptor(global_object, vm.argument(2));
vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Descriptor argument"); if (vm.exception())
return {}; return {};
} vm.argument(0).as_object().define_property_or_throw(key, descriptor);
auto& object = vm.argument(0).as_object(); if (vm.exception())
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());
}
}
return {}; return {};
} return vm.argument(0);
return &object;
} }
// 20.1.2.3 Object.defineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-object.defineproperties // 20.1.2.3 Object.defineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-object.defineproperties
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::define_properties) 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"); vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, "Object argument");
return {}; return {};
} }
auto& object = vm.argument(0).as_object();
auto properties = vm.argument(1); // 2. Return ? ObjectDefineProperties(O, Properties).
object.define_properties(properties); return object.as_object().define_properties(properties);
if (vm.exception())
return {};
return &object;
} }
// 20.1.2.13 Object.is ( value1, value2 ), https://tc39.es/ecma262/#sec-object.is // 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 // 20.1.2.17 Object.keys ( O ), https://tc39.es/ecma262/#sec-object.keys
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::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()) if (vm.exception())
return {}; return {};
auto name_list = object->enumerable_own_property_names(PropertyKind::Key);
return Array::create_from(global_object, obj_arg->get_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 // 20.1.2.22 Object.values ( O ), https://tc39.es/ecma262/#sec-object.values
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::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()) if (vm.exception())
return {}; return {};
auto name_list = object->enumerable_own_property_names(PropertyKind::Value);
return Array::create_from(global_object, obj_arg->get_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 // 20.1.2.5 Object.entries ( O ), https://tc39.es/ecma262/#sec-object.entries
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::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()) if (vm.exception())
return {}; return {};
auto name_list = object->enumerable_own_property_names(PropertyKind::KeyAndValue);
return Array::create_from(global_object, obj_arg->get_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 // 20.1.2.2 Object.create ( O, Properties ), https://tc39.es/ecma262/#sec-object.create
JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::create) JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::create)
{ {
auto prototype_value = vm.argument(0); auto proto = vm.argument(0);
auto properties = vm.argument(1); auto properties = vm.argument(1);
Object* prototype; // 1. If Type(O) is neither Object nor Null, throw a TypeError exception.
if (prototype_value.is_null()) { if (!proto.is_object() && !proto.is_null()) {
prototype = nullptr;
} else if (prototype_value.is_object()) {
prototype = &prototype_value.as_object();
} else {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {}; 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()) { if (!properties.is_undefined()) {
object->define_properties(properties); // a. Return ? ObjectDefineProperties(obj, Properties).
if (vm.exception()) return object->define_properties(properties);
return {};
} }
// 4. Return obj.
return object; return object;
} }
@ -385,18 +432,18 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectConstructor::assign)
continue; continue;
auto from = next_source.to_object(global_object); auto from = next_source.to_object(global_object);
VERIFY(!vm.exception()); VERIFY(!vm.exception());
auto keys = from->get_own_properties(PropertyKind::Key); auto keys = from->internal_own_property_keys();
if (vm.exception()) if (vm.exception())
return {}; return {};
for (auto& key : keys) { for (auto& next_key : keys) {
auto property_name = PropertyName::from_value(global_object, key); auto property_name = PropertyName::from_value(global_object, next_key);
auto property_descriptor = from->get_own_property_descriptor(property_name); auto property_descriptor = from->internal_get_own_property(property_name);
if (!property_descriptor.has_value() || !property_descriptor->attributes.is_enumerable()) if (!property_descriptor.has_value() || !*property_descriptor->enumerable)
continue; continue;
auto value = from->get(property_name); auto prop_value = from->get(property_name);
if (vm.exception()) if (vm.exception())
return {}; return {};
to->put(property_name, value); to->set(property_name, prop_value, true);
if (vm.exception()) if (vm.exception())
return {}; return {};
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org> * Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * 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 Optional<Variable> ObjectEnvironment::get_from_environment(FlyString const& name) const
{ {
auto value = m_binding_object.get(name); if (!m_binding_object.storage_has(name))
if (value.is_empty())
return {}; return {};
auto value = m_binding_object.get(name);
return Variable { value, DeclarationKind::Var }; 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) 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 // 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 bool ObjectEnvironment::has_binding(FlyString const& name) const
{ {
auto& vm = this->vm();
bool found_binding = m_binding_object.has_property(name); bool found_binding = m_binding_object.has_property(name);
if (vm.exception())
return {};
if (!found_binding) if (!found_binding)
return false; return false;
if (!m_with_environment)
// FIXME: Implement the rest of this operation. 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; return true;
} }
// 9.1.1.2.2 CreateMutableBinding ( N, D ), https://tc39.es/ecma262/#sec-object-environment-records-createmutablebinding-n-d // 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) void ObjectEnvironment::create_mutable_binding(GlobalObject&, FlyString const& name, bool can_be_deleted)
{ {
PropertyAttributes attributes; // 1. Let bindingObject be envRec.[[BindingObject]].
attributes.set_enumerable(); // 2. Return ? DefinePropertyOrThrow(bindingObject, N, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }).
attributes.set_has_enumerable(); m_binding_object.define_property_or_throw(name, { .value = js_undefined(), .writable = true, .enumerable = true, .configurable = can_be_deleted });
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);
} }
// 9.1.1.2.3 CreateImmutableBinding ( N, S ), https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s // 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 // 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) 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); bool still_exists = m_binding_object.has_property(name);
if (vm.exception())
return;
if (!still_exists && strict) { if (!still_exists && strict) {
global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name); global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name);
return; return;
} }
// FIXME: This should use the Set abstract operation. m_binding_object.set(name, value, strict);
// FIXME: Set returns a bool, so this may need to return a bool as well.
m_binding_object.put(name, value);
} }
// 9.1.1.2.6 GetBindingValue ( N, S ), https://tc39.es/ecma262/#sec-object-environment-records-getbindingvalue-n-s // 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) 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) if (!strict)
return js_undefined(); return js_undefined();
global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name); global_object.vm().throw_exception<ReferenceError>(global_object, ErrorType::UnknownIdentifier, name);
return {}; return {};
} }
// FIXME: This should use the Get abstract operation.
return m_binding_object.get(name); return m_binding_object.get(name);
} }
// 9.1.1.2.7 DeleteBinding ( N ), https://tc39.es/ecma262/#sec-object-environment-records-deletebinding-n // 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) bool ObjectEnvironment::delete_binding(GlobalObject&, FlyString const& name)
{ {
return m_binding_object.delete_property(name); return m_binding_object.internal_delete(name);
} }
} }

View file

@ -130,7 +130,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::property_is_enumerable)
auto property_descriptor = this_object->get_own_property_descriptor(property_key); auto property_descriptor = this_object->get_own_property_descriptor(property_key);
if (!property_descriptor.has_value()) if (!property_descriptor.has_value())
return Value(false); 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 // 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 {}; return {};
for (;;) { for (;;) {
object = object->prototype(); object = object->internal_get_prototype_of();
if (!object) if (!object)
return Value(false); return Value(false);
if (same_value(this_object, object)) if (same_value(this_object, object))
@ -166,22 +166,16 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_getter)
return {}; return {};
} }
auto descriptor = PropertyDescriptor { .get = &getter.as_function(), .enumerable = true, .configurable = true };
auto key = vm.argument(0).to_property_key(global_object); auto key = vm.argument(0).to_property_key(global_object);
if (vm.exception()) if (vm.exception())
return {}; return {};
auto descriptor = Object::create(global_object, global_object.object_prototype()); object->define_property_or_throw(key, descriptor);
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);
if (vm.exception()) if (vm.exception())
return {}; return {};
if (!success) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse);
return {};
}
return js_undefined(); return js_undefined();
} }
@ -198,22 +192,16 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_setter)
return {}; return {};
} }
auto descriptor = PropertyDescriptor { .set = &setter.as_function(), .enumerable = true, .configurable = true };
auto key = vm.argument(0).to_property_key(global_object); auto key = vm.argument(0).to_property_key(global_object);
if (vm.exception()) if (vm.exception())
return {}; return {};
auto descriptor = Object::create(global_object, global_object.object_prototype()); object->define_property_or_throw(key, descriptor);
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);
if (vm.exception()) if (vm.exception())
return {}; return {};
if (!success) {
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectDefinePropertyReturnedFalse);
return {};
}
return js_undefined(); return js_undefined();
} }
@ -229,12 +217,15 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::lookup_getter)
return {}; return {};
while (object) { while (object) {
auto desc = object->get_own_property_descriptor(key); auto desc = object->internal_get_own_property(key);
if (vm.exception()) if (vm.exception())
return {}; return {};
if (desc.has_value()) if (desc.has_value()) {
return desc->getter ?: js_undefined(); if (desc->is_accessor_descriptor())
object = object->prototype(); return *desc->get ?: js_undefined();
return js_undefined();
}
object = object->internal_get_prototype_of();
if (vm.exception()) if (vm.exception())
return {}; return {};
} }
@ -254,12 +245,15 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::lookup_setter)
return {}; return {};
while (object) { while (object) {
auto desc = object->get_own_property_descriptor(key); auto desc = object->internal_get_own_property(key);
if (vm.exception()) if (vm.exception())
return {}; return {};
if (desc.has_value()) if (desc.has_value()) {
return desc->setter ?: js_undefined(); if (desc->is_accessor_descriptor())
object = object->prototype(); return *desc->set ?: js_undefined();
return js_undefined();
}
object = object->internal_get_prototype_of();
if (vm.exception()) if (vm.exception())
return {}; return {};
} }
@ -273,7 +267,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::proto_getter)
auto object = vm.this_value(global_object).to_object(global_object); auto object = vm.this_value(global_object).to_object(global_object);
if (vm.exception()) if (vm.exception())
return {}; return {};
auto proto = object->prototype(); auto proto = object->internal_get_prototype_of();
if (vm.exception()) if (vm.exception())
return {}; return {};
return proto; return proto;
@ -293,10 +287,11 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::proto_setter)
if (!object.is_object()) if (!object.is_object())
return js_undefined(); 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()) if (vm.exception())
return {}; return {};
if (!status) { if (!status) {
// FIXME: Improve/contextualize error message
vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectSetPrototypeOfReturnedFalse);
return {}; return {};
} }

View file

@ -94,7 +94,7 @@ void OrdinaryFunctionObject::initialize(GlobalObject& global_object)
break; break;
case FunctionKind::Generator: case FunctionKind::Generator:
// prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png) // 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; break;
} }
define_property(vm.names.prototype, prototype, Attribute::Writable); define_property(vm.names.prototype, prototype, Attribute::Writable);

View file

@ -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;
}
}

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -13,73 +14,72 @@ namespace JS {
struct Attribute { struct Attribute {
enum { enum {
Configurable = 1 << 0, Writable = 1 << 0,
Enumerable = 1 << 1, Enumerable = 1 << 1,
Writable = 1 << 2, Configurable = 1 << 2,
HasGetter = 1 << 3,
HasSetter = 1 << 4,
HasConfigurable = 1 << 5,
HasEnumerable = 1 << 6,
HasWritable = 1 << 7,
}; };
}; };
// 6.1.7.1 Property Attributes, https://tc39.es/ecma262/#sec-property-attributes
class PropertyAttributes { class PropertyAttributes {
public: public:
PropertyAttributes(u8 bits = 0) 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; } void set_writable(bool writable = true)
bool has_enumerable() const { return m_bits & Attribute::HasEnumerable; } {
bool has_writable() const { return m_bits & Attribute::HasWritable; } if (writable)
bool has_getter() const { return m_bits & Attribute::HasGetter; } m_bits |= Attribute::Writable;
bool has_setter() const { return m_bits & Attribute::HasSetter; } else
m_bits &= ~Attribute::Writable;
}
bool is_configurable() const { return m_bits & Attribute::Configurable; } void set_enumerable(bool enumerable = true)
bool is_enumerable() const { return m_bits & Attribute::Enumerable; } {
bool is_writable() const { return m_bits & Attribute::Writable; } if (enumerable)
m_bits |= Attribute::Enumerable;
else
m_bits &= ~Attribute::Enumerable;
}
void set_has_configurable() { m_bits |= Attribute::HasConfigurable; } void set_configurable(bool configurable = true)
void set_has_enumerable() { m_bits |= Attribute::HasEnumerable; } {
void set_has_writable() { m_bits |= Attribute::HasWritable; } if (configurable)
void set_configurable() { m_bits |= Attribute::Configurable; } m_bits |= Attribute::Configurable;
void set_enumerable() { m_bits |= Attribute::Enumerable; } else
void set_writable() { m_bits |= Attribute::Writable; } m_bits &= ~Attribute::Configurable;
void set_has_getter() { m_bits |= Attribute::HasGetter; } }
void set_has_setter() { m_bits |= Attribute::HasSetter; }
bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; } bool operator==(const PropertyAttributes& other) const { return m_bits == other.m_bits; }
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; [[nodiscard]] u8 bits() const { return m_bits; }
u8 bits() const { return m_bits; }
private: private:
u8 m_bits; 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 { namespace AK {
template<> template<>
struct Formatter<JS::PropertyAttributes> : Formatter<u8> { struct Formatter<JS::PropertyAttributes> : Formatter<StringView> {
void format(FormatBuilder& builder, const JS::PropertyAttributes& attributes) 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)));
} }
}; };

View file

@ -13,9 +13,6 @@
namespace JS { 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 // 6.2.5.1 IsAccessorDescriptor ( Desc ), https://tc39.es/ecma262/#sec-isaccessordescriptor
bool PropertyDescriptor::is_accessor_descriptor() const bool PropertyDescriptor::is_accessor_descriptor() const
{ {
@ -190,6 +187,5 @@ PropertyAttributes PropertyDescriptor::attributes() const
attributes |= Attribute::Configurable; attributes |= Attribute::Configurable;
return { attributes }; return { attributes };
} }
#endif
} }

View file

@ -12,9 +12,6 @@
namespace JS { 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 // 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&); 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))); Formatter<StringView>::format(builder, String::formatted("PropertyDescriptor {{ {} }}", String::join(", ", parts)));
} }
}; };
#endif
} }

View file

@ -82,8 +82,8 @@ JS_DEFINE_NATIVE_FUNCTION(ProxyConstructor::revocable)
revoker->define_property(vm.names.length, Value(0), Attribute::Configurable); revoker->define_property(vm.names.length, Value(0), Attribute::Configurable);
auto* result = Object::create(global_object, global_object.object_prototype()); auto* result = Object::create(global_object, global_object.object_prototype());
result->define_property(vm.names.proxy, proxy); result->create_data_property_or_throw(vm.names.proxy, proxy);
result->define_property(vm.names.revoke, revoker); result->create_data_property_or_throw(vm.names.revoke, revoker);
return result; return result;
} }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -27,21 +28,23 @@ public:
const Object& target() const { return m_target; } const Object& target() const { return m_target; }
const Object& handler() const { return m_handler; } 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; } bool is_revoked() const { return m_is_revoked; }
void revoke() { m_is_revoked = true; } 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: private:
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;

View file

@ -21,7 +21,7 @@ void Reference::put_value(GlobalObject& global_object, Value value)
throw_reference_error(global_object); throw_reference_error(global_object);
return; return;
} }
global_object.put(m_name, value); global_object.set(m_name, value, false);
return; return;
} }
@ -39,7 +39,9 @@ void Reference::put_value(GlobalObject& global_object, Value value)
if (!base_obj) if (!base_obj)
return; 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) { 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()); 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; return;
@ -108,6 +110,7 @@ Value Reference::get_value(GlobalObject& global_object, bool throw_if_undefined)
return value->value; 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) bool Reference::delete_(GlobalObject& global_object)
{ {
// 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation // 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. // b. If IsSuperReference(ref) is true, throw a ReferenceError exception.
if (is_super_reference()) { if (is_super_reference()) {
vm.throw_exception<ReferenceError>(global_object, ErrorType::UnsupportedDeleteSuperProperty); vm.throw_exception<ReferenceError>(global_object, ErrorType::UnsupportedDeleteSuperProperty);
return false; return {};
} }
// c. Let baseObj be ! ToObject(ref.[[Base]]). // c. Let baseObj be ! ToObject(ref.[[Base]]).
auto* base_obj = m_base_value.to_object(global_object); auto* base_obj = m_base_value.to_object(global_object);
if (!base_obj) VERIFY(base_obj);
return false;
// d. Let deleteStatus be ? baseObj.[[Delete]](ref.[[ReferencedName]]). // 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. // e. If deleteStatus is false and ref.[[Strict]] is true, throw a TypeError exception.
if (!delete_status && m_strict) { 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()); 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. // f. Return deleteStatus.

View file

@ -15,17 +15,6 @@
namespace JS { 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) ReflectObject::ReflectObject(GlobalObject& global_object)
: Object(*global_object.object_prototype()) : Object(*global_object.object_prototype())
{ {
@ -62,182 +51,294 @@ ReflectObject::~ReflectObject()
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::apply) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::apply)
{ {
auto target = vm.argument(0); 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()) { 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 {}; return {};
} }
auto this_arg = vm.argument(1); // 2. Let args be ? CreateListFromArrayLike(argumentsList).
auto arguments = create_list_from_array_like(global_object, vm.argument(2)); auto args = create_list_from_array_like(global_object, arguments_list);
if (vm.exception()) if (vm.exception())
return {}; 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 // 28.1.2 Reflect.construct ( target, argumentsList [ , newTarget ] ), https://tc39.es/ecma262/#sec-reflect.construct
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::construct) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::construct)
{ {
auto target = vm.argument(0); 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()) { 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 {}; return {};
} }
auto* new_target = &target.as_function(); // 2. If newTarget is not present, set newTarget to target.
if (vm.argument_count() > 2) { if (vm.argument_count() < 3) {
auto new_target_value = vm.argument(2); new_target = target;
if (!new_target_value.is_constructor()) { }
vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadNewTarget); // 3. Else if IsConstructor(newTarget) is false, throw a TypeError exception.
return {}; else if (!new_target.is_constructor()) {
} vm.throw_exception<TypeError>(global_object, ErrorType::NotAConstructor, new_target.to_string_without_side_effects());
new_target = &new_target_value.as_function(); 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()) if (vm.exception())
return {}; 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 // 28.1.3 Reflect.defineProperty ( target, propertyKey, attributes ), https://tc39.es/ecma262/#sec-reflect.defineproperty
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::define_property)
{ {
auto* target = get_target_object_from(global_object, "defineProperty"); auto target = vm.argument(0);
if (!target) auto property_key = vm.argument(1);
return {}; auto attributes = vm.argument(2);
auto property_key = vm.argument(1).to_property_key(global_object);
if (vm.exception()) // 1. If Type(target) is not Object, throw a TypeError exception.
return {}; if (!target.is_object()) {
if (!vm.argument(2).is_object()) { vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
vm.throw_exception<TypeError>(global_object, ErrorType::ReflectBadDescriptorArgument);
return {}; return {};
} }
auto& descriptor = vm.argument(2).as_object();
auto success = target->define_property(property_key, descriptor, false); // 2. Let key be ? ToPropertyKey(propertyKey).
auto key = property_key.to_property_key(global_object);
if (vm.exception()) if (vm.exception())
return {}; return {};
return Value(success);
// 3. Let desc be ? ToPropertyDescriptor(attributes).
auto descriptor = to_property_descriptor(global_object, attributes);
if (vm.exception())
return {};
// 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 // 28.1.4 Reflect.deleteProperty ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.deleteproperty
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::delete_property)
{ {
auto* target = get_target_object_from(global_object, "deleteProperty"); auto target = vm.argument(0);
if (!target) 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 {}; 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()) if (vm.exception())
return {}; 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 // 28.1.5 Reflect.get ( target, propertyKey [ , receiver ] ), https://tc39.es/ecma262/#sec-reflect.get
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get)
{ {
auto* target = get_target_object_from(global_object, "get"); auto target = vm.argument(0);
if (!target) 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 {}; 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()) if (vm.exception())
return {}; return {};
Value receiver = {};
if (vm.argument_count() > 2) // 3. If receiver is not present, then
receiver = vm.argument(2); if (vm.argument_count() < 3) {
return target->get(property_key, receiver).value_or(js_undefined()); // 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 // 28.1.6 Reflect.getOwnPropertyDescriptor ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.getownpropertydescriptor
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_own_property_descriptor)
{ {
auto* target = get_target_object_from(global_object, "getOwnPropertyDescriptor"); auto target = vm.argument(0);
if (!target) 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 {}; 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()) if (vm.exception())
return {}; 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 // 28.1.7 Reflect.getPrototypeOf ( target ), https://tc39.es/ecma262/#sec-reflect.getprototypeof
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_prototype_of) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::get_prototype_of)
{ {
auto* target = get_target_object_from(global_object, "getPrototypeOf"); auto target = vm.argument(0);
if (!target)
// 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 {};
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 // 28.1.8 Reflect.has ( target, propertyKey ), https://tc39.es/ecma262/#sec-reflect.has
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::has)
{ {
auto* target = get_target_object_from(global_object, "has"); auto target = vm.argument(0);
if (!target) 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 {}; 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()) if (vm.exception())
return {}; 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 // 28.1.9 Reflect.isExtensible ( target ), https://tc39.es/ecma262/#sec-reflect.isextensible
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::is_extensible) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::is_extensible)
{ {
auto* target = get_target_object_from(global_object, "isExtensible"); auto target = vm.argument(0);
if (!target)
// 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 {};
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 // 28.1.10 Reflect.ownKeys ( target ), https://tc39.es/ecma262/#sec-reflect.ownkeys
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::own_keys)
{ {
auto* target = get_target_object_from(global_object, "ownKeys"); auto target = vm.argument(0);
if (!target)
// 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 {};
return Array::create_from(global_object, target->get_own_properties(PropertyKind::Key)); }
// 2. Let keys be ? target.[[OwnPropertyKeys]]().
auto keys = target.as_object().internal_own_property_keys();
if (vm.exception())
return {};
// 3. Return CreateArrayFromList(keys).
return Array::create_from(global_object, keys);
} }
// 28.1.11 Reflect.preventExtensions ( target ), https://tc39.es/ecma262/#sec-reflect.preventextensions // 28.1.11 Reflect.preventExtensions ( target ), https://tc39.es/ecma262/#sec-reflect.preventextensions
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::prevent_extensions)
{ {
auto* target = get_target_object_from(global_object, "preventExtensions"); auto target = vm.argument(0);
if (!target)
// 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 {};
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 // 28.1.12 Reflect.set ( target, propertyKey, V [ , receiver ] ), https://tc39.es/ecma262/#sec-reflect.set
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set)
{ {
auto* target = get_target_object_from(global_object, "set"); auto target = vm.argument(0);
if (!target) 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 {}; 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()) if (vm.exception())
return {}; return {};
auto value = vm.argument(2);
Value receiver = {}; // 3. If receiver is not present, then
if (vm.argument_count() > 3) if (vm.argument_count() < 4) {
receiver = vm.argument(3); // a. Set receiver to target.
return Value(target->put(property_key, value, receiver)); 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 // 28.1.13 Reflect.setPrototypeOf ( target, proto ), https://tc39.es/ecma262/#sec-reflect.setprototypeof
JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set_prototype_of) JS_DEFINE_NATIVE_FUNCTION(ReflectObject::set_prototype_of)
{ {
auto* target = get_target_object_from(global_object, "setPrototypeOf"); auto target = vm.argument(0);
if (!target) 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 {}; 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); vm.throw_exception<TypeError>(global_object, ErrorType::ObjectPrototypeWrongType);
return {}; return {};
} }
Object* prototype = nullptr;
if (!prototype_value.is_null()) // 3. Return ? target.[[SetPrototypeOf]](proto).
prototype = const_cast<Object*>(&prototype_value.as_object()); return Value(target.as_object().internal_set_prototype_of(proto.is_null() ? nullptr : &proto.as_object()));
return Value(target->set_prototype(prototype));
} }
} }

View file

@ -178,27 +178,27 @@ JS_DEFINE_NATIVE_FUNCTION(RegExpPrototype::exec)
auto* array = Array::create(global_object, result.n_capture_groups + 1); auto* array = Array::create(global_object, result.n_capture_groups + 1);
if (vm.exception()) if (vm.exception())
return {}; return {};
array->define_property(vm.names.index, Value((i32)match.global_offset)); array->create_data_property_or_throw(vm.names.index, Value((i32)match.global_offset));
array->define_property(vm.names.input, js_string(vm, str)); array->create_data_property_or_throw(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(0, js_string(vm, match.view.to_string()));
for (size_t i = 0; i < result.n_capture_groups; ++i) { for (size_t i = 0; i < result.n_capture_groups; ++i) {
auto capture_value = js_undefined(); auto capture_value = js_undefined();
auto& capture = result.capture_group_matches[0][i + 1]; auto& capture = result.capture_group_matches[0][i + 1];
if (!capture.view.is_null()) if (!capture.view.is_null())
capture_value = js_string(vm, capture.view.to_string()); 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(); Value groups = js_undefined();
if (result.n_named_capture_groups > 0) { if (result.n_named_capture_groups > 0) {
auto groups_object = Object::create(global_object, nullptr); auto groups_object = Object::create(global_object, nullptr);
for (auto& entry : result.named_capture_group_matches[0]) 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); groups = move(groups_object);
} }
array->define_property(vm.names.groups, groups); array->create_data_property_or_throw(vm.names.groups, groups);
return array; return array;
} }

View file

@ -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(); ensure_property_table();
if (m_property_table->set(property_name, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry) if (m_property_table->set(property_name, { m_property_count, attributes }) == AK::HashSetResult::InsertedNewEntry)
++m_property_count; ++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) 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);
} }
} }

View file

@ -98,7 +98,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringConstructor::raw)
StringBuilder builder; StringBuilder builder;
for (size_t i = 0; i < literal_segments; ++i) { for (size_t i = 0; i < literal_segments; ++i) {
auto next_key = String::number(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()) if (vm.exception())
return {}; return {};
auto next_segment = next_segment_value.to_string(global_object); auto next_segment = next_segment_value.to_string(global_object);

View file

@ -5,8 +5,10 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PrimitiveString.h> #include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/StringObject.h> #include <LibJS/Runtime/StringObject.h>
namespace JS { namespace JS {
@ -40,18 +42,149 @@ void StringObject::visit_edges(Cell::Visitor& visitor)
visitor.visit(&m_string); 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()) auto& vm = global_object.vm();
return Base::get_own_property_descriptor(property_name);
PropertyDescriptor descriptor; // 1. Assert: S is an Object that has a [[StringData]] internal slot.
descriptor.value = js_string(heap(), m_string.string().substring(property_name.as_number(), 1)); // 2. Assert: IsPropertyKey(P) is true.
descriptor.attributes.set_has_configurable(); VERIFY(property_name.is_valid());
descriptor.attributes.set_has_enumerable();
descriptor.attributes.set_has_writable(); // 3. If Type(P) is not String, return undefined.
descriptor.attributes.set_enumerable(); // NOTE: The spec only uses string and symbol keys, and later coerces to numbers -
return descriptor; // 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;
} }
} }

View file

@ -27,9 +27,12 @@ public:
} }
private: 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 bool is_string_object() const final { return true; }
virtual void visit_edges(Visitor&) override; virtual void visit_edges(Visitor&) override;
virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName const&) const override;
PrimitiveString& m_string; PrimitiveString& m_string;
}; };

View file

@ -596,7 +596,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
size_t result_len = 0; size_t result_len = 0;
auto limit = NumericLimits<u32>::max(); auto limit = NumericLimits<u32>::max();
if (!vm.argument(1).is_undefined()) { if (!limit_argument.is_undefined()) {
limit = limit_argument.to_u32(global_object); limit = limit_argument.to_u32(global_object);
if (vm.exception()) if (vm.exception())
return {}; return {};
@ -609,8 +609,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
if (limit == 0) if (limit == 0)
return result; return result;
if (vm.argument(0).is_undefined()) { if (separator_argument.is_undefined()) {
result->define_property(0, js_string(vm, string)); result->create_data_property_or_throw(0, js_string(vm, string));
return result; return result;
} }
@ -618,7 +618,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
auto separator_len = separator.length(); auto separator_len = separator.length();
if (len == 0) { if (len == 0) {
if (separator_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; return result;
} }
@ -638,7 +638,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
} }
auto segment = string.substring_view(start, pos - start); 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++; result_len++;
if (result_len == limit) if (result_len == limit)
return result; return result;
@ -647,7 +647,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
} }
auto rest = string.substring(start, len - start); 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; return result;
} }
@ -731,6 +731,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::match)
if (!regexp.is_nullish()) { if (!regexp.is_nullish()) {
if (auto* matcher = regexp.get_method(global_object, *vm.well_known_symbol_match())) if (auto* matcher = regexp.get_method(global_object, *vm.well_known_symbol_match()))
return vm.call(*matcher, regexp, this_object); return vm.call(*matcher, regexp, this_object);
if (vm.exception())
return {};
} }
auto s = this_object.to_string(global_object); auto s = this_object.to_string(global_object);
if (vm.exception()) if (vm.exception())
@ -793,6 +795,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::replace)
if (!search_value.is_nullish()) { if (!search_value.is_nullish()) {
if (auto* replacer = search_value.get_method(global_object, *vm.well_known_symbol_replace())) if (auto* replacer = search_value.get_method(global_object, *vm.well_known_symbol_replace()))
return vm.call(*replacer, search_value, this_object, replace_value); return vm.call(*replacer, search_value, this_object, replace_value);
if (vm.exception())
return {};
} }
auto string = this_object.to_string(global_object); auto string = this_object.to_string(global_object);

View file

@ -96,6 +96,8 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object,
} }
auto element_length = src_array.array_length(); 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(); auto element_size = dest_array.element_size();
Checked<size_t> byte_length = element_size; Checked<size_t> byte_length = element_size;
byte_length *= element_length; byte_length *= element_length;
@ -104,6 +106,7 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object,
return; return;
} }
// FIXME: Determine and use bufferConstructor
auto data = ArrayBuffer::create(global_object, byte_length.value()); auto data = ArrayBuffer::create(global_object, byte_length.value());
if (src_data->is_detached()) { if (src_data->is_detached()) {
@ -116,26 +119,19 @@ static void initialize_typed_array_from_typed_array(GlobalObject& global_object,
return; 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_viewed_array_buffer(data);
dest_array.set_byte_length(byte_length.value()); dest_array.set_byte_length(byte_length.value());
dest_array.set_byte_offset(0); dest_array.set_byte_offset(0);
dest_array.set_array_length(element_length); 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 // 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); typed_array.set_array_length(length);
for (size_t k = 0; k < length; k++) { 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()) if (vm.exception())
return; return;
typed_array.put_by_index(k, value); typed_array.set(k, value, true);
if (vm.exception()) if (vm.exception())
return; return;
} }
@ -188,7 +184,7 @@ static void initialize_typed_array_from_list(GlobalObject& global_object, TypedA
auto& vm = global_object.vm(); auto& vm = global_object.vm();
for (size_t k = 0; k < list.size(); k++) { for (size_t k = 0; k < list.size(); k++) {
auto value = list[k]; auto value = list[k];
typed_array.put_by_index(k, value); typed_array.set(k, value, true);
if (vm.exception()) if (vm.exception())
return; return;
} }

View file

@ -1,13 +1,17 @@
/* /*
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org> * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PropertyDescriptor.h>
#include <LibJS/Runtime/PropertyName.h>
#include <LibJS/Runtime/TypedArrayConstructor.h> #include <LibJS/Runtime/TypedArrayConstructor.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
@ -57,6 +61,114 @@ private:
virtual void visit_edges(Visitor&) override; 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> template<typename T>
class TypedArray : public TypedArrayBase { class TypedArray : public TypedArrayBase {
JS_OBJECT(TypedArray, TypedArrayBase); JS_OBJECT(TypedArray, TypedArrayBase);
@ -64,72 +176,286 @@ class TypedArray : public TypedArrayBase {
using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>; using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>;
public: public:
// 10.4.5.11 IntegerIndexedElementSet ( O, index, value ), https://tc39.es/ecma262/#sec-integerindexedelementset // 10.4.5.1 [[GetOwnProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p
// NOTE: In error cases, the function will return as if it succeeded. virtual Optional<PropertyDescriptor> internal_get_own_property(PropertyName const& property_name) const override
virtual bool put_by_index(u32 property_index, Value value) override
{ {
auto& vm = this->vm(); // 1. Assert: IsPropertyKey(P) is true.
auto& global_object = this->global_object(); VERIFY(property_name.is_valid());
Value num_value; // 2. Assert: O is an Integer-Indexed exotic object.
if (content_type() == TypedArrayBase::ContentType::BigInt) {
num_value = value.to_bigint(global_object); // NOTE: If the property name is a number type (An implementation-defined optimized
if (vm.exception()) // property key type), it can be treated as a string property that has already been
return {}; // converted successfully into a canonical numeric index.
} else {
num_value = value.to_number(global_object); // 3. If Type(P) is String, then
if (vm.exception()) // NOTE: This includes an implementation-defined optimization, see note above!
return {}; 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)) // 4. Return OrdinaryGetOwnProperty(O, P).
return true; return Object::internal_get_own_property(property_name);
auto offset = byte_offset();
// 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;
}
viewed_array_buffer()->template set_value<T>(indexed_position.value(), num_value, true, ArrayBuffer::Order::Unordered);
return true;
}
// 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
{
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();
}
return viewed_array_buffer()->template get_value<T>(indexed_position.value(), true, ArrayBuffer::Order::Unordered);
} }
// 10.4.5.2 [[HasProperty]] ( P ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p // 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 virtual bool internal_has_property(PropertyName const& property_name) const override
{ {
if (name.is_number()) { // 1. Assert: IsPropertyKey(P) is true.
return is_valid_integer_index(name.as_number()); 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);
} }
return Object::has_property(name);
// 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;
}
}
// 4. Return ! OrdinaryDefineOwnProperty(O, P, Desc).
return Object::internal_define_own_property(property_name, property_descriptor);
}
// 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.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
{
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;
}
}
// 3. Return ? OrdinarySet(O, P, V, Receiver).
return Object::internal_set(property_name, value, receiver);
}
// 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
{
// 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));
}
}
// 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 Span<const UnderlyingBufferDataType> data() const
@ -160,22 +486,6 @@ protected:
private: private:
virtual bool is_typed_array() const final { return true; } 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) \ #define JS_DECLARE_TYPED_ARRAY(ClassName, snake_name, PrototypeName, ConstructorName, Type) \

View file

@ -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()); auto* rest_object = Object::create(global_object, global_object.object_prototype());
for (auto& object_property : object->shape().property_table()) { for (auto& object_property : object->shape().property_table()) {
if (!object_property.value.attributes.has_enumerable()) if (!object_property.value.attributes.is_enumerable())
continue; continue;
if (seen_names.contains(object_property.key.to_display_string())) if (seen_names.contains(object_property.key.to_display_string()))
continue; continue;
@ -386,10 +386,13 @@ Value VM::get_variable(const FlyString& name, GlobalObject& global_object)
return possible_match.value().value; return possible_match.value().value;
} }
} }
auto value = global_object.get(name);
if (m_underscore_is_last_value && name == "_" && value.is_empty()) if (!global_object.storage_has(name)) {
return m_last_value; if (m_underscore_is_last_value && name == "_")
return value; return m_last_value;
return {};
}
return global_object.get(name);
} }
// 9.1.2.1 GetIdentifierReference ( env, name, strict ), https://tc39.es/ecma262/#sec-getidentifierreference // 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; callee_context.this_value = this_argument;
auto result = function.construct(new_target); 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_execution_context();
pop_guard.disarm(); pop_guard.disarm();
@ -487,7 +487,7 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
if (exception()) if (exception())
return {}; return {};
if (prototype.is_object()) { if (prototype.is_object()) {
result.as_object().set_prototype(&prototype.as_object()); result.as_object().internal_set_prototype_of(&prototype.as_object());
if (exception()) if (exception())
return {}; return {};
} }
@ -500,7 +500,9 @@ Value VM::construct(FunctionObject& function, FunctionObject& new_target, Option
if (result.is_object()) if (result.is_object())
return result; 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) void VM::throw_exception(Exception& exception)

View file

@ -258,7 +258,7 @@ bool Value::is_regexp(GlobalObject& global_object) const
auto matcher = as_object().get(*vm.well_known_symbol_match()); auto matcher = as_object().get(*vm.well_known_symbol_match());
if (vm.exception()) if (vm.exception())
return false; return false;
if (!matcher.is_empty() && !matcher.is_undefined()) if (!matcher.is_undefined())
return matcher.to_boolean(); return matcher.to_boolean();
return is<RegExpObject>(as_object()); return is<RegExpObject>(as_object());
@ -807,7 +807,7 @@ Value Value::get(GlobalObject& global_object, PropertyName const& property_name)
return {}; return {};
// 3. Return ? O.[[Get]](P, V). // 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 // 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()); VERIFY(property_name.is_valid());
// 2. Let func be ? GetV(V, P). // 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()) if (vm.exception())
return nullptr; return nullptr;
@ -1252,6 +1252,7 @@ Value instance_of(GlobalObject& global_object, Value lhs, Value rhs)
return ordinary_has_instance(global_object, lhs, 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) Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs)
{ {
auto& vm = global_object.vm(); auto& vm = global_object.vm();
@ -1277,7 +1278,7 @@ Value ordinary_has_instance(GlobalObject& global_object, Value lhs, Value rhs)
return {}; return {};
} }
while (true) { while (true) {
lhs_object = lhs_object->prototype(); lhs_object = lhs_object->internal_get_prototype_of();
if (vm.exception()) if (vm.exception())
return {}; return {};
if (!lhs_object) if (!lhs_object)

View file

@ -167,10 +167,7 @@ describe("errors", () => {
expect(() => { expect(() => {
Object.defineProperty(o, "foo", { value: 2, writable: true, enumerable: false }); Object.defineProperty(o, "foo", { value: 2, writable: true, enumerable: false });
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
TypeError,
"Cannot change attributes of non-configurable property 'foo'"
);
}); });
test("redefine non-configurable symbol property", () => { test("redefine non-configurable symbol property", () => {
@ -180,10 +177,7 @@ describe("errors", () => {
expect(() => { expect(() => {
Object.defineProperty(o, s, { value: 2, writable: true, enumerable: false }); Object.defineProperty(o, s, { value: 2, writable: true, enumerable: false });
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
TypeError,
"Cannot change attributes of non-configurable property 'Symbol(foo)'"
);
}); });
test("cannot define 'value' and 'get' in the same descriptor", () => { test("cannot define 'value' and 'get' in the same descriptor", () => {
@ -234,9 +228,6 @@ describe("errors", () => {
return this.secret_foo + 2; return this.secret_foo + 2;
}, },
}); });
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
TypeError,
"Cannot change attributes of non-configurable property 'foo'"
);
}); });
}); });

View file

@ -34,10 +34,7 @@ describe("normal behavior", () => {
// expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o); // expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o);
expect(() => { expect(() => {
Object.defineProperty(o, "foo", { configurable: true }); Object.defineProperty(o, "foo", { configurable: true });
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
TypeError,
"Cannot change attributes of non-configurable property 'foo'"
);
}); });
test("prevents changing value of existing properties", () => { test("prevents changing value of existing properties", () => {

View file

@ -47,7 +47,7 @@ describe("errors", () => {
expect(() => { expect(() => {
Object.defineProperty(o, "baz", { value: "baz" }); 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(); expect(o.baz).toBeUndefined();
}); });
@ -59,7 +59,7 @@ describe("errors", () => {
expect(() => { expect(() => {
"use strict"; "use strict";
o.foo = "foo"; 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 = "foo")).toBe("foo");
expect(o.foo).toBeUndefined(); expect(o.foo).toBeUndefined();

View file

@ -34,10 +34,7 @@ describe("normal behavior", () => {
// expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o); // expect(Object.defineProperty(o, "foo", { configurable: false })).toBe(o);
expect(() => { expect(() => {
Object.defineProperty(o, "foo", { configurable: true }); Object.defineProperty(o, "foo", { configurable: true });
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, "Object's [[DefineOwnProperty]] method returned false");
TypeError,
"Cannot change attributes of non-configurable property 'foo'"
);
}); });
test("doesn't prevent changing value of existing properties", () => { test("doesn't prevent changing value of existing properties", () => {

View file

@ -77,7 +77,7 @@ describe("[[DefineProperty]] invariants", () => {
expect(() => { expect(() => {
Object.defineProperty(p, "foo", {}); 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", () => { test("trap cannot return true for a non-extensible target if the property does not exist", () => {

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
expect(() => { expect(() => {
Reflect.apply(value); Reflect.apply(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not a function`);
TypeError,
"First argument of Reflect.apply() must be a function"
);
}); });
}); });

View file

@ -3,14 +3,11 @@ test("length is 2", () => {
}); });
describe("errors", () => { describe("errors", () => {
test("target must be a function", () => { test("target must be a constructor", () => {
[null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
expect(() => { expect(() => {
Reflect.construct(value); Reflect.construct(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not a constructor`);
TypeError,
"First argument of Reflect.construct() must be 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 => { [null, undefined, "foo", 123, NaN, Infinity, {}].forEach(value => {
expect(() => { expect(() => {
Reflect.construct(() => {}, [], value); Reflect.construct(() => {}, [], value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not a constructor`);
TypeError,
"Optional third argument of Reflect.construct() must be a constructor"
);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.defineProperty(value); Reflect.defineProperty(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.defineProperty() must be an object"
);
}); });
}); });
@ -18,7 +15,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.defineProperty({}, "foo", value); Reflect.defineProperty({}, "foo", value);
}).toThrowWithMessage(TypeError, "Descriptor argument is not an object"); }).toThrowWithMessage(TypeError, `${value} is not an object`);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.deleteProperty(value); Reflect.deleteProperty(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.deleteProperty() must be an object"
);
}); });
}); });
}); });

View file

@ -7,7 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.get(value); Reflect.get(value);
}).toThrowWithMessage(TypeError, "First argument of Reflect.get() must be an object"); }).toThrowWithMessage(TypeError, `${value} is not an object`);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.getOwnPropertyDescriptor(value); Reflect.getOwnPropertyDescriptor(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.getOwnPropertyDescriptor() must be an object"
);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.getPrototypeOf(value); Reflect.getPrototypeOf(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.getPrototypeOf() must be an object"
);
}); });
}); });
}); });

View file

@ -7,7 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.has(value); Reflect.has(value);
}).toThrowWithMessage(TypeError, "First argument of Reflect.has() must be an object"); }).toThrowWithMessage(TypeError, `${value} is not an object`);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.isExtensible(value); Reflect.isExtensible(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.isExtensible() must be an object"
);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.ownKeys(value); Reflect.ownKeys(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.ownKeys() must be an object"
);
}); });
}); });
}); });

View file

@ -3,10 +3,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.preventExtensions(value); Reflect.preventExtensions(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.preventExtensions() must be an object"
);
}); });
}); });
}); });

View file

@ -7,7 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.set(value); Reflect.set(value);
}).toThrowWithMessage(TypeError, "First argument of Reflect.set() must be an object"); }).toThrowWithMessage(TypeError, `${value} is not an object`);
}); });
}); });
}); });

View file

@ -7,10 +7,7 @@ describe("errors", () => {
[null, undefined, "foo", 123, NaN, Infinity].forEach(value => { [null, undefined, "foo", 123, NaN, Infinity].forEach(value => {
expect(() => { expect(() => {
Reflect.setPrototypeOf(value); Reflect.setPrototypeOf(value);
}).toThrowWithMessage( }).toThrowWithMessage(TypeError, `${value} is not an object`);
TypeError,
"First argument of Reflect.setPrototypeOf() must be an object"
);
}); });
}); });

View file

@ -13,7 +13,7 @@ test("extending null", () => {
expect(() => { expect(() => {
new A(); new A();
}).toThrowWithMessage(ReferenceError, "|this| has not been initialized"); }).toThrowWithMessage(TypeError, "Super constructor is not a constructor");
}); });
test("extending String", () => { test("extending String", () => {

View file

@ -1,8 +1,8 @@
test("basic functionality", () => { test("basic functionality", () => {
function Foo() {} function Foo() {}
Foo[Symbol.hasInstance] = value => { Object.defineProperty(Foo, Symbol.hasInstance, {
return value === 2; value: instance => instance === 2,
}; });
expect(new Foo() instanceof Foo).toBeFalse(); expect(new Foo() instanceof Foo).toBeFalse();
expect(2 instanceof Foo).toBeTrue(); expect(2 instanceof Foo).toBeTrue();

View file

@ -357,7 +357,7 @@ inline JSFileResult TestRunner::run_file_test(const String& test_path)
// Collect logged messages // Collect logged messages
auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array(); auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array();
for (auto& entry : arr.indexed_properties()) { 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()); file_result.logged_messages.append(message.to_string_without_side_effects());
} }

View file

@ -11,12 +11,12 @@
namespace Web::Bindings { 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! // FIXME: These should actually use camelCase versions of the property names!
auto property_id = CSS::property_id_from_string(name.to_string()); auto property_id = CSS::property_id_from_string(name.to_string());
if (property_id == CSS::PropertyID::Invalid) 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()) { for (auto& property : impl().properties()) {
if (property.property_id == property_id) if (property.property_id == property_id)
return js_string(vm(), property.value->to_string()); 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()); 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! // FIXME: These should actually use camelCase versions of the property names!
auto property_id = CSS::property_id_from_string(name.to_string()); auto property_id = CSS::property_id_from_string(name.to_string());
if (property_id == CSS::PropertyID::Invalid) 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()); auto css_text = value.to_string(global_object());
if (vm().exception()) if (vm().exception())

View file

@ -12,21 +12,17 @@
namespace Web::Bindings { 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()) if (property_name.is_symbol())
return Base::get(name, receiver, allow_side_effects); return Base::internal_get(property_name, receiver);
auto* item = const_cast<DOM::HTMLCollection&>(impl()).named_item(name.to_string()); 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) if (!item)
return Base::get(name, receiver, allow_side_effects); return Base::internal_get(property_name, receiver);
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 wrap(global_object(), *item); return wrap(global_object(), *item);
} }

View file

@ -43,7 +43,8 @@ void WindowObject::initialize_global_object()
{ {
Base::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("window", this, JS::Attribute::Enumerable);
define_property("frames", this, JS::Attribute::Enumerable); define_property("frames", this, JS::Attribute::Enumerable);

View file

@ -1,4 +1,4 @@
[CustomGet,CustomPut] [CustomGet,CustomSet]
interface CSSStyleDeclaration { interface CSSStyleDeclaration {
readonly attribute unsigned long length; readonly attribute unsigned long length;

View file

@ -786,17 +786,12 @@ public:
if (interface.extended_attributes.contains("CustomGet")) { if (interface.extended_attributes.contains("CustomGet")) {
generator.append(R"~~~( 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"~~~( generator.append(R"~~~(
virtual JS::Value get_by_index(u32 property_index, JS::AllowSideEffects = JS::AllowSideEffects::Yes) const override; virtual bool internal_set(const JS::PropertyName&, JS::Value, JS::Value receiver) override;
)~~~");
}
if (interface.extended_attributes.contains("CustomPut")) {
generator.append(R"~~~(
virtual bool put(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_class@::@wrapper_class@(JS::GlobalObject& global_object, @fully_qualified_name@& impl)
: @wrapper_base_class@(global_object, 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 // https://heycam.github.io/webidl/#es-DOMException-specialness
// Object.getPrototypeOf(DOMException.prototype) === Error.prototype // Object.getPrototypeOf(DOMException.prototype) === Error.prototype
generator.append(R"~~~( 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()) { } else if (!interface.parent_name.is_empty()) {
generator.append(R"~~~( 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);
)~~~"); )~~~");
} }

View file

@ -1,4 +1,4 @@
[CustomGet,CustomGetByIndex] [CustomGet]
interface HTMLCollection { interface HTMLCollection {
readonly attribute unsigned long length; readonly attribute unsigned long length;

View file

@ -192,7 +192,7 @@ static void print_array(JS::Array& array, HashTable<JS::Object*>& seen_objects)
bool first = true; bool first = true;
for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) { for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) {
print_separator(first); 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 // The V8 repl doesn't throw an exception here, and instead just
// prints 'undefined'. We may choose to replicate that behavior in // prints 'undefined'. We may choose to replicate that behavior in
// the future, but for now lets just catch the error // 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()) { for (auto& entry : object.indexed_properties()) {
print_separator(first); print_separator(first);
out("\"\033[33;1m{}\033[0m\": ", entry.index()); 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 // The V8 repl doesn't throw an exception here, and instead just
// prints 'undefined'. We may choose to replicate that behavior in // prints 'undefined'. We may choose to replicate that behavior in
// the future, but for now lets just catch the error // the future, but for now lets just catch the error