Browse Source

LibJS: Allow `let` and `const` to create locals in global code

This is actually safe everywhere but in the topmost program scope.
For web compatibility reasons, we have to flush all top-level bindings
to the environment, in case a subsequent separate <script> program
comes looking for them.
Andreas Kling 1 year ago
parent
commit
9d21d88374
2 changed files with 27 additions and 13 deletions
  1. 25 11
      Userland/Libraries/LibJS/Parser.cpp
  2. 2 2
      Userland/Libraries/LibJS/Parser.h

+ 25 - 11
Userland/Libraries/LibJS/Parser.cpp

@@ -351,12 +351,24 @@ public:
                     continue;
                     continue;
 
 
                 if (!identifier_group.captured_by_nested_function && !identifier_group.used_inside_with_statement) {
                 if (!identifier_group.captured_by_nested_function && !identifier_group.used_inside_with_statement) {
-                    auto function_scope = last_function_scope();
-                    if (!function_scope || m_screwed_by_eval_in_scope_chain) {
+                    if (m_screwed_by_eval_in_scope_chain)
                         continue;
                         continue;
+
+                    auto local_scope = last_function_scope();
+                    if (!local_scope) {
+                        // NOTE: If there is no function scope, we are in a *descendant* of the global program scope.
+                        //       While we cannot make `let` and `const` into locals in the topmost program scope,
+                        //       as that would break expected web behavior where subsequent <script> elements should see
+                        //       lexical bindings created by earlier <script> elements, we *can* promote them in descendant scopes.
+                        //       Of course, global `var` bindings can never be made into locals, as they get hoisted to the topmost program scope.
+                        if (identifier_group.declaration_kind == DeclarationKind::Var)
+                            continue;
+                        // Add these locals to the top-level scope. (We only produce one executable for the entire program
+                        // scope, so that's where the locals have to be stored.)
+                        local_scope = m_top_level_scope;
                     }
                     }
 
 
-                    auto local_variable_index = function_scope->m_node->add_local_variable(identifier_group_name);
+                    auto local_variable_index = local_scope->m_node->add_local_variable(identifier_group_name);
                     for (auto& identifier : identifier_group.identifiers)
                     for (auto& identifier : identifier_group.identifiers)
                         identifier->set_local_variable_index(local_variable_index);
                         identifier->set_local_variable_index(local_variable_index);
                 }
                 }
@@ -421,7 +433,7 @@ public:
         return m_scope_level != ScopeLevel::ScriptTopLevel;
         return m_scope_level != ScopeLevel::ScriptTopLevel;
     }
     }
 
 
-    void register_identifier(NonnullRefPtr<Identifier> id)
+    void register_identifier(NonnullRefPtr<Identifier> id, Optional<DeclarationKind> declaration_kind = {})
     {
     {
         if (auto maybe_identifier_group = m_identifier_groups.get(id->string()); maybe_identifier_group.has_value()) {
         if (auto maybe_identifier_group = m_identifier_groups.get(id->string()); maybe_identifier_group.has_value()) {
             maybe_identifier_group.value().identifiers.append(id);
             maybe_identifier_group.value().identifiers.append(id);
@@ -429,6 +441,7 @@ public:
             m_identifier_groups.set(id->string(), IdentifierGroup {
             m_identifier_groups.set(id->string(), IdentifierGroup {
                                                       .captured_by_nested_function = false,
                                                       .captured_by_nested_function = false,
                                                       .identifiers = { id },
                                                       .identifiers = { id },
+                                                      .declaration_kind = declaration_kind,
                                                   });
                                                   });
         }
         }
     }
     }
@@ -495,6 +508,7 @@ private:
         bool used_inside_scope_with_eval { false };
         bool used_inside_scope_with_eval { false };
         bool might_be_variable_in_lexical_scope_in_named_function_assignment { false };
         bool might_be_variable_in_lexical_scope_in_named_function_assignment { false };
         Vector<NonnullRefPtr<Identifier>> identifiers;
         Vector<NonnullRefPtr<Identifier>> identifiers;
+        Optional<DeclarationKind> declaration_kind;
     };
     };
     HashMap<DeprecatedFlyString, IdentifierGroup> m_identifier_groups;
     HashMap<DeprecatedFlyString, IdentifierGroup> m_identifier_groups;
 
 
@@ -3273,24 +3287,24 @@ RefPtr<BindingPattern const> Parser::parse_binding_pattern(Parser::AllowDuplicat
     return pattern;
     return pattern;
 }
 }
 
 
-RefPtr<Identifier const> Parser::parse_lexical_binding()
+RefPtr<Identifier const> Parser::parse_lexical_binding(Optional<DeclarationKind> declaration_kind)
 {
 {
     auto binding_start = push_start();
     auto binding_start = push_start();
 
 
     if (match_identifier()) {
     if (match_identifier()) {
-        return create_identifier_and_register_in_current_scope({ m_source_code, binding_start.position(), position() }, consume_identifier().DeprecatedFlyString_value());
+        return create_identifier_and_register_in_current_scope({ m_source_code, binding_start.position(), position() }, consume_identifier().DeprecatedFlyString_value(), declaration_kind);
     }
     }
     if (!m_state.in_generator_function_context && match(TokenType::Yield)) {
     if (!m_state.in_generator_function_context && match(TokenType::Yield)) {
         if (m_state.strict_mode)
         if (m_state.strict_mode)
             syntax_error("Identifier must not be a reserved word in strict mode ('yield')");
             syntax_error("Identifier must not be a reserved word in strict mode ('yield')");
 
 
-        return create_identifier_and_register_in_current_scope({ m_source_code, binding_start.position(), position() }, consume().DeprecatedFlyString_value());
+        return create_identifier_and_register_in_current_scope({ m_source_code, binding_start.position(), position() }, consume().DeprecatedFlyString_value(), declaration_kind);
     }
     }
     if (!m_state.await_expression_is_valid && match(TokenType::Async)) {
     if (!m_state.await_expression_is_valid && match(TokenType::Async)) {
         if (m_program_type == Program::Type::Module)
         if (m_program_type == Program::Type::Module)
             syntax_error("Identifier must not be a reserved word in modules ('async')");
             syntax_error("Identifier must not be a reserved word in modules ('async')");
 
 
-        return create_identifier_and_register_in_current_scope({ m_source_code, binding_start.position(), position() }, consume().DeprecatedFlyString_value());
+        return create_identifier_and_register_in_current_scope({ m_source_code, binding_start.position(), position() }, consume().DeprecatedFlyString_value(), declaration_kind);
     }
     }
 
 
     return {};
     return {};
@@ -3329,7 +3343,7 @@ NonnullRefPtr<VariableDeclaration const> Parser::parse_variable_declaration(IsFo
             }
             }
 
 
             target = pattern.release_nonnull();
             target = pattern.release_nonnull();
-        } else if (auto lexical_binding = parse_lexical_binding()) {
+        } else if (auto lexical_binding = parse_lexical_binding(declaration_kind)) {
             check_identifier_name_for_assignment_validity(lexical_binding->string());
             check_identifier_name_for_assignment_validity(lexical_binding->string());
             if ((declaration_kind == DeclarationKind::Let || declaration_kind == DeclarationKind::Const) && lexical_binding->string() == "let"sv)
             if ((declaration_kind == DeclarationKind::Let || declaration_kind == DeclarationKind::Const) && lexical_binding->string() == "let"sv)
                 syntax_error("Lexical binding may not be called 'let'");
                 syntax_error("Lexical binding may not be called 'let'");
@@ -5145,11 +5159,11 @@ Parser::ForbiddenTokens Parser::ForbiddenTokens::forbid(std::initializer_list<To
 template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u16, Optional<Position> const&);
 template NonnullRefPtr<FunctionExpression> Parser::parse_function_node(u16, Optional<Position> const&);
 template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u16, Optional<Position> const&);
 template NonnullRefPtr<FunctionDeclaration> Parser::parse_function_node(u16, Optional<Position> const&);
 
 
-NonnullRefPtr<Identifier const> Parser::create_identifier_and_register_in_current_scope(SourceRange range, DeprecatedFlyString string)
+NonnullRefPtr<Identifier const> Parser::create_identifier_and_register_in_current_scope(SourceRange range, DeprecatedFlyString string, Optional<DeclarationKind> declaration_kind)
 {
 {
     auto id = create_ast_node<Identifier const>(range, string);
     auto id = create_ast_node<Identifier const>(range, string);
     if (m_state.current_scope_pusher)
     if (m_state.current_scope_pusher)
-        m_state.current_scope_pusher->register_identifier(const_cast<Identifier&>(*id));
+        m_state.current_scope_pusher->register_identifier(const_cast<Identifier&>(*id), declaration_kind);
     return id;
     return id;
 }
 }
 
 

+ 2 - 2
Userland/Libraries/LibJS/Parser.h

@@ -96,7 +96,7 @@ public:
     };
     };
 
 
     NonnullRefPtr<VariableDeclaration const> parse_variable_declaration(IsForLoopVariableDeclaration is_for_loop_variable_declaration = IsForLoopVariableDeclaration::No);
     NonnullRefPtr<VariableDeclaration const> parse_variable_declaration(IsForLoopVariableDeclaration is_for_loop_variable_declaration = IsForLoopVariableDeclaration::No);
-    RefPtr<Identifier const> parse_lexical_binding();
+    [[nodiscard]] RefPtr<Identifier const> parse_lexical_binding(Optional<DeclarationKind> = {});
     NonnullRefPtr<UsingDeclaration const> parse_using_declaration(IsForLoopVariableDeclaration is_for_loop_variable_declaration = IsForLoopVariableDeclaration::No);
     NonnullRefPtr<UsingDeclaration const> parse_using_declaration(IsForLoopVariableDeclaration is_for_loop_variable_declaration = IsForLoopVariableDeclaration::No);
     NonnullRefPtr<Statement const> parse_for_statement();
     NonnullRefPtr<Statement const> parse_for_statement();
 
 
@@ -347,7 +347,7 @@ private:
         }
         }
     };
     };
 
 
-    NonnullRefPtr<Identifier const> create_identifier_and_register_in_current_scope(SourceRange range, DeprecatedFlyString string);
+    [[nodiscard]] NonnullRefPtr<Identifier const> create_identifier_and_register_in_current_scope(SourceRange range, DeprecatedFlyString string, Optional<DeclarationKind> = {});
 
 
     NonnullRefPtr<SourceCode const> m_source_code;
     NonnullRefPtr<SourceCode const> m_source_code;
     Vector<Position> m_rule_starts;
     Vector<Position> m_rule_starts;