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