tor-browser

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

Compartment-inl.h (12089B)


      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 #ifndef vm_Compartment_inl_h
      8 #define vm_Compartment_inl_h
      9 
     10 #include "vm/Compartment.h"
     11 
     12 #include "jsapi.h"
     13 #include "jsfriendapi.h"
     14 #include "jsnum.h"
     15 #include "js/CallArgs.h"
     16 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     17 #include "js/Wrapper.h"
     18 #include "vm/Iteration.h"
     19 #include "vm/JSObject.h"
     20 
     21 #include "vm/JSContext-inl.h"
     22 
     23 struct JSClass;
     24 
     25 inline js::StringWrapperMap::Ptr JS::Compartment::lookupWrapper(
     26    JSString* str) const {
     27  return zone()->crossZoneStringWrappers().lookup(str);
     28 }
     29 
     30 inline bool JS::Compartment::wrap(JSContext* cx, JS::MutableHandleValue vp) {
     31  /* Only GC things have to be wrapped or copied. */
     32  if (!vp.isGCThing()) {
     33    return true;
     34  }
     35 
     36  /*
     37   * Symbols are GC things, but never need to be wrapped or copied because
     38   * they are always allocated in the atoms zone. They still need to be
     39   * marked in the new compartment's zone, however.
     40   */
     41  if (vp.isSymbol()) {
     42    cx->markAtomValue(vp);
     43    return true;
     44  }
     45 
     46  /* Handle strings. */
     47  if (vp.isString()) {
     48    JS::RootedString str(cx, vp.toString());
     49    if (!wrap(cx, &str)) {
     50      return false;
     51    }
     52    vp.setString(str);
     53    return true;
     54  }
     55 
     56  if (vp.isBigInt()) {
     57    JS::RootedBigInt bi(cx, vp.toBigInt());
     58    if (!wrap(cx, &bi)) {
     59      return false;
     60    }
     61    vp.setBigInt(bi);
     62    return true;
     63  }
     64 
     65  MOZ_ASSERT(vp.isObject());
     66 
     67  /*
     68   * All that's left are objects.
     69   *
     70   * Object wrapping isn't the fastest thing in the world, in part because
     71   * we have to unwrap and invoke the prewrap hook to find the identity
     72   * object before we even start checking the cache. Neither of these
     73   * operations are needed in the common case, where we're just wrapping
     74   * a plain JS object from the wrappee's side of the membrane to the
     75   * wrapper's side.
     76   *
     77   * To optimize this, we note that the cache should only ever contain
     78   * identity objects - that is to say, objects that serve as the
     79   * canonical representation for a unique object identity observable by
     80   * script. Unwrap and prewrap are both steps that we take to get to the
     81   * identity of an incoming objects, and as such, they shuld never map
     82   * one identity object to another object. This means that we can safely
     83   * check the cache immediately, and only risk false negatives. Do this
     84   * in opt builds, and do both in debug builds so that we can assert
     85   * that we get the same answer.
     86   */
     87 #ifdef DEBUG
     88  JS::AssertValueIsNotGray(vp);
     89  JS::RootedObject cacheResult(cx);
     90 #endif
     91  if (js::ObjectWrapperMap::Ptr p = lookupWrapper(&vp.toObject())) {
     92 #ifdef DEBUG
     93    cacheResult = p->value().get();
     94 #else
     95    vp.setObject(*p->value().get());
     96    return true;
     97 #endif
     98  }
     99 
    100  JS::RootedObject obj(cx, &vp.toObject());
    101  if (!wrap(cx, &obj)) {
    102    return false;
    103  }
    104  vp.setObject(*obj);
    105  MOZ_ASSERT_IF(cacheResult, obj == cacheResult);
    106  return true;
    107 }
    108 
    109 inline bool JS::Compartment::wrap(JSContext* cx,
    110                                  MutableHandle<mozilla::Maybe<Value>> vp) {
    111  if (vp.get().isNothing()) {
    112    return true;
    113  }
    114 
    115  return wrap(cx, MutableHandle<Value>::fromMarkedLocation(vp.get().ptr()));
    116 }
    117 
    118 namespace js {
    119 namespace detail {
    120 
    121 /**
    122 * Return the name of class T as a static null-terminated ASCII string constant
    123 * (for error messages).
    124 */
    125 template <class T>
    126 const char* ClassName() {
    127  return T::class_.name;
    128 }
    129 
    130 template <class T, class ErrorCallback>
    131 [[nodiscard]] T* UnwrapAndTypeCheckValueSlowPath(JSContext* cx,
    132                                                 HandleValue value,
    133                                                 ErrorCallback throwTypeError) {
    134  JSObject* obj = nullptr;
    135  if (value.isObject()) {
    136    obj = &value.toObject();
    137    if (IsWrapper(obj)) {
    138      obj = CheckedUnwrapStatic(obj);
    139      if (!obj) {
    140        ReportAccessDenied(cx);
    141        return nullptr;
    142      }
    143    }
    144  }
    145 
    146  if (!obj || !obj->is<T>()) {
    147    throwTypeError();
    148    return nullptr;
    149  }
    150 
    151  return &obj->as<T>();
    152 }
    153 
    154 template <class ErrorCallback>
    155 [[nodiscard]] JSObject* UnwrapAndTypeCheckValueSlowPath(
    156    JSContext* cx, HandleValue value, const JSClass* clasp,
    157    ErrorCallback throwTypeError) {
    158  JSObject* obj = nullptr;
    159  if (value.isObject()) {
    160    obj = &value.toObject();
    161    if (IsWrapper(obj)) {
    162      obj = CheckedUnwrapStatic(obj);
    163      if (!obj) {
    164        ReportAccessDenied(cx);
    165        return nullptr;
    166      }
    167    }
    168  }
    169 
    170  if (!obj || !obj->hasClass(clasp)) {
    171    throwTypeError();
    172    return nullptr;
    173  }
    174 
    175  return obj;
    176 }
    177 
    178 }  // namespace detail
    179 
    180 /**
    181 * Remove all wrappers from `val` and try to downcast the result to class `T`.
    182 *
    183 * DANGER: The result may not be same-compartment with `cx`.
    184 *
    185 * This calls `throwTypeError` if the value isn't an object, cannot be
    186 * unwrapped, or isn't an instance of the expected type. `throwTypeError` must
    187 * in fact throw a TypeError (or OOM trying).
    188 */
    189 template <class T, class ErrorCallback>
    190 [[nodiscard]] inline T* UnwrapAndTypeCheckValue(JSContext* cx,
    191                                                HandleValue value,
    192                                                ErrorCallback throwTypeError) {
    193  cx->check(value);
    194 
    195  static_assert(!std::is_convertible_v<T*, Wrapper*>,
    196                "T can't be a Wrapper type; this function discards wrappers");
    197 
    198  if (value.isObject() && value.toObject().is<T>()) {
    199    return &value.toObject().as<T>();
    200  }
    201 
    202  return detail::UnwrapAndTypeCheckValueSlowPath<T>(cx, value, throwTypeError);
    203 }
    204 
    205 /**
    206 * Remove all wrappers from |val| and try to downcast the result to an object of
    207 * the class |clasp|.
    208 *
    209 * DANGER: The result may not be same-compartment with |cx|.
    210 *
    211 * This calls |throwTypeError| if the value isn't an object, cannot be
    212 * unwrapped, or isn't an instance of the expected type.  |throwTypeError| must
    213 * in fact throw a TypeError (or OOM trying).
    214 */
    215 template <class ErrorCallback>
    216 [[nodiscard]] inline JSObject* UnwrapAndTypeCheckValue(
    217    JSContext* cx, HandleValue value, const JSClass* clasp,
    218    ErrorCallback throwTypeError) {
    219  cx->check(value);
    220 
    221  if (value.isObject() && value.toObject().hasClass(clasp)) {
    222    return &value.toObject();
    223  }
    224 
    225  return detail::UnwrapAndTypeCheckValueSlowPath(cx, value, clasp,
    226                                                 throwTypeError);
    227 }
    228 
    229 /**
    230 * Remove all wrappers from `args.thisv()` and try to downcast the result to
    231 * class `T`.
    232 *
    233 * DANGER: The result may not be same-compartment with `cx`.
    234 *
    235 * This throws a TypeError if the value isn't an object, cannot be unwrapped,
    236 * or isn't an instance of the expected type.
    237 */
    238 template <class T>
    239 [[nodiscard]] inline T* UnwrapAndTypeCheckThis(JSContext* cx,
    240                                               const CallArgs& args,
    241                                               const char* methodName) {
    242  HandleValue thisv = args.thisv();
    243  return UnwrapAndTypeCheckValue<T>(cx, thisv, [cx, methodName, thisv] {
    244    JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
    245                               JSMSG_INCOMPATIBLE_PROTO, detail::ClassName<T>(),
    246                               methodName, InformalValueTypeName(thisv));
    247  });
    248 }
    249 
    250 /**
    251 * Remove all wrappers from `args[argIndex]` and try to downcast the result to
    252 * class `T`.
    253 *
    254 * DANGER: The result may not be same-compartment with `cx`.
    255 *
    256 * This throws a TypeError if the specified argument is missing, isn't an
    257 * object, cannot be unwrapped, or isn't an instance of the expected type.
    258 */
    259 template <class T>
    260 [[nodiscard]] inline T* UnwrapAndTypeCheckArgument(JSContext* cx,
    261                                                   CallArgs& args,
    262                                                   const char* methodName,
    263                                                   int argIndex) {
    264  HandleValue val = args.get(argIndex);
    265  return UnwrapAndTypeCheckValue<T>(cx, val, [cx, val, methodName, argIndex] {
    266    Int32ToCStringBuf cbuf;
    267    char* numStr = Int32ToCString(&cbuf, argIndex + 1);
    268    MOZ_ASSERT(numStr);
    269    JS_ReportErrorNumberLatin1(
    270        cx, GetErrorMessage, nullptr, JSMSG_WRONG_TYPE_ARG, numStr, methodName,
    271        detail::ClassName<T>(), InformalValueTypeName(val));
    272  });
    273 }
    274 
    275 /**
    276 * Unwrap an object of a known type.
    277 *
    278 * If `obj` is an object of class T, this returns a pointer to that object. If
    279 * `obj` is a wrapper for such an object, this tries to unwrap the object and
    280 * return a pointer to it. If access is denied, or `obj` was a wrapper but has
    281 * been nuked, this reports an error and returns null.
    282 *
    283 * In all other cases, the behavior is undefined, so call this only if `obj` is
    284 * known to have been an object of class T, or a wrapper to a T, at some point.
    285 */
    286 template <class T>
    287 [[nodiscard]] inline T* UnwrapAndDowncastObject(JSContext* cx, JSObject* obj) {
    288  static_assert(!std::is_convertible_v<T*, Wrapper*>,
    289                "T can't be a Wrapper type; this function discards wrappers");
    290 
    291  if (IsProxy(obj)) {
    292    if (JS_IsDeadWrapper(obj)) {
    293      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    294                                JSMSG_DEAD_OBJECT);
    295      return nullptr;
    296    }
    297 
    298    // It would probably be OK to do an unchecked unwrap here, but we allow
    299    // arbitrary security policies, so check anyway.
    300    obj = obj->maybeUnwrapAs<T>();
    301    if (!obj) {
    302      ReportAccessDenied(cx);
    303      return nullptr;
    304    }
    305  }
    306 
    307  return &obj->as<T>();
    308 }
    309 
    310 /**
    311 * Unwrap an object of a known (but not compile-time-known) class.
    312 *
    313 * If |obj| is an object with class |clasp|, this returns |obj|.  If |obj| is a
    314 * wrapper for such an object, this tries to unwrap the object and return a
    315 * pointer to it.  If access is denied, or |obj| was a wrapper but has been
    316 * nuked, this reports an error and returns null.
    317 *
    318 * In all other cases, the behavior is undefined, so call this only if |obj| is
    319 * known to have had class |clasp|, or been a wrapper to such an object, at some
    320 * point.
    321 */
    322 [[nodiscard]] inline JSObject* UnwrapAndDowncastObject(JSContext* cx,
    323                                                       JSObject* obj,
    324                                                       const JSClass* clasp) {
    325  if (IsProxy(obj)) {
    326    if (JS_IsDeadWrapper(obj)) {
    327      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    328                                JSMSG_DEAD_OBJECT);
    329      return nullptr;
    330    }
    331 
    332    // It would probably be OK to do an unchecked unwrap here, but we allow
    333    // arbitrary security policies, so check anyway.
    334    obj = obj->maybeUnwrapAs(clasp);
    335    if (!obj) {
    336      ReportAccessDenied(cx);
    337      return nullptr;
    338    }
    339  }
    340 
    341  MOZ_ASSERT(obj->hasClass(clasp));
    342  return obj;
    343 }
    344 
    345 /**
    346 * Unwrap a value of a known type. See UnwrapAndDowncastObject.
    347 */
    348 template <class T>
    349 [[nodiscard]] inline T* UnwrapAndDowncastValue(JSContext* cx,
    350                                               const Value& value) {
    351  return UnwrapAndDowncastObject<T>(cx, &value.toObject());
    352 }
    353 
    354 /**
    355 * Unwrap an object of a known (but not compile-time-known) class.  See
    356 * UnwrapAndDowncastObject.
    357 */
    358 [[nodiscard]] inline JSObject* UnwrapAndDowncastValue(JSContext* cx,
    359                                                      const Value& value,
    360                                                      const JSClass* clasp) {
    361  return UnwrapAndDowncastObject(cx, &value.toObject(), clasp);
    362 }
    363 
    364 }  // namespace js
    365 
    366 MOZ_ALWAYS_INLINE bool JS::Compartment::objectMaybeInIteration(JSObject* obj) {
    367  MOZ_ASSERT(obj->compartment() == this);
    368 
    369  js::NativeIteratorListIter iter(&enumerators_);
    370 
    371  // If the list is empty, we're not iterating any objects.
    372  if (iter.done()) {
    373    return false;
    374  }
    375 
    376  // If the list contains a single object, check if it's |obj|.
    377  js::NativeIterator* next = iter.next();
    378  if (iter.done()) {
    379    return next->objectBeingIterated() == obj;
    380  }
    381 
    382  return true;
    383 }
    384 
    385 #endif /* vm_Compartment_inl_h */