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

LibJS: Implement constructor/non-constructor function calls

This adds Function::construct() for constructor function calls via `new`
keyword. NativeFunction doesn't have constructor behaviour by default,
ScriptFunction simply calls call() in construct()
This commit is contained in:
Linus Groh 2020-04-01 18:31:24 +01:00 committed by Andreas Kling
parent a27884e4be
commit 849e2c77e4
11 changed files with 66 additions and 1 deletions

View file

@ -31,6 +31,7 @@
#include <LibJS/Interpreter.h> #include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Array.h> #include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h> #include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/PrimitiveString.h> #include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/ScriptFunction.h> #include <LibJS/Runtime/ScriptFunction.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
@ -87,6 +88,14 @@ Value CallExpression::execute(Interpreter& interpreter) const
if (interpreter.exception()) if (interpreter.exception())
return {}; return {};
if (is_new_expression()) {
if (!callee.is_object()
|| !callee.as_object()->is_function()
|| (callee.as_object()->is_native_function()
&& !static_cast<NativeFunction*>(callee.as_object())->has_constructor()))
return interpreter.throw_exception<Error>("TypeError", String::format("%s is not a constructor", callee.to_string().characters()));
}
if (!callee.is_object() || !callee.as_object()->is_function()) if (!callee.is_object() || !callee.as_object()->is_function())
return interpreter.throw_exception<Error>("TypeError", String::format("%s is not a function", callee.to_string().characters())); return interpreter.throw_exception<Error>("TypeError", String::format("%s is not a function", callee.to_string().characters()));
@ -103,17 +112,19 @@ Value CallExpression::execute(Interpreter& interpreter) const
} }
Object* new_object = nullptr; Object* new_object = nullptr;
Value result;
if (is_new_expression()) { if (is_new_expression()) {
new_object = interpreter.heap().allocate<Object>(); new_object = interpreter.heap().allocate<Object>();
auto prototype = function->get("prototype"); auto prototype = function->get("prototype");
if (prototype.has_value() && prototype.value().is_object()) if (prototype.has_value() && prototype.value().is_object())
new_object->set_prototype(prototype.value().as_object()); new_object->set_prototype(prototype.value().as_object());
call_frame.this_value = new_object; call_frame.this_value = new_object;
result = function->construct(interpreter);
} else { } else {
call_frame.this_value = this_value; call_frame.this_value = this_value;
result = function->call(interpreter);
} }
auto result = function->call(interpreter);
interpreter.pop_call_frame(); interpreter.pop_call_frame();
if (is_new_expression()) { if (is_new_expression()) {
@ -955,4 +966,5 @@ void SwitchCase::dump(int indent) const
statement.dump(indent + 1); statement.dump(indent + 1);
} }
} }
} }

View file

@ -46,6 +46,14 @@ DateConstructor::~DateConstructor()
} }
Value DateConstructor::call(Interpreter& interpreter) Value DateConstructor::call(Interpreter& interpreter)
{
auto* date = static_cast<Date*>(construct(interpreter).as_object());
if (!date)
return {};
return js_string(interpreter.heap(), date->string());
}
Value DateConstructor::construct(Interpreter& interpreter)
{ {
// TODO: Support args // TODO: Support args
struct timeval tv; struct timeval tv;

View file

@ -36,8 +36,10 @@ public:
virtual ~DateConstructor() override; virtual ~DateConstructor() override;
virtual Value call(Interpreter&) override; virtual Value call(Interpreter&) override;
virtual Value construct(Interpreter&) override;
private: private:
virtual bool has_constructor() const override { return true; }
virtual const char* class_name() const override { return "DateConstructor"; } virtual const char* class_name() const override { return "DateConstructor"; }
static Value now(Interpreter&); static Value now(Interpreter&);

View file

@ -36,6 +36,7 @@ public:
virtual ~Function(); virtual ~Function();
virtual Value call(Interpreter&) = 0; virtual Value call(Interpreter&) = 0;
virtual Value construct(Interpreter&) = 0;
protected: protected:
Function(); Function();

View file

@ -44,4 +44,9 @@ Value NativeFunction::call(Interpreter& interpreter)
return m_native_function(interpreter); return m_native_function(interpreter);
} }
Value NativeFunction::construct(Interpreter&)
{
return {};
}
} }

View file

@ -37,6 +37,9 @@ public:
virtual ~NativeFunction() override; virtual ~NativeFunction() override;
virtual Value call(Interpreter&) override; virtual Value call(Interpreter&) override;
virtual Value construct(Interpreter&) override;
virtual bool has_constructor() const { return false; }
protected: protected:
NativeFunction() {} NativeFunction() {}

View file

@ -50,6 +50,11 @@ Value ObjectConstructor::call(Interpreter& interpreter)
return interpreter.heap().allocate<Object>(); return interpreter.heap().allocate<Object>();
} }
Value ObjectConstructor::construct(Interpreter& interpreter)
{
return call(interpreter);
}
Value ObjectConstructor::get_own_property_names(Interpreter& interpreter) Value ObjectConstructor::get_own_property_names(Interpreter& interpreter)
{ {
if (interpreter.call_frame().arguments.size() < 1) if (interpreter.call_frame().arguments.size() < 1)

View file

@ -36,8 +36,10 @@ public:
virtual ~ObjectConstructor() override; virtual ~ObjectConstructor() override;
virtual Value call(Interpreter&) override; virtual Value call(Interpreter&) override;
virtual Value construct(Interpreter&) override;
private: private:
virtual bool has_constructor() const override { return true; }
virtual const char* class_name() const override { return "ObjectConstructor"; } virtual const char* class_name() const override { return "ObjectConstructor"; }
static Value get_own_property_names(Interpreter&); static Value get_own_property_names(Interpreter&);

View file

@ -55,4 +55,9 @@ Value ScriptFunction::call(Interpreter& interpreter)
return interpreter.run(m_body, arguments, ScopeType::Function); return interpreter.run(m_body, arguments, ScopeType::Function);
} }
Value ScriptFunction::construct(Interpreter& interpreter)
{
return call(interpreter);
}
} }

View file

@ -39,6 +39,7 @@ public:
const Vector<FlyString>& parameters() const { return m_parameters; }; const Vector<FlyString>& parameters() const { return m_parameters; };
virtual Value call(Interpreter&) override; virtual Value call(Interpreter&) override;
virtual Value construct(Interpreter&) override;
private: private:
virtual bool is_script_function() const final { return true; } virtual bool is_script_function() const final { return true; }

View file

@ -25,6 +25,27 @@ try {
assert(e.message === "undefined is not a function"); assert(e.message === "undefined is not a function");
} }
try {
Math();
} catch(e) {
assert(e.name === "TypeError");
assert(e.message === "[object MathObject] is not a function");
}
try {
new Math();
} catch(e) {
assert(e.name === "TypeError");
assert(e.message === "[object MathObject] is not a constructor");
}
try {
new isNaN();
} catch(e) {
assert(e.name === "TypeError");
assert(e.message === "[object NativeFunction] is not a constructor");
}
console.log("PASS"); console.log("PASS");
} catch(e) { } catch(e) {
console.log("FAIL: " + e); console.log("FAIL: " + e);