tor-browser

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

Compartment.h (17456B)


      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_h
      8 #define vm_Compartment_h
      9 
     10 #include "mozilla/Maybe.h"
     11 #include "mozilla/MemoryReporting.h"
     12 
     13 #include <stddef.h>
     14 #include <utility>
     15 
     16 #include "gc/NurseryAwareHashMap.h"
     17 #include "gc/ZoneAllocator.h"
     18 #include "vm/Iteration.h"
     19 #include "vm/JSObject.h"
     20 #include "vm/JSScript.h"
     21 
     22 namespace js {
     23 
     24 JSString* CopyStringPure(JSContext* cx, JSString* str);
     25 
     26 // The data structure use to storing JSObject CCWs for a given source
     27 // compartment. These are partitioned by target compartment so that we can
     28 // easily select wrappers by source and target compartment. String CCWs are
     29 // stored in a per-zone separate map.
     30 class ObjectWrapperMap {
     31  static const size_t InitialInnerMapSize = 4;
     32 
     33  using InnerMap = NurseryAwareHashMap<JSObject*, JSObject*, ZoneAllocPolicy>;
     34  using OuterMap = GCHashMap<JS::Compartment*, InnerMap,
     35                             DefaultHasher<JS::Compartment*>, ZoneAllocPolicy>;
     36 
     37  OuterMap map;
     38  Zone* zone;
     39 
     40 public:
     41  class Enum {
     42    Enum(const Enum&) = delete;
     43    void operator=(const Enum&) = delete;
     44 
     45    void goToNext() {
     46      if (outer.isNothing()) {
     47        return;
     48      }
     49      for (; !outer->empty(); outer->popFront()) {
     50        JS::Compartment* c = outer->front().key();
     51        MOZ_ASSERT(c);
     52        if (filter && !filter->match(c)) {
     53          continue;
     54        }
     55        InnerMap& m = outer->front().value();
     56        if (!m.empty()) {
     57          if (inner.isSome()) {
     58            inner.reset();
     59          }
     60          inner.emplace(m);
     61          outer->popFront();
     62          return;
     63        }
     64      }
     65    }
     66 
     67    mozilla::Maybe<OuterMap::Enum> outer;
     68    mozilla::Maybe<InnerMap::Enum> inner;
     69    const CompartmentFilter* filter;
     70 
     71   public:
     72    explicit Enum(ObjectWrapperMap& m) : filter(nullptr) {
     73      outer.emplace(m.map);
     74      goToNext();
     75    }
     76 
     77    Enum(ObjectWrapperMap& m, const CompartmentFilter& f) : filter(&f) {
     78      outer.emplace(m.map);
     79      goToNext();
     80    }
     81 
     82    Enum(ObjectWrapperMap& m, JS::Compartment* target) {
     83      // Leave the outer map as nothing and only iterate the inner map we
     84      // find here.
     85      auto p = m.map.lookup(target);
     86      if (p) {
     87        inner.emplace(p->value());
     88      }
     89    }
     90 
     91    bool empty() const {
     92      return (outer.isNothing() || outer->empty()) &&
     93             (inner.isNothing() || inner->empty());
     94    }
     95 
     96    InnerMap::Entry& front() const {
     97      MOZ_ASSERT(inner.isSome() && !inner->empty());
     98      return inner->front();
     99    }
    100 
    101    void popFront() {
    102      MOZ_ASSERT(!empty());
    103      if (!inner->empty()) {
    104        inner->popFront();
    105        if (!inner->empty()) {
    106          return;
    107        }
    108      }
    109      goToNext();
    110    }
    111 
    112    void removeFront() {
    113      MOZ_ASSERT(inner.isSome());
    114      inner->removeFront();
    115    }
    116  };
    117 
    118  class Ptr : public InnerMap::Ptr {
    119    friend class ObjectWrapperMap;
    120 
    121    InnerMap* map;
    122 
    123    Ptr() : map(nullptr) {}
    124    Ptr(const InnerMap::Ptr& p, InnerMap& m) : InnerMap::Ptr(p), map(&m) {}
    125  };
    126 
    127  // Iterator over compartments that the ObjectWrapperMap has wrappers for.
    128  class WrappedCompartmentEnum {
    129    OuterMap::Enum iter;
    130 
    131    void settle() {
    132      // It's possible for InnerMap to be empty after wrappers have been
    133      // removed, e.g. by being nuked.
    134      while (!iter.empty() && iter.front().value().empty()) {
    135        iter.popFront();
    136      }
    137    }
    138 
    139   public:
    140    explicit WrappedCompartmentEnum(ObjectWrapperMap& map) : iter(map.map) {
    141      settle();
    142    }
    143    bool empty() const { return iter.empty(); }
    144    JS::Compartment* front() const { return iter.front().key(); }
    145    operator JS::Compartment*() const { return front(); }
    146    void popFront() {
    147      iter.popFront();
    148      settle();
    149    }
    150  };
    151 
    152  explicit ObjectWrapperMap(Zone* zone) : map(zone), zone(zone) {}
    153  ObjectWrapperMap(Zone* zone, size_t aLen) : map(zone, aLen), zone(zone) {}
    154 
    155  bool empty() {
    156    if (map.empty()) {
    157      return true;
    158    }
    159    for (OuterMap::Enum e(map); !e.empty(); e.popFront()) {
    160      if (!e.front().value().empty()) {
    161        return false;
    162      }
    163    }
    164    return true;
    165  }
    166 
    167  Ptr lookup(JSObject* obj) const {
    168    auto op = map.lookup(obj->compartment());
    169    if (op) {
    170      auto ip = op->value().lookup(obj);
    171      if (ip) {
    172        return Ptr(ip, op->value());
    173      }
    174    }
    175    return Ptr();
    176  }
    177 
    178  void remove(Ptr p) {
    179    if (p) {
    180      p.map->remove(p);
    181    }
    182  }
    183 
    184  [[nodiscard]] bool put(JSObject* key, JSObject* value) {
    185    JS::Compartment* comp = key->compartment();
    186    auto ptr = map.lookupForAdd(comp);
    187    if (!ptr) {
    188      InnerMap m(zone, InitialInnerMapSize);
    189      if (!map.add(ptr, comp, std::move(m))) {
    190        return false;
    191      }
    192    }
    193    return ptr->value().put(key, value);
    194  }
    195 
    196  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
    197    size_t size = map.shallowSizeOfExcludingThis(mallocSizeOf);
    198    for (OuterMap::Enum e(map); !e.empty(); e.popFront()) {
    199      size += e.front().value().sizeOfExcludingThis(mallocSizeOf);
    200    }
    201    return size;
    202  }
    203  size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
    204    return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
    205  }
    206 
    207  bool hasNurseryAllocatedWrapperEntries(const CompartmentFilter& f) {
    208    for (OuterMap::Enum e(map); !e.empty(); e.popFront()) {
    209      JS::Compartment* c = e.front().key();
    210      if (c && !f.match(c)) {
    211        continue;
    212      }
    213      InnerMap& m = e.front().value();
    214      if (m.hasNurseryEntries()) {
    215        return true;
    216      }
    217    }
    218    return false;
    219  }
    220 
    221  void sweepAfterMinorGC(JSTracer* trc) {
    222    for (OuterMap::Enum e(map); !e.empty(); e.popFront()) {
    223      InnerMap& m = e.front().value();
    224      m.sweepAfterMinorGC(trc);
    225      if (m.empty()) {
    226        e.removeFront();
    227      }
    228    }
    229  }
    230 
    231  void traceWeak(JSTracer* trc) {
    232    for (OuterMap::Enum e(map); !e.empty(); e.popFront()) {
    233      InnerMap& m = e.front().value();
    234      m.traceWeak(trc);
    235      if (m.empty()) {
    236        e.removeFront();
    237      }
    238    }
    239    map.compact();
    240  }
    241 };
    242 
    243 using StringWrapperMap =
    244    NurseryAwareHashMap<JSString*, JSString*, ZoneAllocPolicy,
    245                        DuplicatesPossible>;
    246 
    247 }  // namespace js
    248 
    249 class JS::Compartment {
    250  JS::Zone* zone_;
    251  JSRuntime* runtime_;
    252  bool invisibleToDebugger_;
    253 
    254  js::ObjectWrapperMap crossCompartmentObjectWrappers;
    255 
    256  using RealmVector = js::Vector<JS::Realm*, 1, js::ZoneAllocPolicy>;
    257  RealmVector realms_;
    258 
    259 public:
    260  /*
    261   * During GC, stores the head of a list of incoming pointers from gray cells.
    262   *
    263   * The objects in the list are either cross-compartment wrappers, or
    264   * debugger wrapper objects.  The list link is either in the second extra
    265   * slot for the former, or a special slot for the latter.
    266   */
    267  JSObject* gcIncomingGrayPointers = nullptr;
    268 
    269  void* data = nullptr;
    270 
    271  // Fields set and used by the GC. Be careful, may be stale after we return
    272  // to the mutator.
    273  struct {
    274    // These flags help us to discover if a compartment that shouldn't be
    275    // alive manages to outlive a GC. Note that these flags have to be on
    276    // the compartment, not the realm, because same-compartment realms can
    277    // have cross-realm pointers without wrappers.
    278    bool scheduledForDestruction = false;
    279    bool hasMarkedCells = false;
    280    bool maybeAlive = true;
    281 
    282    // During GC, we may set this to |true| if we entered a realm in this
    283    // compartment. Note that (without a stack walk) we don't know exactly
    284    // *which* realms, because Realm::enterRealmDepthIgnoringJit_ does not
    285    // account for cross-Realm calls in JIT code updating cx->realm_. See
    286    // also the enterRealmDepthIgnoringJit_ comment.
    287    bool hasEnteredRealm = false;
    288  } gcState;
    289 
    290  // True if all outgoing wrappers have been nuked. This happens when all realms
    291  // have been nuked and NukeCrossCompartmentWrappers is called with the
    292  // NukeAllReferences option. This prevents us from creating new wrappers for
    293  // the compartment.
    294  bool nukedOutgoingWrappers = false;
    295 
    296  JS::Zone* zone() { return zone_; }
    297  const JS::Zone* zone() const { return zone_; }
    298 
    299  JSRuntime* runtimeFromMainThread() const {
    300    MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_));
    301    return runtime_;
    302  }
    303 
    304  // Note: Unrestricted access to the zone's runtime from an arbitrary
    305  // thread can easily lead to races. Use this method very carefully.
    306  JSRuntime* runtimeFromAnyThread() const { return runtime_; }
    307 
    308  // Certain compartments are implementation details of the embedding, and
    309  // references to them should never leak out to script. For realms belonging to
    310  // this compartment, onNewGlobalObject does not fire, and addDebuggee is a
    311  // no-op.
    312  bool invisibleToDebugger() const { return invisibleToDebugger_; }
    313 
    314  RealmVector& realms() { return realms_; }
    315 
    316  // Cross-compartment wrappers are shared by all realms in the compartment, but
    317  // are still associated with a realm. To prevent us from having multiple
    318  // realms, each with some cross-compartment wrappers potentially keeping the
    319  // realm alive longer than necessary, we always allocate CCWs in the first
    320  // realm.
    321  js::GlobalObject& firstGlobal() const;
    322  js::GlobalObject& globalForNewCCW() const { return firstGlobal(); }
    323 
    324  void assertNoCrossCompartmentWrappers() {
    325    MOZ_ASSERT(crossCompartmentObjectWrappers.empty());
    326  }
    327 
    328  void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
    329                              size_t* compartmentObjects,
    330                              size_t* crossCompartmentWrappersTables,
    331                              size_t* compartmentsPrivateData);
    332 
    333 #ifdef JSGC_HASH_TABLE_CHECKS
    334  void checkObjectWrappersAfterMovingGC();
    335 #endif
    336 
    337 private:
    338  bool getNonWrapperObjectForCurrentCompartment(JSContext* cx,
    339                                                js::HandleObject origObj,
    340                                                js::MutableHandleObject obj);
    341  bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing,
    342                          js::MutableHandleObject obj);
    343 
    344 public:
    345  explicit Compartment(JS::Zone* zone, bool invisibleToDebugger);
    346 
    347  void destroy(JS::GCContext* gcx);
    348 
    349  [[nodiscard]] inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
    350 
    351  [[nodiscard]] inline bool wrap(JSContext* cx,
    352                                 MutableHandle<mozilla::Maybe<Value>> vp);
    353 
    354  [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandleString strp);
    355  [[nodiscard]] bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi);
    356  [[nodiscard]] bool wrap(JSContext* cx, JS::MutableHandleObject obj);
    357  [[nodiscard]] bool wrap(JSContext* cx,
    358                          JS::MutableHandle<JS::PropertyDescriptor> desc);
    359  [[nodiscard]] bool wrap(
    360      JSContext* cx,
    361      JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> desc);
    362  [[nodiscard]] bool wrap(JSContext* cx,
    363                          JS::MutableHandle<JS::GCVector<JS::Value>> vec);
    364  [[nodiscard]] bool rewrap(JSContext* cx, JS::MutableHandleObject obj,
    365                            JS::HandleObject existing);
    366 
    367  [[nodiscard]] bool putWrapper(JSContext* cx, JSObject* wrapped,
    368                                JSObject* wrapper);
    369 
    370  [[nodiscard]] bool putWrapper(JSContext* cx, JSString* wrapped,
    371                                JSString* wrapper);
    372 
    373  js::ObjectWrapperMap::Ptr lookupWrapper(JSObject* obj) const {
    374    return crossCompartmentObjectWrappers.lookup(obj);
    375  }
    376 
    377  inline js::StringWrapperMap::Ptr lookupWrapper(JSString* str) const;
    378 
    379  void removeWrapper(js::ObjectWrapperMap::Ptr p);
    380 
    381  bool hasNurseryAllocatedObjectWrapperEntries(const js::CompartmentFilter& f) {
    382    return crossCompartmentObjectWrappers.hasNurseryAllocatedWrapperEntries(f);
    383  }
    384 
    385  // Iterator over |wrapped -> wrapper| entries for object CCWs in a given
    386  // compartment. Can be optionally restricted by target compartment.
    387  struct ObjectWrapperEnum : public js::ObjectWrapperMap::Enum {
    388    explicit ObjectWrapperEnum(Compartment* c)
    389        : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers) {}
    390    explicit ObjectWrapperEnum(Compartment* c, const js::CompartmentFilter& f)
    391        : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers, f) {}
    392    explicit ObjectWrapperEnum(Compartment* c, Compartment* target)
    393        : js::ObjectWrapperMap::Enum(c->crossCompartmentObjectWrappers,
    394                                     target) {
    395      MOZ_ASSERT(target);
    396    }
    397  };
    398 
    399  // Iterator over compartments that this compartment has CCWs for.
    400  struct WrappedObjectCompartmentEnum
    401      : public js::ObjectWrapperMap::WrappedCompartmentEnum {
    402    explicit WrappedObjectCompartmentEnum(Compartment* c)
    403        : js::ObjectWrapperMap::WrappedCompartmentEnum(
    404              c->crossCompartmentObjectWrappers) {}
    405  };
    406 
    407  /*
    408   * These methods mark pointers that cross compartment boundaries. They are
    409   * called in per-zone GCs to prevent the wrappers' outgoing edges from
    410   * dangling (full GCs naturally follow pointers across compartments) and
    411   * when compacting to update cross-compartment pointers.
    412   */
    413  enum EdgeSelector { AllEdges, NonGrayEdges, GrayEdges, BlackEdges };
    414  void traceWrapperTargetsInCollectedZones(JSTracer* trc,
    415                                           EdgeSelector whichEdges);
    416  static void traceIncomingCrossCompartmentEdgesForZoneGC(
    417      JSTracer* trc, EdgeSelector whichEdges);
    418 
    419  void sweepRealms(JS::GCContext* gcx, bool keepAtleastOne,
    420                   bool destroyingRuntime);
    421  void sweepAfterMinorGC(JSTracer* trc);
    422  void traceCrossCompartmentObjectWrapperEdges(JSTracer* trc);
    423 
    424  void fixupCrossCompartmentObjectWrappersAfterMovingGC(JSTracer* trc);
    425  void fixupAfterMovingGC(JSTracer* trc);
    426 
    427  [[nodiscard]] bool findSweepGroupEdges();
    428 
    429 private:
    430  // Head node of list of active iterators that may need deleted property
    431  // suppression.
    432  js::NativeIteratorListHead enumerators_;
    433 
    434 public:
    435  js::NativeIteratorListHead* enumeratorsAddr() { return &enumerators_; }
    436  MOZ_ALWAYS_INLINE bool objectMaybeInIteration(JSObject* obj);
    437 
    438  void traceWeakNativeIterators(JSTracer* trc);
    439 };
    440 
    441 namespace js {
    442 
    443 // We only set the hasMarkedCells flag for objects and scripts. It's assumed
    444 // that, if a compartment is alive, then it will have at least some live object
    445 // or script it in. Even if we get this wrong, the worst that will happen is
    446 // that scheduledForDestruction will be set on the compartment, which will cause
    447 // some extra GC activity to try to free the compartment.
    448 template <typename T>
    449 inline void SetCompartmentHasMarkedCells(T* thing) {}
    450 
    451 template <>
    452 inline void SetCompartmentHasMarkedCells(JSObject* thing) {
    453  thing->compartment()->gcState.hasMarkedCells = true;
    454 }
    455 
    456 template <>
    457 inline void SetCompartmentHasMarkedCells(JSScript* thing) {
    458  thing->compartment()->gcState.hasMarkedCells = true;
    459 }
    460 
    461 /*
    462 * AutoWrapperVector and AutoWrapperRooter can be used to store wrappers that
    463 * are obtained from the cross-compartment map. However, these classes should
    464 * not be used if the wrapper will escape. For example, it should not be stored
    465 * in the heap.
    466 *
    467 * The AutoWrapper rooters are different from other autorooters because their
    468 * wrappers are marked on every GC slice rather than just the first one. If
    469 * there's some wrapper that we want to use temporarily without causing it to be
    470 * marked, we can use these AutoWrapper classes. If we get unlucky and a GC
    471 * slice runs during the code using the wrapper, the GC will mark the wrapper so
    472 * that it doesn't get swept out from under us. Otherwise, the wrapper needn't
    473 * be marked. This is useful in functions like JS_TransplantObject that
    474 * manipulate wrappers in compartments that may no longer be alive.
    475 */
    476 
    477 /*
    478 * This class stores the data for AutoWrapperVector and AutoWrapperRooter. It
    479 * should not be used in any other situations.
    480 */
    481 struct WrapperValue {
    482  /*
    483   * We use unsafeGet() in the constructors to avoid invoking a read barrier
    484   * on the wrapper, which may be dead (see the comment about bug 803376 in
    485   * gc/GC.cpp regarding this). If there is an incremental GC while the
    486   * wrapper is in use, the AutoWrapper rooter will ensure the wrapper gets
    487   * marked.
    488   */
    489  explicit WrapperValue(const ObjectWrapperMap::Ptr& ptr)
    490      : value(*ptr->value().unsafeGet()) {}
    491 
    492  explicit WrapperValue(const ObjectWrapperMap::Enum& e)
    493      : value(*e.front().value().unsafeGet()) {}
    494 
    495  JSObject*& get() { return value; }
    496  JSObject* get() const { return value; }
    497  operator JSObject*() const { return value; }
    498 
    499 private:
    500  JSObject* value;
    501 };
    502 
    503 class MOZ_RAII AutoWrapperVector : public JS::GCVector<WrapperValue, 8>,
    504                                   public JS::AutoGCRooter {
    505 public:
    506  explicit AutoWrapperVector(JSContext* cx)
    507      : JS::GCVector<WrapperValue, 8>(cx),
    508        JS::AutoGCRooter(cx, JS::AutoGCRooter::Kind::WrapperVector) {}
    509 
    510  void trace(JSTracer* trc);
    511 
    512 private:
    513 };
    514 
    515 class MOZ_RAII AutoWrapperRooter : public JS::AutoGCRooter {
    516 public:
    517  AutoWrapperRooter(JSContext* cx, const WrapperValue& v)
    518      : JS::AutoGCRooter(cx, JS::AutoGCRooter::Kind::Wrapper), value(v) {}
    519 
    520  operator JSObject*() const { return value; }
    521 
    522  void trace(JSTracer* trc);
    523 
    524 private:
    525  WrapperValue value;
    526 };
    527 
    528 } /* namespace js */
    529 
    530 #endif /* vm_Compartment_h */