1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 04:08:11 +00:00

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.
This commit is contained in:
Andreas Kling 2020-03-27 12:54:18 +01:00
parent 04ced9e24a
commit c60dc84a33
7 changed files with 45 additions and 10 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);
}

View file

@ -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);
}