Browse Source

LibJS: Invalidate cached environment coordinate after delete in global

Fixes the bug in interpreter when cached environment coordinate is not
invalidated after `delete` operator usage on global `this`.
Aliaksandr Kalenik 2 years ago
parent
commit
331f6a9e60

+ 3 - 1
Userland/Libraries/LibJS/AST.cpp

@@ -1497,8 +1497,10 @@ ThrowCompletionOr<Reference> Identifier::to_reference(Interpreter& interpreter)
 {
     if (m_cached_environment_coordinate.is_valid()) {
         Environment* environment = nullptr;
+        bool coordinate_screwed_by_delete_in_global_environment = false;
         if (m_cached_environment_coordinate.index == EnvironmentCoordinate::global_marker) {
             environment = &interpreter.vm().current_realm()->global_environment();
+            coordinate_screwed_by_delete_in_global_environment = !TRY(environment->has_binding(string()));
         } else {
             environment = interpreter.vm().running_execution_context().lexical_environment;
             for (size_t i = 0; i < m_cached_environment_coordinate.hops; ++i)
@@ -1506,7 +1508,7 @@ ThrowCompletionOr<Reference> Identifier::to_reference(Interpreter& interpreter)
             VERIFY(environment);
             VERIFY(environment->is_declarative_environment());
         }
-        if (!environment->is_permanently_screwed_by_eval()) {
+        if (!coordinate_screwed_by_delete_in_global_environment && !environment->is_permanently_screwed_by_eval()) {
             return Reference { *environment, string(), interpreter.vm().in_strict_mode(), m_cached_environment_coordinate };
         }
         m_cached_environment_coordinate = {};

+ 3 - 1
Userland/Libraries/LibJS/Bytecode/Op.cpp

@@ -392,8 +392,10 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
         auto const& string = interpreter.current_executable().get_identifier(m_identifier);
         if (m_cached_environment_coordinate.has_value()) {
             Environment* environment = nullptr;
+            bool coordinate_screwed_by_delete_in_global_environment = false;
             if (m_cached_environment_coordinate->index == EnvironmentCoordinate::global_marker) {
                 environment = &interpreter.vm().current_realm()->global_environment();
+                coordinate_screwed_by_delete_in_global_environment = !TRY(environment->has_binding(string));
             } else {
                 environment = vm.running_execution_context().lexical_environment;
                 for (size_t i = 0; i < m_cached_environment_coordinate->hops; ++i)
@@ -401,7 +403,7 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
                 VERIFY(environment);
                 VERIFY(environment->is_declarative_environment());
             }
-            if (!environment->is_permanently_screwed_by_eval()) {
+            if (!coordinate_screwed_by_delete_in_global_environment && !environment->is_permanently_screwed_by_eval()) {
                 return Reference { *environment, string, vm.in_strict_mode(), m_cached_environment_coordinate };
             }
             m_cached_environment_coordinate = {};

+ 14 - 0
Userland/Libraries/LibJS/Tests/operators/delete-global-variable.js

@@ -1,4 +1,5 @@
 a = 1;
+b = 42;
 
 test("basic functionality", () => {
     expect(delete a).toBeTrue();
@@ -7,3 +8,16 @@ test("basic functionality", () => {
         a;
     }).toThrowWithMessage(ReferenceError, "'a' is not defined");
 });
+
+test("delete global var after usage", () => {
+    let errors = 0;
+    for (let i = 0; i < 3; ++i) {
+        try {
+            b++;
+        } catch {
+            ++errors;
+        }
+        delete globalThis.b;
+    }
+    expect(errors).toBe(2);
+});