tor-browser

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

WrappedFunctionObject.cpp (11697B)


      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 "builtin/WrappedFunctionObject.h"
      8 
      9 #include <string_view>
     10 
     11 #include "jsapi.h"
     12 
     13 #include "builtin/ShadowRealm.h"
     14 #include "js/CallAndConstruct.h"
     15 #include "js/Class.h"
     16 #include "js/ErrorReport.h"
     17 #include "js/Exception.h"
     18 #include "js/TypeDecls.h"
     19 #include "js/Value.h"
     20 #include "util/StringBuilder.h"
     21 #include "vm/Compartment.h"
     22 #include "vm/Interpreter.h"
     23 #include "vm/JSFunction.h"
     24 #include "vm/ObjectOperations.h"
     25 
     26 #include "vm/JSFunction-inl.h"
     27 #include "vm/JSObject-inl.h"
     28 #include "vm/Realm-inl.h"
     29 
     30 using namespace js;
     31 using namespace JS;
     32 
     33 // GetWrappedValue ( callerRealm: a Realm Record, value: unknown )
     34 bool js::GetWrappedValue(JSContext* cx, Realm* callerRealm, Handle<Value> value,
     35                         MutableHandle<Value> res) {
     36  cx->check(value);
     37 
     38  // Step 2. Return value (Reordered)
     39  if (!value.isObject()) {
     40    res.set(value);
     41    return true;
     42  }
     43 
     44  // Step 1. If Type(value) is Object, then
     45  //      a. If IsCallable(value) is false, throw a TypeError exception.
     46  Rooted<JSObject*> objectVal(cx, &value.toObject());
     47  if (!IsCallable(objectVal)) {
     48    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
     49                              JSMSG_SHADOW_REALM_INVALID_RETURN);
     50    return false;
     51  }
     52 
     53  //     b. Return ? WrappedFunctionCreate(callerRealm, value).
     54  return WrappedFunctionCreate(cx, callerRealm, objectVal, res);
     55 }
     56 
     57 // [[Call]]
     58 // https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist
     59 // https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call
     60 static bool WrappedFunction_Call(JSContext* cx, unsigned argc, Value* vp) {
     61  CallArgs args = CallArgsFromVp(argc, vp);
     62 
     63  Rooted<JSObject*> callee(cx, &args.callee());
     64  MOZ_ASSERT(callee->is<WrappedFunctionObject>());
     65 
     66  Handle<WrappedFunctionObject*> fun = callee.as<WrappedFunctionObject>();
     67 
     68  // PrepareForWrappedFunctionCall is a no-op in our implementation, because
     69  // we've already entered the correct realm.
     70  MOZ_ASSERT(cx->realm() == fun->realm());
     71 
     72  // The next steps refer to the OrdinaryWrappedFunctionCall operation.
     73 
     74  // 1. Let target be F.[[WrappedTargetFunction]].
     75  Rooted<JSObject*> target(cx, fun->getTargetFunction());
     76 
     77  // 2. Assert: IsCallable(target) is true.
     78  MOZ_ASSERT(IsCallable(ObjectValue(*target)));
     79 
     80  // 3. Let callerRealm be F.[[Realm]].
     81  Rooted<Realm*> callerRealm(cx, fun->realm());
     82 
     83  // 4. NOTE: Any exception objects produced after this point are associated
     84  //    with callerRealm.
     85  //
     86  // Implicit in our implementation, because |callerRealm| is already the
     87  // current realm.
     88 
     89  // 5. Let targetRealm be ? GetFunctionRealm(target).
     90  Rooted<Realm*> targetRealm(cx, GetFunctionRealm(cx, target));
     91  if (!targetRealm) {
     92    return false;
     93  }
     94 
     95  // 6. Let wrappedArgs be a new empty List.
     96  InvokeArgs wrappedArgs(cx);
     97  if (!wrappedArgs.init(cx, args.length())) {
     98    return false;
     99  }
    100 
    101  // 7. For each element arg of argumentsList, do
    102  //     a. Let wrappedValue be ? GetWrappedValue(targetRealm, arg).
    103  //     b. Append wrappedValue to wrappedArgs.
    104  Rooted<Value> element(cx);
    105  for (size_t i = 0; i < args.length(); i++) {
    106    element = args.get(i);
    107    if (!GetWrappedValue(cx, targetRealm, element, &element)) {
    108      return false;
    109    }
    110 
    111    wrappedArgs[i].set(element);
    112  }
    113 
    114  // 8. Let wrappedThisArgument to ? GetWrappedValue(targetRealm,
    115  // thisArgument).
    116  Rooted<Value> wrappedThisArgument(cx);
    117  if (!GetWrappedValue(cx, targetRealm, args.thisv(), &wrappedThisArgument)) {
    118    return false;
    119  }
    120 
    121  // 9. Let result be the Completion Record of Call(target,
    122  //    wrappedThisArgument, wrappedArgs).
    123  Rooted<Value> targetValue(cx, ObjectValue(*target));
    124  Rooted<Value> result(cx);
    125  if (!js::Call(cx, targetValue, wrappedThisArgument, wrappedArgs, &result)) {
    126    // 11. Else (reordered);
    127    //     a. Throw a TypeError exception.
    128    ReportPotentiallyDetailedMessage(
    129        cx, JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE_DETAIL,
    130        JSMSG_SHADOW_REALM_WRAPPED_EXECUTION_FAILURE);
    131    return false;
    132  }
    133 
    134  // 10. If result.[[Type]] is normal or result.[[Type]] is return, then
    135  //     a. Return ? GetWrappedValue(callerRealm, result.[[Value]]).
    136  if (!GetWrappedValue(cx, callerRealm, result, args.rval())) {
    137    return false;
    138  }
    139 
    140  return true;
    141 }
    142 
    143 static bool CopyNameAndLength(JSContext* cx, HandleObject fun,
    144                              HandleObject target) {
    145  // 1. If argCount is undefined, then set argCount to 0 (implicit)
    146  constexpr int32_t argCount = 0;
    147 
    148  // 2. Let L be 0.
    149  double length = 0;
    150 
    151  // 3. Let targetHasLength be ? HasOwnProperty(Target, "length").
    152  //
    153  // Try to avoid invoking the resolve hook.
    154  // Also see ComputeLengthValue in BoundFunctionObject.cpp.
    155  if (target->is<JSFunction>() &&
    156      !target->as<JSFunction>().hasResolvedLength()) {
    157    uint16_t targetLen;
    158    if (!JSFunction::getUnresolvedLength(cx, target.as<JSFunction>(),
    159                                         &targetLen)) {
    160      return false;
    161    }
    162 
    163    length = std::max(0.0, double(targetLen) - argCount);
    164  } else {
    165    Rooted<jsid> lengthId(cx, NameToId(cx->names().length));
    166 
    167    bool targetHasLength;
    168    if (!HasOwnProperty(cx, target, lengthId, &targetHasLength)) {
    169      return false;
    170    }
    171 
    172    // 4. If targetHasLength is true, then
    173    if (targetHasLength) {
    174      //     a. Let targetLen be ? Get(Target, "length").
    175      Rooted<Value> targetLen(cx);
    176      if (!GetProperty(cx, target, target, lengthId, &targetLen)) {
    177        return false;
    178      }
    179 
    180      //     b. If Type(targetLen) is Number, then
    181      //         i. If targetLen is +∞𝔽, set L to +∞.
    182      //         ii. Else if targetLen is -∞𝔽, set L to 0.
    183      //         iii. Else,
    184      //             1. Let targetLenAsInt be ! ToIntegerOrInfinity(targetLen).
    185      //             2. Assert: targetLenAsInt is finite.
    186      //             3. Set L to max(targetLenAsInt - argCount, 0).
    187      if (targetLen.isNumber()) {
    188        length = std::max(0.0, JS::ToInteger(targetLen.toNumber()) - argCount);
    189      }
    190    }
    191  }
    192 
    193  // 5. Perform ! SetFunctionLength(F, L).
    194  Rooted<Value> rootedLength(cx, NumberValue(length));
    195  if (!DefineDataProperty(cx, fun, cx->names().length, rootedLength,
    196                          JSPROP_READONLY)) {
    197    return false;
    198  }
    199 
    200  // 6. Let targetName be ? Get(Target, "name").
    201  //
    202  // Try to avoid invoking the resolve hook.
    203  Rooted<Value> targetName(cx);
    204  if (target->is<JSFunction>() && !target->as<JSFunction>().hasResolvedName()) {
    205    JSFunction* targetFun = &target->as<JSFunction>();
    206    JSString* targetNameStr = targetFun->getUnresolvedName(cx);
    207    if (!targetNameStr) {
    208      return false;
    209    }
    210    targetName.setString(targetNameStr);
    211  } else {
    212    if (!GetProperty(cx, target, target, cx->names().name, &targetName)) {
    213      return false;
    214    }
    215  }
    216 
    217  // 7. If Type(targetName) is not String, set targetName to the empty String.
    218  if (!targetName.isString()) {
    219    targetName = StringValue(cx->runtime()->emptyString);
    220  }
    221 
    222  // 8. Perform ! SetFunctionName(F, targetName, prefix).
    223  return DefineDataProperty(cx, fun, cx->names().name, targetName,
    224                            JSPROP_READONLY);
    225 }
    226 
    227 static JSString* ToStringOp(JSContext* cx, JS::HandleObject obj,
    228                            bool isToSource) {
    229  // Return an unnamed native function to match the behavior of bound
    230  // functions.
    231  //
    232  // NOTE: The current value of the "name" property can be any value, it's not
    233  // necessarily a string value. It can also be an accessor property which could
    234  // lead to executing side-effects, which isn't allowed per the spec, cf.
    235  // <https://tc39.es/ecma262/#sec-function.prototype.tostring>. Even if it's a
    236  // data property with a string value, we'd still need to validate the string
    237  // can be parsed as a |PropertyName| production before using it as part of the
    238  // output.
    239  constexpr std::string_view nativeCode = "function () {\n    [native code]\n}";
    240 
    241  return NewStringCopy<CanGC>(cx, nativeCode);
    242 }
    243 
    244 static const JSClassOps classOps = {
    245    nullptr,               // addProperty
    246    nullptr,               // delProperty
    247    nullptr,               // enumerate
    248    nullptr,               // newEnumerate
    249    nullptr,               // resolve
    250    nullptr,               // mayResolve
    251    nullptr,               // finalize
    252    WrappedFunction_Call,  // call
    253    nullptr,               // construct
    254    nullptr,               // trace
    255 };
    256 
    257 static const ObjectOps objOps = {
    258    nullptr,     // lookupProperty
    259    nullptr,     // defineProperty
    260    nullptr,     // hasProperty
    261    nullptr,     // getProperty
    262    nullptr,     // setProperty
    263    nullptr,     // getOwnPropertyDescriptor
    264    nullptr,     // deleteProperty
    265    nullptr,     // getElements
    266    ToStringOp,  // funToString
    267 };
    268 
    269 const JSClass WrappedFunctionObject::class_ = {
    270    "WrappedFunctionObject",
    271    JSCLASS_HAS_CACHED_PROTO(
    272        JSProto_Function) |  // This sets the prototype to Function.prototype,
    273                             // Step 3 of WrappedFunctionCreate
    274        JSCLASS_HAS_RESERVED_SLOTS(WrappedFunctionObject::SlotCount),
    275    &classOps,
    276    JS_NULL_CLASS_SPEC,
    277    JS_NULL_CLASS_EXT,
    278    &objOps,
    279 };
    280 
    281 // WrappedFunctionCreate ( callerRealm: a Realm Record, Target: a function
    282 // object)
    283 bool js::WrappedFunctionCreate(JSContext* cx, Realm* callerRealm,
    284                               HandleObject target, MutableHandle<Value> res) {
    285  cx->check(target);
    286 
    287  WrappedFunctionObject* wrapped = nullptr;
    288  {
    289    // Ensure that the function object has the correct realm by allocating it
    290    // into that realm.
    291    Rooted<JSObject*> global(cx, callerRealm->maybeGlobal());
    292    MOZ_RELEASE_ASSERT(
    293        global, "global is null; executing in a realm that's being GC'd?");
    294    AutoRealm ar(cx, global);
    295 
    296    MOZ_ASSERT(target);
    297 
    298    // Target *could* be a function from another compartment.
    299    Rooted<JSObject*> maybeWrappedTarget(cx, target);
    300    if (!cx->compartment()->wrap(cx, &maybeWrappedTarget)) {
    301      return false;
    302    }
    303 
    304    // 1. Let internalSlotsList be the internal slots listed in Table 2, plus
    305    // [[Prototype]] and [[Extensible]].
    306    // 2. Let wrapped be ! MakeBasicObject(internalSlotsList).
    307    // 3. Set wrapped.[[Prototype]] to
    308    //    callerRealm.[[Intrinsics]].[[%Function.prototype%]].
    309    wrapped = NewBuiltinClassInstance<WrappedFunctionObject>(cx);
    310    if (!wrapped) {
    311      return false;
    312    }
    313 
    314    // 4. Set wrapped.[[Call]] as described in 2.1 (implicit in JSClass call
    315    // hook)
    316    // 5. Set wrapped.[[WrappedTargetFunction]] to Target.
    317    wrapped->setTargetFunction(*maybeWrappedTarget);
    318    // 6. Set wrapped.[[Realm]] to callerRealm. (implicitly the realm of
    319    //    wrapped, which we assured with the AutoRealm
    320 
    321    MOZ_ASSERT(wrapped->realm() == callerRealm);
    322  }
    323 
    324  // Wrap |wrapped| to the current compartment.
    325  RootedObject obj(cx, wrapped);
    326  if (!cx->compartment()->wrap(cx, &obj)) {
    327    return false;
    328  }
    329 
    330  // 7. Let result be CopyNameAndLength(wrapped, Target).
    331  if (!CopyNameAndLength(cx, obj, target)) {
    332    // 8. If result is an Abrupt Completion, throw a TypeError exception.
    333    cx->clearPendingException();
    334 
    335    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    336                              JSMSG_SHADOW_REALM_WRAP_FAILURE);
    337    return false;
    338  }
    339 
    340  // 9. Return wrapped.
    341  res.set(ObjectValue(*obj));
    342  return true;
    343 }