Bläddra i källkod

LibJS: Allow function calls with missing arguments

We were interpreting "undefined" as a variable lookup failure in some
cases and throwing a ReferenceError exception instead of treating it
as the valid value "undefined".

This patch wraps the result of variable lookup in Optional<>, which
allows us to only throw ReferenceError when lookup actually fails.
Andreas Kling 5 år sedan
förälder
incheckning
c60dc84a33

+ 8 - 5
Libraries/LibJS/AST.cpp

@@ -478,10 +478,10 @@ void ForStatement::dump(int indent) const
 
 Value Identifier::execute(Interpreter& interpreter) const
 {
-    auto value = interpreter.get_variable(string());
-    if (value.is_undefined())
+    auto variable = interpreter.get_variable(string());
+    if (!variable.has_value())
         return interpreter.throw_exception<Error>("ReferenceError", String::format("'%s' not known", string().characters()));
-    return value;
+    return variable.value();
 }
 
 void Identifier::dump(int indent) const
@@ -536,7 +536,9 @@ Value UpdateExpression::execute(Interpreter& interpreter) const
     ASSERT(m_argument->is_identifier());
     auto name = static_cast<const Identifier&>(*m_argument).string();
 
-    auto previous_value = interpreter.get_variable(name);
+    auto previous_variable = interpreter.get_variable(name);
+    ASSERT(previous_variable.has_value());
+    auto previous_value = previous_variable.value();
     ASSERT(previous_value.is_number());
 
     int op_result = 0;
@@ -687,7 +689,8 @@ Value MemberExpression::execute(Interpreter& interpreter) const
 {
     auto object_result = m_object->execute(interpreter).to_object(interpreter.heap());
     ASSERT(object_result.is_object());
-    return object_result.as_object()->get(computed_property_name(interpreter));
+    auto result = object_result.as_object()->get(computed_property_name(interpreter));
+    return result.value_or({});
 }
 
 Value StringLiteral::execute(Interpreter& interpreter) const

+ 1 - 1
Libraries/LibJS/Interpreter.cpp

@@ -142,7 +142,7 @@ void Interpreter::set_variable(const FlyString& name, Value value, bool first_as
     global_object().put(move(name), move(value));
 }
 
-Value Interpreter::get_variable(const FlyString& name)
+Optional<Value> Interpreter::get_variable(const FlyString& name)
 {
     for (ssize_t i = m_scope_stack.size() - 1; i >= 0; --i) {
         auto& scope = m_scope_stack.at(i);

+ 1 - 1
Libraries/LibJS/Interpreter.h

@@ -79,7 +79,7 @@ public:
 
     void unwind(ScopeType type) { m_unwind_until = type; }
 
-    Value get_variable(const FlyString& name);
+    Optional<Value> get_variable(const FlyString& name);
     void set_variable(const FlyString& name, Value, bool first_assignment = false);
     void declare_variable(const FlyString& name, DeclarationType);
 

+ 2 - 2
Libraries/LibJS/Runtime/Object.cpp

@@ -63,7 +63,7 @@ bool Object::put_own_property(Object& this_object, const FlyString& property_nam
     return true;
 }
 
-Value Object::get(const FlyString& property_name) const
+Optional<Value> Object::get(const FlyString& property_name) const
 {
     const Object* object = this;
     while (object) {
@@ -72,7 +72,7 @@ Value Object::get(const FlyString& property_name) const
             return value.value();
         object = object->prototype();
     }
-    return js_undefined();
+    return {};
 }
 
 void Object::put(const FlyString& property_name, Value value)

+ 1 - 1
Libraries/LibJS/Runtime/Object.h

@@ -40,7 +40,7 @@ public:
     Object();
     virtual ~Object();
 
-    Value get(const FlyString& property_name) const;
+    Optional<Value> get(const FlyString& property_name) const;
     void put(const FlyString& property_name, Value);
 
     virtual Optional<Value> get_own_property(const Object& this_object, const FlyString& property_name) const;

+ 13 - 0
Libraries/LibJS/Tests/function-missing-arg.js

@@ -0,0 +1,13 @@
+function assert(x) { if (!x) throw 1; }
+
+function foo(a, b) { return a + b; }
+
+try {
+    assert(isNaN(foo()) === true);
+    assert(isNaN(foo(1)) === true);
+    assert(foo(2, 3) === 5);
+    assert(foo(2, 3, 4) === 5);
+    console.log("PASS");
+} catch (e) {
+    console.log("FAIL: " + e);
+}

+ 19 - 0
Libraries/LibJS/Tests/variable-undefined.js

@@ -0,0 +1,19 @@
+function assert(x) { if (!x) throw 1; }
+
+function foo(a) {
+    return a;
+}
+
+try {
+    var x = undefined;
+    assert(x === undefined);
+    assert(foo(x) === undefined);
+
+    var o = {};
+    o.x = x;
+    assert(o.x === undefined);
+    assert(o.x === x);
+    console.log("PASS");
+} catch (e) {
+    console.log("FAIL: " + e);
+}