Quellcode durchsuchen

LibJS: Allow "delete someGlobalVariable"

This is solved by allowing Identifier nodes to produce a Reference with
the global object as base.
Andreas Kling vor 5 Jahren
Ursprung
Commit
3c4a9e421f

+ 7 - 0
Libraries/LibJS/AST.cpp

@@ -382,6 +382,11 @@ Reference Expression::to_reference(Interpreter&) const
     return {};
 }
 
+Reference Identifier::to_reference(Interpreter& interpreter) const
+{
+    return interpreter.get_reference(string());
+}
+
 Reference MemberExpression::to_reference(Interpreter& interpreter) const
 {
     auto object_value = m_object->execute(interpreter);
@@ -404,6 +409,8 @@ Value UnaryExpression::execute(Interpreter& interpreter) const
             return {};
         if (reference.is_unresolvable())
             return Value(true);
+        // FIXME: Support deleting locals
+        ASSERT(!reference.is_local_variable());
         auto* base_object = reference.base().to_object(interpreter.heap());
         if (!base_object)
             return {};

+ 1 - 0
Libraries/LibJS/AST.h

@@ -516,6 +516,7 @@ public:
     virtual Value execute(Interpreter&) const override;
     virtual void dump(int indent) const override;
     virtual bool is_identifier() const override { return true; }
+    virtual Reference to_reference(Interpreter&) const override;
 
 private:
     virtual const char* class_name() const override { return "Identifier"; }

+ 13 - 0
Libraries/LibJS/Interpreter.cpp

@@ -33,6 +33,7 @@
 #include <LibJS/Runtime/MarkedValueList.h>
 #include <LibJS/Runtime/NativeFunction.h>
 #include <LibJS/Runtime/Object.h>
+#include <LibJS/Runtime/Reference.h>
 #include <LibJS/Runtime/Shape.h>
 #include <LibJS/Runtime/Value.h>
 
@@ -163,6 +164,18 @@ Value Interpreter::get_variable(const FlyString& name)
     return global_object().get(name);
 }
 
+Reference Interpreter::get_reference(const FlyString& name)
+{
+    if (m_call_stack.size()) {
+        for (auto* environment = current_environment(); environment; environment = environment->parent()) {
+            auto possible_match = environment->get(name);
+            if (possible_match.has_value())
+                return { Reference::LocalVariable, name };
+        }
+    }
+    return { &global_object(), PropertyName(name) };
+}
+
 void Interpreter::gather_roots(Badge<Heap>, HashTable<Cell*>& roots)
 {
     roots.set(m_global_object);

+ 2 - 0
Libraries/LibJS/Interpreter.h

@@ -96,6 +96,8 @@ public:
     Value get_variable(const FlyString& name);
     void set_variable(const FlyString& name, Value, bool first_assignment = false);
 
+    Reference get_reference(const FlyString& name);
+
     void gather_roots(Badge<Heap>, HashTable<Cell*>&);
 
     void enter_scope(const ScopeNode&, ArgumentVector, ScopeType);

+ 15 - 0
Libraries/LibJS/Runtime/Reference.h

@@ -42,6 +42,15 @@ public:
     {
     }
 
+    enum LocalVariableTag { LocalVariable };
+    Reference(LocalVariableTag, const String& name, bool strict = false)
+        : m_base(js_null())
+        , m_name(name)
+        , m_strict(strict)
+        , m_local_variable(true)
+    {
+    }
+
     Value base() const { return m_base; }
     const PropertyName& name() const { return m_name; }
     bool is_strict() const { return m_strict; }
@@ -57,10 +66,16 @@ public:
         return m_base.is_boolean() || m_base.is_string() || m_base.is_number();
     }
 
+    bool is_local_variable() const
+    {
+        return m_local_variable;
+    }
+
 private:
     Value m_base { js_undefined() };
     PropertyName m_name;
     bool m_strict { false };
+    bool m_local_variable { false };
 };
 
 const LogStream& operator<<(const LogStream&, const Value&);

+ 16 - 0
Libraries/LibJS/Tests/delete-global-variable.js

@@ -0,0 +1,16 @@
+load("test-common.js");
+
+try {
+    a = 1;
+    assert(delete a === true);
+
+    assertThrowsError(() => {
+        a;
+    }, {
+        error: ReferenceError
+    });
+
+    console.log("PASS");
+} catch (e) {
+    console.log("FAIL: " + e);
+}