summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorLuke Wilde <lukew@serenityos.org>2022-02-10 00:00:27 +0000
committerLinus Groh <mail@linusgroh.de>2022-02-10 08:45:03 +0000
commit12231068bd38a53650cce857afbec79dbc7e1025 (patch)
tree18fa01856e94c8b02730775112a31f39ddac5f9e /Userland/Libraries
parent898ad7c682c50fa7deac8df6a3397a8466938c7b (diff)
downloadserenity-12231068bd38a53650cce857afbec79dbc7e1025.zip
LibJS: Don't coerce this value to an object in Function.prototype.apply
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp27
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js16
2 files changed, 38 insertions, 5 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp
index 6b355d51c0..a6f59f662b 100644
--- a/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/FunctionPrototype.cpp
@@ -46,15 +46,32 @@ FunctionPrototype::~FunctionPrototype()
// 20.2.3.1 Function.prototype.apply ( thisArg, argArray ), https://tc39.es/ecma262/#sec-function.prototype.apply
JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::apply)
{
- auto* this_object = TRY(vm.this_value(global_object).to_object(global_object));
- if (!this_object->is_function())
- return vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObjectOfType, "Function");
- auto& function = static_cast<FunctionObject&>(*this_object);
+ // 1. Let func be the this value.
+ auto function_value = vm.this_value(global_object);
+
+ // 2. If IsCallable(func) is false, throw a TypeError exception.
+ if (!function_value.is_function())
+ return vm.throw_completion<TypeError>(global_object, ErrorType::NotAFunction, function_value.to_string_without_side_effects());
+
+ auto& function = static_cast<FunctionObject&>(function_value.as_object());
+
auto this_arg = vm.argument(0);
auto arg_array = vm.argument(1);
- if (arg_array.is_nullish())
+
+ // 3. If argArray is undefined or null, then
+ if (arg_array.is_nullish()) {
+ // FIXME: a. Perform PrepareForTailCall().
+
+ // b. Return ? Call(func, thisArg).
return TRY(JS::call(global_object, function, this_arg));
+ }
+
+ // 4. Let argList be ? CreateListFromArrayLike(argArray).
auto arguments = TRY(create_list_from_array_like(global_object, arg_array));
+
+ // FIXME: 5. Perform PrepareForTailCall().
+
+ // 6. Return ? Call(func, thisArg, argList).
return TRY(JS::call(global_object, function, this_arg, move(arguments)));
}
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js
index 1c57e4a657..9a2b83ec37 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js
@@ -49,3 +49,19 @@ test("basic functionality", () => {
expect((() => this).apply("foo")).toBe(globalThis);
});
+
+describe("errors", () => {
+ test("does not accept non-function values", () => {
+ expect(() => {
+ Function.prototype.apply.call("foo");
+ }).toThrowWithMessage(TypeError, "foo is not a function");
+
+ expect(() => {
+ Function.prototype.apply.call(undefined);
+ }).toThrowWithMessage(TypeError, "undefined is not a function");
+
+ expect(() => {
+ Function.prototype.apply.call(null);
+ }).toThrowWithMessage(TypeError, "null is not a function");
+ });
+});