Przeglądaj źródła

LibJS/Bytecode: Create global variables before setting them

This allows them to be accessed before assignment, and also prevents
throwing in strict mode as we are trying to set a non-existent
variable.
Luke Wilde 3 lat temu
rodzic
commit
c55a4c7f30

+ 7 - 6
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -181,9 +181,7 @@ Bytecode::CodeGenerationErrorOr<void> ScopeNode::generate_bytecode(Bytecode::Gen
                 // ii. If declaredFunctionOrVarNames does not contain F, then
                 if (!declared_function_names.contains(function_name) && !declared_var_names.contains(function_name)) {
                     // i. Perform ? env.CreateGlobalVarBinding(F, false).
-                    generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Var, false);
-                    generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
-                    generator.emit<Bytecode::Op::SetVariable>(index, Bytecode::Op::SetVariable::InitializationMode::Initialize, Bytecode::Op::EnvironmentMode::Var);
+                    generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Var, false, true);
 
                     // ii. Append F to declaredFunctionOrVarNames.
                     declared_function_names.set(function_name);
@@ -238,9 +236,12 @@ Bytecode::CodeGenerationErrorOr<void> ScopeNode::generate_bytecode(Bytecode::Gen
         }
 
         // 17. For each String vn of declaredVarNames, do
-        // a. Perform ? env.CreateGlobalVarBinding(vn, false).
-        for (auto& var_name : declared_var_names)
-            generator.register_binding(generator.intern_identifier(var_name), Bytecode::Generator::BindingMode::Var);
+        for (auto& var_name : declared_var_names) {
+            // a. Perform ? env.CreateGlobalVarBinding(vn, false).
+            auto index = generator.intern_identifier(var_name);
+            generator.register_binding(index, Bytecode::Generator::BindingMode::Var);
+            generator.emit<Bytecode::Op::CreateVariable>(index, Bytecode::Op::EnvironmentMode::Var, false, true);
+        }
     } else {
         // Perform the steps of FunctionDeclarationInstantiation.
         generator.begin_variable_scope(Bytecode::Generator::BindingMode::Var, Bytecode::Generator::SurroundingScopeKind::Function);

+ 14 - 5
Userland/Libraries/LibJS/Bytecode/Op.cpp

@@ -15,6 +15,7 @@
 #include <LibJS/Runtime/DeclarativeEnvironment.h>
 #include <LibJS/Runtime/ECMAScriptFunctionObject.h>
 #include <LibJS/Runtime/Environment.h>
+#include <LibJS/Runtime/GlobalEnvironment.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Iterator.h>
 #include <LibJS/Runtime/IteratorOperations.h>
@@ -339,6 +340,8 @@ ThrowCompletionOr<void> CreateVariable::execute_impl(Bytecode::Interpreter& inte
     auto const& name = interpreter.current_executable().get_identifier(m_identifier);
 
     if (m_mode == EnvironmentMode::Lexical) {
+        VERIFY(!m_is_global);
+
         // Note: This is papering over an issue where "FunctionDeclarationInstantiation" creates these bindings for us.
         //       Instead of crashing in there, we'll just raise an exception here.
         if (TRY(vm.lexical_environment()->has_binding(name)))
@@ -349,10 +352,16 @@ ThrowCompletionOr<void> CreateVariable::execute_impl(Bytecode::Interpreter& inte
         else
             vm.lexical_environment()->create_mutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
     } else {
-        if (m_is_immutable)
-            vm.variable_environment()->create_immutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
-        else
-            vm.variable_environment()->create_mutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
+        if (!m_is_global) {
+            if (m_is_immutable)
+                vm.variable_environment()->create_immutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
+            else
+                vm.variable_environment()->create_mutable_binding(interpreter.global_object(), name, vm.in_strict_mode());
+        } else {
+            // NOTE: CreateVariable with m_is_global set to true is expected to only be used in GlobalDeclarationInstantiation currently, which only uses "false" for "can_be_deleted".
+            //       The only area that sets "can_be_deleted" to true is EvalDeclarationInstantiation, which is currently fully implemented in C++ and not in Bytecode.
+            verify_cast<GlobalEnvironment>(vm.variable_environment())->create_global_var_binding(name, false);
+        }
     }
     return {};
 }
@@ -882,7 +891,7 @@ String CreateEnvironment::to_string_impl(Bytecode::Executable const&) const
 String CreateVariable::to_string_impl(Bytecode::Executable const& executable) const
 {
     auto mode_string = m_mode == EnvironmentMode::Lexical ? "Lexical" : "Variable";
-    return String::formatted("CreateVariable env:{} immutable:{} {} ({})", mode_string, m_is_immutable, m_identifier, executable.identifier_table->get(m_identifier));
+    return String::formatted("CreateVariable env:{} immutable:{} global:{} {} ({})", mode_string, m_is_immutable, m_is_global, m_identifier, executable.identifier_table->get(m_identifier));
 }
 
 String EnterObjectEnvironment::to_string_impl(Executable const&) const

+ 4 - 2
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -317,11 +317,12 @@ public:
 
 class CreateVariable final : public Instruction {
 public:
-    explicit CreateVariable(IdentifierTableIndex identifier, EnvironmentMode mode, bool is_immutable)
+    explicit CreateVariable(IdentifierTableIndex identifier, EnvironmentMode mode, bool is_immutable, bool is_global = false)
         : Instruction(Type::CreateVariable)
         , m_identifier(identifier)
         , m_mode(mode)
         , m_is_immutable(is_immutable)
+        , m_is_global(is_global)
     {
     }
 
@@ -332,7 +333,8 @@ public:
 private:
     IdentifierTableIndex m_identifier;
     EnvironmentMode m_mode;
-    bool m_is_immutable { false };
+    bool m_is_immutable : 4 { false };
+    bool m_is_global : 4 { false };
 };
 
 class SetVariable final : public Instruction {