From 7cc53b7ef1e831e9d6235d44bc844caf4d764b34 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Sun, 27 Mar 2022 19:50:09 +0100 Subject: [PATCH] LibJS/Bytecode: Implement the delete unary expression `delete` has to operate directly on Reference Records, so this introduces a new set of operations called DeleteByValue, DeleteVariable and DeleteById. They operate similarly to their Get counterparts, except they end in creating a (temporary) Reference and calling delete_ on it. --- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 9 ++-- .../Libraries/LibJS/Bytecode/Generator.cpp | 46 ++++++++++++++++++ Userland/Libraries/LibJS/Bytecode/Generator.h | 1 + .../Libraries/LibJS/Bytecode/Instruction.h | 3 ++ Userland/Libraries/LibJS/Bytecode/Op.cpp | 43 +++++++++++++++++ Userland/Libraries/LibJS/Bytecode/Op.h | 48 +++++++++++++++++++ 6 files changed, 146 insertions(+), 4 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 93a1b58b4d6..4ec394e34c3 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -427,6 +427,9 @@ Bytecode::CodeGenerationErrorOr LogicalExpression::generate_bytecode(Bytec Bytecode::CodeGenerationErrorOr UnaryExpression::generate_bytecode(Bytecode::Generator& generator) const { + if (m_op == UnaryOp::Delete) + return generator.emit_delete_reference(m_lhs); + TRY(m_lhs->generate_bytecode(generator)); switch (m_op) { @@ -448,11 +451,9 @@ Bytecode::CodeGenerationErrorOr UnaryExpression::generate_bytecode(Bytecod case UnaryOp::Void: generator.emit(js_undefined()); break; + case UnaryOp::Delete: // Delete is implemented above. default: - return Bytecode::CodeGenerationError { - this, - "Unimplemented operation"sv, - }; + VERIFY_NOT_REACHED(); } return {}; diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index 36d6b54c413..74dff1df701 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -200,6 +200,52 @@ CodeGenerationErrorOr Generator::emit_store_to_reference(JS::ASTNode const }; } +CodeGenerationErrorOr Generator::emit_delete_reference(JS::ASTNode const& node) +{ + if (is(node)) { + auto& identifier = static_cast(node); + emit(intern_identifier(identifier.string())); + return {}; + } + + if (is(node)) { + auto& expression = static_cast(node); + TRY(expression.object().generate_bytecode(*this)); + + if (expression.is_computed()) { + auto object_reg = allocate_register(); + emit(object_reg); + + TRY(expression.property().generate_bytecode(*this)); + emit(object_reg); + } else if (expression.property().is_identifier()) { + auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); + emit(identifier_table_ref); + } else { + // NOTE: Trying to delete a private field generates a SyntaxError in the parser. + return CodeGenerationError { + &expression, + "Unimplemented non-computed member expression"sv + }; + } + return {}; + } + + // Though this will have no deletion effect, we still have to evaluate the node as it can have side effects. + // For example: delete a(); delete ++c.b; etc. + + // 13.5.1.2 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation + // 1. Let ref be the result of evaluating UnaryExpression. + // 2. ReturnIfAbrupt(ref). + TRY(node.generate_bytecode(*this)); + + // 3. If ref is not a Reference Record, return true. + emit(Value(true)); + + // NOTE: The rest of the steps are handled by Delete{Variable,ByValue,Id}. + return {}; +} + String CodeGenerationError::to_string() { return String::formatted("CodeGenerationError in {}: {}", failing_node ? failing_node->class_name() : "", reason_literal); diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index b7abb81aaf0..f63f9e7b0f7 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -79,6 +79,7 @@ public: CodeGenerationErrorOr emit_load_from_reference(JS::ASTNode const&); CodeGenerationErrorOr emit_store_to_reference(JS::ASTNode const&); + CodeGenerationErrorOr emit_delete_reference(JS::ASTNode const&); void begin_continuable_scope(Label continue_target); void end_continuable_scope(); diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index f9c8dffa0c6..c56e25c7969 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -23,6 +23,9 @@ O(CreateEnvironment) \ O(CreateVariable) \ O(Decrement) \ + O(DeleteById) \ + O(DeleteByValue) \ + O(DeleteVariable) \ O(Div) \ O(EnterUnwindContext) \ O(EnterObjectEnvironment) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index dea1aad0b11..76b7414ae31 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -263,6 +263,14 @@ ThrowCompletionOr GetVariable::execute_impl(Bytecode::Interpreter& interpr return {}; } +ThrowCompletionOr DeleteVariable::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto const& string = interpreter.current_executable().get_identifier(m_identifier); + auto reference = TRY(interpreter.vm().resolve_binding(string)); + interpreter.accumulator() = Value(TRY(reference.delete_(interpreter.global_object()))); + return {}; +} + ThrowCompletionOr CreateEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const { auto make_and_swap_envs = [&](auto*& old_environment) { @@ -346,6 +354,16 @@ ThrowCompletionOr PutById::execute_impl(Bytecode::Interpreter& interpreter return {}; } +ThrowCompletionOr DeleteById::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto* object = TRY(interpreter.accumulator().to_object(interpreter.global_object())); + auto const& identifier = interpreter.current_executable().get_identifier(m_property); + bool strict = interpreter.vm().in_strict_mode(); + auto reference = Reference { object, identifier, {}, strict }; + interpreter.accumulator() = Value(TRY(reference.delete_(interpreter.global_object()))); + return {}; +}; + ThrowCompletionOr Jump::execute_impl(Bytecode::Interpreter& interpreter) const { interpreter.jump(*m_true_target); @@ -578,6 +596,16 @@ ThrowCompletionOr PutByValue::execute_impl(Bytecode::Interpreter& interpre return {}; } +ThrowCompletionOr DeleteByValue::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto* object = TRY(interpreter.reg(m_base).to_object(interpreter.global_object())); + auto property_key = TRY(interpreter.accumulator().to_property_key(interpreter.global_object())); + bool strict = interpreter.vm().in_strict_mode(); + auto reference = Reference { object, property_key, {}, strict }; + interpreter.accumulator() = Value(TRY(reference.delete_(interpreter.global_object()))); + return {}; +} + ThrowCompletionOr GetIterator::execute_impl(Bytecode::Interpreter& interpreter) const { auto iterator = TRY(get_iterator(interpreter.global_object(), interpreter.accumulator())); @@ -776,6 +804,11 @@ String GetVariable::to_string_impl(Bytecode::Executable const& executable) const return String::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); } +String DeleteVariable::to_string_impl(Bytecode::Executable const& executable) const +{ + return String::formatted("DeleteVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier)); +} + String CreateEnvironment::to_string_impl(Bytecode::Executable const&) const { auto mode_string = m_mode == EnvironmentMode::Lexical @@ -814,6 +847,11 @@ String GetById::to_string_impl(Bytecode::Executable const& executable) const return String::formatted("GetById {} ({})", m_property, executable.identifier_table->get(m_property)); } +String DeleteById::to_string_impl(Bytecode::Executable const& executable) const +{ + return String::formatted("DeleteById {} ({})", m_property, executable.identifier_table->get(m_property)); +} + String Jump::to_string_impl(Bytecode::Executable const&) const { if (m_true_target.has_value()) @@ -950,6 +988,11 @@ String PutByValue::to_string_impl(const Bytecode::Executable&) const return String::formatted("PutByValue base:{}, property:{}", m_base, m_property); } +String DeleteByValue::to_string_impl(Bytecode::Executable const&) const +{ + return String::formatted("DeleteByValue base:{}", m_base); +} + String GetIterator::to_string_impl(Executable const&) const { return "GetIterator"; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index d3c5284af47..7c4f35c8a74 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -378,6 +378,22 @@ private: Optional mutable m_cached_environment_coordinate; }; +class DeleteVariable final : public Instruction { +public: + explicit DeleteVariable(IdentifierTableIndex identifier) + : Instruction(Type::DeleteVariable) + , m_identifier(identifier) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + +private: + IdentifierTableIndex m_identifier; +}; + class GetById final : public Instruction { public: explicit GetById(IdentifierTableIndex property) @@ -412,6 +428,22 @@ private: IdentifierTableIndex m_property; }; +class DeleteById final : public Instruction { +public: + explicit DeleteById(IdentifierTableIndex property) + : Instruction(Type::DeleteById) + , m_property(property) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + +private: + IdentifierTableIndex m_property; +}; + class GetByValue final : public Instruction { public: explicit GetByValue(Register base) @@ -446,6 +478,22 @@ private: Register m_property; }; +class DeleteByValue final : public Instruction { +public: + DeleteByValue(Register base) + : Instruction(Type::DeleteByValue) + , m_base(base) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + String to_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + +private: + Register m_base; +}; + class Jump : public Instruction { public: constexpr static bool IsTerminator = true;