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