1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sts=4 et sw=4 tw=99:
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 "jscompartmentinlines.h"
8
9 #include "mozilla/DebugOnly.h"
10 #include "mozilla/MemoryReporting.h"
11
12 #include "jscntxt.h"
13 #include "jsfriendapi.h"
14 #include "jsgc.h"
15 #include "jsiter.h"
16 #include "jswatchpoint.h"
17 #include "jswrapper.h"
18
19 #include "gc/Marking.h"
20 #include "jit/JitCompartment.h"
21 #include "jit/JitOptions.h"
22 #include "js/Date.h"
23 #include "js/Proxy.h"
24 #include "js/RootingAPI.h"
25 #include "proxy/DeadObjectProxy.h"
26 #include "vm/Debugger.h"
27 #include "vm/StopIterationObject.h"
28 #include "vm/WrapperObject.h"
29
30 #include "jsatominlines.h"
31 #include "jsfuninlines.h"
32 #include "jsgcinlines.h"
33 #include "jsobjinlines.h"
34 #include "jsscriptinlines.h"
35
36 using namespace js;
37 using namespace js::gc;
38 using namespace js::jit;
39
40 using mozilla::DebugOnly;
41 using mozilla::PodArrayZero;
42
JSCompartment(Zone * zone,const JS::CompartmentOptions & options=JS::CompartmentOptions ())43 JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = JS::CompartmentOptions())
44 : options_(options),
45 zone_(zone),
46 runtime_(zone->runtimeFromMainThread()),
47 principals_(nullptr),
48 isSystem_(false),
49 isAtomsCompartment_(false),
50 isSelfHosting(false),
51 marked(true),
52 warnedAboutFlagsArgument(false),
53 warnedAboutExprClosure(false),
54 addonId(options.addonIdOrNull()),
55 #ifdef DEBUG
56 firedOnNewGlobalObject(false),
57 #endif
58 global_(nullptr),
59 enterCompartmentDepth(0),
60 performanceMonitoring(runtime_),
61 data(nullptr),
62 objectMetadataCallback(nullptr),
63 lastAnimationTime(0),
64 regExps(runtime_),
65 globalWriteBarriered(false),
66 neuteredTypedObjects(0),
67 objectMetadataState(ImmediateMetadata()),
68 propertyTree(thisForCtor()),
69 selfHostingScriptSource(nullptr),
70 objectMetadataTable(nullptr),
71 lazyArrayBuffers(nullptr),
72 nonSyntacticLexicalScopes_(nullptr),
73 gcIncomingGrayPointers(nullptr),
74 gcPreserveJitCode(options.preserveJitCode()),
75 debugModeBits(0),
76 randomKeyGenerator_(runtime_->forkRandomKeyGenerator()),
77 watchpointMap(nullptr),
78 scriptCountsMap(nullptr),
79 debugScriptMap(nullptr),
80 debugScopes(nullptr),
81 enumerators(nullptr),
82 compartmentStats(nullptr),
83 scheduledForDestruction(false),
84 maybeAlive(true),
85 jitCompartment_(nullptr),
86 mappedArgumentsTemplate_(nullptr),
87 unmappedArgumentsTemplate_(nullptr),
88 lcovOutput()
89 {
90 PodArrayZero(sawDeprecatedLanguageExtension);
91 runtime_->numCompartments++;
92 MOZ_ASSERT_IF(options.mergeable(), options.invisibleToDebugger());
93 }
94
~JSCompartment()95 JSCompartment::~JSCompartment()
96 {
97 reportTelemetry();
98
99 // Write the code coverage information in a file.
100 JSRuntime* rt = runtimeFromMainThread();
101 if (rt->lcovOutput.isEnabled())
102 rt->lcovOutput.writeLCovResult(lcovOutput);
103
104 js_delete(jitCompartment_);
105 js_delete(watchpointMap);
106 js_delete(scriptCountsMap);
107 js_delete(debugScriptMap);
108 js_delete(debugScopes);
109 js_delete(objectMetadataTable);
110 js_delete(lazyArrayBuffers);
111 js_delete(nonSyntacticLexicalScopes_),
112 js_free(enumerators);
113
114 runtime_->numCompartments--;
115 }
116
117 bool
init(JSContext * maybecx)118 JSCompartment::init(JSContext* maybecx)
119 {
120 /*
121 * maybecx is null when called to create the atoms compartment from
122 * JSRuntime::init().
123 *
124 * As a hack, we clear our timezone cache every time we create a new
125 * compartment. This ensures that the cache is always relatively fresh, but
126 * shouldn't interfere with benchmarks that create tons of date objects
127 * (unless they also create tons of iframes, which seems unlikely).
128 */
129 JS::ResetTimeZone();
130
131 if (!crossCompartmentWrappers.init(0)) {
132 if (maybecx)
133 ReportOutOfMemory(maybecx);
134 return false;
135 }
136
137 if (!regExps.init(maybecx))
138 return false;
139
140 enumerators = NativeIterator::allocateSentinel(maybecx);
141 if (!enumerators)
142 return false;
143
144 if (!savedStacks_.init()) {
145 if (maybecx)
146 ReportOutOfMemory(maybecx);
147 return false;
148 }
149
150 return true;
151 }
152
153 jit::JitRuntime*
createJitRuntime(JSContext * cx)154 JSRuntime::createJitRuntime(JSContext* cx)
155 {
156 // The shared stubs are created in the atoms compartment, which may be
157 // accessed by other threads with an exclusive context.
158 AutoLockForExclusiveAccess atomsLock(cx);
159
160 MOZ_ASSERT(!jitRuntime_);
161
162 if (!CanLikelyAllocateMoreExecutableMemory()) {
163 // Report OOM instead of potentially hitting the MOZ_CRASH below.
164 ReportOutOfMemory(cx);
165 return nullptr;
166 }
167
168 jit::JitRuntime* jrt = cx->new_<jit::JitRuntime>();
169 if (!jrt)
170 return nullptr;
171
172 // Protect jitRuntime_ from being observed (by InterruptRunningJitCode)
173 // while it is being initialized. Unfortunately, initialization depends on
174 // jitRuntime_ being non-null, so we can't just wait to assign jitRuntime_.
175 JitRuntime::AutoMutateBackedges amb(jrt);
176 jitRuntime_ = jrt;
177
178 AutoEnterOOMUnsafeRegion noOOM;
179 if (!jitRuntime_->initialize(cx)) {
180 // Handling OOM here is complicated: if we delete jitRuntime_ now, we
181 // will destroy the ExecutableAllocator, even though there may still be
182 // JitCode instances holding references to ExecutablePools.
183 noOOM.crash("OOM in createJitRuntime");
184 }
185
186 return jitRuntime_;
187 }
188
189 bool
ensureJitCompartmentExists(JSContext * cx)190 JSCompartment::ensureJitCompartmentExists(JSContext* cx)
191 {
192 using namespace js::jit;
193 if (jitCompartment_)
194 return true;
195
196 if (!zone()->getJitZone(cx))
197 return false;
198
199 /* Set the compartment early, so linking works. */
200 jitCompartment_ = cx->new_<JitCompartment>();
201
202 if (!jitCompartment_)
203 return false;
204
205 if (!jitCompartment_->initialize(cx)) {
206 js_delete(jitCompartment_);
207 jitCompartment_ = nullptr;
208 return false;
209 }
210
211 return true;
212 }
213
214 /*
215 * This class is used to add a post barrier on the crossCompartmentWrappers map,
216 * as the key is calculated based on objects which may be moved by generational
217 * GC.
218 */
219 class WrapperMapRef : public BufferableRef
220 {
221 WrapperMap* map;
222 CrossCompartmentKey key;
223
224 public:
WrapperMapRef(WrapperMap * map,const CrossCompartmentKey & key)225 WrapperMapRef(WrapperMap* map, const CrossCompartmentKey& key)
226 : map(map), key(key) {}
227
trace(JSTracer * trc)228 void trace(JSTracer* trc) override {
229 CrossCompartmentKey prior = key;
230 if (key.debugger)
231 TraceManuallyBarrieredEdge(trc, &key.debugger, "CCW debugger");
232 if (key.kind == CrossCompartmentKey::ObjectWrapper ||
233 key.kind == CrossCompartmentKey::DebuggerObject ||
234 key.kind == CrossCompartmentKey::DebuggerEnvironment ||
235 key.kind == CrossCompartmentKey::DebuggerSource)
236 {
237 MOZ_ASSERT(IsInsideNursery(key.wrapped) ||
238 key.wrapped->asTenured().getTraceKind() == JS::TraceKind::Object);
239 TraceManuallyBarrieredEdge(trc, reinterpret_cast<JSObject**>(&key.wrapped),
240 "CCW wrapped object");
241 }
242 if (key.debugger == prior.debugger && key.wrapped == prior.wrapped)
243 return;
244
245 /* Look for the original entry, which might have been removed. */
246 WrapperMap::Ptr p = map->lookup(prior);
247 if (!p)
248 return;
249
250 /* Rekey the entry. */
251 map->rekeyAs(prior, key, key);
252 }
253 };
254
255 #ifdef JSGC_HASH_TABLE_CHECKS
256 void
checkWrapperMapAfterMovingGC()257 JSCompartment::checkWrapperMapAfterMovingGC()
258 {
259 /*
260 * Assert that the postbarriers have worked and that nothing is left in
261 * wrapperMap that points into the nursery, and that the hash table entries
262 * are discoverable.
263 */
264 for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
265 CrossCompartmentKey key = e.front().key();
266 CheckGCThingAfterMovingGC(key.debugger);
267 CheckGCThingAfterMovingGC(key.wrapped);
268 CheckGCThingAfterMovingGC(
269 static_cast<Cell*>(e.front().value().unbarrieredGet().toGCThing()));
270
271 WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(key);
272 MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
273 }
274 }
275 #endif
276
277 bool
putWrapper(JSContext * cx,const CrossCompartmentKey & wrapped,const js::Value & wrapper)278 JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, const js::Value& wrapper)
279 {
280 MOZ_ASSERT(wrapped.wrapped);
281 MOZ_ASSERT_IF(wrapped.kind == CrossCompartmentKey::StringWrapper, wrapper.isString());
282 MOZ_ASSERT_IF(wrapped.kind != CrossCompartmentKey::StringWrapper, wrapper.isObject());
283
284 /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */
285 MOZ_ASSERT(!IsInsideNursery(static_cast<gc::Cell*>(wrapper.toGCThing())));
286
287 if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
288 ReportOutOfMemory(cx);
289 return false;
290 }
291
292 if (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger)) {
293 WrapperMapRef ref(&crossCompartmentWrappers, wrapped);
294 cx->runtime()->gc.storeBuffer.putGeneric(ref);
295 }
296
297 return true;
298 }
299
300 static JSString*
CopyStringPure(JSContext * cx,JSString * str)301 CopyStringPure(JSContext* cx, JSString* str)
302 {
303 /*
304 * Directly allocate the copy in the destination compartment, rather than
305 * first flattening it (and possibly allocating in source compartment),
306 * because we don't know whether the flattening will pay off later.
307 */
308
309 size_t len = str->length();
310 JSString* copy;
311 if (str->isLinear()) {
312 /* Only use AutoStableStringChars if the NoGC allocation fails. */
313 if (str->hasLatin1Chars()) {
314 JS::AutoCheckCannotGC nogc;
315 copy = NewStringCopyN<NoGC>(cx, str->asLinear().latin1Chars(nogc), len);
316 } else {
317 JS::AutoCheckCannotGC nogc;
318 copy = NewStringCopyNDontDeflate<NoGC>(cx, str->asLinear().twoByteChars(nogc), len);
319 }
320 if (copy)
321 return copy;
322
323 AutoStableStringChars chars(cx);
324 if (!chars.init(cx, str))
325 return nullptr;
326
327 return chars.isLatin1()
328 ? NewStringCopyN<CanGC>(cx, chars.latin1Range().start().get(), len)
329 : NewStringCopyNDontDeflate<CanGC>(cx, chars.twoByteRange().start().get(), len);
330 }
331
332 if (str->hasLatin1Chars()) {
333 ScopedJSFreePtr<Latin1Char> copiedChars;
334 if (!str->asRope().copyLatin1CharsZ(cx, copiedChars))
335 return nullptr;
336
337 return NewString<CanGC>(cx, copiedChars.forget(), len);
338 }
339
340 ScopedJSFreePtr<char16_t> copiedChars;
341 if (!str->asRope().copyTwoByteCharsZ(cx, copiedChars))
342 return nullptr;
343
344 return NewStringDontDeflate<CanGC>(cx, copiedChars.forget(), len);
345 }
346
347 bool
wrap(JSContext * cx,MutableHandleString strp)348 JSCompartment::wrap(JSContext* cx, MutableHandleString strp)
349 {
350 MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(this));
351 MOZ_ASSERT(cx->compartment() == this);
352
353 /* If the string is already in this compartment, we are done. */
354 JSString* str = strp;
355 if (str->zoneFromAnyThread() == zone())
356 return true;
357
358 /* If the string is an atom, we don't have to copy. */
359 if (str->isAtom()) {
360 MOZ_ASSERT(str->isPermanentAtom() || str->zone()->isAtomsZone());
361 return true;
362 }
363
364 /* Check the cache. */
365 RootedValue key(cx, StringValue(str));
366 if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
367 strp.set(p->value().get().toString());
368 return true;
369 }
370
371 /* No dice. Make a copy, and cache it. */
372 JSString* copy = CopyStringPure(cx, str);
373 if (!copy)
374 return false;
375 if (!putWrapper(cx, CrossCompartmentKey(key), StringValue(copy)))
376 return false;
377
378 strp.set(copy);
379 return true;
380 }
381
382 bool
wrap(JSContext * cx,MutableHandleObject obj,HandleObject existingArg)383 JSCompartment::wrap(JSContext* cx, MutableHandleObject obj, HandleObject existingArg)
384 {
385 MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(this));
386 MOZ_ASSERT(cx->compartment() == this);
387 MOZ_ASSERT_IF(existingArg, existingArg->compartment() == cx->compartment());
388 MOZ_ASSERT_IF(existingArg, IsDeadProxyObject(existingArg));
389
390 if (!obj)
391 return true;
392 AutoDisableProxyCheck adpc(cx->runtime());
393
394 // Wrappers should really be parented to the wrapped parent of the wrapped
395 // object, but in that case a wrapped global object would have a nullptr
396 // parent without being a proper global object (JSCLASS_IS_GLOBAL). Instead,
397 // we parent all wrappers to the global object in their home compartment.
398 // This loses us some transparency, and is generally very cheesy.
399 HandleObject global = cx->global();
400 RootedObject objGlobal(cx, &obj->global());
401 MOZ_ASSERT(global);
402 MOZ_ASSERT(objGlobal);
403
404 const JSWrapObjectCallbacks* cb = cx->runtime()->wrapObjectCallbacks;
405
406 if (obj->compartment() == this) {
407 obj.set(ToWindowProxyIfWindow(obj));
408 return true;
409 }
410
411 // If we have a cross-compartment wrapper, make sure that the cx isn't
412 // associated with the self-hosting global. We don't want to create
413 // wrappers for objects in other runtimes, which may be the case for the
414 // self-hosting global.
415 MOZ_ASSERT(!cx->runtime()->isSelfHostingGlobal(global) &&
416 !cx->runtime()->isSelfHostingGlobal(objGlobal));
417
418 // Unwrap the object, but don't unwrap outer windows.
419 RootedObject objectPassedToWrap(cx, obj);
420 obj.set(UncheckedUnwrap(obj, /* stopAtWindowProxy = */ true));
421
422 if (obj->compartment() == this) {
423 MOZ_ASSERT(!IsWindow(obj));
424 return true;
425 }
426
427 // Translate StopIteration singleton.
428 if (obj->is<StopIterationObject>()) {
429 // StopIteration isn't a constructor, but it's stored in GlobalObject
430 // as one, out of laziness. Hence the GetBuiltinConstructor call here.
431 RootedObject stopIteration(cx);
432 if (!GetBuiltinConstructor(cx, JSProto_StopIteration, &stopIteration))
433 return false;
434 obj.set(stopIteration);
435 return true;
436 }
437
438 // Invoke the prewrap callback. We're a bit worried about infinite
439 // recursion here, so we do a check - see bug 809295.
440 JS_CHECK_SYSTEM_RECURSION(cx, return false);
441 if (cb->preWrap) {
442 obj.set(cb->preWrap(cx, global, obj, objectPassedToWrap));
443 if (!obj)
444 return false;
445 }
446 MOZ_ASSERT(!IsWindow(obj));
447
448 if (obj->compartment() == this)
449 return true;
450
451 // If we already have a wrapper for this value, use it.
452 RootedValue key(cx, ObjectValue(*obj));
453 if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
454 obj.set(&p->value().get().toObject());
455 MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>());
456 return true;
457 }
458
459 RootedObject existing(cx, existingArg);
460 if (existing) {
461 // Is it possible to reuse |existing|?
462 if (!existing->getTaggedProto().isLazy() ||
463 // Note: Class asserted above, so all that's left to check is callability
464 existing->isCallable() ||
465 obj->isCallable())
466 {
467 existing = nullptr;
468 }
469 }
470
471 RootedObject wrapper(cx, cb->wrap(cx, existing, obj));
472 if (!wrapper)
473 return false;
474
475 // We maintain the invariant that the key in the cross-compartment wrapper
476 // map is always directly wrapped by the value.
477 MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject());
478
479 if (!putWrapper(cx, CrossCompartmentKey(key), ObjectValue(*wrapper))) {
480 // Enforce the invariant that all cross-compartment wrapper object are
481 // in the map by nuking the wrapper if we couldn't add it.
482 // Unfortunately it's possible for the wrapper to still be marked if we
483 // took this path, for example if the object metadata callback stashes a
484 // reference to it.
485 if (wrapper->is<CrossCompartmentWrapperObject>())
486 NukeCrossCompartmentWrapper(cx, wrapper);
487 return false;
488 }
489
490 obj.set(wrapper);
491 return true;
492 }
493
494 bool
wrap(JSContext * cx,MutableHandle<PropertyDescriptor> desc)495 JSCompartment::wrap(JSContext* cx, MutableHandle<PropertyDescriptor> desc)
496 {
497 if (!wrap(cx, desc.object()))
498 return false;
499
500 if (desc.hasGetterObject()) {
501 if (!wrap(cx, desc.getterObject()))
502 return false;
503 }
504 if (desc.hasSetterObject()) {
505 if (!wrap(cx, desc.setterObject()))
506 return false;
507 }
508
509 return wrap(cx, desc.value());
510 }
511
512 ClonedBlockObject*
getOrCreateNonSyntacticLexicalScope(JSContext * cx,HandleObject enclosingStatic,HandleObject enclosingScope)513 JSCompartment::getOrCreateNonSyntacticLexicalScope(JSContext* cx,
514 HandleObject enclosingStatic,
515 HandleObject enclosingScope)
516 {
517 if (!nonSyntacticLexicalScopes_) {
518 nonSyntacticLexicalScopes_ = cx->new_<ObjectWeakMap>(cx);
519 if (!nonSyntacticLexicalScopes_ || !nonSyntacticLexicalScopes_->init())
520 return nullptr;
521 }
522
523 // The key is the unwrapped dynamic scope, as we may be creating different
524 // DynamicWithObject wrappers each time.
525 MOZ_ASSERT(!enclosingScope->as<DynamicWithObject>().isSyntactic());
526 RootedObject key(cx, &enclosingScope->as<DynamicWithObject>().object());
527 RootedObject lexicalScope(cx, nonSyntacticLexicalScopes_->lookup(key));
528
529 if (!lexicalScope) {
530 lexicalScope = ClonedBlockObject::createNonSyntactic(cx, enclosingStatic, enclosingScope);
531 if (!lexicalScope)
532 return nullptr;
533 if (!nonSyntacticLexicalScopes_->add(cx, key, lexicalScope))
534 return nullptr;
535 }
536
537 return &lexicalScope->as<ClonedBlockObject>();
538 }
539
540 ClonedBlockObject*
getNonSyntacticLexicalScope(JSObject * enclosingScope) const541 JSCompartment::getNonSyntacticLexicalScope(JSObject* enclosingScope) const
542 {
543 if (!nonSyntacticLexicalScopes_)
544 return nullptr;
545 if (!enclosingScope->is<DynamicWithObject>())
546 return nullptr;
547 JSObject* key = &enclosingScope->as<DynamicWithObject>().object();
548 JSObject* lexicalScope = nonSyntacticLexicalScopes_->lookup(key);
549 if (!lexicalScope)
550 return nullptr;
551 return &lexicalScope->as<ClonedBlockObject>();
552 }
553
554 void
traceOutgoingCrossCompartmentWrappers(JSTracer * trc)555 JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc)
556 {
557 MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
558 MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->gc.isHeapCompacting());
559
560 for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
561 Value v = e.front().value().unbarrieredGet();
562 if (e.front().key().kind == CrossCompartmentKey::ObjectWrapper) {
563 ProxyObject* wrapper = &v.toObject().as<ProxyObject>();
564
565 /*
566 * We have a cross-compartment wrapper. Its private pointer may
567 * point into the compartment being collected, so we should mark it.
568 */
569 TraceEdge(trc, wrapper->slotOfPrivate(), "cross-compartment wrapper");
570 }
571 }
572 }
573
574 /* static */ void
traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer * trc)575 JSCompartment::traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc)
576 {
577 MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
578 for (CompartmentsIter c(trc->runtime(), SkipAtoms); !c.done(); c.next()) {
579 if (!c->zone()->isCollecting())
580 c->traceOutgoingCrossCompartmentWrappers(trc);
581 }
582 Debugger::markIncomingCrossCompartmentEdges(trc);
583 }
584
585 void
trace(JSTracer * trc)586 JSCompartment::trace(JSTracer* trc)
587 {
588 savedStacks_.trace(trc);
589 }
590
591 void
traceRoots(JSTracer * trc,js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark)592 JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark)
593 {
594 if (objectMetadataState.is<PendingMetadata>()) {
595 TraceRoot(trc,
596 objectMetadataState.as<PendingMetadata>().unsafeUnbarrieredForTracing(),
597 "on-stack object pending metadata");
598 }
599
600 if (!trc->runtime()->isHeapMinorCollecting()) {
601 // JIT code and the global are never nursery allocated, so we only need
602 // to trace them when not doing a minor collection.
603
604 if (jitCompartment_)
605 jitCompartment_->mark(trc, this);
606
607 // If a compartment is on-stack, we mark its global so that
608 // JSContext::global() remains valid.
609 if (enterCompartmentDepth && global_.unbarrieredGet())
610 TraceRoot(trc, global_.unsafeUnbarrieredForTracing(), "on-stack compartment global");
611 }
612
613 // Nothing below here needs to be treated as a root if we aren't marking
614 // this zone for a collection.
615 if (traceOrMark == js::gc::GCRuntime::MarkRuntime && !zone()->isCollecting())
616 return;
617
618 // During a GC, these are treated as weak pointers.
619 if (traceOrMark == js::gc::GCRuntime::TraceRuntime) {
620 if (watchpointMap)
621 watchpointMap->markAll(trc);
622 }
623
624 /* Mark debug scopes, if present */
625 if (debugScopes)
626 debugScopes->mark(trc);
627
628 if (lazyArrayBuffers)
629 lazyArrayBuffers->trace(trc);
630
631 if (objectMetadataTable)
632 objectMetadataTable->trace(trc);
633
634 // If code coverage is only enabled with the Debugger or the LCovOutput,
635 // then the following comment holds.
636 //
637 // The scriptCountsMap maps JSScript weak-pointers to ScriptCounts
638 // structures. It uses a HashMap instead of a WeakMap, so that we can keep
639 // the data alive for the JSScript::finalize call. Thus, we do not trace the
640 // keys of the HashMap to avoid adding a strong reference to the JSScript
641 // pointers. Additionally, we assert that the JSScripts have not been moved
642 // in JSCompartment::fixupAfterMovingGC.
643 //
644 // If the code coverage is either enabled with the --dump-bytecode command
645 // line option, or with the PCCount JSFriend API functions, then we mark the
646 // keys of the map to hold the JSScript alive.
647 if (scriptCountsMap &&
648 trc->runtime()->profilingScripts &&
649 !trc->runtime()->isHeapMinorCollecting())
650 {
651 MOZ_ASSERT_IF(!trc->runtime()->isBeingDestroyed(), collectCoverage());
652 for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
653 JSScript* script = const_cast<JSScript*>(r.front().key());
654 MOZ_ASSERT(script->hasScriptCounts());
655 TraceRoot(trc, &script, "profilingScripts");
656 MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
657 }
658 }
659
660 if (nonSyntacticLexicalScopes_)
661 nonSyntacticLexicalScopes_->trace(trc);
662 }
663
664 void
sweepAfterMinorGC()665 JSCompartment::sweepAfterMinorGC()
666 {
667 globalWriteBarriered = false;
668
669 if (innerViews.needsSweepAfterMinorGC())
670 innerViews.sweepAfterMinorGC();
671 }
672
673 void
sweepInnerViews()674 JSCompartment::sweepInnerViews()
675 {
676 innerViews.sweep();
677 }
678
679 void
sweepSavedStacks()680 JSCompartment::sweepSavedStacks()
681 {
682 savedStacks_.sweep(runtimeFromAnyThread());
683 }
684
685 void
sweepGlobalObject(FreeOp * fop)686 JSCompartment::sweepGlobalObject(FreeOp* fop)
687 {
688 if (global_ && IsAboutToBeFinalized(&global_)) {
689 if (isDebuggee())
690 Debugger::detachAllDebuggersFromGlobal(fop, global_.unbarrieredGet());
691 global_.set(nullptr);
692 }
693 }
694
695 void
sweepObjectPendingMetadata()696 JSCompartment::sweepObjectPendingMetadata()
697 {
698 if (objectMetadataState.is<PendingMetadata>()) {
699 // We should never finalize an object before it gets its metadata! That
700 // would mean we aren't calling the object metadata callback for every
701 // object!
702 MOZ_ALWAYS_TRUE(!IsAboutToBeFinalized(&objectMetadataState.as<PendingMetadata>()));
703 }
704 }
705
706 void
sweepSelfHostingScriptSource()707 JSCompartment::sweepSelfHostingScriptSource()
708 {
709 if (selfHostingScriptSource.unbarrieredGet() &&
710 IsAboutToBeFinalized(&selfHostingScriptSource))
711 {
712 selfHostingScriptSource.set(nullptr);
713 }
714 }
715
716 void
sweepJitCompartment(FreeOp * fop)717 JSCompartment::sweepJitCompartment(FreeOp* fop)
718 {
719 if (jitCompartment_)
720 jitCompartment_->sweep(fop, this);
721 }
722
723 void
sweepRegExps()724 JSCompartment::sweepRegExps()
725 {
726 /*
727 * JIT code increments activeWarmUpCounter for any RegExpShared used by jit
728 * code for the lifetime of the JIT script. Thus, we must perform
729 * sweeping after clearing jit code.
730 */
731 regExps.sweep(runtimeFromAnyThread());
732 }
733
734 void
sweepDebugScopes()735 JSCompartment::sweepDebugScopes()
736 {
737 JSRuntime* rt = runtimeFromAnyThread();
738 if (debugScopes)
739 debugScopes->sweep(rt);
740 }
741
742 void
sweepNativeIterators()743 JSCompartment::sweepNativeIterators()
744 {
745 /* Sweep list of native iterators. */
746 NativeIterator* ni = enumerators->next();
747 while (ni != enumerators) {
748 JSObject* iterObj = ni->iterObj();
749 NativeIterator* next = ni->next();
750 if (gc::IsAboutToBeFinalizedUnbarriered(&iterObj))
751 ni->unlink();
752 ni = next;
753 }
754 }
755
756 /*
757 * Remove dead wrappers from the table. We must sweep all compartments, since
758 * string entries in the crossCompartmentWrappers table are not marked during
759 * markCrossCompartmentWrappers.
760 */
761 void
sweepCrossCompartmentWrappers()762 JSCompartment::sweepCrossCompartmentWrappers()
763 {
764 /* Remove dead wrappers from the table. */
765 for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
766 CrossCompartmentKey key = e.front().key();
767 bool keyDying;
768 switch (key.kind) {
769 case CrossCompartmentKey::ObjectWrapper:
770 case CrossCompartmentKey::DebuggerObject:
771 case CrossCompartmentKey::DebuggerEnvironment:
772 case CrossCompartmentKey::DebuggerSource:
773 MOZ_ASSERT(IsInsideNursery(key.wrapped) ||
774 key.wrapped->asTenured().getTraceKind() == JS::TraceKind::Object);
775 keyDying = IsAboutToBeFinalizedUnbarriered(
776 reinterpret_cast<JSObject**>(&key.wrapped));
777 break;
778 case CrossCompartmentKey::StringWrapper:
779 MOZ_ASSERT(key.wrapped->asTenured().getTraceKind() == JS::TraceKind::String);
780 keyDying = IsAboutToBeFinalizedUnbarriered(
781 reinterpret_cast<JSString**>(&key.wrapped));
782 break;
783 case CrossCompartmentKey::DebuggerScript:
784 MOZ_ASSERT(key.wrapped->asTenured().getTraceKind() == JS::TraceKind::Script);
785 keyDying = IsAboutToBeFinalizedUnbarriered(
786 reinterpret_cast<JSScript**>(&key.wrapped));
787 break;
788 default:
789 MOZ_CRASH("Unknown key kind");
790 }
791 bool valDying = IsAboutToBeFinalized(&e.front().value());
792 bool dbgDying = key.debugger && IsAboutToBeFinalizedUnbarriered(&key.debugger);
793 if (keyDying || valDying || dbgDying) {
794 MOZ_ASSERT(key.kind != CrossCompartmentKey::StringWrapper);
795 e.removeFront();
796 } else if (key.wrapped != e.front().key().wrapped ||
797 key.debugger != e.front().key().debugger)
798 {
799 e.rekeyFront(key);
800 }
801 }
802 }
803
804 void
sweepTemplateObjects()805 JSCompartment::sweepTemplateObjects()
806 {
807 if (mappedArgumentsTemplate_ && IsAboutToBeFinalized(&mappedArgumentsTemplate_))
808 mappedArgumentsTemplate_.set(nullptr);
809
810 if (unmappedArgumentsTemplate_ && IsAboutToBeFinalized(&unmappedArgumentsTemplate_))
811 unmappedArgumentsTemplate_.set(nullptr);
812 }
813
814 /* static */ void
fixupCrossCompartmentWrappersAfterMovingGC(JSTracer * trc)815 JSCompartment::fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc)
816 {
817 MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting());
818
819 for (CompartmentsIter comp(trc->runtime(), SkipAtoms); !comp.done(); comp.next()) {
820 // Sweep the wrapper map to update its pointers to the wrappers.
821 comp->sweepCrossCompartmentWrappers();
822 // Trace the wrappers in the map to update their edges to their referents.
823 comp->traceOutgoingCrossCompartmentWrappers(trc);
824 }
825 }
826
827 void
fixupAfterMovingGC()828 JSCompartment::fixupAfterMovingGC()
829 {
830 fixupGlobal();
831 fixupInitialShapeTable();
832 objectGroups.fixupTablesAfterMovingGC();
833
834 #ifdef DEBUG
835 // Assert that none of the JSScript pointers, which are used as key of the
836 // scriptCountsMap HashMap are moved. We do not mark these keys because we
837 // need weak references. We do not use a WeakMap because these entries would
838 // be collected before the JSScript::finalize calls which is used to
839 // summarized the content of the code coverage.
840 if (scriptCountsMap) {
841 for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront())
842 MOZ_ASSERT(!IsForwarded(r.front().key()));
843 }
844 #endif
845 }
846
847 void
fixupGlobal()848 JSCompartment::fixupGlobal()
849 {
850 GlobalObject* global = *global_.unsafeGet();
851 if (global)
852 global_.set(MaybeForwarded(global));
853 }
854
855 void
purge()856 JSCompartment::purge()
857 {
858 dtoaCache.purge();
859 }
860
861 void
clearTables()862 JSCompartment::clearTables()
863 {
864 global_.set(nullptr);
865
866 // No scripts should have run in this compartment. This is used when
867 // merging a compartment that has been used off thread into another
868 // compartment and zone.
869 MOZ_ASSERT(crossCompartmentWrappers.empty());
870 MOZ_ASSERT(!jitCompartment_);
871 MOZ_ASSERT(!debugScopes);
872 MOZ_ASSERT(enumerators->next() == enumerators);
873 MOZ_ASSERT(regExps.empty());
874
875 objectGroups.clearTables();
876 if (baseShapes.initialized())
877 baseShapes.clear();
878 if (initialShapes.initialized())
879 initialShapes.clear();
880 if (savedStacks_.initialized())
881 savedStacks_.clear();
882 }
883
884 void
setObjectMetadataCallback(js::ObjectMetadataCallback callback)885 JSCompartment::setObjectMetadataCallback(js::ObjectMetadataCallback callback)
886 {
887 // Clear any jitcode in the runtime, which behaves differently depending on
888 // whether there is a creation callback.
889 ReleaseAllJITCode(runtime_->defaultFreeOp());
890
891 objectMetadataCallback = callback;
892 }
893
894 void
clearObjectMetadata()895 JSCompartment::clearObjectMetadata()
896 {
897 js_delete(objectMetadataTable);
898 objectMetadataTable = nullptr;
899 }
900
901 void
setNewObjectMetadata(JSContext * cx,JSObject * obj)902 JSCompartment::setNewObjectMetadata(JSContext* cx, JSObject* obj)
903 {
904 assertSameCompartment(cx, this, obj);
905
906 if (JSObject* metadata = objectMetadataCallback(cx, obj)) {
907 AutoEnterOOMUnsafeRegion oomUnsafe;
908 assertSameCompartment(cx, metadata);
909 if (!objectMetadataTable) {
910 objectMetadataTable = cx->new_<ObjectWeakMap>(cx);
911 if (!objectMetadataTable || !objectMetadataTable->init())
912 oomUnsafe.crash("setNewObjectMetadata");
913 }
914 if (!objectMetadataTable->add(cx, obj, metadata))
915 oomUnsafe.crash("setNewObjectMetadata");
916 }
917 }
918
919 static bool
AddInnerLazyFunctionsFromScript(JSScript * script,AutoObjectVector & lazyFunctions)920 AddInnerLazyFunctionsFromScript(JSScript* script, AutoObjectVector& lazyFunctions)
921 {
922 if (!script->hasObjects())
923 return true;
924 ObjectArray* objects = script->objects();
925 for (size_t i = script->innerObjectsStart(); i < objects->length; i++) {
926 JSObject* obj = objects->vector[i];
927 if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
928 if (!lazyFunctions.append(obj))
929 return false;
930 }
931 }
932 return true;
933 }
934
935 static bool
AddLazyFunctionsForCompartment(JSContext * cx,AutoObjectVector & lazyFunctions,AllocKind kind)936 AddLazyFunctionsForCompartment(JSContext* cx, AutoObjectVector& lazyFunctions, AllocKind kind)
937 {
938 // Find all live root lazy functions in the compartment: those which have a
939 // source object, indicating that they have a parent, and which do not have
940 // an uncompiled enclosing script. The last condition is so that we don't
941 // compile lazy scripts whose enclosing scripts failed to compile,
942 // indicating that the lazy script did not escape the script.
943 //
944 // Some LazyScripts have a non-null |JSScript* script| pointer. We still
945 // want to delazify in that case: this pointer is weak so the JSScript
946 // could be destroyed at the next GC.
947
948 for (gc::ZoneCellIter i(cx->zone(), kind); !i.done(); i.next()) {
949 JSFunction* fun = &i.get<JSObject>()->as<JSFunction>();
950
951 // Sweeping is incremental; take care to not delazify functions that
952 // are about to be finalized. GC things referenced by objects that are
953 // about to be finalized (e.g., in slots) may already be freed.
954 if (gc::IsAboutToBeFinalizedUnbarriered(&fun) ||
955 fun->compartment() != cx->compartment())
956 {
957 continue;
958 }
959
960 if (fun->isInterpretedLazy()) {
961 LazyScript* lazy = fun->lazyScriptOrNull();
962 if (lazy && lazy->sourceObject() && !lazy->hasUncompiledEnclosingScript()) {
963 if (!lazyFunctions.append(fun))
964 return false;
965 }
966 }
967 }
968
969 return true;
970 }
971
972 static bool
CreateLazyScriptsForCompartment(JSContext * cx)973 CreateLazyScriptsForCompartment(JSContext* cx)
974 {
975 AutoObjectVector lazyFunctions(cx);
976
977 if (!AddLazyFunctionsForCompartment(cx, lazyFunctions, AllocKind::FUNCTION))
978 return false;
979
980 // Methods, for instance {get method() {}}, are extended functions that can
981 // be relazified, so we need to handle those as well.
982 if (!AddLazyFunctionsForCompartment(cx, lazyFunctions, AllocKind::FUNCTION_EXTENDED))
983 return false;
984
985 // Create scripts for each lazy function, updating the list of functions to
986 // process with any newly exposed inner functions in created scripts.
987 // A function cannot be delazified until its outer script exists.
988 for (size_t i = 0; i < lazyFunctions.length(); i++) {
989 JSFunction* fun = &lazyFunctions[i]->as<JSFunction>();
990
991 // lazyFunctions may have been populated with multiple functions for
992 // a lazy script.
993 if (!fun->isInterpretedLazy())
994 continue;
995
996 LazyScript* lazy = fun->lazyScript();
997 bool lazyScriptHadNoScript = !lazy->maybeScript();
998
999 JSScript* script = fun->getOrCreateScript(cx);
1000 if (!script)
1001 return false;
1002 if (lazyScriptHadNoScript && !AddInnerLazyFunctionsFromScript(script, lazyFunctions))
1003 return false;
1004 }
1005
1006 return true;
1007 }
1008
1009 bool
ensureDelazifyScriptsForDebugger(JSContext * cx)1010 JSCompartment::ensureDelazifyScriptsForDebugger(JSContext* cx)
1011 {
1012 MOZ_ASSERT(cx->compartment() == this);
1013 if (needsDelazificationForDebugger() && !CreateLazyScriptsForCompartment(cx))
1014 return false;
1015 debugModeBits &= ~DebuggerNeedsDelazification;
1016 return true;
1017 }
1018
1019 void
updateDebuggerObservesFlag(unsigned flag)1020 JSCompartment::updateDebuggerObservesFlag(unsigned flag)
1021 {
1022 MOZ_ASSERT(isDebuggee());
1023 MOZ_ASSERT(flag == DebuggerObservesAllExecution ||
1024 flag == DebuggerObservesCoverage ||
1025 flag == DebuggerObservesAsmJS);
1026
1027 GlobalObject* global = zone()->runtimeFromMainThread()->gc.isForegroundSweeping()
1028 ? unsafeUnbarrieredMaybeGlobal()
1029 : maybeGlobal();
1030 const GlobalObject::DebuggerVector* v = global->getDebuggers();
1031 for (Debugger * const* p = v->begin(); p != v->end(); p++) {
1032 Debugger* dbg = *p;
1033 if (flag == DebuggerObservesAllExecution ? dbg->observesAllExecution() :
1034 flag == DebuggerObservesCoverage ? dbg->observesCoverage() :
1035 dbg->observesAsmJS())
1036 {
1037 debugModeBits |= flag;
1038 return;
1039 }
1040 }
1041
1042 debugModeBits &= ~flag;
1043 }
1044
1045 void
unsetIsDebuggee()1046 JSCompartment::unsetIsDebuggee()
1047 {
1048 if (isDebuggee()) {
1049 debugModeBits &= ~DebuggerObservesMask;
1050 DebugScopes::onCompartmentUnsetIsDebuggee(this);
1051 }
1052 }
1053
1054 void
updateDebuggerObservesCoverage()1055 JSCompartment::updateDebuggerObservesCoverage()
1056 {
1057 bool previousState = debuggerObservesCoverage();
1058 updateDebuggerObservesFlag(DebuggerObservesCoverage);
1059 if (previousState == debuggerObservesCoverage())
1060 return;
1061
1062 if (debuggerObservesCoverage()) {
1063 // Interrupt any running interpreter frame. The scriptCounts are
1064 // allocated on demand when a script resume its execution.
1065 for (ActivationIterator iter(runtimeFromMainThread()); !iter.done(); ++iter) {
1066 if (iter->isInterpreter())
1067 iter->asInterpreter()->enableInterruptsUnconditionally();
1068 }
1069 return;
1070 }
1071
1072 // If code coverage is enabled by any other means, keep it.
1073 if (collectCoverage())
1074 return;
1075
1076 clearScriptCounts();
1077 }
1078
1079 bool
collectCoverage() const1080 JSCompartment::collectCoverage() const
1081 {
1082 return !JitOptions.disablePgo ||
1083 debuggerObservesCoverage() ||
1084 runtimeFromAnyThread()->profilingScripts ||
1085 runtimeFromAnyThread()->lcovOutput.isEnabled();
1086 }
1087
1088 void
clearScriptCounts()1089 JSCompartment::clearScriptCounts()
1090 {
1091 if (!scriptCountsMap)
1092 return;
1093
1094 // Clear all hasScriptCounts_ flags of JSScript, in order to release all
1095 // ScriptCounts entry of the current compartment.
1096 for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
1097 ScriptCounts* value = &r.front().value();
1098 r.front().key()->takeOverScriptCountsMapEntry(value);
1099 }
1100
1101 js_delete(scriptCountsMap);
1102 scriptCountsMap = nullptr;
1103 }
1104
1105 void
clearBreakpointsIn(FreeOp * fop,js::Debugger * dbg,HandleObject handler)1106 JSCompartment::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, HandleObject handler)
1107 {
1108 for (gc::ZoneCellIter i(zone(), gc::AllocKind::SCRIPT); !i.done(); i.next()) {
1109 JSScript* script = i.get<JSScript>();
1110 if (script->compartment() == this && script->hasAnyBreakpointsOrStepMode())
1111 script->clearBreakpointsIn(fop, dbg, handler);
1112 }
1113 }
1114
1115 void
addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,size_t * tiAllocationSiteTables,size_t * tiArrayTypeTables,size_t * tiObjectTypeTables,size_t * compartmentObject,size_t * compartmentTables,size_t * innerViewsArg,size_t * lazyArrayBuffersArg,size_t * objectMetadataTablesArg,size_t * crossCompartmentWrappersArg,size_t * regexpCompartment,size_t * savedStacksSet,size_t * nonSyntacticLexicalScopesArg)1116 JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
1117 size_t* tiAllocationSiteTables,
1118 size_t* tiArrayTypeTables,
1119 size_t* tiObjectTypeTables,
1120 size_t* compartmentObject,
1121 size_t* compartmentTables,
1122 size_t* innerViewsArg,
1123 size_t* lazyArrayBuffersArg,
1124 size_t* objectMetadataTablesArg,
1125 size_t* crossCompartmentWrappersArg,
1126 size_t* regexpCompartment,
1127 size_t* savedStacksSet,
1128 size_t* nonSyntacticLexicalScopesArg)
1129 {
1130 *compartmentObject += mallocSizeOf(this);
1131 objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
1132 tiArrayTypeTables, tiObjectTypeTables,
1133 compartmentTables);
1134 *compartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf)
1135 + initialShapes.sizeOfExcludingThis(mallocSizeOf);
1136 *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
1137 if (lazyArrayBuffers)
1138 *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf);
1139 if (objectMetadataTable)
1140 *objectMetadataTablesArg += objectMetadataTable->sizeOfIncludingThis(mallocSizeOf);
1141 *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
1142 *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf);
1143 *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
1144 if (nonSyntacticLexicalScopes_)
1145 *nonSyntacticLexicalScopesArg += nonSyntacticLexicalScopes_->sizeOfIncludingThis(mallocSizeOf);
1146 }
1147
1148 void
reportTelemetry()1149 JSCompartment::reportTelemetry()
1150 {
1151 // Only report telemetry for web content and add-ons, not chrome JS.
1152 if (isSystem_)
1153 return;
1154
1155 // Hazard analysis can't tell that the telemetry callbacks don't GC.
1156 JS::AutoSuppressGCAnalysis nogc;
1157
1158 int id = addonId
1159 ? JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS
1160 : JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT;
1161
1162 // Call back into Firefox's Telemetry reporter.
1163 for (size_t i = 0; i < DeprecatedLanguageExtensionCount; i++) {
1164 if (sawDeprecatedLanguageExtension[i])
1165 runtime_->addTelemetry(id, i);
1166 }
1167 }
1168
1169 void
addTelemetry(const char * filename,DeprecatedLanguageExtension e)1170 JSCompartment::addTelemetry(const char* filename, DeprecatedLanguageExtension e)
1171 {
1172 // Only report telemetry for web content and add-ons, not chrome JS.
1173 if (isSystem_ || (!addonId && (!filename || strncmp(filename, "http", 4) != 0)))
1174 return;
1175
1176 sawDeprecatedLanguageExtension[e] = true;
1177 }
1178
1179 HashNumber
randomHashCode()1180 JSCompartment::randomHashCode()
1181 {
1182 ensureRandomNumberGenerator();
1183 return HashNumber(randomNumberGenerator.ref().next());
1184 }
1185
1186 mozilla::HashCodeScrambler
randomHashCodeScrambler()1187 JSCompartment::randomHashCodeScrambler()
1188 {
1189 return mozilla::HashCodeScrambler(randomKeyGenerator_.next(),
1190 randomKeyGenerator_.next());
1191 }
1192
AutoSetNewObjectMetadata(ExclusiveContext * ecx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)1193 AutoSetNewObjectMetadata::AutoSetNewObjectMetadata(ExclusiveContext* ecx
1194 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
1195 : CustomAutoRooter(ecx)
1196 , cx_(ecx->maybeJSContext())
1197 , prevState_(ecx->compartment()->objectMetadataState)
1198 {
1199 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
1200 if (cx_)
1201 cx_->compartment()->objectMetadataState = NewObjectMetadataState(DelayMetadata());
1202 }
1203
~AutoSetNewObjectMetadata()1204 AutoSetNewObjectMetadata::~AutoSetNewObjectMetadata()
1205 {
1206 // If we don't have a cx, we didn't change the metadata state, so no need to
1207 // reset it here.
1208 if (!cx_)
1209 return;
1210
1211 if (!cx_->isExceptionPending() && cx_->compartment()->hasObjectPendingMetadata()) {
1212 JSObject* obj = cx_->compartment()->objectMetadataState.as<PendingMetadata>();
1213 // Make sure to restore the previous state before setting the object's
1214 // metadata. SetNewObjectMetadata asserts that the state is not
1215 // PendingMetadata in order to ensure that metadata callbacks are called
1216 // in order.
1217 cx_->compartment()->objectMetadataState = prevState_;
1218 SetNewObjectMetadata(cx_, obj);
1219 } else {
1220 cx_->compartment()->objectMetadataState = prevState_;
1221 }
1222 }
1223
1224