diff --git a/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Libraries/LibJS/Runtime/FunctionPrototype.cpp index dce44e3232e..9da730b00fc 100644 --- a/Libraries/LibJS/Runtime/FunctionPrototype.cpp +++ b/Libraries/LibJS/Runtime/FunctionPrototype.cpp @@ -70,6 +70,22 @@ JS_DEFINE_NATIVE_FUNCTION(FunctionPrototype::apply) return TRY(JS::call(vm, function, this_arg)); } + // NOTE: Do the check performed by CreateListFromArrayLike here, so we could avoid branching in optimized code path. + if (!arg_array.is_object()) + return vm.throw_completion(ErrorType::NotAnObject, arg_array.to_string_without_side_effects()); + + // OPTIMIZATION: If argArray has a simple indexed storage without holes and doesn't interfere with indexed property access, + // we can skip CreateListFromArrayLike and directly use the storage elements. + auto& arg_array_object = arg_array.as_object(); + auto* storage = arg_array_object.indexed_properties().storage(); + if (!arg_array_object.may_interfere_with_indexed_property_access() && storage && storage->is_simple_storage()) { + auto length = TRY(length_of_array_like(vm, arg_array_object)); + auto const* simple_storage = static_cast(storage); + auto storage_elements = simple_storage->elements().span(); + if (!simple_storage->has_empty_elements() && storage_elements.size() >= length) + return TRY(JS::call(vm, function, this_arg, storage_elements.slice(0, length))); + } + // 4. Let argList be ? CreateListFromArrayLike(argArray). auto arguments = TRY(create_list_from_array_like(vm, arg_array)); diff --git a/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js b/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js index 9a2b83ec379..d9873868487 100644 --- a/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js +++ b/Libraries/LibJS/Tests/builtins/Function/Function.prototype.apply.js @@ -50,6 +50,16 @@ test("basic functionality", () => { expect((() => this).apply("foo")).toBe(globalThis); }); +test("array with holes", () => { + function target(a, b, c) { + return a + b + c; + } + + const args = [1, , 3]; + const result = target.apply(null, args); + expect(result).toBe(NaN); +}); + describe("errors", () => { test("does not accept non-function values", () => { expect(() => {