1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "vm/BoundFunctionObject.h"
11 #include "util/StringBuffer.h"
12 #include "vm/Interpreter.h"
16 #include "gc/ObjectKind-inl.h"
17 #include "vm/JSFunction-inl.h"
18 #include "vm/JSObject-inl.h"
19 #include "vm/NativeObject-inl.h"
20 #include "vm/Shape-inl.h"
24 // Helper function to initialize `args` with all bound arguments + the arguments
25 // supplied in `callArgs`.
26 template <typename Args
>
27 static MOZ_ALWAYS_INLINE
void FillArguments(Args
& args
,
28 BoundFunctionObject
* bound
,
30 const CallArgs
& callArgs
) {
31 MOZ_ASSERT(args
.length() == numBoundArgs
+ callArgs
.length());
33 if (numBoundArgs
<= BoundFunctionObject::MaxInlineBoundArgs
) {
34 for (size_t i
= 0; i
< numBoundArgs
; i
++) {
35 args
[i
].set(bound
->getInlineBoundArg(i
));
38 ArrayObject
* boundArgs
= bound
->getBoundArgsArray();
39 for (size_t i
= 0; i
< numBoundArgs
; i
++) {
40 args
[i
].set(boundArgs
->getDenseElement(i
));
44 for (size_t i
= 0; i
< callArgs
.length(); i
++) {
45 args
[numBoundArgs
+ i
].set(callArgs
[i
]);
49 // ES2023 10.4.1.1 [[Call]]
50 // https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist
52 bool BoundFunctionObject::call(JSContext
* cx
, unsigned argc
, Value
* vp
) {
53 CallArgs args
= CallArgsFromVp(argc
, vp
);
54 Rooted
<BoundFunctionObject
*> bound(cx
,
55 &args
.callee().as
<BoundFunctionObject
>());
58 Rooted
<Value
> target(cx
, bound
->getTargetVal());
61 Rooted
<Value
> boundThis(cx
, bound
->getBoundThis());
64 size_t numBoundArgs
= bound
->numBoundArgs();
66 if (!args2
.init(cx
, uint64_t(numBoundArgs
) + args
.length())) {
69 FillArguments(args2
, bound
, numBoundArgs
, args
);
72 return Call(cx
, target
, boundThis
, args2
, args
.rval());
75 // ES2023 10.4.1.2 [[Construct]]
76 // https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget
78 bool BoundFunctionObject::construct(JSContext
* cx
, unsigned argc
, Value
* vp
) {
79 CallArgs args
= CallArgsFromVp(argc
, vp
);
80 Rooted
<BoundFunctionObject
*> bound(cx
,
81 &args
.callee().as
<BoundFunctionObject
>());
83 MOZ_ASSERT(bound
->isConstructor(),
84 "shouldn't have called this hook if not a constructor");
87 Rooted
<Value
> target(cx
, bound
->getTargetVal());
90 MOZ_ASSERT(IsConstructor(target
));
93 size_t numBoundArgs
= bound
->numBoundArgs();
94 ConstructArgs
args2(cx
);
95 if (!args2
.init(cx
, uint64_t(numBoundArgs
) + args
.length())) {
98 FillArguments(args2
, bound
, numBoundArgs
, args
);
101 Rooted
<Value
> newTarget(cx
, args
.newTarget());
102 if (newTarget
== ObjectValue(*bound
)) {
107 Rooted
<JSObject
*> res(cx
);
108 if (!Construct(cx
, target
, args2
, newTarget
, &res
)) {
111 args
.rval().setObject(*res
);
116 JSString
* BoundFunctionObject::funToString(JSContext
* cx
, Handle
<JSObject
*> obj
,
118 // Implementation of the funToString hook used by Function.prototype.toString.
120 // For the non-standard toSource extension, we include "bound" to indicate
121 // it's a bound function.
123 static constexpr std::string_view nativeCodeBound
=
124 "function bound() {\n [native code]\n}";
125 return NewStringCopy
<CanGC
>(cx
, nativeCodeBound
);
128 static constexpr std::string_view nativeCode
=
129 "function() {\n [native code]\n}";
130 return NewStringCopy
<CanGC
>(cx
, nativeCode
);
134 SharedShape
* BoundFunctionObject::assignInitialShape(
135 JSContext
* cx
, Handle
<BoundFunctionObject
*> obj
) {
136 MOZ_ASSERT(obj
->empty());
138 constexpr PropertyFlags propFlags
= {PropertyFlag::Configurable
};
139 if (!NativeObject::addPropertyInReservedSlot(cx
, obj
, cx
->names().length
,
140 LengthSlot
, propFlags
)) {
143 if (!NativeObject::addPropertyInReservedSlot(cx
, obj
, cx
->names().name
,
144 NameSlot
, propFlags
)) {
148 SharedShape
* shape
= obj
->sharedShape();
149 if (shape
->proto() == TaggedProto(&cx
->global()->getFunctionPrototype())) {
150 cx
->global()->setBoundFunctionShapeWithDefaultProto(shape
);
155 static MOZ_ALWAYS_INLINE
bool ComputeLengthValue(
156 JSContext
* cx
, Handle
<BoundFunctionObject
*> bound
, Handle
<JSObject
*> target
,
157 size_t numBoundArgs
, double* length
) {
160 // Try to avoid invoking the JSFunction resolve hook.
161 if (target
->is
<JSFunction
>() &&
162 !target
->as
<JSFunction
>().hasResolvedLength()) {
163 uint16_t targetLength
;
164 if (!JSFunction::getUnresolvedLength(cx
, target
.as
<JSFunction
>(),
169 if (size_t(targetLength
) > numBoundArgs
) {
170 *length
= size_t(targetLength
) - numBoundArgs
;
175 // Use a fast path for getting the .length value if the target is a bound
176 // function with its initial shape.
178 if (target
->is
<BoundFunctionObject
>() && target
->shape() == bound
->shape()) {
179 BoundFunctionObject
* targetFn
= &target
->as
<BoundFunctionObject
>();
180 targetLength
= targetFn
->getLengthForInitialShape();
183 Rooted
<PropertyKey
> key(cx
, NameToId(cx
->names().length
));
184 if (!HasOwnProperty(cx
, target
, key
, &hasLength
)) {
192 Rooted
<Value
> targetLengthRoot(cx
);
193 if (!GetProperty(cx
, target
, target
, key
, &targetLengthRoot
)) {
196 targetLength
= targetLengthRoot
;
199 if (targetLength
.isNumber()) {
201 0.0, JS::ToInteger(targetLength
.toNumber()) - double(numBoundArgs
));
206 static MOZ_ALWAYS_INLINE JSAtom
* AppendBoundFunctionPrefix(JSContext
* cx
,
208 auto& cache
= cx
->zone()->boundPrefixCache();
210 JSAtom
* strAtom
= str
->isAtom() ? &str
->asAtom() : nullptr;
212 if (auto p
= cache
.lookup(strAtom
)) {
218 if (!sb
.append("bound ") || !sb
.append(str
)) {
221 JSAtom
* atom
= sb
.finishAtom();
227 (void)cache
.putNew(strAtom
, atom
);
232 static MOZ_ALWAYS_INLINE JSAtom
* ComputeNameValue(
233 JSContext
* cx
, Handle
<BoundFunctionObject
*> bound
,
234 Handle
<JSObject
*> target
) {
235 // Try to avoid invoking the JSFunction resolve hook.
236 JSString
* name
= nullptr;
237 if (target
->is
<JSFunction
>() && !target
->as
<JSFunction
>().hasResolvedName()) {
238 JSFunction
* targetFn
= &target
->as
<JSFunction
>();
239 name
= targetFn
->getUnresolvedName(cx
);
244 // Use a fast path for getting the .name value if the target is a bound
245 // function with its initial shape.
247 if (target
->is
<BoundFunctionObject
>() &&
248 target
->shape() == bound
->shape()) {
249 BoundFunctionObject
* targetFn
= &target
->as
<BoundFunctionObject
>();
250 targetName
= targetFn
->getNameForInitialShape();
252 Rooted
<Value
> targetNameRoot(cx
);
253 if (!GetProperty(cx
, target
, target
, cx
->names().name
, &targetNameRoot
)) {
256 targetName
= targetNameRoot
;
258 if (!targetName
.isString()) {
259 return cx
->names().boundWithSpace_
;
261 name
= targetName
.toString();
264 return AppendBoundFunctionPrefix(cx
, name
);
267 // ES2023 20.2.3.2 Function.prototype.bind
268 // https://tc39.es/ecma262/#sec-function.prototype.bind
270 bool BoundFunctionObject::functionBind(JSContext
* cx
, unsigned argc
,
272 CallArgs args
= CallArgsFromVp(argc
, vp
);
275 if (!IsCallable(args
.thisv())) {
276 ReportIncompatibleMethod(cx
, args
, &FunctionClass
);
280 if (MOZ_UNLIKELY(args
.length() > ARGS_LENGTH_MAX
)) {
281 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
282 JSMSG_TOO_MANY_ARGUMENTS
);
286 Rooted
<JSObject
*> target(cx
, &args
.thisv().toObject());
288 BoundFunctionObject
* bound
=
289 functionBindImpl(cx
, target
, args
.array(), args
.length(), nullptr);
295 args
.rval().setObject(*bound
);
299 // ES2023 20.2.3.2 Function.prototype.bind
300 // https://tc39.es/ecma262/#sec-function.prototype.bind
302 // ES2023 10.4.1.3 BoundFunctionCreate
303 // https://tc39.es/ecma262/#sec-boundfunctioncreate
305 // BoundFunctionCreate has been inlined in Function.prototype.bind for
306 // performance reasons.
309 BoundFunctionObject
* BoundFunctionObject::functionBindImpl(
310 JSContext
* cx
, Handle
<JSObject
*> target
, Value
* args
, uint32_t argc
,
311 Handle
<BoundFunctionObject
*> maybeBound
) {
312 MOZ_ASSERT(target
->isCallable());
314 // Make sure the arguments on the stack are rooted when we're called directly
316 RootedExternalValueArray
argsRoot(cx
, argc
, args
);
318 size_t numBoundArgs
= argc
> 0 ? argc
- 1 : 0;
319 MOZ_ASSERT(numBoundArgs
<= ARGS_LENGTH_MAX
, "ensured by callers");
321 // If this assertion fails, make sure we use the correct AllocKind and that we
322 // use all of its slots (consider increasing MaxInlineBoundArgs).
323 static_assert(gc::GetGCKindSlots(allocKind
) == SlotCount
);
325 // ES2023 10.4.1.3 BoundFunctionCreate
327 Rooted
<BoundFunctionObject
*> bound(cx
);
329 // We allocated a bound function in JIT code. In the uncommon case of the
330 // target not having Function.prototype as proto, we have to set the right
333 if (MOZ_UNLIKELY(bound
->staticPrototype() != target
->staticPrototype())) {
334 Rooted
<JSObject
*> proto(cx
, target
->staticPrototype());
335 if (!SetPrototype(cx
, bound
, proto
)) {
341 Rooted
<JSObject
*> proto(cx
);
342 if (!GetPrototype(cx
, target
, &proto
)) {
347 if (proto
== &cx
->global()->getFunctionPrototype() &&
348 cx
->global()->maybeBoundFunctionShapeWithDefaultProto()) {
349 Rooted
<SharedShape
*> shape(
350 cx
, cx
->global()->maybeBoundFunctionShapeWithDefaultProto());
351 bound
= NativeObject::create
<BoundFunctionObject
>(
352 cx
, allocKind
, gc::Heap::Default
, shape
);
357 bound
= NewObjectWithGivenProto
<BoundFunctionObject
>(cx
, proto
);
361 if (!SharedShape::ensureInitialCustomShape
<BoundFunctionObject
>(cx
,
368 MOZ_ASSERT(bound
->lookupPure(cx
->names().length
)->slot() == LengthSlot
);
369 MOZ_ASSERT(bound
->lookupPure(cx
->names().name
)->slot() == NameSlot
);
372 bound
->initFlags(numBoundArgs
, target
->isConstructor());
375 bound
->initReservedSlot(TargetSlot
, ObjectValue(*target
));
379 bound
->initReservedSlot(BoundThisSlot
, args
[0]);
382 if (numBoundArgs
<= MaxInlineBoundArgs
) {
383 for (size_t i
= 0; i
< numBoundArgs
; i
++) {
384 bound
->initReservedSlot(BoundArg0Slot
+ i
, args
[i
+ 1]);
387 ArrayObject
* arr
= NewDenseCopiedArray(cx
, numBoundArgs
, args
+ 1);
391 bound
->initReservedSlot(BoundArg0Slot
, ObjectValue(*arr
));
394 // ES2023 20.2.3.2 Function.prototype.bind
399 if (!ComputeLengthValue(cx
, bound
, target
, numBoundArgs
, &length
)) {
404 bound
->initLength(length
);
407 JSAtom
* name
= ComputeNameValue(cx
, bound
, target
);
413 bound
->initName(name
);
420 BoundFunctionObject
* BoundFunctionObject::createWithTemplate(
421 JSContext
* cx
, Handle
<BoundFunctionObject
*> templateObj
) {
422 Rooted
<SharedShape
*> shape(cx
, templateObj
->sharedShape());
423 auto* bound
= NativeObject::create
<BoundFunctionObject
>(
424 cx
, allocKind
, gc::Heap::Default
, shape
);
428 bound
->initFlags(templateObj
->numBoundArgs(), templateObj
->isConstructor());
429 bound
->initLength(templateObj
->getLengthForInitialShape().toInt32());
430 bound
->initName(&templateObj
->getNameForInitialShape().toString()->asAtom());
435 BoundFunctionObject
* BoundFunctionObject::functionBindSpecializedBaseline(
436 JSContext
* cx
, Handle
<JSObject
*> target
, Value
* args
, uint32_t argc
,
437 Handle
<BoundFunctionObject
*> templateObj
) {
438 // Root the Values on the stack.
439 RootedExternalValueArray
argsRoot(cx
, argc
, args
);
441 MOZ_ASSERT(target
->is
<JSFunction
>() || target
->is
<BoundFunctionObject
>());
442 MOZ_ASSERT(target
->isCallable());
443 MOZ_ASSERT(target
->isConstructor() == templateObj
->isConstructor());
444 MOZ_ASSERT(target
->staticPrototype() == templateObj
->staticPrototype());
446 size_t numBoundArgs
= argc
> 0 ? argc
- 1 : 0;
447 MOZ_ASSERT(numBoundArgs
<= MaxInlineBoundArgs
);
449 BoundFunctionObject
* bound
= createWithTemplate(cx
, templateObj
);
454 MOZ_ASSERT(bound
->lookupPure(cx
->names().length
)->slot() == LengthSlot
);
455 MOZ_ASSERT(bound
->lookupPure(cx
->names().name
)->slot() == NameSlot
);
457 bound
->initReservedSlot(TargetSlot
, ObjectValue(*target
));
459 bound
->initReservedSlot(BoundThisSlot
, args
[0]);
461 for (size_t i
= 0; i
< numBoundArgs
; i
++) {
462 bound
->initReservedSlot(BoundArg0Slot
+ i
, args
[i
+ 1]);
468 BoundFunctionObject
* BoundFunctionObject::createTemplateObject(JSContext
* cx
) {
469 Rooted
<JSObject
*> proto(cx
, &cx
->global()->getFunctionPrototype());
470 Rooted
<BoundFunctionObject
*> bound(
471 cx
, NewTenuredObjectWithGivenProto
<BoundFunctionObject
>(cx
, proto
));
475 if (!SharedShape::ensureInitialCustomShape
<BoundFunctionObject
>(cx
, bound
)) {
481 bool BoundFunctionObject::initTemplateSlotsForSpecializedBind(
482 JSContext
* cx
, uint32_t numBoundArgs
, bool targetIsConstructor
,
483 uint32_t targetLength
, JSAtom
* targetName
) {
485 if (targetLength
> numBoundArgs
) {
486 len
= targetLength
- numBoundArgs
;
489 JSAtom
* name
= AppendBoundFunctionPrefix(cx
, targetName
);
494 initFlags(numBoundArgs
, targetIsConstructor
);
500 static const JSClassOps classOps
= {
501 nullptr, // addProperty
502 nullptr, // delProperty
503 nullptr, // enumerate
504 nullptr, // newEnumerate
506 nullptr, // mayResolve
508 BoundFunctionObject::call
, // call
509 BoundFunctionObject::construct
, // construct
513 static const ObjectOps objOps
= {
514 nullptr, // lookupProperty
515 nullptr, // qdefineProperty
516 nullptr, // hasProperty
517 nullptr, // getProperty
518 nullptr, // setProperty
519 nullptr, // getOwnPropertyDescriptor
520 nullptr, // deleteProperty
521 nullptr, // getElements
522 BoundFunctionObject::funToString
, // funToString
525 const JSClass
BoundFunctionObject::class_
= {
526 "BoundFunctionObject",
527 // Note: bound functions don't have their own constructor or prototype (they
528 // use the prototype of the target object), but we give them a JSProtoKey
529 // because that's what Xray wrappers use to identify builtin objects.
530 JSCLASS_HAS_CACHED_PROTO(JSProto_BoundFunction
) |
531 JSCLASS_HAS_RESERVED_SLOTS(BoundFunctionObject::SlotCount
),