LibJS: Rework how native functions are called to improve |this| value

Native functions now only get the Interpreter& as an argument. They can
then extract |this| along with any indexed arguments it wants from it.

This forces functions that want |this| to actually deal with calling
interpreter.this_value().to_object(), and dealing with the possibility
of a non-object |this|.

This is still not great but let's keep massaging it forward.
This commit is contained in:
Andreas Kling 2020-03-28 22:48:35 +01:00
parent c209ea1985
commit 7c4e53f31e
Notes: sideshowbarker 2024-07-19 08:05:16 +09:00
25 changed files with 145 additions and 102 deletions

View file

@ -99,7 +99,7 @@ Value CallExpression::execute(Interpreter& interpreter) const
}
}
auto result = function->call(interpreter, call_frame.arguments);
auto result = function->call(interpreter);
interpreter.pop_call_frame();
if (is_new_expression()) {
@ -554,10 +554,10 @@ Value AssignmentExpression::execute(Interpreter& interpreter) const
};
} else if (m_lhs->is_member_expression()) {
commit = [&](Value value) {
auto object = static_cast<const MemberExpression&>(*m_lhs).object().execute(interpreter).to_object(interpreter.heap());
ASSERT(object.is_object());
auto property_name = static_cast<const MemberExpression&>(*m_lhs).computed_property_name(interpreter);
object.as_object()->put(property_name, value);
if (auto* object = static_cast<const MemberExpression&>(*m_lhs).object().execute(interpreter).to_object(interpreter.heap())) {
auto property_name = static_cast<const MemberExpression&>(*m_lhs).computed_property_name(interpreter);
object->put(property_name, value);
}
};
} else {
ASSERT_NOT_REACHED();
@ -751,11 +751,10 @@ FlyString MemberExpression::computed_property_name(Interpreter& interpreter) con
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());
if (interpreter.exception())
return {};
ASSERT(object_result.is_object());
auto result = object_result.as_object()->get(computed_property_name(interpreter));
auto result = object_result->get(computed_property_name(interpreter));
return result.value_or({});
}

View file

@ -190,7 +190,7 @@ Value Interpreter::call(Function* function, Value this_value, const Vector<Value
auto& call_frame = push_call_frame();
call_frame.this_value = this_value;
call_frame.arguments = arguments;
auto result = function->call(*this, call_frame.arguments);
auto result = function->call(*this);
pop_call_frame();
return result;
}

View file

@ -96,6 +96,8 @@ public:
return m_call_stack.last();
}
void pop_call_frame() { m_call_stack.take_last(); }
const CallFrame& call_frame() { return m_call_stack.last(); }
Value this_value() const
{
if (m_call_stack.is_empty())

View file

@ -35,24 +35,30 @@ namespace JS {
ArrayPrototype::ArrayPrototype()
{
put_native_function("shift", [](Object* this_object, const Vector<Value>&) -> Value {
ASSERT(this_object);
put_native_function("shift", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
ASSERT(this_object->is_array());
return static_cast<Array*>(this_object)->shift();
});
put_native_function("pop", [](Object* this_object, const Vector<Value>&) -> Value {
ASSERT(this_object);
put_native_function("pop", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
ASSERT(this_object->is_array());
return static_cast<Array*>(this_object)->pop();
});
put_native_function("push", [](Object* this_object, const Vector<Value>& arguments) -> Value {
if (arguments.is_empty())
return js_undefined();
ASSERT(this_object);
put_native_function("push", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
ASSERT(this_object->is_array());
static_cast<Array*>(this_object)->push(arguments[0]);
if (interpreter.call_frame().arguments.is_empty())
return js_undefined();
static_cast<Array*>(this_object)->push(interpreter.call_frame().arguments[0]);
return Value(static_cast<const Array*>(this_object)->length());
});
}

View file

@ -24,8 +24,9 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Function.h>
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/ConsoleObject.h>
#include <stdio.h>
@ -33,10 +34,10 @@ namespace JS {
ConsoleObject::ConsoleObject()
{
put_native_function("log", [](Object*, const Vector<Value>& arguments) -> Value {
for (size_t i = 0; i < arguments.size(); ++i) {
printf("%s", arguments[i].to_string().characters());
if (i != arguments.size() - 1)
put_native_function("log", [](Interpreter& interpreter) -> Value {
for (size_t i = 0; i < interpreter.call_frame().arguments.size(); ++i) {
printf("%s", interpreter.call_frame().arguments[i].to_string().characters());
if (i != interpreter.call_frame().arguments.size() - 1)
putchar(' ');
}
putchar('\n');

View file

@ -35,7 +35,7 @@ class Function : public Object {
public:
virtual ~Function();
virtual Value call(Interpreter&, const Vector<Value>&) = 0;
virtual Value call(Interpreter&) = 0;
protected:
Function();

View file

@ -14,15 +14,15 @@ namespace JS {
GlobalObject::GlobalObject()
{
put("console", heap().allocate<ConsoleObject>());
put_native_function("gc", [](Object* this_object, Vector<Value>) -> Value {
put_native_function("gc", [](Interpreter& interpreter) -> Value {
dbg() << "Forced garbage collection requested!";
this_object->heap().collect_garbage();
interpreter.heap().collect_garbage();
return js_undefined();
});
put_native_function("isNaN", [](Object*, Vector<Value> arguments) -> Value {
if (arguments.size() < 1)
put_native_function("isNaN", [](Interpreter& interpreter) -> Value {
if (interpreter.call_frame().arguments.size() < 1)
return js_undefined();
return Value(arguments[0].to_number().is_nan());
return Value(interpreter.call_frame().arguments[0].to_number().is_nan());
});
put("Math", heap().allocate<MathObject>());
put("Object", heap().allocate<ObjectConstructor>());

View file

@ -32,7 +32,7 @@ namespace JS {
MathObject::MathObject()
{
put_native_function("random", [](Object*, const Vector<Value>&) {
put_native_function("random", [](Interpreter&) {
#ifdef __serenity__
double r = (double)arc4random() / (double)UINT32_MAX;
#else

View file

@ -30,7 +30,7 @@
namespace JS {
NativeFunction::NativeFunction(AK::Function<Value(Object*, const Vector<Value>&)> native_function)
NativeFunction::NativeFunction(AK::Function<Value(Interpreter&)> native_function)
: m_native_function(move(native_function))
{
}
@ -39,14 +39,9 @@ NativeFunction::~NativeFunction()
{
}
Value NativeFunction::call(Interpreter& interpreter, const Vector<Value>& arguments)
Value NativeFunction::call(Interpreter& interpreter)
{
auto this_value = interpreter.this_value();
// FIXME: Why are we here with a non-object 'this'?
Object* this_object = nullptr;
if (this_value.is_object())
this_object = this_value.as_object();
return m_native_function(this_object, arguments);
return m_native_function(interpreter);
}
}

View file

@ -33,10 +33,10 @@ namespace JS {
class NativeFunction : public Function {
public:
explicit NativeFunction(AK::Function<Value(Object*, const Vector<Value>&)>);
explicit NativeFunction(AK::Function<Value(Interpreter&)>);
virtual ~NativeFunction() override;
virtual Value call(Interpreter&, const Vector<Value>&) override;
virtual Value call(Interpreter&) override;
protected:
NativeFunction() {}
@ -45,7 +45,7 @@ private:
virtual bool is_native_function() const override { return true; }
virtual const char* class_name() const override { return "NativeFunction"; }
AK::Function<Value(Object*, const Vector<Value>&)> m_native_function;
AK::Function<Value(Interpreter&)> m_native_function;
};
}

View file

@ -102,7 +102,7 @@ void Object::put(const FlyString& property_name, Value value)
put_own_property(*this, property_name, value);
}
void Object::put_native_function(const FlyString& property_name, AK::Function<Value(Object*, Vector<Value>)> native_function)
void Object::put_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)> native_function)
{
put(property_name, heap().allocate<NativeFunction>(move(native_function)));
}

View file

@ -46,7 +46,7 @@ public:
virtual Optional<Value> get_own_property(const Object& this_object, const FlyString& property_name) const;
virtual bool put_own_property(Object& this_object, const FlyString& property_name, Value);
void put_native_function(const FlyString& property_name, AK::Function<Value(Object*, Vector<Value>)>);
void put_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)>);
void put_native_property(const FlyString& property_name, AK::Function<Value(Object*)> getter, AK::Function<void(Object*, Value)> setter);
virtual bool is_error() const { return false; }

View file

@ -35,39 +35,40 @@ ObjectConstructor::ObjectConstructor()
{
put("prototype", interpreter().object_prototype());
put_native_function("getPrototypeOf", [this](Object*, const Vector<Value>& arguments) -> Value {
if (arguments.size() < 1)
return {};
auto object = arguments[0].to_object(heap());
if (interpreter().exception())
return {};
if (!object.is_object())
return {};
return object.as_object()->prototype();
});
put_native_function("setPrototypeOf", [this](Object*, const Vector<Value>& arguments) -> Value {
if (arguments.size() < 2)
return {};
auto object = arguments[0].to_object(heap());
if (interpreter().exception())
return {};
if (!object.is_object())
return {};
if (!arguments[1].is_object())
return {};
const_cast<Object*>(object.as_object())->set_prototype(const_cast<Object*>(arguments[1].as_object()));
return {};
});
put_native_function("getPrototypeOf", get_prototype_of);
put_native_function("setPrototypeOf", set_prototype_of);
}
ObjectConstructor::~ObjectConstructor()
{
}
Value ObjectConstructor::call(Interpreter& interpreter, const Vector<Value>&)
Value ObjectConstructor::call(Interpreter& interpreter)
{
return interpreter.heap().allocate<Object>();
}
Value ObjectConstructor::get_prototype_of(Interpreter& interpreter)
{
if (interpreter.call_frame().arguments.size() < 1)
return {};
auto* object = interpreter.call_frame().arguments[0].to_object(interpreter.heap());
if (interpreter.exception())
return {};
return object->prototype();
}
Value ObjectConstructor::set_prototype_of(Interpreter& interpreter)
{
if (interpreter.call_frame().arguments.size() < 2)
return {};
if (!interpreter.call_frame().arguments[1].is_object())
return {};
auto* object = interpreter.call_frame().arguments[0].to_object(interpreter.heap());
if (interpreter.exception())
return {};
object->set_prototype(const_cast<Object*>(interpreter.call_frame().arguments[1].as_object()));
return {};
}
}

View file

@ -35,10 +35,13 @@ public:
ObjectConstructor();
virtual ~ObjectConstructor() override;
virtual Value call(Interpreter&, const Vector<Value>&) override;
virtual Value call(Interpreter&) override;
private:
virtual const char* class_name() const override { return "ObjectConstructor"; }
static Value get_prototype_of(Interpreter&);
static Value set_prototype_of(Interpreter&);
};
}

View file

@ -37,21 +37,26 @@ ObjectPrototype::ObjectPrototype()
{
set_prototype(nullptr);
put_native_function("hasOwnProperty", [](Object* this_object, const Vector<Value>& arguments) -> Value {
ASSERT(this_object);
if (arguments.is_empty())
put_native_function("hasOwnProperty", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
if (interpreter.call_frame().arguments.is_empty())
return js_undefined();
return Value(this_object->has_own_property(arguments[0].to_string()));
return Value(this_object->has_own_property(interpreter.call_frame().arguments[0].to_string()));
});
put_native_function("toString", [](Object* this_object, Vector<Value>) -> Value {
ASSERT(this_object);
put_native_function("toString", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
return Value(this_object->to_string());
});
put_native_function("valueOf", [](Object* this_object, Vector<Value>) -> Value {
ASSERT(this_object);
put_native_function("valueOf", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
return this_object->value_of();
});
}

View file

@ -41,8 +41,9 @@ ScriptFunction::~ScriptFunction()
{
}
Value ScriptFunction::call(Interpreter& interpreter, const Vector<Value>& argument_values)
Value ScriptFunction::call(Interpreter& interpreter)
{
auto& argument_values = interpreter.call_frame().arguments;
Vector<Argument> arguments;
for (size_t i = 0; i < m_parameters.size(); ++i) {
auto name = parameters()[i];

View file

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

View file

@ -44,23 +44,28 @@ StringPrototype::StringPrototype()
return Value((i32) static_cast<const StringObject*>(this_object)->primitive_string()->string().length());
},
nullptr);
put_native_function("charAt", [](Object* this_object, const Vector<Value>& arguments) -> Value {
ASSERT(this_object);
put_native_function("charAt", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
i32 index = 0;
if (!arguments.is_empty())
index = arguments[0].to_i32();
if (!interpreter.call_frame().arguments.is_empty())
index = interpreter.call_frame().arguments[0].to_i32();
ASSERT(this_object->is_string_object());
auto underlying_string = static_cast<const StringObject*>(this_object)->primitive_string()->string();
if (index < 0 || index >= static_cast<i32>(underlying_string.length()))
return js_string(this_object->heap(), String::empty());
return js_string(this_object->heap(), underlying_string.substring(index, 1));
});
put_native_function("repeat", [](Object* this_object, const Vector<Value>& arguments) -> Value {
put_native_function("repeat", [](Interpreter& interpreter) -> Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
ASSERT(this_object->is_string_object());
if (arguments.is_empty())
if (interpreter.call_frame().arguments.is_empty())
return js_string(this_object->heap(), String::empty());
i32 count = 0;
count = arguments[0].to_i32();
count = interpreter.call_frame().arguments[0].to_i32();
if (count < 0) {
// FIXME: throw RangeError
return js_undefined();

View file

@ -27,6 +27,8 @@
#include <AK/FlyString.h>
#include <AK/String.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/StringObject.h>
@ -86,7 +88,7 @@ bool Value::to_boolean() const
}
}
Value Value::to_object(Heap& heap) const
Object* Value::to_object(Heap& heap) const
{
if (is_object())
return const_cast<Object*>(as_object());
@ -94,6 +96,11 @@ Value Value::to_object(Heap& heap) const
if (is_string())
return heap.allocate<StringObject>(m_value.as_string);
if (is_null() || is_undefined()) {
heap.interpreter().throw_exception<Error>("TypeError", "ToObject on null or undefined.");
return nullptr;
}
ASSERT_NOT_REACHED();
}

View file

@ -144,7 +144,7 @@ public:
Value to_number() const;
i32 to_i32() const;
Value to_object(Heap&) const;
Object* to_object(Heap&) const;
private:
Type m_type { Type::Undefined };

View file

@ -24,8 +24,9 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Function.h>
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
@ -50,7 +51,8 @@ CanvasRenderingContext2DWrapper::CanvasRenderingContext2DWrapper(CanvasRendering
[this](JS::Object*, JS::Value value) {
m_impl->set_fill_style(value.to_string());
});
put_native_function("fillRect", [this](JS::Object*, const Vector<JS::Value>& arguments) {
put_native_function("fillRect", [this](JS::Interpreter& interpreter) {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() >= 4) {
m_impl->fill_rect(arguments[0].to_i32(), arguments[1].to_i32(), arguments[2].to_i32(), arguments[3].to_i32());
}

View file

@ -25,6 +25,7 @@
*/
#include <AK/FlyString.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/DocumentWrapper.h>
@ -37,7 +38,8 @@ namespace Bindings {
DocumentWrapper::DocumentWrapper(Document& document)
: NodeWrapper(document)
{
put_native_function("getElementById", [this](JS::Object*, const Vector<JS::Value>& arguments) -> JS::Value {
put_native_function("getElementById", [this](JS::Interpreter& interpreter) -> JS::Value {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.is_empty())
return JS::js_null();
auto id = arguments[0].to_string();

View file

@ -24,8 +24,9 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Function.h>
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/Function.h>
#include <LibWeb/Bindings/EventListenerWrapper.h>
#include <LibWeb/Bindings/EventTargetWrapper.h>
@ -38,7 +39,12 @@ namespace Bindings {
EventTargetWrapper::EventTargetWrapper(EventTarget& impl)
: m_impl(impl)
{
put_native_function("addEventListener", [](Object* this_object, const Vector<JS::Value>& arguments) {
put_native_function("addEventListener", [](JS::Interpreter& interpreter) -> JS::Value {
auto* this_object = interpreter.this_value().to_object(interpreter.heap());
if (!this_object)
return {};
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() < 2)
return JS::js_undefined();

View file

@ -24,8 +24,9 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <AK/Function.h>
#include <AK/FlyString.h>
#include <AK/Function.h>
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/PrimitiveString.h>
#include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/CanvasRenderingContext2DWrapper.h>
@ -39,7 +40,8 @@ namespace Bindings {
HTMLCanvasElementWrapper::HTMLCanvasElementWrapper(HTMLCanvasElement& element)
: ElementWrapper(element)
{
put_native_function("getContext", [this](JS::Object*, const Vector<JS::Value>& arguments) -> JS::Value {
put_native_function("getContext", [this](JS::Interpreter& interpreter) -> JS::Value {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() >= 1) {
auto* context = node().get_context(arguments[0].to_string());
return wrap(heap(), *context);

View file

@ -338,14 +338,16 @@ JS::Interpreter& Document::interpreter()
if (!m_interpreter) {
m_interpreter = make<JS::Interpreter>();
m_interpreter->global_object().put_native_function("alert", [](JS::Object*, const Vector<JS::Value>& arguments) -> JS::Value {
m_interpreter->global_object().put_native_function("alert", [](JS::Interpreter& interpreter) -> JS::Value {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() < 1)
return JS::js_undefined();
GUI::MessageBox::show(arguments[0].to_string(), "Alert", GUI::MessageBox::Type::Information);
return JS::js_undefined();
});
m_interpreter->global_object().put_native_function("setInterval", [this](JS::Object*, const Vector<JS::Value>& arguments) -> JS::Value {
m_interpreter->global_object().put_native_function("setInterval", [this](JS::Interpreter& interpreter) -> JS::Value {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() < 2)
return JS::js_undefined();
ASSERT(arguments[0].is_object());
@ -355,14 +357,16 @@ JS::Interpreter& Document::interpreter()
// FIXME: This timer should not be leaked! It should also be removable with clearInterval()!
(void)Core::Timer::construct(
arguments[1].to_i32(), [this, callback] {
const_cast<JS::Function*>(static_cast<const JS::Function*>(callback.cell()))->call(*m_interpreter, {});
// FIXME: Perform the call through Interpreter so it can set up a call frame!
const_cast<JS::Function*>(static_cast<const JS::Function*>(callback.cell()))->call(*m_interpreter);
})
.leak_ref();
return JS::js_undefined();
});
m_interpreter->global_object().put_native_function("requestAnimationFrame", [this](JS::Object*, const Vector<JS::Value>& arguments) -> JS::Value {
m_interpreter->global_object().put_native_function("requestAnimationFrame", [this](JS::Interpreter& interpreter) -> JS::Value {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() < 1)
return JS::js_undefined();
ASSERT(arguments[0].is_object());
@ -370,13 +374,15 @@ JS::Interpreter& Document::interpreter()
auto callback = make_handle(const_cast<JS::Object*>(arguments[0].as_object()));
// FIXME: Don't hand out raw DisplayLink ID's to JavaScript!
i32 link_id = GUI::DisplayLink::register_callback([this, callback](i32 link_id) {
const_cast<JS::Function*>(static_cast<const JS::Function*>(callback.cell()))->call(*m_interpreter, {});
// FIXME: Perform the call through Interpreter so it can set up a call frame!
const_cast<JS::Function*>(static_cast<const JS::Function*>(callback.cell()))->call(*m_interpreter);
GUI::DisplayLink::unregister_callback(link_id);
});
return JS::Value(link_id);
});
m_interpreter->global_object().put_native_function("cancelAnimationFrame", [](JS::Object*, const Vector<JS::Value>& arguments) -> JS::Value {
m_interpreter->global_object().put_native_function("cancelAnimationFrame", [](JS::Interpreter& interpreter) -> JS::Value {
auto& arguments = interpreter.call_frame().arguments;
if (arguments.size() < 1)
return JS::js_undefined();
// FIXME: We should not be passing untrusted numbers to DisplayLink::unregistered_callback()!