diff --git a/Userland/Libraries/LibJS/AST.cpp b/Userland/Libraries/LibJS/AST.cpp index 3bde7974b9..4bdb39771b 100644 --- a/Userland/Libraries/LibJS/AST.cpp +++ b/Userland/Libraries/LibJS/AST.cpp @@ -256,10 +256,21 @@ Completion FunctionDeclaration::execute(Interpreter& interpreter, GlobalObject& if (m_is_hoisted) { // Perform special annexB steps see step 3 of: https://tc39.es/ecma262/#sec-web-compat-functiondeclarationinstantiation + + // i. Let genv be the running execution context's VariableEnvironment. auto* variable_environment = interpreter.vm().running_execution_context().variable_environment; + + // ii. Let benv be the running execution context's LexicalEnvironment. auto* lexical_environment = interpreter.vm().running_execution_context().lexical_environment; + + // iii. Let fobj be ! benv.GetBindingValue(F, false). auto function_object = MUST(lexical_environment->get_binding_value(global_object, name(), false)); - MUST(variable_environment->set_mutable_binding(global_object, name(), function_object, false)); + + // iv. Perform ? genv.SetMutableBinding(F, fobj, false). + TRY(variable_environment->set_mutable_binding(global_object, name(), function_object, false)); + + // v. Return NormalCompletion(empty). + return normal_completion({}); } // 1. Return NormalCompletion(empty). @@ -2113,10 +2124,12 @@ void ClassDeclaration::dump(int indent) const m_class_expression->dump(indent + 1); } -void ClassDeclaration::for_each_bound_name(IteratorOrVoidFunction callback) const +ThrowCompletionOr ClassDeclaration::for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const { - if (!m_class_expression->name().is_empty()) - callback(m_class_expression->name()); + if (m_class_expression->name().is_empty()) + return {}; + + return callback(m_class_expression->name()); } void ClassExpression::dump(int indent) const @@ -2241,6 +2254,23 @@ bool BindingPattern::contains_expression() const return false; } +ThrowCompletionOr BindingPattern::for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const +{ + for (auto const& entry : entries) { + auto const& alias = entry.alias; + if (alias.has>()) { + TRY(callback(alias.get>()->string())); + } else if (alias.has>()) { + TRY(alias.get>()->for_each_bound_name(forward(callback))); + } else { + auto const& name = entry.name; + if (name.has>()) + TRY(callback(name.get>()->string())); + } + } + return {}; +} + void BindingPattern::dump(int indent) const { print_indent(indent); @@ -2324,10 +2354,11 @@ void FunctionDeclaration::dump(int indent) const FunctionNode::dump(indent, class_name()); } -void FunctionDeclaration::for_each_bound_name(IteratorOrVoidFunction callback) const +ThrowCompletionOr FunctionDeclaration::for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const { - if (!name().is_empty()) - callback(name()); + if (name().is_empty()) + return {}; + return callback(name()); } void FunctionExpression::dump(int indent) const @@ -2861,19 +2892,21 @@ Completion VariableDeclarator::execute(Interpreter& interpreter, GlobalObject&) VERIFY_NOT_REACHED(); } -void VariableDeclaration::for_each_bound_name(IteratorOrVoidFunction callback) const +ThrowCompletionOr VariableDeclaration::for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const { - for (auto& entry : declarations()) { - entry.target().template visit( - [&](const NonnullRefPtr& id) { - callback(id->string()); + for (auto const& entry : declarations()) { + TRY(entry.target().visit( + [&](NonnullRefPtr const& id) { + return callback(id->string()); }, - [&](const NonnullRefPtr& binding) { - binding->for_each_bound_name([&](const auto& name) { - callback(name); + [&](NonnullRefPtr const& binding) { + return binding->for_each_bound_name([&](auto const& name) { + return callback(name); }); - }); + })); } + + return {}; } void VariableDeclaration::dump(int indent) const @@ -4097,76 +4130,62 @@ Completion DebuggerStatement::execute(Interpreter& interpreter, GlobalObject&) c // 3. Return result. return result; } - -void ScopeNode::for_each_lexically_scoped_declaration(IteratorOrVoidFunction&& callback) const +ThrowCompletionOr ScopeNode::for_each_lexically_scoped_declaration(ThrowCompletionOrVoidCallback&& callback) const { - for (auto& declaration : m_lexical_declarations) { - if (callback(declaration) == IterationDecision::Break) - break; - } + for (auto& declaration : m_lexical_declarations) + TRY(callback(declaration)); + + return {}; } -void ScopeNode::for_each_lexically_declared_name(IteratorOrVoidFunction&& callback) const +ThrowCompletionOr ScopeNode::for_each_lexically_declared_name(ThrowCompletionOrVoidCallback&& callback) const { - auto running = true; - for (auto& declaration : m_lexical_declarations) { - declaration.for_each_bound_name([&](auto const& name) { - if (callback(name) == IterationDecision::Break) { - running = false; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - if (!running) - break; + for (auto const& declaration : m_lexical_declarations) { + TRY(declaration.for_each_bound_name([&](auto const& name) { + return callback(name); + })); } + return {}; } -void ScopeNode::for_each_var_declared_name(IteratorOrVoidFunction&& callback) const +ThrowCompletionOr ScopeNode::for_each_var_declared_name(ThrowCompletionOrVoidCallback&& callback) const { - auto running = true; for (auto& declaration : m_var_declarations) { - declaration.for_each_bound_name([&](auto const& name) { - if (callback(name) == IterationDecision::Break) { - running = false; - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); - if (!running) - break; + TRY(declaration.for_each_bound_name([&](auto const& name) { + return callback(name); + })); } + return {}; } -void ScopeNode::for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction&& callback) const +ThrowCompletionOr ScopeNode::for_each_var_function_declaration_in_reverse_order(ThrowCompletionOrVoidCallback&& callback) const { for (ssize_t i = m_var_declarations.size() - 1; i >= 0; i--) { auto& declaration = m_var_declarations[i]; - if (is(declaration)) { - if (callback(static_cast(declaration)) == IterationDecision::Break) - break; - } + if (is(declaration)) + TRY(callback(static_cast(declaration))); } + return {}; } -void ScopeNode::for_each_var_scoped_variable_declaration(IteratorOrVoidFunction&& callback) const +ThrowCompletionOr ScopeNode::for_each_var_scoped_variable_declaration(ThrowCompletionOrVoidCallback&& callback) const { for (auto& declaration : m_var_declarations) { if (!is(declaration)) { VERIFY(is(declaration)); - if (callback(static_cast(declaration)) == IterationDecision::Break) - break; + TRY(callback(static_cast(declaration))); } } + return {}; } -void ScopeNode::for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction&& callback) const +ThrowCompletionOr ScopeNode::for_each_function_hoistable_with_annexB_extension(ThrowCompletionOrVoidCallback&& callback) const { for (auto& function : m_functions_hoistable_with_annexB_extension) { // We need const_cast here since it might have to set a property on function declaration. - if (callback(const_cast(function)) == IterationDecision::Break) - break; + TRY(callback(const_cast(function))); } + return {}; } void ScopeNode::add_lexical_declaration(NonnullRefPtr declaration) @@ -4362,6 +4381,7 @@ void ScopeNode::block_declaration_instantiation(GlobalObject& global_object, Env // See also B.3.2.6 Changes to BlockDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-blockdeclarationinstantiation VERIFY(environment); auto* private_environment = global_object.vm().running_execution_context().private_environment; + // Note: All the calls here are ! and thus we do not need to TRY this callback. for_each_lexically_scoped_declaration([&](Declaration const& declaration) { auto is_constant_declaration = declaration.is_constant_declaration(); declaration.for_each_bound_name([&](auto const& name) { @@ -4385,150 +4405,196 @@ void ScopeNode::block_declaration_instantiation(GlobalObject& global_object, Env // 16.1.7 GlobalDeclarationInstantiation ( script, env ), https://tc39.es/ecma262/#sec-globaldeclarationinstantiation ThrowCompletionOr Program::global_declaration_instantiation(Interpreter& interpreter, GlobalObject& global_object, GlobalEnvironment& global_environment) const { - for_each_lexically_declared_name([&](FlyString const& name) { - if (global_environment.has_var_declaration(name) || global_environment.has_lexical_declaration(name)) { - interpreter.vm().throw_exception(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - return IterationDecision::Break; - } + // 1. Let lexNames be the LexicallyDeclaredNames of script. + // 2. Let varNames be the VarDeclaredNames of script. + // 3. For each element name of lexNames, do + TRY(for_each_lexically_declared_name([&](FlyString const& name) -> ThrowCompletionOr { + // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. + if (global_environment.has_var_declaration(name)) + return interpreter.vm().throw_completion(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - auto restricted_global_or_error = global_environment.has_restricted_global_property(name); - if (restricted_global_or_error.is_error()) - return IterationDecision::Break; - auto restricted_global = restricted_global_or_error.release_value(); + // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if (global_environment.has_lexical_declaration(name)) + return interpreter.vm().throw_completion(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - if (restricted_global) - interpreter.vm().throw_exception(global_object, ErrorType::RestrictedGlobalProperty, name); + // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). + auto has_restricted_global = TRY(global_environment.has_restricted_global_property(name)); - return IterationDecision::Continue; - }); + // d. If hasRestrictedGlobal is true, throw a SyntaxError exception. + if (has_restricted_global) + return interpreter.vm().throw_completion(global_object, ErrorType::RestrictedGlobalProperty, name); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + return {}; + })); - for_each_var_declared_name([&](auto const& name) { - if (global_environment.has_lexical_declaration(name)) { - interpreter.vm().throw_exception(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - return IterationDecision::Break; - } + // 4. For each element name of varNames, do + TRY(for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr { + // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if (global_environment.has_lexical_declaration(name)) + return interpreter.vm().throw_completion(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - return IterationDecision::Continue; - }); + return {}; + })); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); - - HashTable declared_function_names; + // 5. Let varDeclarations be the VarScopedDeclarations of script. + // 6. Let functionsToInitialize be a new empty List. Vector functions_to_initialize; - for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { + // 7. Let declaredFunctionNames be a new empty List. + HashTable declared_function_names; + + // 8. For each element d of varDeclarations, in reverse List order, do + + TRY(for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + // Note: This is checked in for_each_var_function_declaration_in_reverse_order. + + // ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + + // iii. Let fn be the sole element of the BoundNames of d. + + // iv. If fn is not an element of declaredFunctionNames, then if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry) - return IterationDecision::Continue; + return {}; - auto function_definable_or_error = global_environment.can_declare_global_function(function.name()); - if (function_definable_or_error.is_error()) - return IterationDecision::Break; - auto function_definable = function_definable_or_error.release_value(); + // 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn). + auto function_definable = TRY(global_environment.can_declare_global_function(function.name())); - if (!function_definable) { - interpreter.vm().throw_exception(global_object, ErrorType::CannotDeclareGlobalFunction, function.name()); - return IterationDecision::Break; - } + // 2. If fnDefinable is false, throw a TypeError exception. + if (!function_definable) + return interpreter.vm().throw_completion(global_object, ErrorType::CannotDeclareGlobalFunction, function.name()); + // 3. Append fn to declaredFunctionNames. + // Note: Already done in step iv. above. + + // 4. Insert d as the first element of functionsToInitialize. functions_to_initialize.append(function); - return IterationDecision::Continue; - }); - - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + return {}; + })); + // 9. Let declaredVarNames be a new empty List. HashTable declared_var_names; - for_each_var_scoped_variable_declaration([&](Declaration const& declaration) { - declaration.for_each_bound_name([&](auto const& name) { + // 10. For each element d of varDeclarations, do + TRY(for_each_var_scoped_variable_declaration([&](Declaration const& declaration) { + // a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + // Note: This is done in for_each_var_scoped_variable_declaration. + + // i. For each String vn of the BoundNames of d, do + return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { + // 1. If vn is not an element of declaredFunctionNames, then if (declared_function_names.contains(name)) - return IterationDecision::Continue; + return {}; - auto var_definable_or_error = global_environment.can_declare_global_var(name); - if (var_definable_or_error.is_error()) - return IterationDecision::Break; - auto var_definable = var_definable_or_error.release_value(); + // a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn). + auto var_definable = TRY(global_environment.can_declare_global_var(name)); - if (!var_definable) { - interpreter.vm().throw_exception(global_object, ErrorType::CannotDeclareGlobalVariable, name); - return IterationDecision::Break; - } + // b. If vnDefinable is false, throw a TypeError exception. + if (!var_definable) + return interpreter.vm().throw_completion(global_object, ErrorType::CannotDeclareGlobalVariable, name); + // c. If vn is not an element of declaredVarNames, then + // i. Append vn to declaredVarNames. declared_var_names.set(name); - return IterationDecision::Continue; + return {}; }); - if (interpreter.exception()) - return IterationDecision::Break; - return IterationDecision::Continue; - }); + })); - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + // 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. However, if the global object is a Proxy exotic object it may exhibit behaviours that cause abnormal terminations in some of the following steps. + // 12. NOTE: Annex B.3.2.2 adds additional steps at this point. + // 12. Let strict be IsStrict of script. + // 13. If strict is false, then if (!m_is_strict_mode) { - for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { + // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. + // b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within script, do + TRY(for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) -> ThrowCompletionOr { + // i. Let F be StringValue of the BindingIdentifier of f. auto& function_name = function_declaration.name(); + + // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for script, then + // Note: This step is already performed during parsing and for_each_function_hoistable_with_annexB_extension so this always passes here. + + // 1. If env.HasLexicalDeclaration(F) is false, then if (global_environment.has_lexical_declaration(function_name)) - return IterationDecision::Continue; + return {}; - auto function_definable_or_error = global_environment.can_declare_global_function(function_name); - if (function_definable_or_error.is_error()) - return IterationDecision::Break; - auto function_definable = function_definable_or_error.release_value(); + // a. Let fnDefinable be ? env.CanDeclareGlobalVar(F). + auto function_definable = TRY(global_environment.can_declare_global_function(function_name)); + // b. If fnDefinable is true, then - if (!function_definable) { - interpreter.vm().throw_exception(global_object, ErrorType::CannotDeclareGlobalFunction, function_name); - return IterationDecision::Break; - } + if (!function_definable) + return {}; + + // i. NOTE: A var binding for F is only instantiated here if it is neither a VarDeclaredName nor the name of another FunctionDeclaration. + + // ii. If declaredFunctionOrVarNames does not contain F, then if (!declared_function_names.contains(function_name) && !declared_var_names.contains(function_name)) { - auto result = global_environment.create_global_var_binding(function_name, false); - if (result.is_error()) - return IterationDecision::Break; + // i. Perform ? env.CreateGlobalVarBinding(F, false). + TRY(global_environment.create_global_var_binding(function_name, false)); + + // ii. Append F to declaredFunctionOrVarNames. declared_function_names.set(function_name); } + // iii. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: + // i. Let genv be the running execution context's VariableEnvironment. + // ii. Let benv be the running execution context's LexicalEnvironment. + // iii. Let fobj be ! benv.GetBindingValue(F, false). + // iv. Perform ? genv.SetMutableBinding(F, fobj, false). + // v. Return NormalCompletion(empty). function_declaration.set_should_do_additional_annexB_steps(); - return IterationDecision::Continue; - }); - - if (auto* exception = interpreter.exception()) - return throw_completion(exception->value()); + return {}; + })); // We should not use declared function names below here anymore since these functions are not in there in the spec. declared_function_names.clear(); } + // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. + // 14. Let privateEnv be null. PrivateEnvironment* private_environment = nullptr; - for_each_lexically_scoped_declaration([&](Declaration const& declaration) { - declaration.for_each_bound_name([&](auto const& name) { - if (declaration.is_constant_declaration()) - (void)global_environment.create_immutable_binding(global_object, name, true); - else - (void)global_environment.create_mutable_binding(global_object, name, false); - if (interpreter.exception()) - return IterationDecision::Break; - return IterationDecision::Continue; - }); - if (interpreter.exception()) - return IterationDecision::Break; - return IterationDecision::Continue; - }); + // 15. For each element d of lexDeclarations, do + TRY(for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + // a. NOTE: Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { + // i. If IsConstantDeclaration of d is true, then + if (declaration.is_constant_declaration()) { + // 1. Perform ? env.CreateImmutableBinding(dn, true). + TRY(global_environment.create_immutable_binding(global_object, name, true)); + } + // ii. Else, + else { + // 1. Perform ? env.CreateMutableBinding(dn, false). + TRY(global_environment.create_mutable_binding(global_object, name, false)); + } + return {}; + }); + })); + + // 16. For each Parse Node f of functionsToInitialize, do for (auto& declaration : functions_to_initialize) { + // a. Let fn be the sole element of the BoundNames of f. + // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv. auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), &global_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.might_need_arguments_object(), declaration.contains_direct_call_to_eval()); + + // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). TRY(global_environment.create_global_function_binding(declaration.name(), function, false)); } - for (auto& var_name : declared_var_names) + // 17. For each String vn of declaredVarNames, do + for (auto& var_name : declared_var_names) { + // a. Perform ? env.CreateGlobalVarBinding(vn, false). TRY(global_environment.create_global_var_binding(var_name, false)); + } + // 18. Return NormalCompletion(empty). return {}; } diff --git a/Userland/Libraries/LibJS/AST.h b/Userland/Libraries/LibJS/AST.h index edc11473f1..f4f5bd9fa2 100644 --- a/Userland/Libraries/LibJS/AST.h +++ b/Userland/Libraries/LibJS/AST.h @@ -167,21 +167,30 @@ private: NonnullRefPtr m_expression; }; +template +concept ThrowCompletionOrVoidFunction = requires(Func func, Args... args) +{ + { + func(args...) + } + ->SameAs>; +}; + template -class IteratorOrVoidFunction : public Function { +class ThrowCompletionOrVoidCallback : public Function(Args...)> { public: template - IteratorOrVoidFunction(CallableType&& callable) requires(VoidFunction) - : Function([callable = forward(callable)](Args... args) { + ThrowCompletionOrVoidCallback(CallableType&& callable) requires(VoidFunction) + : Function(Args...)>([callable = forward(callable)](Args... args) { callable(args...); - return IterationDecision::Continue; + return ThrowCompletionOr {}; }) { } template - IteratorOrVoidFunction(CallableType&& callable) requires(IteratorFunction) - : Function(forward(callable)) + ThrowCompletionOrVoidCallback(CallableType&& callable) requires(ThrowCompletionOrVoidFunction) + : Function(Args...)>(forward(callable)) { } }; @@ -216,17 +225,17 @@ public: [[nodiscard]] size_t var_declaration_count() const { return m_var_declarations.size(); } [[nodiscard]] size_t lexical_declaration_count() const { return m_lexical_declarations.size(); } - void for_each_lexically_scoped_declaration(IteratorOrVoidFunction&& callback) const; - void for_each_lexically_declared_name(IteratorOrVoidFunction&& callback) const; + ThrowCompletionOr for_each_lexically_scoped_declaration(ThrowCompletionOrVoidCallback&& callback) const; + ThrowCompletionOr for_each_lexically_declared_name(ThrowCompletionOrVoidCallback&& callback) const; - void for_each_var_declared_name(IteratorOrVoidFunction&& callback) const; + ThrowCompletionOr for_each_var_declared_name(ThrowCompletionOrVoidCallback&& callback) const; - void for_each_var_function_declaration_in_reverse_order(IteratorOrVoidFunction&& callback) const; - void for_each_var_scoped_variable_declaration(IteratorOrVoidFunction&& callback) const; + ThrowCompletionOr for_each_var_function_declaration_in_reverse_order(ThrowCompletionOrVoidCallback&& callback) const; + ThrowCompletionOr for_each_var_scoped_variable_declaration(ThrowCompletionOrVoidCallback&& callback) const; void block_declaration_instantiation(GlobalObject& global_object, Environment* environment) const; - void for_each_function_hoistable_with_annexB_extension(IteratorOrVoidFunction&& callback) const; + ThrowCompletionOr for_each_function_hoistable_with_annexB_extension(ThrowCompletionOrVoidCallback&& callback) const; protected: explicit ScopeNode(SourceRange source_range) @@ -519,7 +528,7 @@ public: { } - virtual void for_each_bound_name(IteratorOrVoidFunction callback) const = 0; + virtual ThrowCompletionOr for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const = 0; // 8.1.3 Static Semantics: IsConstantDeclaration, https://tc39.es/ecma262/#sec-static-semantics-isconstantdeclaration virtual bool is_constant_declaration() const { return false; } @@ -535,7 +544,7 @@ public: } Completion execute(Interpreter&, GlobalObject&) const override { return {}; } - void for_each_bound_name(IteratorOrVoidFunction) const override + ThrowCompletionOr for_each_bound_name(ThrowCompletionOrVoidCallback&&) const override { VERIFY_NOT_REACHED(); } @@ -560,8 +569,7 @@ struct BindingPattern : RefCounted { void dump(int indent) const; - template - void for_each_bound_name(C&& callback) const; + ThrowCompletionOr for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const; bool contains_expression() const; @@ -643,7 +651,7 @@ public: virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; - void for_each_bound_name(IteratorOrVoidFunction callback) const override; + virtual ThrowCompletionOr for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const override; virtual bool is_function_declaration() const override { return true; } @@ -1372,7 +1380,7 @@ public: virtual void dump(int indent) const override; virtual void generate_bytecode(Bytecode::Generator&) const override; - void for_each_bound_name(IteratorOrVoidFunction callback) const override; + virtual ThrowCompletionOr for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const override; virtual bool is_lexical_declaration() const override { return true; } @@ -1599,7 +1607,7 @@ public: NonnullRefPtrVector const& declarations() const { return m_declarations; } - virtual void for_each_bound_name(IteratorOrVoidFunction callback) const override; + virtual ThrowCompletionOr for_each_bound_name(ThrowCompletionOrVoidCallback&& callback) const override; virtual bool is_constant_declaration() const override { return m_declaration_kind == DeclarationKind::Const; }; @@ -2035,23 +2043,6 @@ private: Value m_value; }; -template -void BindingPattern::for_each_bound_name(C&& callback) const -{ - for (auto& entry : entries) { - auto& alias = entry.alias; - if (alias.has>()) { - callback(alias.get>()->string()); - } else if (alias.has>()) { - alias.get>()->for_each_bound_name(forward(callback)); - } else { - auto& name = entry.name; - if (name.has>()) - callback(name.get>()->string()); - } - } -} - template<> inline bool ASTNode::fast_is() const { return is_new_expression(); } diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 9919c0bab1..39a1e430e1 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -36,12 +36,10 @@ void ScopeNode::generate_bytecode(Bytecode::Generator& generator) const HashTable functions_initialized; for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { if (functions_initialized.set(function.name()) != AK::HashSetResult::InsertedNewEntry) - return IterationDecision::Continue; + return; generator.emit(function); generator.emit(generator.intern_identifier(function.name())); - - return IterationDecision::Continue; }); // FIXME: Register lexical and variable scope declarations diff --git a/Userland/Libraries/LibJS/Parser.cpp b/Userland/Libraries/LibJS/Parser.cpp index 14729734a2..89324db44b 100644 --- a/Userland/Libraries/LibJS/Parser.cpp +++ b/Userland/Libraries/LibJS/Parser.cpp @@ -550,20 +550,14 @@ void Parser::parse_module(Program& program) auto const& exported_name = entry.local_or_import_name; bool found = false; program.for_each_lexically_declared_name([&](auto const& name) { - if (name == exported_name) { + if (name == exported_name) found = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; }); if (found) continue; program.for_each_var_declared_name([&](auto const& name) { - if (name == exported_name) { + if (name == exported_name) found = true; - return IterationDecision::Break; - } - return IterationDecision::Continue; }); if (!found) syntax_error(String::formatted("'{}' is not declared", exported_name)); diff --git a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp index 9874eb1fb3..e466b2bed2 100644 --- a/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -598,195 +598,310 @@ ThrowCompletionOr perform_eval(Value x, GlobalObject& caller_realm, Calle // 19.2.1.3 EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict ), https://tc39.es/ecma262/#sec-evaldeclarationinstantiation ThrowCompletionOr eval_declaration_instantiation(VM& vm, GlobalObject& global_object, Program const& program, Environment* variable_environment, Environment* lexical_environment, PrivateEnvironment* private_environment, bool strict) { - // FIXME: I'm not sure if the global object is correct here. And this is quite a crucial spot! GlobalEnvironment* global_var_environment = variable_environment->is_global_environment() ? static_cast(variable_environment) : nullptr; + // 1. Let varNames be the VarDeclaredNames of body. + // 2. Let varDeclarations be the VarScopedDeclarations of body. + // 3. If strict is false, then if (!strict) { + // a. If varEnv is a global Environment Record, then if (global_var_environment) { - program.for_each_var_declared_name([&](auto const& name) { - if (global_var_environment->has_lexical_declaration(name)) { - vm.throw_exception(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - return IterationDecision::Break; - } - return IterationDecision::Continue; - }); + // i. For each element name of varNames, do + TRY(program.for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr { + // 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + if (global_var_environment->has_lexical_declaration(name)) + return vm.throw_completion(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); + + // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration. + return {}; + })); } + // b. Let thisEnv be lexEnv. auto* this_environment = lexical_environment; + // c. Assert: The following loop will terminate. + + // d. Repeat, while thisEnv is not the same as varEnv, while (this_environment != variable_environment) { + // i. If thisEnv is not an object Environment Record, then if (!is(*this_environment)) { - program.for_each_var_declared_name([&](auto const& name) { + // 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts. + // 2. For each element name of varNames, do + TRY(program.for_each_var_declared_name([&](auto const& name) -> ThrowCompletionOr { + // a. If thisEnv.HasBinding(name) is true, then if (MUST(this_environment->has_binding(name))) { - vm.throw_exception(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); - return IterationDecision::Break; + // i. Throw a SyntaxError exception. + return vm.throw_completion(global_object, ErrorType::TopLevelVariableAlreadyDeclared, name); + + // FIXME: ii. NOTE: Annex B.3.4 defines alternate semantics for the above step. + // In particular it only throw the syntax error if it is not an environment from a catchclause. } - // FIXME: NOTE: Annex B.3.4 defines alternate semantics for the above step. - // In particular it only throw the syntax error if it is not an environment from a catchclause. - return IterationDecision::Continue; - }); - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration. + return {}; + })); } + // ii. Set thisEnv to thisEnv.[[OuterEnv]]. this_environment = this_environment->outer_environment(); VERIFY(this_environment); } } + // 4. Let privateIdentifiers be a new empty List. + // 5. Let pointer be privateEnv. + // 6. Repeat, while pointer is not null, + // a. For each Private Name binding of pointer.[[Names]], do + // i. If privateIdentifiers does not contain binding.[[Description]], append binding.[[Description]] to privateIdentifiers. + // b. Set pointer to pointer.[[OuterPrivateEnvironment]]. + // 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception. // FIXME: Add Private identifiers check here. - HashTable declared_function_names; + // 8. Let functionsToInitialize be a new empty List. Vector functions_to_initialize; - program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) { + + // 9. Let declaredFunctionNames be a new empty List. + HashTable declared_function_names; + + // 10. For each element d of varDeclarations, in reverse List order, do + TRY(program.for_each_var_function_declaration_in_reverse_order([&](FunctionDeclaration const& function) -> ThrowCompletionOr { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + // i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + // Note: This is done by for_each_var_function_declaration_in_reverse_order. + + // ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + // iii. Let fn be the sole element of the BoundNames of d. + // iv. If fn is not an element of declaredFunctionNames, then if (declared_function_names.set(function.name()) != AK::HashSetResult::InsertedNewEntry) - return IterationDecision::Continue; + return {}; + // 1. If varEnv is a global Environment Record, then if (global_var_environment) { - auto function_definable_or_error = global_var_environment->can_declare_global_function(function.name()); - if (function_definable_or_error.is_error()) - return IterationDecision::Break; - auto function_definable = function_definable_or_error.release_value(); + // a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn). - if (!function_definable) { - vm.throw_exception(global_object, ErrorType::CannotDeclareGlobalFunction, function.name()); - return IterationDecision::Break; - } + auto function_definable = TRY(global_var_environment->can_declare_global_function(function.name())); + + // b. If fnDefinable is false, throw a TypeError exception. + if (!function_definable) + return vm.throw_completion(global_object, ErrorType::CannotDeclareGlobalFunction, function.name()); } + + // 2. Append fn to declaredFunctionNames. + // Note: Already done in step iv. + + // 3. Insert d as the first element of functionsToInitialize. functions_to_initialize.append(function); - return IterationDecision::Continue; - }); - - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + return {}; + })); + // 11. NOTE: Annex B.3.2.3 adds additional steps at this point. + // B.3.2.3 Changes to EvalDeclarationInstantiation, https://tc39.es/ecma262/#sec-web-compat-evaldeclarationinstantiation + // 11. If strict is false, then if (!strict) { + // a. Let declaredFunctionOrVarNames be the list-concatenation of declaredFunctionNames and declaredVarNames. // The spec here uses 'declaredVarNames' but that has not been declared yet. HashTable hoisted_functions; - program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { + + // b. For each FunctionDeclaration f that is directly contained in the StatementList of a Block, CaseClause, or DefaultClause Contained within body, do + TRY(program.for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) -> ThrowCompletionOr { + // i. Let F be StringValue of the BindingIdentifier of f. auto& function_name = function_declaration.name(); + + // ii. If replacing the FunctionDeclaration f with a VariableStatement that has F as a BindingIdentifier would not produce any Early Errors for body, then + // Note: This is checked during parsing and for_each_function_hoistable_with_annexB_extension so it always passes here. + + // 1. Let bindingExists be false. + // 2. Let thisEnv be lexEnv. auto* this_environment = lexical_environment; - while (this_environment != variable_environment) { - if (!is(*this_environment) && MUST(this_environment->has_binding(function_name))) - return IterationDecision::Continue; + // 3. Assert: The following loop will terminate. + // 4. Repeat, while thisEnv is not the same as varEnv, + while (this_environment != variable_environment) { + // a. If thisEnv is not an object Environment Record, then + if (!is(*this_environment)) { + // i. If thisEnv.HasBinding(F) is true, then + + if (MUST(this_environment->has_binding(function_name))) { + // i. Let bindingExists be true. + // Note: When bindingExists is true we skip all the other steps. + return {}; + } + } + + // b. Set thisEnv to thisEnv.[[OuterEnv]]. this_environment = this_environment->outer_environment(); VERIFY(this_environment); } + // Note: At this point bindingExists is false. + // 5. If bindingExists is false and varEnv is a global Environment Record, then if (global_var_environment) { - if (global_var_environment->has_lexical_declaration(function_name)) - return IterationDecision::Continue; - - auto var_definable_or_error = global_var_environment->can_declare_global_var(function_name); - if (var_definable_or_error.is_error()) - return IterationDecision::Break; - auto var_definable = var_definable_or_error.release_value(); - - if (!var_definable) - return IterationDecision::Continue; + // a. If varEnv.HasLexicalDeclaration(F) is false, then + if (!global_var_environment->has_lexical_declaration(function_name)) { + // i. Let fnDefinable be ? varEnv.CanDeclareGlobalVar(F). + if (!TRY(global_var_environment->can_declare_global_var(function_name))) + return {}; + } + // b. Else, + else { + // i. Let fnDefinable be false. + return {}; + } } + // 6. Else, + // a. Let fnDefinable be true. + // Note: At this point fnDefinable is true. + // 7. If bindingExists is false and fnDefinable is true, then + + // a. If declaredFunctionOrVarNames does not contain F, then if (!declared_function_names.contains(function_name) && !hoisted_functions.contains(function_name)) { + // i. If varEnv is a global Environment Record, then if (global_var_environment) { - auto result = global_var_environment->create_global_var_binding(function_name, true); - if (result.is_error()) - return IterationDecision::Break; - } else { + // i. Perform ? varEnv.CreateGlobalVarBinding(F, true). + TRY(global_var_environment->create_global_var_binding(function_name, true)); + } + // ii. Else, + else { + + // i. Let bindingExists be varEnv.HasBinding(F). + // ii. If bindingExists is false, then if (!MUST(variable_environment->has_binding(function_name))) { + // i. Perform ! varEnv.CreateMutableBinding(F, true). + // ii. Perform ! varEnv.InitializeBinding(F, undefined). MUST(variable_environment->create_mutable_binding(global_object, function_name, true)); MUST(variable_environment->initialize_binding(global_object, function_name, js_undefined())); } } - - hoisted_functions.set(function_name); } + // iii. Append F to declaredFunctionOrVarNames. + hoisted_functions.set(function_name); + + // b. When the FunctionDeclaration f is evaluated, perform the following steps in place of the FunctionDeclaration Evaluation algorithm provided in 15.2.6: + // i. Let genv be the running execution context's VariableEnvironment. + // ii. Let benv be the running execution context's LexicalEnvironment. + // iii. Let fobj be ! benv.GetBindingValue(F, false). + // iv. Perform ? genv.SetMutableBinding(F, fobj, false). + // v. Return NormalCompletion(empty). function_declaration.set_should_do_additional_annexB_steps(); - return IterationDecision::Continue; - }); - - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + return {}; + })); } + // 12. Let declaredVarNames be a new empty List. HashTable declared_var_names; - program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) { - declaration.for_each_bound_name([&](auto const& name) { + // 13. For each element d of varDeclarations, do + TRY(program.for_each_var_scoped_variable_declaration([&](VariableDeclaration const& declaration) { + // a. If d is a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + // Note: This is handled by for_each_var_scoped_variable_declaration. + + // i. For each String vn of the BoundNames of d, do + return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { + // 1. If vn is not an element of declaredFunctionNames, then if (!declared_function_names.contains(name)) { + // a. If varEnv is a global Environment Record, then if (global_var_environment) { - auto variable_definable_or_error = global_var_environment->can_declare_global_var(name); - if (variable_definable_or_error.is_error()) - return IterationDecision::Break; - auto variable_definable = variable_definable_or_error.release_value(); - if (!variable_definable) { - vm.throw_exception(global_object, ErrorType::CannotDeclareGlobalVariable, name); - return IterationDecision::Break; - } + // i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn). + auto variable_definable = TRY(global_var_environment->can_declare_global_var(name)); + + // ii. If vnDefinable is false, throw a TypeError exception. + if (!variable_definable) + return vm.throw_completion(global_object, ErrorType::CannotDeclareGlobalVariable, name); } + + // b. If vn is not an element of declaredVarNames, then + // i. Append vn to declaredVarNames. declared_var_names.set(name); } - return IterationDecision::Continue; + return {}; }); - if (vm.exception()) - return IterationDecision::Break; - return IterationDecision::Continue; - }); - - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + })); // 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a global Environment Record and the global object is a Proxy exotic object. - program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) { - declaration.for_each_bound_name([&](auto const& name) { - if (declaration.is_constant_declaration()) - (void)lexical_environment->create_immutable_binding(global_object, name, true); - else - (void)lexical_environment->create_mutable_binding(global_object, name, false); - if (vm.exception()) - return IterationDecision::Break; - return IterationDecision::Continue; + // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. + // 16. For each element d of lexDeclarations, do + TRY(program.for_each_lexically_scoped_declaration([&](Declaration const& declaration) { + // a. NOTE: Lexically declared names are only instantiated here but not initialized. + + // b. For each element dn of the BoundNames of d, do + return declaration.for_each_bound_name([&](auto const& name) -> ThrowCompletionOr { + // i. If IsConstantDeclaration of d is true, then + if (declaration.is_constant_declaration()) { + // 1. Perform ? lexEnv.CreateImmutableBinding(dn, true). + TRY(lexical_environment->create_immutable_binding(global_object, name, true)); + } + // ii. Else, + else { + // 1. Perform ? lexEnv.CreateMutableBinding(dn, false). + TRY(lexical_environment->create_mutable_binding(global_object, name, false)); + } + return {}; }); - if (vm.exception()) - return IterationDecision::Break; - return IterationDecision::Continue; - }); - - if (auto* exception = vm.exception()) - return throw_completion(exception->value()); + })); + // 17. For each Parse Node f of functionsToInitialize, do for (auto& declaration : functions_to_initialize) { + // a. Let fn be the sole element of the BoundNames of f. + // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. auto* function = ECMAScriptFunctionObject::create(global_object, declaration.name(), declaration.source_text(), declaration.body(), declaration.parameters(), declaration.function_length(), lexical_environment, private_environment, declaration.kind(), declaration.is_strict_mode(), declaration.might_need_arguments_object()); + + // c. If varEnv is a global Environment Record, then if (global_var_environment) { + // i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true). TRY(global_var_environment->create_global_function_binding(declaration.name(), function, true)); - } else { + } + // d. Else, + else { + // i. Let bindingExists be varEnv.HasBinding(fn). auto binding_exists = MUST(variable_environment->has_binding(declaration.name())); + // ii. If bindingExists is false, then if (!binding_exists) { - TRY(variable_environment->create_mutable_binding(global_object, declaration.name(), true)); - TRY(variable_environment->initialize_binding(global_object, declaration.name(), function)); - } else { - TRY(variable_environment->set_mutable_binding(global_object, declaration.name(), function, false)); + // 1. Let status be ! varEnv.CreateMutableBinding(fn, true). + // 2. Assert: status is not an abrupt completion because of validation preceding step 14. + MUST(variable_environment->create_mutable_binding(global_object, declaration.name(), true)); + + // 3. Perform ! varEnv.InitializeBinding(fn, fo). + MUST(variable_environment->initialize_binding(global_object, declaration.name(), function)); + } + // iii. Else, + else { + // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). + MUST(variable_environment->set_mutable_binding(global_object, declaration.name(), function, false)); } } } + // 18. For each String vn of declaredVarNames, do + for (auto& var_name : declared_var_names) { + // a. If varEnv is a global Environment Record, then if (global_var_environment) { + // i. Perform ? varEnv.CreateGlobalVarBinding(vn, true). TRY(global_var_environment->create_global_var_binding(var_name, true)); - } else { + } + // b. Else, + else { + // i. Let bindingExists be varEnv.HasBinding(vn). auto binding_exists = MUST(variable_environment->has_binding(var_name)); + // ii. If bindingExists is false, then if (!binding_exists) { - TRY(variable_environment->create_mutable_binding(global_object, var_name, true)); - TRY(variable_environment->initialize_binding(global_object, var_name, js_undefined())); + // 1. Let status be ! varEnv.CreateMutableBinding(vn, true). + // 2. Assert: status is not an abrupt completion because of validation preceding step 14. + MUST(variable_environment->create_mutable_binding(global_object, var_name, true)); + + // 3. Perform ! varEnv.InitializeBinding(vn, undefined). + MUST(variable_environment->initialize_binding(global_object, var_name, js_undefined())); } } } + // 19. Return NormalCompletion(empty). return {}; } diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 3e95d060c4..ffcdf335a6 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -375,11 +375,8 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia if (!has_parameter_expressions && arguments_object_needed) { scope_body->for_each_lexically_declared_name([&](auto const& name) { - if (name == arguments_name) { + if (name == arguments_name) arguments_object_needed = false; - return IterationDecision::Break; - } - return IterationDecision::Continue; }); } } else { @@ -485,7 +482,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia if (scope_body) { scope_body->for_each_var_declared_name([&](auto const& name) { if (instantiated_var_names.set(name) != AK::HashSetResult::InsertedNewEntry) - return IterationDecision::Continue; + return; MUST(var_environment->create_mutable_binding(global_object(), name, false)); Value initial_value; @@ -495,8 +492,6 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia initial_value = MUST(environment->get_binding_value(global_object(), name, false)); MUST(var_environment->initialize_binding(global_object(), name, initial_value)); - - return IterationDecision::Continue; }); } } @@ -506,7 +501,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia scope_body->for_each_function_hoistable_with_annexB_extension([&](FunctionDeclaration& function_declaration) { auto& function_name = function_declaration.name(); if (parameter_names.contains(function_name)) - return IterationDecision::Continue; + return; // The spec says 'initializedBindings' here but that does not exist and it then adds it to 'instantiatedVarNames' so it probably means 'instantiatedVarNames'. if (!instantiated_var_names.contains(function_name) && function_name != vm.names.arguments.as_string()) { MUST(var_environment->create_mutable_binding(global_object(), function_name, false)); @@ -515,7 +510,6 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia } function_declaration.set_should_do_additional_annexB_steps(); - return IterationDecision::Continue; }); } @@ -554,7 +548,6 @@ ThrowCompletionOr ECMAScriptFunctionObject::function_declaration_instantia MUST(lex_environment->create_immutable_binding(global_object(), name, true)); else MUST(lex_environment->create_mutable_binding(global_object(), name, false)); - return IterationDecision::Continue; }); });