Bug 1874684 - Part 22: Compute the precise fraction in Duration.p.total and ZonedDate...
[gecko.git] / js / src / vm / BoundFunctionObject.cpp
blob1b80415598037562df39fd25a82bfc49ad1901e4
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"
9 #include <string_view>
11 #include "util/StringBuffer.h"
12 #include "vm/Interpreter.h"
13 #include "vm/Shape.h"
14 #include "vm/Stack.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"
22 using namespace js;
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,
29 size_t numBoundArgs,
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));
37 } else {
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
51 // static
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>());
57 // Step 1.
58 Rooted<Value> target(cx, bound->getTargetVal());
60 // Step 2.
61 Rooted<Value> boundThis(cx, bound->getBoundThis());
63 // Steps 3-4.
64 size_t numBoundArgs = bound->numBoundArgs();
65 InvokeArgs args2(cx);
66 if (!args2.init(cx, uint64_t(numBoundArgs) + args.length())) {
67 return false;
69 FillArguments(args2, bound, numBoundArgs, args);
71 // Step 5.
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
77 // static
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");
86 // Step 1.
87 Rooted<Value> target(cx, bound->getTargetVal());
89 // Step 2.
90 MOZ_ASSERT(IsConstructor(target));
92 // Steps 3-4.
93 size_t numBoundArgs = bound->numBoundArgs();
94 ConstructArgs args2(cx);
95 if (!args2.init(cx, uint64_t(numBoundArgs) + args.length())) {
96 return false;
98 FillArguments(args2, bound, numBoundArgs, args);
100 // Step 5.
101 Rooted<Value> newTarget(cx, args.newTarget());
102 if (newTarget == ObjectValue(*bound)) {
103 newTarget = target;
106 // Step 6.
107 Rooted<JSObject*> res(cx);
108 if (!Construct(cx, target, args2, newTarget, &res)) {
109 return false;
111 args.rval().setObject(*res);
112 return true;
115 // static
116 JSString* BoundFunctionObject::funToString(JSContext* cx, Handle<JSObject*> obj,
117 bool isToSource) {
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.
122 if (isToSource) {
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);
133 // static
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)) {
141 return nullptr;
143 if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().name,
144 NameSlot, propFlags)) {
145 return nullptr;
148 SharedShape* shape = obj->sharedShape();
149 if (shape->proto() == TaggedProto(&cx->global()->getFunctionPrototype())) {
150 cx->global()->setBoundFunctionShapeWithDefaultProto(shape);
152 return shape;
155 static MOZ_ALWAYS_INLINE bool ComputeLengthValue(
156 JSContext* cx, Handle<BoundFunctionObject*> bound, Handle<JSObject*> target,
157 size_t numBoundArgs, double* length) {
158 *length = 0.0;
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>(),
165 &targetLength)) {
166 return false;
169 if (size_t(targetLength) > numBoundArgs) {
170 *length = size_t(targetLength) - numBoundArgs;
172 return true;
175 // Use a fast path for getting the .length value if the target is a bound
176 // function with its initial shape.
177 Value targetLength;
178 if (target->is<BoundFunctionObject>() && target->shape() == bound->shape()) {
179 BoundFunctionObject* targetFn = &target->as<BoundFunctionObject>();
180 targetLength = targetFn->getLengthForInitialShape();
181 } else {
182 bool hasLength;
183 Rooted<PropertyKey> key(cx, NameToId(cx->names().length));
184 if (!HasOwnProperty(cx, target, key, &hasLength)) {
185 return false;
188 if (!hasLength) {
189 return true;
192 Rooted<Value> targetLengthRoot(cx);
193 if (!GetProperty(cx, target, target, key, &targetLengthRoot)) {
194 return false;
196 targetLength = targetLengthRoot;
199 if (targetLength.isNumber()) {
200 *length = std::max(
201 0.0, JS::ToInteger(targetLength.toNumber()) - double(numBoundArgs));
203 return true;
206 static MOZ_ALWAYS_INLINE JSAtom* AppendBoundFunctionPrefix(JSContext* cx,
207 JSString* str) {
208 auto& cache = cx->zone()->boundPrefixCache();
210 JSAtom* strAtom = str->isAtom() ? &str->asAtom() : nullptr;
211 if (strAtom) {
212 if (auto p = cache.lookup(strAtom)) {
213 return p->value();
217 StringBuffer sb(cx);
218 if (!sb.append("bound ") || !sb.append(str)) {
219 return nullptr;
221 JSAtom* atom = sb.finishAtom();
222 if (!atom) {
223 return nullptr;
226 if (strAtom) {
227 (void)cache.putNew(strAtom, atom);
229 return 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);
240 if (!name) {
241 return nullptr;
243 } else {
244 // Use a fast path for getting the .name value if the target is a bound
245 // function with its initial shape.
246 Value targetName;
247 if (target->is<BoundFunctionObject>() &&
248 target->shape() == bound->shape()) {
249 BoundFunctionObject* targetFn = &target->as<BoundFunctionObject>();
250 targetName = targetFn->getNameForInitialShape();
251 } else {
252 Rooted<Value> targetNameRoot(cx);
253 if (!GetProperty(cx, target, target, cx->names().name, &targetNameRoot)) {
254 return nullptr;
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
269 // static
270 bool BoundFunctionObject::functionBind(JSContext* cx, unsigned argc,
271 Value* vp) {
272 CallArgs args = CallArgsFromVp(argc, vp);
274 // Steps 1-2.
275 if (!IsCallable(args.thisv())) {
276 ReportIncompatibleMethod(cx, args, &FunctionClass);
277 return false;
280 if (MOZ_UNLIKELY(args.length() > ARGS_LENGTH_MAX)) {
281 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
282 JSMSG_TOO_MANY_ARGUMENTS);
283 return false;
286 Rooted<JSObject*> target(cx, &args.thisv().toObject());
288 BoundFunctionObject* bound =
289 functionBindImpl(cx, target, args.array(), args.length(), nullptr);
290 if (!bound) {
291 return false;
294 // Step 11.
295 args.rval().setObject(*bound);
296 return true;
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.
308 // static
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
315 // from JIT code.
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
326 // Steps 1-5.
327 Rooted<BoundFunctionObject*> bound(cx);
328 if (maybeBound) {
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
331 // proto here.
332 bound = maybeBound;
333 if (MOZ_UNLIKELY(bound->staticPrototype() != target->staticPrototype())) {
334 Rooted<JSObject*> proto(cx, target->staticPrototype());
335 if (!SetPrototype(cx, bound, proto)) {
336 return nullptr;
339 } else {
340 // Step 1.
341 Rooted<JSObject*> proto(cx);
342 if (!GetPrototype(cx, target, &proto)) {
343 return nullptr;
346 // Steps 2-5.
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);
353 if (!bound) {
354 return nullptr;
356 } else {
357 bound = NewObjectWithGivenProto<BoundFunctionObject>(cx, proto);
358 if (!bound) {
359 return nullptr;
361 if (!SharedShape::ensureInitialCustomShape<BoundFunctionObject>(cx,
362 bound)) {
363 return nullptr;
368 MOZ_ASSERT(bound->lookupPure(cx->names().length)->slot() == LengthSlot);
369 MOZ_ASSERT(bound->lookupPure(cx->names().name)->slot() == NameSlot);
371 // Steps 6 and 9.
372 bound->initFlags(numBoundArgs, target->isConstructor());
374 // Step 7.
375 bound->initReservedSlot(TargetSlot, ObjectValue(*target));
377 // Step 8.
378 if (argc > 0) {
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]);
386 } else {
387 ArrayObject* arr = NewDenseCopiedArray(cx, numBoundArgs, args + 1);
388 if (!arr) {
389 return nullptr;
391 bound->initReservedSlot(BoundArg0Slot, ObjectValue(*arr));
394 // ES2023 20.2.3.2 Function.prototype.bind
395 // Step 4.
396 double length = 0.0;
398 // Steps 5-6.
399 if (!ComputeLengthValue(cx, bound, target, numBoundArgs, &length)) {
400 return nullptr;
403 // Step 7.
404 bound->initLength(length);
406 // Steps 8-9.
407 JSAtom* name = ComputeNameValue(cx, bound, target);
408 if (!name) {
409 return nullptr;
412 // Step 10.
413 bound->initName(name);
415 // Step 11.
416 return bound;
419 // static
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);
425 if (!bound) {
426 return nullptr;
428 bound->initFlags(templateObj->numBoundArgs(), templateObj->isConstructor());
429 bound->initLength(templateObj->getLengthForInitialShape().toInt32());
430 bound->initName(&templateObj->getNameForInitialShape().toString()->asAtom());
431 return bound;
434 // static
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);
450 if (!bound) {
451 return nullptr;
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));
458 if (argc > 0) {
459 bound->initReservedSlot(BoundThisSlot, args[0]);
461 for (size_t i = 0; i < numBoundArgs; i++) {
462 bound->initReservedSlot(BoundArg0Slot + i, args[i + 1]);
464 return bound;
467 // static
468 BoundFunctionObject* BoundFunctionObject::createTemplateObject(JSContext* cx) {
469 Rooted<JSObject*> proto(cx, &cx->global()->getFunctionPrototype());
470 Rooted<BoundFunctionObject*> bound(
471 cx, NewTenuredObjectWithGivenProto<BoundFunctionObject>(cx, proto));
472 if (!bound) {
473 return nullptr;
475 if (!SharedShape::ensureInitialCustomShape<BoundFunctionObject>(cx, bound)) {
476 return nullptr;
478 return bound;
481 bool BoundFunctionObject::initTemplateSlotsForSpecializedBind(
482 JSContext* cx, uint32_t numBoundArgs, bool targetIsConstructor,
483 uint32_t targetLength, JSAtom* targetName) {
484 size_t len = 0;
485 if (targetLength > numBoundArgs) {
486 len = targetLength - numBoundArgs;
489 JSAtom* name = AppendBoundFunctionPrefix(cx, targetName);
490 if (!name) {
491 return false;
494 initFlags(numBoundArgs, targetIsConstructor);
495 initLength(len);
496 initName(name);
497 return true;
500 static const JSClassOps classOps = {
501 nullptr, // addProperty
502 nullptr, // delProperty
503 nullptr, // enumerate
504 nullptr, // newEnumerate
505 nullptr, // resolve
506 nullptr, // mayResolve
507 nullptr, // finalize
508 BoundFunctionObject::call, // call
509 BoundFunctionObject::construct, // construct
510 nullptr, // trace
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),
532 &classOps,
533 JS_NULL_CLASS_SPEC,
534 JS_NULL_CLASS_EXT,
535 &objOps,