tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BoundFunctionObject.cpp (17108B)


      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/. */
      6 
      7 #include "vm/BoundFunctionObject.h"
      8 
      9 #include <string_view>
     10 
     11 #include "util/StringBuilder.h"
     12 #include "vm/Interpreter.h"
     13 #include "vm/Shape.h"
     14 #include "vm/Stack.h"
     15 
     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"
     21 
     22 using namespace js;
     23 
     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());
     32 
     33  if (numBoundArgs <= BoundFunctionObject::MaxInlineBoundArgs) {
     34    for (size_t i = 0; i < numBoundArgs; i++) {
     35      args[i].set(bound->getInlineBoundArg(i));
     36    }
     37  } else {
     38    ArrayObject* boundArgs = bound->getBoundArgsArray();
     39    for (size_t i = 0; i < numBoundArgs; i++) {
     40      args[i].set(boundArgs->getDenseElement(i));
     41    }
     42  }
     43 
     44  for (size_t i = 0; i < callArgs.length(); i++) {
     45    args[numBoundArgs + i].set(callArgs[i]);
     46  }
     47 }
     48 
     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>());
     56 
     57  // Step 1.
     58  Rooted<Value> target(cx, bound->getTargetVal());
     59 
     60  // Step 2.
     61  Rooted<Value> boundThis(cx, bound->getBoundThis());
     62 
     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;
     68  }
     69  FillArguments(args2, bound, numBoundArgs, args);
     70 
     71  // Step 5.
     72  return Call(cx, target, boundThis, args2, args.rval());
     73 }
     74 
     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>());
     82 
     83  MOZ_ASSERT(bound->isConstructor(),
     84             "shouldn't have called this hook if not a constructor");
     85 
     86  // Step 1.
     87  Rooted<Value> target(cx, bound->getTargetVal());
     88 
     89  // Step 2.
     90  MOZ_ASSERT(IsConstructor(target));
     91 
     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;
     97  }
     98  FillArguments(args2, bound, numBoundArgs, args);
     99 
    100  // Step 5.
    101  Rooted<Value> newTarget(cx, args.newTarget());
    102  if (newTarget == ObjectValue(*bound)) {
    103    newTarget = target;
    104  }
    105 
    106  // Step 6.
    107  Rooted<JSObject*> res(cx);
    108  if (!Construct(cx, target, args2, newTarget, &res)) {
    109    return false;
    110  }
    111  args.rval().setObject(*res);
    112  return true;
    113 }
    114 
    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.
    119 
    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);
    126  }
    127 
    128  static constexpr std::string_view nativeCode =
    129      "function() {\n    [native code]\n}";
    130  return NewStringCopy<CanGC>(cx, nativeCode);
    131 }
    132 
    133 // static
    134 SharedShape* BoundFunctionObject::assignInitialShape(
    135    JSContext* cx, Handle<BoundFunctionObject*> obj) {
    136  MOZ_ASSERT(obj->empty());
    137 
    138  constexpr PropertyFlags propFlags = {PropertyFlag::Configurable};
    139  if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().length,
    140                                               LengthSlot, propFlags)) {
    141    return nullptr;
    142  }
    143  if (!NativeObject::addPropertyInReservedSlot(cx, obj, cx->names().name,
    144                                               NameSlot, propFlags)) {
    145    return nullptr;
    146  }
    147 
    148  SharedShape* shape = obj->sharedShape();
    149  if (shape->proto() == TaggedProto(&cx->global()->getFunctionPrototype())) {
    150    cx->global()->setBoundFunctionShapeWithDefaultProto(shape);
    151  }
    152  return shape;
    153 }
    154 
    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;
    159 
    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;
    167    }
    168 
    169    if (size_t(targetLength) > numBoundArgs) {
    170      *length = size_t(targetLength) - numBoundArgs;
    171    }
    172    return true;
    173  }
    174 
    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;
    186    }
    187 
    188    if (!hasLength) {
    189      return true;
    190    }
    191 
    192    Rooted<Value> targetLengthRoot(cx);
    193    if (!GetProperty(cx, target, target, key, &targetLengthRoot)) {
    194      return false;
    195    }
    196    targetLength = targetLengthRoot;
    197  }
    198 
    199  if (targetLength.isNumber()) {
    200    *length = std::max(
    201        0.0, JS::ToInteger(targetLength.toNumber()) - double(numBoundArgs));
    202  }
    203  return true;
    204 }
    205 
    206 static MOZ_ALWAYS_INLINE JSAtom* AppendBoundFunctionPrefix(JSContext* cx,
    207                                                           JSString* str) {
    208  auto& cache = cx->zone()->boundPrefixCache();
    209 
    210  JSAtom* strAtom = str->isAtom() ? &str->asAtom() : nullptr;
    211  if (strAtom) {
    212    if (auto p = cache.lookup(strAtom)) {
    213      return p->value();
    214    }
    215  }
    216 
    217  StringBuilder sb(cx);
    218  if (!sb.append("bound ") || !sb.append(str)) {
    219    return nullptr;
    220  }
    221  JSAtom* atom = sb.finishAtom();
    222  if (!atom) {
    223    return nullptr;
    224  }
    225 
    226  if (strAtom) {
    227    (void)cache.putNew(strAtom, atom);
    228  }
    229  return atom;
    230 }
    231 
    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;
    242    }
    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;
    255      }
    256      targetName = targetNameRoot;
    257    }
    258    if (!targetName.isString()) {
    259      return cx->names().boundWithSpace_;
    260    }
    261    name = targetName.toString();
    262  }
    263 
    264  return AppendBoundFunctionPrefix(cx, name);
    265 }
    266 
    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);
    273 
    274  // Steps 1-2.
    275  if (!IsCallable(args.thisv())) {
    276    ReportIncompatibleMethod(cx, args, &FunctionClass);
    277    return false;
    278  }
    279 
    280  if (MOZ_UNLIKELY(args.length() > ARGS_LENGTH_MAX)) {
    281    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    282                              JSMSG_TOO_MANY_ARGUMENTS);
    283    return false;
    284  }
    285 
    286  Rooted<JSObject*> target(cx, &args.thisv().toObject());
    287 
    288  BoundFunctionObject* bound =
    289      functionBindImpl(cx, target, args.array(), args.length(), nullptr);
    290  if (!bound) {
    291    return false;
    292  }
    293 
    294  // Step 11.
    295  args.rval().setObject(*bound);
    296  return true;
    297 }
    298 
    299 // ES2023 20.2.3.2 Function.prototype.bind
    300 // https://tc39.es/ecma262/#sec-function.prototype.bind
    301 //
    302 // ES2023 10.4.1.3 BoundFunctionCreate
    303 // https://tc39.es/ecma262/#sec-boundfunctioncreate
    304 //
    305 // BoundFunctionCreate has been inlined in Function.prototype.bind for
    306 // performance reasons.
    307 //
    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());
    313 
    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);
    317 
    318  size_t numBoundArgs = argc > 0 ? argc - 1 : 0;
    319  MOZ_ASSERT(numBoundArgs <= ARGS_LENGTH_MAX, "ensured by callers");
    320 
    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);
    324  static_assert(gc::GetFinalizeKind(allocKind) == gc::FinalizeKind::None);
    325 
    326  // ES2023 10.4.1.3 BoundFunctionCreate
    327  // Steps 1-5.
    328  Rooted<BoundFunctionObject*> bound(cx);
    329  if (maybeBound) {
    330    // We allocated a bound function in JIT code. In the uncommon case of the
    331    // target not having Function.prototype as proto, we have to set the right
    332    // proto here.
    333    bound = maybeBound;
    334    if (MOZ_UNLIKELY(bound->staticPrototype() != target->staticPrototype())) {
    335      Rooted<JSObject*> proto(cx, target->staticPrototype());
    336      if (!SetPrototype(cx, bound, proto)) {
    337        return nullptr;
    338      }
    339    }
    340  } else {
    341    // Step 1.
    342    Rooted<JSObject*> proto(cx);
    343    if (!GetPrototype(cx, target, &proto)) {
    344      return nullptr;
    345    }
    346 
    347    // Steps 2-5.
    348    if (proto == &cx->global()->getFunctionPrototype() &&
    349        cx->global()->maybeBoundFunctionShapeWithDefaultProto()) {
    350      Rooted<SharedShape*> shape(
    351          cx, cx->global()->maybeBoundFunctionShapeWithDefaultProto());
    352      bound = NativeObject::create<BoundFunctionObject>(
    353          cx, allocKind, gc::Heap::Default, shape);
    354      if (!bound) {
    355        return nullptr;
    356      }
    357    } else {
    358      bound = NewObjectWithGivenProto<BoundFunctionObject>(cx, proto);
    359      if (!bound) {
    360        return nullptr;
    361      }
    362      if (!SharedShape::ensureInitialCustomShape<BoundFunctionObject>(cx,
    363                                                                      bound)) {
    364        return nullptr;
    365      }
    366    }
    367  }
    368 
    369  MOZ_ASSERT(bound->lookupPure(cx->names().length)->slot() == LengthSlot);
    370  MOZ_ASSERT(bound->lookupPure(cx->names().name)->slot() == NameSlot);
    371 
    372  // Steps 6 and 9.
    373  bound->initFlags(numBoundArgs, target->isConstructor());
    374 
    375  // Step 7.
    376  bound->initReservedSlot(TargetSlot, ObjectValue(*target));
    377 
    378  // Step 8.
    379  if (argc > 0) {
    380    bound->initReservedSlot(BoundThisSlot, args[0]);
    381  }
    382 
    383  if (numBoundArgs <= MaxInlineBoundArgs) {
    384    for (size_t i = 0; i < numBoundArgs; i++) {
    385      bound->initReservedSlot(BoundArg0Slot + i, args[i + 1]);
    386    }
    387  } else {
    388    ArrayObject* arr = NewDenseCopiedArray(cx, numBoundArgs, args + 1);
    389    if (!arr) {
    390      return nullptr;
    391    }
    392    bound->initReservedSlot(BoundArg0Slot, ObjectValue(*arr));
    393  }
    394 
    395  // ES2023 20.2.3.2 Function.prototype.bind
    396  // Step 4.
    397  double length = 0.0;
    398 
    399  // Steps 5-6.
    400  if (!ComputeLengthValue(cx, bound, target, numBoundArgs, &length)) {
    401    return nullptr;
    402  }
    403 
    404  // Step 7.
    405  bound->initLength(length);
    406 
    407  // Steps 8-9.
    408  JSAtom* name = ComputeNameValue(cx, bound, target);
    409  if (!name) {
    410    return nullptr;
    411  }
    412 
    413  // Step 10.
    414  bound->initName(name);
    415 
    416  // Step 11.
    417  return bound;
    418 }
    419 
    420 // static
    421 BoundFunctionObject* BoundFunctionObject::createWithTemplate(
    422    JSContext* cx, Handle<BoundFunctionObject*> templateObj) {
    423  Rooted<SharedShape*> shape(cx, templateObj->sharedShape());
    424  auto* bound = NativeObject::create<BoundFunctionObject>(
    425      cx, allocKind, gc::Heap::Default, shape);
    426  if (!bound) {
    427    return nullptr;
    428  }
    429  bound->initFlags(templateObj->numBoundArgs(), templateObj->isConstructor());
    430  bound->initLength(templateObj->getLengthForInitialShape().toInt32());
    431  bound->initName(&templateObj->getNameForInitialShape().toString()->asAtom());
    432  return bound;
    433 }
    434 
    435 // static
    436 BoundFunctionObject* BoundFunctionObject::functionBindSpecializedBaseline(
    437    JSContext* cx, Handle<JSObject*> target, Value* args, uint32_t argc,
    438    Handle<BoundFunctionObject*> templateObj) {
    439  // Root the Values on the stack.
    440  RootedExternalValueArray argsRoot(cx, argc, args);
    441 
    442  MOZ_ASSERT(target->is<JSFunction>() || target->is<BoundFunctionObject>());
    443  MOZ_ASSERT(target->isCallable());
    444  MOZ_ASSERT(target->isConstructor() == templateObj->isConstructor());
    445  MOZ_ASSERT(target->staticPrototype() == templateObj->staticPrototype());
    446 
    447  size_t numBoundArgs = argc > 0 ? argc - 1 : 0;
    448  MOZ_ASSERT(numBoundArgs <= MaxInlineBoundArgs);
    449 
    450  BoundFunctionObject* bound = createWithTemplate(cx, templateObj);
    451  if (!bound) {
    452    return nullptr;
    453  }
    454 
    455  MOZ_ASSERT(bound->lookupPure(cx->names().length)->slot() == LengthSlot);
    456  MOZ_ASSERT(bound->lookupPure(cx->names().name)->slot() == NameSlot);
    457 
    458  bound->initReservedSlot(TargetSlot, ObjectValue(*target));
    459  if (argc > 0) {
    460    bound->initReservedSlot(BoundThisSlot, args[0]);
    461  }
    462  for (size_t i = 0; i < numBoundArgs; i++) {
    463    bound->initReservedSlot(BoundArg0Slot + i, args[i + 1]);
    464  }
    465  return bound;
    466 }
    467 
    468 // static
    469 BoundFunctionObject* BoundFunctionObject::createTemplateObject(JSContext* cx) {
    470  Rooted<JSObject*> proto(cx, &cx->global()->getFunctionPrototype());
    471  Rooted<BoundFunctionObject*> bound(
    472      cx, NewTenuredObjectWithGivenProto<BoundFunctionObject>(cx, proto));
    473  if (!bound) {
    474    return nullptr;
    475  }
    476  if (!SharedShape::ensureInitialCustomShape<BoundFunctionObject>(cx, bound)) {
    477    return nullptr;
    478  }
    479  return bound;
    480 }
    481 
    482 bool BoundFunctionObject::initTemplateSlotsForSpecializedBind(
    483    JSContext* cx, uint32_t numBoundArgs, bool targetIsConstructor,
    484    uint32_t targetLength, JSAtom* targetName) {
    485  size_t len = 0;
    486  if (targetLength > numBoundArgs) {
    487    len = targetLength - numBoundArgs;
    488  }
    489 
    490  JSAtom* name = AppendBoundFunctionPrefix(cx, targetName);
    491  if (!name) {
    492    return false;
    493  }
    494 
    495  initFlags(numBoundArgs, targetIsConstructor);
    496  initLength(len);
    497  initName(name);
    498  return true;
    499 }
    500 
    501 static const JSClassOps classOps = {
    502    nullptr,                         // addProperty
    503    nullptr,                         // delProperty
    504    nullptr,                         // enumerate
    505    nullptr,                         // newEnumerate
    506    nullptr,                         // resolve
    507    nullptr,                         // mayResolve
    508    nullptr,                         // finalize
    509    BoundFunctionObject::call,       // call
    510    BoundFunctionObject::construct,  // construct
    511    nullptr,                         // trace
    512 };
    513 
    514 static const ObjectOps objOps = {
    515    nullptr,                           // lookupProperty
    516    nullptr,                           // qdefineProperty
    517    nullptr,                           // hasProperty
    518    nullptr,                           // getProperty
    519    nullptr,                           // setProperty
    520    nullptr,                           // getOwnPropertyDescriptor
    521    nullptr,                           // deleteProperty
    522    nullptr,                           // getElements
    523    BoundFunctionObject::funToString,  // funToString
    524 };
    525 
    526 const JSClass BoundFunctionObject::class_ = {
    527    "BoundFunctionObject",
    528    // Note: bound functions don't have their own constructor or prototype (they
    529    // use the prototype of the target object), but we give them a JSProtoKey
    530    // because that's what Xray wrappers use to identify builtin objects.
    531    JSCLASS_HAS_CACHED_PROTO(JSProto_BoundFunction) |
    532        JSCLASS_HAS_RESERVED_SLOTS(BoundFunctionObject::SlotCount),
    533    &classOps,
    534    JS_NULL_CLASS_SPEC,
    535    JS_NULL_CLASS_EXT,
    536    &objOps,
    537 };