From 8f7021faf7b30f2094a360019083229ca0117d9c Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Fri, 18 Mar 2022 20:18:19 +0330 Subject: [PATCH] LibJS: Implement bytecode generation for For-In/Of statements This also implements the rather interesting behaviour that #12772 relies on, so this fixes that bug in BC mode (the AST interp remains affected). --- Userland/Libraries/LibJS/AST.h | 2 + .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 364 +++++++++++++++++- .../Libraries/LibJS/Bytecode/Generator.cpp | 5 +- .../Libraries/LibJS/Bytecode/Instruction.h | 1 + Userland/Libraries/LibJS/Bytecode/Op.cpp | 85 ++++ Userland/Libraries/LibJS/Bytecode/Op.h | 12 + 6 files changed, 454 insertions(+), 15 deletions(-) diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index 8723c657683..4880b30766b 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -894,6 +894,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Bytecode::CodeGenerationErrorOr generate_bytecode(Bytecode::Generator&) const override; virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector const&) const override; virtual void dump(int indent) const override; @@ -918,6 +919,7 @@ public: Statement const& body() const { return *m_body; } virtual Completion execute(Interpreter&, GlobalObject&) const override; + virtual Bytecode::CodeGenerationErrorOr generate_bytecode(Bytecode::Generator&) const override; virtual Completion loop_evaluation(Interpreter&, GlobalObject&, Vector const&) const override; virtual void dump(int indent) const override; diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 8860a00ef18..cd68d08209d 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1155,6 +1155,23 @@ static Bytecode::CodeGenerationErrorOr generate_binding_pattern_bytecode(B return generate_array_binding_pattern_bytecode(generator, pattern, initialization_mode, value_reg); } +static Bytecode::CodeGenerationErrorOr assign_accumulator_to_variable_declarator(Bytecode::Generator& generator, VariableDeclarator const& declarator, VariableDeclaration const& declaration) +{ + auto initialization_mode = declaration.is_lexical_declaration() ? Bytecode::Op::SetVariable::InitializationMode::Initialize : Bytecode::Op::SetVariable::InitializationMode::Set; + auto environment_mode = declaration.is_lexical_declaration() ? Bytecode::Op::EnvironmentMode::Lexical : Bytecode::Op::EnvironmentMode::Var; + + return declarator.target().visit( + [&](NonnullRefPtr const& id) -> Bytecode::CodeGenerationErrorOr { + generator.emit(generator.intern_identifier(id->string()), initialization_mode, environment_mode); + return {}; + }, + [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { + auto value_register = generator.allocate_register(); + generator.emit(value_register); + return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value_register); + }); +} + Bytecode::CodeGenerationErrorOr VariableDeclaration::generate_bytecode(Bytecode::Generator& generator) const { for (auto& declarator : m_declarations) { @@ -1162,20 +1179,7 @@ Bytecode::CodeGenerationErrorOr VariableDeclaration::generate_bytecode(Byt TRY(declarator.init()->generate_bytecode(generator)); else generator.emit(js_undefined()); - - auto initialization_mode = is_lexical_declaration() ? Bytecode::Op::SetVariable::InitializationMode::Initialize : Bytecode::Op::SetVariable::InitializationMode::Set; - auto environment_mode = is_lexical_declaration() ? Bytecode::Op::EnvironmentMode::Lexical : Bytecode::Op::EnvironmentMode::Var; - - TRY(declarator.target().visit( - [&](NonnullRefPtr const& id) -> Bytecode::CodeGenerationErrorOr { - generator.emit(generator.intern_identifier(id->string()), initialization_mode, environment_mode); - return {}; - }, - [&](NonnullRefPtr const& pattern) -> Bytecode::CodeGenerationErrorOr { - auto value_register = generator.allocate_register(); - generator.emit(value_register); - return generate_binding_pattern_bytecode(generator, pattern, initialization_mode, value_register); - })); + TRY(assign_accumulator_to_variable_declarator(generator, declarator, *this)); } return {}; @@ -1695,4 +1699,336 @@ Bytecode::CodeGenerationErrorOr WithStatement::generate_bytecode(Bytecode: return {}; } +enum class LHSKind { + Assignment, + VarBinding, + LexicalBinding, +}; + +enum class IterationKind { + Enumerate, + Iterate, + AsyncIterate, +}; + +// 14.7.5.6 ForIn/OfHeadEvaluation ( uninitializedBoundNames, expr, iterationKind ), https://tc39.es/ecma262/#sec-runtime-semantics-forinofheadevaluation +struct ForInOfHeadEvaluationResult { + bool is_destructuring { false }; + LHSKind lhs_kind { LHSKind::Assignment }; +}; +static Bytecode::CodeGenerationErrorOr for_in_of_head_evaluation(Bytecode::Generator& generator, IterationKind iteration_kind, Variant, NonnullRefPtr> const& lhs, NonnullRefPtr const& rhs) +{ + ForInOfHeadEvaluationResult result {}; + + if (auto* ast_ptr = lhs.get_pointer>(); ast_ptr && is(**ast_ptr)) { + // Runtime Semantics: ForInOfLoopEvaluation, for any of: + // ForInOfStatement : for ( var ForBinding in Expression ) Statement + // ForInOfStatement : for ( ForDeclaration in Expression ) Statement + // ForInOfStatement : for ( var ForBinding of AssignmentExpression ) Statement + // ForInOfStatement : for ( ForDeclaration of AssignmentExpression ) Statement + + auto& variable_declaration = static_cast(**ast_ptr); + result.is_destructuring = variable_declaration.declarations().first().target().has>(); + result.lhs_kind = variable_declaration.is_lexical_declaration() ? LHSKind::LexicalBinding : LHSKind::VarBinding; + + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + + // NOTE: 'uninitializedBoundNames' refers to the lexical bindings (i.e. Const/Let) present in the second and last form. + // 2. If uninitializedBoundNames is not an empty List, then + bool entered_lexical_scope = false; + if (variable_declaration.declaration_kind() != DeclarationKind::Var) { + entered_lexical_scope = true; + // a. Assert: uninitializedBoundNames has no duplicate entries. + // b. Let newEnv be NewDeclarativeEnvironment(oldEnv). + generator.begin_variable_scope(); + // c. For each String name of uninitializedBoundNames, do + variable_declaration.for_each_bound_name([&](auto const& name) { + // i. Perform ! newEnv.CreateMutableBinding(name, false). + auto identifier = generator.intern_identifier(name); + generator.register_binding(identifier); + generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, false); + }); + // d. Set the running execution context's LexicalEnvironment to newEnv. + // NOTE: Done by CreateEnvironment. + } + // 3. Let exprRef be the result of evaluating expr. + TRY(rhs->generate_bytecode(generator)); + + // 4. Set the running execution context's LexicalEnvironment to oldEnv. + if (entered_lexical_scope) + generator.end_variable_scope(); + + // 5. Let exprValue be ? GetValue(exprRef). + // NOTE: No need to store this anywhere. + + // 6. If iterationKind is enumerate, then + if (iteration_kind == IterationKind::Enumerate) { + // a. If exprValue is undefined or null, then + auto& nullish_block = generator.make_block(); + auto& continuation_block = generator.make_block(); + auto& jump = generator.emit(); + jump.set_targets(Bytecode::Label { nullish_block }, Bytecode::Label { continuation_block }); + + // i. Return Completion Record { [[Type]]: break, [[Value]]: empty, [[Target]]: empty }. + generator.switch_to_basic_block(nullish_block); + generator.perform_needed_unwinds(true); + generator.emit().set_targets(generator.nearest_breakable_scope(), {}); + + generator.switch_to_basic_block(continuation_block); + // b. Let obj be ! ToObject(exprValue). + // NOTE: GetObjectPropertyIterator does this. + // c. Let iterator be EnumerateObjectProperties(obj). + // d. Let nextMethod be ! GetV(iterator, "next"). + // e. Return the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }. + generator.emit(); + } + // 7. Else, + else { + // a. Assert: iterationKind is iterate or async-iterate. + // b. If iterationKind is async-iterate, let iteratorHint be async. + if (iteration_kind == IterationKind::AsyncIterate) { + return Bytecode::CodeGenerationError { + rhs.ptr(), + "Unimplemented iteration mode: AsyncIterate"sv, + }; + } + // c. Else, let iteratorHint be sync. + + // d. Return ? GetIterator(exprValue, iteratorHint). + generator.emit(); + } + } else { + // Runtime Semantics: ForInOfLoopEvaluation, for any of: + // ForInOfStatement : for ( LeftHandSideExpression in Expression ) Statement + // ForInOfStatement : for ( LeftHandSideExpression of AssignmentExpression ) Statement + + // Skip everything except steps 3, 5 and 7 (see above true branch for listing). + result.lhs_kind = LHSKind::Assignment; + + // 3. Let exprRef be the result of evaluating expr. + TRY(rhs->generate_bytecode(generator)); + + // 5. Let exprValue be ? GetValue(exprRef). + // NOTE: No need to store this anywhere. + + // a. Assert: iterationKind is iterate or async-iterate. + // b. If iterationKind is async-iterate, let iteratorHint be async. + if (iteration_kind == IterationKind::AsyncIterate) { + return Bytecode::CodeGenerationError { + rhs.ptr(), + "Unimplemented iteration mode: AsyncIterate"sv, + }; + } + // c. Else, let iteratorHint be sync. + + // d. Return ? GetIterator(exprValue, iteratorHint). + generator.emit(); + } + + return result; +} + +// 14.7.5.7 ForIn/OfBodyEvaluation ( lhs, stmt, iteratorRecord, iterationKind, lhsKind, labelSet [ , iteratorKind ] ), https://tc39.es/ecma262/#sec-runtime-semantics-forin-div-ofbodyevaluation-lhs-stmt-iterator-lhskind-labelset +static Bytecode::CodeGenerationErrorOr for_in_of_body_evaluation(Bytecode::Generator& generator, ASTNode const& node, Variant, NonnullRefPtr> const& lhs, ASTNode const& body, ForInOfHeadEvaluationResult const& head_result, Bytecode::BasicBlock& loop_end, Bytecode::BasicBlock& loop_update) +{ + auto iterator_register = generator.allocate_register(); + generator.emit(iterator_register); + + // FIXME: Implement this + // 1. If iteratorKind is not present, set iteratorKind to sync. + + // 2. Let oldEnv be the running execution context's LexicalEnvironment. + bool has_lexical_binding = false; + + // 3. Let V be undefined. + // NOTE: We don't need 'V' as the resulting value will naturally flow through via the accumulator register. + + // 4. Let destructuring be IsDestructuring of lhs. + auto destructuring = head_result.is_destructuring; + + // 5. If destructuring is true and if lhsKind is assignment, then + if (destructuring) { + // a. Assert: lhs is a LeftHandSideExpression. + // b. Let assignmentPattern be the AssignmentPattern that is covered by lhs. + // FIXME: Implement this. + return Bytecode::CodeGenerationError { + &node, + "Unimplemented: destructuring in for-in/of"sv, + }; + } + // 6. Repeat, + generator.emit(Bytecode::Label { loop_update }); + generator.switch_to_basic_block(loop_update); + + // a. Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + generator.emit(iterator_register); + generator.emit(); + + // FIXME: Implement this: + // b. If iteratorKind is async, set nextResult to ? Await(nextResult). + + // c. If Type(nextResult) is not Object, throw a TypeError exception. + // NOTE: IteratorComplete already does this. + + // d. Let done be ? IteratorComplete(nextResult). + auto iterator_result_register = generator.allocate_register(); + generator.emit(iterator_result_register); + + generator.emit(); + // e. If done is true, return V. + auto& loop_continue = generator.make_block(); + generator.emit().set_targets(Bytecode::Label { loop_end }, Bytecode::Label { loop_continue }); + generator.switch_to_basic_block(loop_continue); + + // f. Let nextValue be ? IteratorValue(nextResult). + generator.emit(iterator_result_register); + generator.emit(); + + // g. If lhsKind is either assignment or varBinding, then + if (head_result.lhs_kind != LHSKind::LexicalBinding) { + // i. If destructuring is false, then + if (!destructuring) { + // 1. Let lhsRef be the result of evaluating lhs. (It may be evaluated repeatedly.) + // NOTE: We're skipping all the completion stuff that the spec does, as the unwinding mechanism will take case of doing that. + if (head_result.lhs_kind == LHSKind::VarBinding) { + auto& declaration = static_cast(*lhs.get>()); + VERIFY(declaration.declarations().size() == 1); + TRY(assign_accumulator_to_variable_declarator(generator, declaration.declarations().first(), declaration)); + } else { + TRY(generator.emit_store_to_reference(*lhs.get>())); + } + } + } + // h. Else, + else { + // i. Assert: lhsKind is lexicalBinding. + // ii. Assert: lhs is a ForDeclaration. + // iii. Let iterationEnv be NewDeclarativeEnvironment(oldEnv). + // iv. Perform ForDeclarationBindingInstantiation of lhs with argument iterationEnv. + // v. Set the running execution context's LexicalEnvironment to iterationEnv. + generator.begin_variable_scope(Bytecode::Generator::BindingMode::Lexical); + has_lexical_binding = true; + + // 14.7.5.4 Runtime Semantics: ForDeclarationBindingInstantiation, https://tc39.es/ecma262/#sec-runtime-semantics-fordeclarationbindinginstantiation + // 1. Assert: environment is a declarative Environment Record. + // NOTE: We just made it. + auto& variable_declaration = static_cast(*lhs.get>()); + // 2. For each element name of the BoundNames of ForBinding, do + variable_declaration.for_each_bound_name([&](auto const& name) { + auto identifier = generator.intern_identifier(name); + generator.register_binding(identifier, Bytecode::Generator::BindingMode::Lexical); + // a. If IsConstantDeclaration of LetOrConst is true, then + if (variable_declaration.is_constant_declaration()) { + // i. Perform ! environment.CreateImmutableBinding(name, true). + generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, true); + } + // b. Else, + else { + // i. Perform ! environment.CreateMutableBinding(name, false). + generator.emit(identifier, Bytecode::Op::EnvironmentMode::Lexical, false); + } + }); + // 3. Return unused. + // NOTE: No need to do that as we've inlined this. + + // vi. If destructuring is false, then + if (!destructuring) { + // 1. Assert: lhs binds a single name. + // 2. Let lhsName be the sole element of BoundNames of lhs. + auto lhs_name = variable_declaration.declarations().first().target().get>()->string(); + // 3. Let lhsRef be ! ResolveBinding(lhsName). + // NOTE: We're skipping all the completion stuff that the spec does, as the unwinding mechanism will take case of doing that. + auto identifier = generator.intern_identifier(lhs_name); + generator.emit(identifier, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Lexical); + } + } + // i. If destructuring is false, then + if (!destructuring) { + // i. If lhsRef is an abrupt completion, then + // 1. Let status be lhsRef. + // ii. Else if lhsKind is lexicalBinding, then + // 1. Let status be Completion(InitializeReferencedBinding(lhsRef, nextValue)). + // iii. Else, + // 1. Let status be Completion(PutValue(lhsRef, nextValue)). + // NOTE: This is performed above. + } + // j. Else, + else { + // FIXME: Implement destructuring + // i. If lhsKind is assignment, then + // 1. Let status be Completion(DestructuringAssignmentEvaluation of assignmentPattern with argument nextValue). + // ii. Else if lhsKind is varBinding, then + // 1. Assert: lhs is a ForBinding. + // 2. Let status be Completion(BindingInitialization of lhs with arguments nextValue and undefined). + // iii. Else, + // 1. Assert: lhsKind is lexicalBinding. + // 2. Assert: lhs is a ForDeclaration. + // 3. Let status be Completion(ForDeclarationBindingInitialization of lhs with arguments nextValue and iterationEnv). + return Bytecode::CodeGenerationError { + &node, + "Unimplemented: destructuring in for-in/of"sv, + }; + } + + // FIXME: Implement iteration closure. + // k. If status is an abrupt completion, then + // i. Set the running execution context's LexicalEnvironment to oldEnv. + // ii. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). + // iii. If iterationKind is enumerate, then + // 1. Return ? status. + // iv. Else, + // 1. Assert: iterationKind is iterate. + // 2. Return ? IteratorClose(iteratorRecord, status). + + // l. Let result be the result of evaluating stmt. + TRY(body.generate_bytecode(generator)); + + // m. Set the running execution context's LexicalEnvironment to oldEnv. + if (has_lexical_binding) + generator.end_variable_scope(); + generator.end_continuable_scope(); + generator.end_breakable_scope(); + + // NOTE: If we're here, then the loop definitely continues. + // n. If LoopContinues(result, labelSet) is false, then + // i. If iterationKind is enumerate, then + // 1. Return ? UpdateEmpty(result, V). + // ii. Else, + // 1. Assert: iterationKind is iterate. + // 2. Set status to Completion(UpdateEmpty(result, V)). + // 3. If iteratorKind is async, return ? AsyncIteratorClose(iteratorRecord, status). + // 4. Return ? IteratorClose(iteratorRecord, status). + // o. If result.[[Value]] is not empty, set V to result.[[Value]]. + generator.emit().set_targets(Bytecode::Label { loop_update }, {}); + generator.switch_to_basic_block(loop_end); + return {}; +} + +// 14.7.5.5 Runtime Semantics: ForInOfLoopEvaluation, https://tc39.es/ecma262/#sec-runtime-semantics-forinofloopevaluation +Bytecode::CodeGenerationErrorOr ForInStatement::generate_bytecode(Bytecode::Generator& generator) const +{ + auto& loop_end = generator.make_block(); + auto& loop_update = generator.make_block(); + generator.begin_breakable_scope(Bytecode::Label { loop_end }); + generator.begin_continuable_scope(Bytecode::Label { loop_update }); + + auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Enumerate, m_lhs, m_rhs)); + + // Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over. + return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, loop_end, loop_update); +} + +Bytecode::CodeGenerationErrorOr ForOfStatement::generate_bytecode(Bytecode::Generator& generator) const +{ + auto& loop_end = generator.make_block(); + auto& loop_update = generator.make_block(); + generator.begin_breakable_scope(Bytecode::Label { loop_end }); + generator.begin_continuable_scope(Bytecode::Label { loop_update }); + + auto head_result = TRY(for_in_of_head_evaluation(generator, IterationKind::Iterate, m_lhs, m_rhs)); + + // Now perform the rest of ForInOfLoopEvaluation, given that the accumulator holds the iterator we're supposed to iterate over. + return for_in_of_body_evaluation(generator, *this, m_lhs, body(), head_result, loop_end, loop_update); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index f2cd3399d55..f6221072b79 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -194,7 +194,10 @@ CodeGenerationErrorOr Generator::emit_store_to_reference(JS::ASTNode const return {}; } - VERIFY_NOT_REACHED(); + return CodeGenerationError { + &node, + "Unimplemented/invalid node used a reference"sv + }; } String CodeGenerationError::to_string() diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index e76529ee896..cf287337517 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -31,6 +31,7 @@ O(GetById) \ O(GetByValue) \ O(GetIterator) \ + O(GetObjectPropertyIterator) \ O(GetVariable) \ O(GreaterThan) \ O(GreaterThanEquals) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 942725271b8..76d3e606bbe 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -574,6 +575,85 @@ ThrowCompletionOr GetIterator::execute_impl(Bytecode::Interpreter& interpr return {}; } +// 14.7.5.9 EnumerateObjectProperties ( O ), https://tc39.es/ecma262/#sec-enumerate-object-properties +ThrowCompletionOr GetObjectPropertyIterator::execute_impl(Bytecode::Interpreter& interpreter) const +{ + // While the spec does provide an algorithm, it allows us to implement it ourselves so long as we meet the following invariants: + // 1- Returned property keys do not include keys that are Symbols + // 2- Properties of the target object may be deleted during enumeration. A property that is deleted before it is processed by the iterator's next method is ignored + // 3- If new properties are added to the target object during enumeration, the newly added properties are not guaranteed to be processed in the active enumeration + // 4- A property name will be returned by the iterator's next method at most once in any enumeration. + // 5- Enumerating the properties of the target object includes enumerating properties of its prototype, and the prototype of the prototype, and so on, recursively; + // but a property of a prototype is not processed if it has the same name as a property that has already been processed by the iterator's next method. + // 6- The values of [[Enumerable]] attributes are not considered when determining if a property of a prototype object has already been processed. + // 7- The enumerable property names of prototype objects must be obtained by invoking EnumerateObjectProperties passing the prototype object as the argument. + // 8- EnumerateObjectProperties must obtain the own property keys of the target object by calling its [[OwnPropertyKeys]] internal method. + // 9- Property attributes of the target object must be obtained by calling its [[GetOwnProperty]] internal method + + // Invariant 3 effectively allows the implementation to ignore newly added keys, and we do so (similar to other implementations). + // Invariants 1 and 6 through 9 are implemented in `enumerable_own_property_names`, which implements the EnumerableOwnPropertyNames AO. + auto* object = TRY(interpreter.accumulator().to_object(interpreter.global_object())); + // Note: While the spec doesn't explicitly require these to be ordered, it says that the values should be retrieved via OwnPropertyKeys, + // so we just keep the order consistent anyway. + OrderedHashTable properties; + HashTable seen_objects; + // Collect all keys immediately (invariant no. 5) + for (auto* object_to_check = object; object_to_check && !seen_objects.contains(object_to_check); object_to_check = TRY(object_to_check->internal_get_prototype_of())) { + seen_objects.set(object_to_check); + for (auto& key : TRY(object_to_check->enumerable_own_property_names(Object::PropertyKind::Key))) { + properties.set(TRY(PropertyKey::from_value(interpreter.global_object(), key))); + } + } + Iterator iterator { + .iterator = object, + .next_method = NativeFunction::create( + interpreter.global_object(), + [seen_items = HashTable(), items = move(properties)](VM& vm, GlobalObject& global_object) mutable -> ThrowCompletionOr { + auto iterated_object_value = vm.this_value(global_object); + if (!iterated_object_value.is_object()) + return vm.throw_completion(global_object, "Invalid state for GetObjectPropertyIterator.next"); + + auto& iterated_object = iterated_object_value.as_object(); + auto* result_object = Object::create(global_object, nullptr); + while (true) { + if (items.is_empty()) { + result_object->define_direct_property(vm.names.done, JS::Value(true), default_attributes); + return result_object; + } + + auto it = items.begin(); + auto key = *it; + items.remove(it); + + // If the key was already seen, skip over it (invariant no. 4) + auto result = seen_items.set(key); + if (result != AK::HashSetResult::InsertedNewEntry) + continue; + + // If the property is deleted, don't include it (invariant no. 2) + if (!TRY(iterated_object.has_property(key))) + continue; + + result_object->define_direct_property(vm.names.done, JS::Value(false), default_attributes); + + if (key.is_number()) + result_object->define_direct_property(vm.names.value, JS::Value(key.as_number()), default_attributes); + else if (key.is_string()) + result_object->define_direct_property(vm.names.value, js_string(vm.heap(), key.as_string()), default_attributes); + else + VERIFY_NOT_REACHED(); // We should not have non-string/number keys. + + return result_object; + } + }, + 1, + interpreter.vm().names.next), + .done = false, + }; + interpreter.accumulator() = iterator_to_object(interpreter.global_object(), move(iterator)); + return {}; +} + ThrowCompletionOr IteratorNext::execute_impl(Bytecode::Interpreter& interpreter) const { auto* iterator_object = TRY(interpreter.accumulator().to_object(interpreter.global_object())); @@ -871,6 +951,11 @@ String GetIterator::to_string_impl(Executable const&) const return "GetIterator"; } +String GetObjectPropertyIterator::to_string_impl(const Bytecode::Executable&) const +{ + return "GetObjectPropertyIterator"; +} + String IteratorNext::to_string_impl(Executable const&) const { return "IteratorNext"; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index ced232b32e3..0c6f4116a86 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -778,6 +778,18 @@ public: void replace_references_impl(BasicBlock const&, BasicBlock const&) { } }; +class GetObjectPropertyIterator final : public Instruction { +public: + GetObjectPropertyIterator() + : Instruction(Type::GetObjectPropertyIterator) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } +}; + class IteratorNext final : public Instruction { public: IteratorNext()