summaryrefslogtreecommitdiffstats
path: root/js/src/jscompartment.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jscompartment.cpp')
-rw-r--r--js/src/jscompartment.cpp1361
1 files changed, 1361 insertions, 0 deletions
diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp
new file mode 100644
index 000000000..4e4ccdf2a
--- /dev/null
+++ b/js/src/jscompartment.cpp
@@ -0,0 +1,1361 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jscompartmentinlines.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MemoryReporting.h"
+
+#include "jscntxt.h"
+#include "jsfriendapi.h"
+#include "jsgc.h"
+#include "jsiter.h"
+#include "jswatchpoint.h"
+#include "jswrapper.h"
+
+#include "gc/Marking.h"
+#include "gc/Policy.h"
+#include "jit/JitCompartment.h"
+#include "jit/JitOptions.h"
+#include "js/Date.h"
+#include "js/Proxy.h"
+#include "js/RootingAPI.h"
+#include "proxy/DeadObjectProxy.h"
+#include "vm/Debugger.h"
+#include "vm/StopIterationObject.h"
+#include "vm/WrapperObject.h"
+
+#include "jsatominlines.h"
+#include "jsfuninlines.h"
+#include "jsgcinlines.h"
+#include "jsobjinlines.h"
+#include "jsscriptinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::gc;
+using namespace js::jit;
+
+using mozilla::DebugOnly;
+using mozilla::PodArrayZero;
+
+JSCompartment::JSCompartment(Zone* zone, const JS::CompartmentOptions& options = JS::CompartmentOptions())
+ : creationOptions_(options.creationOptions()),
+ behaviors_(options.behaviors()),
+ zone_(zone),
+ runtime_(zone->runtimeFromMainThread()),
+ principals_(nullptr),
+ isSystem_(false),
+ isAtomsCompartment_(false),
+ isSelfHosting(false),
+ marked(true),
+ warnedAboutExprClosure(false),
+ warnedAboutForEach(false),
+#ifdef DEBUG
+ firedOnNewGlobalObject(false),
+#endif
+ global_(nullptr),
+ enterCompartmentDepth(0),
+ performanceMonitoring(runtime_),
+ data(nullptr),
+ allocationMetadataBuilder(nullptr),
+ lastAnimationTime(0),
+ regExps(runtime_),
+ globalWriteBarriered(0),
+ detachedTypedObjects(0),
+ objectMetadataState(ImmediateMetadata()),
+ selfHostingScriptSource(nullptr),
+ objectMetadataTable(nullptr),
+ innerViews(zone, InnerViewTable()),
+ lazyArrayBuffers(nullptr),
+ wasm(zone),
+ nonSyntacticLexicalEnvironments_(nullptr),
+ gcIncomingGrayPointers(nullptr),
+ debugModeBits(0),
+ randomKeyGenerator_(runtime_->forkRandomKeyGenerator()),
+ watchpointMap(nullptr),
+ scriptCountsMap(nullptr),
+ debugScriptMap(nullptr),
+ debugEnvs(nullptr),
+ enumerators(nullptr),
+ lastCachedNativeIterator(nullptr),
+ compartmentStats_(nullptr),
+ scheduledForDestruction(false),
+ maybeAlive(true),
+ jitCompartment_(nullptr),
+ mappedArgumentsTemplate_(nullptr),
+ unmappedArgumentsTemplate_(nullptr),
+ lcovOutput()
+{
+ PodArrayZero(sawDeprecatedLanguageExtension);
+ runtime_->numCompartments++;
+ MOZ_ASSERT_IF(creationOptions_.mergeable(),
+ creationOptions_.invisibleToDebugger());
+}
+
+JSCompartment::~JSCompartment()
+{
+ reportTelemetry();
+
+ // Write the code coverage information in a file.
+ JSRuntime* rt = runtimeFromMainThread();
+ if (rt->lcovOutput.isEnabled())
+ rt->lcovOutput.writeLCovResult(lcovOutput);
+
+ js_delete(jitCompartment_);
+ js_delete(watchpointMap);
+ js_delete(scriptCountsMap);
+ js_delete(debugScriptMap);
+ js_delete(debugEnvs);
+ js_delete(objectMetadataTable);
+ js_delete(lazyArrayBuffers);
+ js_delete(nonSyntacticLexicalEnvironments_),
+ js_free(enumerators);
+
+#ifdef DEBUG
+ // Avoid assertion destroying the unboxed layouts list if the embedding
+ // leaked GC things.
+ if (!rt->gc.shutdownCollectedEverything())
+ unboxedLayouts.clear();
+#endif
+
+ runtime_->numCompartments--;
+}
+
+bool
+JSCompartment::init(JSContext* maybecx)
+{
+ /*
+ * maybecx is null when called to create the atoms compartment from
+ * JSRuntime::init().
+ *
+ * As a hack, we clear our timezone cache every time we create a new
+ * compartment. This ensures that the cache is always relatively fresh, but
+ * shouldn't interfere with benchmarks that create tons of date objects
+ * (unless they also create tons of iframes, which seems unlikely).
+ */
+ JS::ResetTimeZone();
+
+ if (!crossCompartmentWrappers.init(0)) {
+ if (maybecx)
+ ReportOutOfMemory(maybecx);
+ return false;
+ }
+
+ if (!regExps.init(maybecx))
+ return false;
+
+ enumerators = NativeIterator::allocateSentinel(maybecx);
+ if (!enumerators)
+ return false;
+
+ if (!savedStacks_.init() || !varNames_.init()) {
+ if (maybecx)
+ ReportOutOfMemory(maybecx);
+ return false;
+ }
+
+ return true;
+}
+
+jit::JitRuntime*
+JSRuntime::createJitRuntime(JSContext* cx)
+{
+ // The shared stubs are created in the atoms compartment, which may be
+ // accessed by other threads with an exclusive context.
+ AutoLockForExclusiveAccess atomsLock(cx);
+
+ MOZ_ASSERT(!jitRuntime_);
+
+ if (!CanLikelyAllocateMoreExecutableMemory()) {
+ // Report OOM instead of potentially hitting the MOZ_CRASH below.
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ jit::JitRuntime* jrt = cx->new_<jit::JitRuntime>(cx->runtime());
+ if (!jrt)
+ return nullptr;
+
+ // Protect jitRuntime_ from being observed (by InterruptRunningJitCode)
+ // while it is being initialized. Unfortunately, initialization depends on
+ // jitRuntime_ being non-null, so we can't just wait to assign jitRuntime_.
+ JitRuntime::AutoPreventBackedgePatching apbp(cx->runtime(), jrt);
+ jitRuntime_ = jrt;
+
+ AutoEnterOOMUnsafeRegion noOOM;
+ if (!jitRuntime_->initialize(cx, atomsLock)) {
+ // Handling OOM here is complicated: if we delete jitRuntime_ now, we
+ // will destroy the ExecutableAllocator, even though there may still be
+ // JitCode instances holding references to ExecutablePools.
+ noOOM.crash("OOM in createJitRuntime");
+ }
+
+ return jitRuntime_;
+}
+
+bool
+JSCompartment::ensureJitCompartmentExists(JSContext* cx)
+{
+ using namespace js::jit;
+ if (jitCompartment_)
+ return true;
+
+ if (!zone()->getJitZone(cx))
+ return false;
+
+ /* Set the compartment early, so linking works. */
+ jitCompartment_ = cx->new_<JitCompartment>();
+
+ if (!jitCompartment_)
+ return false;
+
+ if (!jitCompartment_->initialize(cx)) {
+ js_delete(jitCompartment_);
+ jitCompartment_ = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef JSGC_HASH_TABLE_CHECKS
+namespace {
+struct CheckGCThingAfterMovingGCFunctor {
+ template <class T> void operator()(T* t) { CheckGCThingAfterMovingGC(*t); }
+};
+} // namespace (anonymous)
+
+void
+JSCompartment::checkWrapperMapAfterMovingGC()
+{
+ /*
+ * Assert that the postbarriers have worked and that nothing is left in
+ * wrapperMap that points into the nursery, and that the hash table entries
+ * are discoverable.
+ */
+ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
+ e.front().mutableKey().applyToWrapped(CheckGCThingAfterMovingGCFunctor());
+ e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor());
+
+ WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
+ MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
+ }
+}
+#endif
+
+bool
+JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
+ const js::Value& wrapper)
+{
+ MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
+ MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
+
+ if (!crossCompartmentWrappers.put(wrapped, wrapper)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+static JSString*
+CopyStringPure(JSContext* cx, JSString* str)
+{
+ /*
+ * Directly allocate the copy in the destination compartment, rather than
+ * first flattening it (and possibly allocating in source compartment),
+ * because we don't know whether the flattening will pay off later.
+ */
+
+ size_t len = str->length();
+ JSString* copy;
+ if (str->isLinear()) {
+ /* Only use AutoStableStringChars if the NoGC allocation fails. */
+ if (str->hasLatin1Chars()) {
+ JS::AutoCheckCannotGC nogc;
+ copy = NewStringCopyN<NoGC>(cx, str->asLinear().latin1Chars(nogc), len);
+ } else {
+ JS::AutoCheckCannotGC nogc;
+ copy = NewStringCopyNDontDeflate<NoGC>(cx, str->asLinear().twoByteChars(nogc), len);
+ }
+ if (copy)
+ return copy;
+
+ AutoStableStringChars chars(cx);
+ if (!chars.init(cx, str))
+ return nullptr;
+
+ return chars.isLatin1()
+ ? NewStringCopyN<CanGC>(cx, chars.latin1Range().begin().get(), len)
+ : NewStringCopyNDontDeflate<CanGC>(cx, chars.twoByteRange().begin().get(), len);
+ }
+
+ if (str->hasLatin1Chars()) {
+ ScopedJSFreePtr<Latin1Char> copiedChars;
+ if (!str->asRope().copyLatin1CharsZ(cx, copiedChars))
+ return nullptr;
+
+ return NewString<CanGC>(cx, copiedChars.forget(), len);
+ }
+
+ ScopedJSFreePtr<char16_t> copiedChars;
+ if (!str->asRope().copyTwoByteCharsZ(cx, copiedChars))
+ return nullptr;
+
+ return NewStringDontDeflate<CanGC>(cx, copiedChars.forget(), len);
+}
+
+bool
+JSCompartment::wrap(JSContext* cx, MutableHandleString strp)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(this));
+ MOZ_ASSERT(cx->compartment() == this);
+
+ /* If the string is already in this compartment, we are done. */
+ JSString* str = strp;
+ if (str->zoneFromAnyThread() == zone())
+ return true;
+
+ /* If the string is an atom, we don't have to copy. */
+ if (str->isAtom()) {
+ MOZ_ASSERT(str->isPermanentAtom() || str->zone()->isAtomsZone());
+ return true;
+ }
+
+ /* Check the cache. */
+ RootedValue key(cx, StringValue(str));
+ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
+ strp.set(p->value().get().toString());
+ return true;
+ }
+
+ /* No dice. Make a copy, and cache it. */
+ JSString* copy = CopyStringPure(cx, str);
+ if (!copy)
+ return false;
+ if (!putWrapper(cx, CrossCompartmentKey(key), StringValue(copy)))
+ return false;
+
+ strp.set(copy);
+ return true;
+}
+
+bool
+JSCompartment::getNonWrapperObjectForCurrentCompartment(JSContext* cx, MutableHandleObject obj)
+{
+ // Ensure that we have entered a compartment.
+ MOZ_ASSERT(cx->global());
+
+ // If we have a cross-compartment wrapper, make sure that the cx isn't
+ // associated with the self-hosting global. We don't want to create
+ // wrappers for objects in other runtimes, which may be the case for the
+ // self-hosting global.
+ MOZ_ASSERT(!cx->runtime()->isSelfHostingGlobal(cx->global()));
+ MOZ_ASSERT(!cx->runtime()->isSelfHostingGlobal(&obj->global()));
+
+ // The object is already in the right compartment. Normally same-
+ // compartment returns the object itself, however, windows are always
+ // wrapped by a proxy, so we have to check for that case here manually.
+ if (obj->compartment() == this) {
+ obj.set(ToWindowProxyIfWindow(obj));
+ return true;
+ }
+
+ // Note that if the object is same-compartment, but has been wrapped into a
+ // different compartment, we need to unwrap it and return the bare same-
+ // compartment object. Note again that windows are always wrapped by a
+ // WindowProxy even when same-compartment so take care not to strip this
+ // particular wrapper.
+ RootedObject objectPassedToWrap(cx, obj);
+ obj.set(UncheckedUnwrap(obj, /* stopAtWindowProxy = */ true));
+ if (obj->compartment() == this) {
+ MOZ_ASSERT(!IsWindow(obj));
+ return true;
+ }
+
+ // Translate StopIteration singleton.
+ if (obj->is<StopIterationObject>()) {
+ // StopIteration isn't a constructor, but it's stored in GlobalObject
+ // as one, out of laziness. Hence the GetBuiltinConstructor call here.
+ RootedObject stopIteration(cx);
+ if (!GetBuiltinConstructor(cx, JSProto_StopIteration, &stopIteration))
+ return false;
+ obj.set(stopIteration);
+ return true;
+ }
+
+ // Invoke the prewrap callback. The prewrap callback is responsible for
+ // doing similar reification as above, but can account for any additional
+ // embedder requirements.
+ //
+ // We're a bit worried about infinite recursion here, so we do a check -
+ // see bug 809295.
+ auto preWrap = cx->runtime()->wrapObjectCallbacks->preWrap;
+ JS_CHECK_SYSTEM_RECURSION(cx, return false);
+ if (preWrap) {
+ preWrap(cx, cx->global(), obj, objectPassedToWrap, obj);
+ if (!obj)
+ return false;
+ }
+ MOZ_ASSERT(!IsWindow(obj));
+
+ return true;
+}
+
+bool
+JSCompartment::getOrCreateWrapper(JSContext* cx, HandleObject existing, MutableHandleObject obj)
+{
+ // If we already have a wrapper for this value, use it.
+ RootedValue key(cx, ObjectValue(*obj));
+ if (WrapperMap::Ptr p = crossCompartmentWrappers.lookup(CrossCompartmentKey(key))) {
+ obj.set(&p->value().get().toObject());
+ MOZ_ASSERT(obj->is<CrossCompartmentWrapperObject>());
+ return true;
+ }
+
+ // Ensure that the wrappee is exposed in case we are creating a new wrapper
+ // for a gray object.
+ ExposeObjectToActiveJS(obj);
+
+ // Create a new wrapper for the object.
+ auto wrap = cx->runtime()->wrapObjectCallbacks->wrap;
+ RootedObject wrapper(cx, wrap(cx, existing, obj));
+ if (!wrapper)
+ return false;
+
+ // We maintain the invariant that the key in the cross-compartment wrapper
+ // map is always directly wrapped by the value.
+ MOZ_ASSERT(Wrapper::wrappedObject(wrapper) == &key.get().toObject());
+
+ if (!putWrapper(cx, CrossCompartmentKey(key), ObjectValue(*wrapper))) {
+ // Enforce the invariant that all cross-compartment wrapper object are
+ // in the map by nuking the wrapper if we couldn't add it.
+ // Unfortunately it's possible for the wrapper to still be marked if we
+ // took this path, for example if the object metadata callback stashes a
+ // reference to it.
+ if (wrapper->is<CrossCompartmentWrapperObject>())
+ NukeCrossCompartmentWrapper(cx, wrapper);
+ return false;
+ }
+
+ obj.set(wrapper);
+ return true;
+}
+
+bool
+JSCompartment::wrap(JSContext* cx, MutableHandleObject obj)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(this));
+ MOZ_ASSERT(cx->compartment() == this);
+
+ if (!obj)
+ return true;
+
+ AutoDisableProxyCheck adpc(cx->runtime());
+
+ // Anything we're wrapping has already escaped into script, so must have
+ // been unmarked-gray at some point in the past.
+ MOZ_ASSERT(!ObjectIsMarkedGray(obj));
+
+ // The passed object may already be wrapped, or may fit a number of special
+ // cases that we need to check for and manually correct.
+ if (!getNonWrapperObjectForCurrentCompartment(cx, obj))
+ return false;
+
+ // If the reification above did not result in a same-compartment object,
+ // get or create a new wrapper object in this compartment for it.
+ if (obj->compartment() != this) {
+ if (!getOrCreateWrapper(cx, nullptr, obj))
+ return false;
+ }
+
+ // Ensure that the wrapper is also exposed.
+ ExposeObjectToActiveJS(obj);
+ return true;
+}
+
+bool
+JSCompartment::rewrap(JSContext* cx, MutableHandleObject obj, HandleObject existingArg)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(this));
+ MOZ_ASSERT(cx->compartment() == this);
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(existingArg);
+ MOZ_ASSERT(existingArg->compartment() == cx->compartment());
+ MOZ_ASSERT(IsDeadProxyObject(existingArg));
+
+ AutoDisableProxyCheck adpc(cx->runtime());
+
+ // It may not be possible to re-use existing; if so, clear it so that we
+ // are forced to create a new wrapper. Note that this cannot call out to
+ // |wrap| because of the different gray unmarking semantics.
+ RootedObject existing(cx, existingArg);
+ if (existing->hasStaticPrototype() ||
+ // Note: Class asserted above, so all that's left to check is callability
+ existing->isCallable() ||
+ obj->isCallable())
+ {
+ existing.set(nullptr);
+ }
+
+ // The passed object may already be wrapped, or may fit a number of special
+ // cases that we need to check for and manually correct.
+ if (!getNonWrapperObjectForCurrentCompartment(cx, obj))
+ return false;
+
+ // If the reification above resulted in a same-compartment object, we do
+ // not need to create or return an existing wrapper.
+ if (obj->compartment() == this)
+ return true;
+
+ return getOrCreateWrapper(cx, existing, obj);
+}
+
+bool
+JSCompartment::wrap(JSContext* cx, MutableHandle<PropertyDescriptor> desc)
+{
+ if (!wrap(cx, desc.object()))
+ return false;
+
+ if (desc.hasGetterObject()) {
+ if (!wrap(cx, desc.getterObject()))
+ return false;
+ }
+ if (desc.hasSetterObject()) {
+ if (!wrap(cx, desc.setterObject()))
+ return false;
+ }
+
+ return wrap(cx, desc.value());
+}
+
+bool
+JSCompartment::wrap(JSContext* cx, MutableHandle<GCVector<Value>> vec)
+{
+ for (size_t i = 0; i < vec.length(); ++i) {
+ if (!wrap(cx, vec[i]))
+ return false;
+ }
+ return true;
+}
+
+LexicalEnvironmentObject*
+JSCompartment::getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx, HandleObject enclosing)
+{
+ if (!nonSyntacticLexicalEnvironments_) {
+ nonSyntacticLexicalEnvironments_ = cx->new_<ObjectWeakMap>(cx);
+ if (!nonSyntacticLexicalEnvironments_ || !nonSyntacticLexicalEnvironments_->init())
+ return nullptr;
+ }
+
+ // If a wrapped WithEnvironmentObject was passed in, unwrap it, as we may
+ // be creating different WithEnvironmentObject wrappers each time.
+ RootedObject key(cx, enclosing);
+ if (enclosing->is<WithEnvironmentObject>()) {
+ MOZ_ASSERT(!enclosing->as<WithEnvironmentObject>().isSyntactic());
+ key = &enclosing->as<WithEnvironmentObject>().object();
+ }
+ RootedObject lexicalEnv(cx, nonSyntacticLexicalEnvironments_->lookup(key));
+
+ if (!lexicalEnv) {
+ lexicalEnv = LexicalEnvironmentObject::createNonSyntactic(cx, enclosing);
+ if (!lexicalEnv)
+ return nullptr;
+ if (!nonSyntacticLexicalEnvironments_->add(cx, key, lexicalEnv))
+ return nullptr;
+ }
+
+ return &lexicalEnv->as<LexicalEnvironmentObject>();
+}
+
+LexicalEnvironmentObject*
+JSCompartment::getNonSyntacticLexicalEnvironment(JSObject* enclosing) const
+{
+ if (!nonSyntacticLexicalEnvironments_)
+ return nullptr;
+ // If a wrapped WithEnvironmentObject was passed in, unwrap it as in
+ // getOrCreateNonSyntacticLexicalEnvironment.
+ JSObject* key = enclosing;
+ if (enclosing->is<WithEnvironmentObject>()) {
+ MOZ_ASSERT(!enclosing->as<WithEnvironmentObject>().isSyntactic());
+ key = &enclosing->as<WithEnvironmentObject>().object();
+ }
+ JSObject* lexicalEnv = nonSyntacticLexicalEnvironments_->lookup(key);
+ if (!lexicalEnv)
+ return nullptr;
+ return &lexicalEnv->as<LexicalEnvironmentObject>();
+}
+
+bool
+JSCompartment::addToVarNames(JSContext* cx, JS::Handle<JSAtom*> name)
+{
+ MOZ_ASSERT(name);
+ MOZ_ASSERT(!isAtomsCompartment());
+
+ if (varNames_.put(name))
+ return true;
+
+ ReportOutOfMemory(cx);
+ return false;
+}
+
+void
+JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc)
+{
+ MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
+ MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->gc.isHeapCompacting());
+
+ for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
+ Value v = e.front().value().unbarrieredGet();
+ if (e.front().key().is<JSObject*>()) {
+ ProxyObject* wrapper = &v.toObject().as<ProxyObject>();
+
+ /*
+ * We have a cross-compartment wrapper. Its private pointer may
+ * point into the compartment being collected, so we should mark it.
+ */
+ TraceEdge(trc, wrapper->slotOfPrivate(), "cross-compartment wrapper");
+ }
+ }
+}
+
+/* static */ void
+JSCompartment::traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc)
+{
+ gcstats::AutoPhase ap(trc->runtime()->gc.stats, gcstats::PHASE_MARK_CCWS);
+ MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
+ for (CompartmentsIter c(trc->runtime(), SkipAtoms); !c.done(); c.next()) {
+ if (!c->zone()->isCollecting())
+ c->traceOutgoingCrossCompartmentWrappers(trc);
+ }
+ Debugger::markIncomingCrossCompartmentEdges(trc);
+}
+
+void
+JSCompartment::trace(JSTracer* trc)
+{
+ savedStacks_.trace(trc);
+
+ // Atoms are always tenured.
+ if (!trc->runtime()->isHeapMinorCollecting())
+ varNames_.trace(trc);
+}
+
+void
+JSCompartment::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark)
+{
+ if (objectMetadataState.is<PendingMetadata>()) {
+ TraceRoot(trc,
+ &objectMetadataState.as<PendingMetadata>(),
+ "on-stack object pending metadata");
+ }
+
+ if (!trc->runtime()->isHeapMinorCollecting()) {
+ // JIT code and the global are never nursery allocated, so we only need
+ // to trace them when not doing a minor collection.
+
+ if (jitCompartment_)
+ jitCompartment_->mark(trc, this);
+
+ // If a compartment is on-stack, we mark its global so that
+ // JSContext::global() remains valid.
+ if (enterCompartmentDepth && global_.unbarrieredGet())
+ TraceRoot(trc, global_.unsafeUnbarrieredForTracing(), "on-stack compartment global");
+ }
+
+ // Nothing below here needs to be treated as a root if we aren't marking
+ // this zone for a collection.
+ if (traceOrMark == js::gc::GCRuntime::MarkRuntime && !zone()->isCollecting())
+ return;
+
+ // During a GC, these are treated as weak pointers.
+ if (traceOrMark == js::gc::GCRuntime::TraceRuntime) {
+ if (watchpointMap)
+ watchpointMap->markAll(trc);
+ }
+
+ /* Mark debug scopes, if present */
+ if (debugEnvs)
+ debugEnvs->mark(trc);
+
+ if (lazyArrayBuffers)
+ lazyArrayBuffers->trace(trc);
+
+ if (objectMetadataTable)
+ objectMetadataTable->trace(trc);
+
+ // If code coverage is only enabled with the Debugger or the LCovOutput,
+ // then the following comment holds.
+ //
+ // The scriptCountsMap maps JSScript weak-pointers to ScriptCounts
+ // structures. It uses a HashMap instead of a WeakMap, so that we can keep
+ // the data alive for the JSScript::finalize call. Thus, we do not trace the
+ // keys of the HashMap to avoid adding a strong reference to the JSScript
+ // pointers.
+ //
+ // If the code coverage is either enabled with the --dump-bytecode command
+ // line option, or with the PCCount JSFriend API functions, then we mark the
+ // keys of the map to hold the JSScript alive.
+ if (scriptCountsMap &&
+ trc->runtime()->profilingScripts &&
+ !trc->runtime()->isHeapMinorCollecting())
+ {
+ MOZ_ASSERT_IF(!trc->runtime()->isBeingDestroyed(), collectCoverage());
+ for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
+ JSScript* script = const_cast<JSScript*>(r.front().key());
+ MOZ_ASSERT(script->hasScriptCounts());
+ TraceRoot(trc, &script, "profilingScripts");
+ MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around");
+ }
+ }
+
+ if (nonSyntacticLexicalEnvironments_)
+ nonSyntacticLexicalEnvironments_->trace(trc);
+
+ wasm.trace(trc);
+}
+
+void
+JSCompartment::finishRoots()
+{
+ if (watchpointMap)
+ watchpointMap->clear();
+
+ if (debugEnvs)
+ debugEnvs->finish();
+
+ if (lazyArrayBuffers)
+ lazyArrayBuffers->clear();
+
+ if (objectMetadataTable)
+ objectMetadataTable->clear();
+
+ clearScriptCounts();
+
+ if (nonSyntacticLexicalEnvironments_)
+ nonSyntacticLexicalEnvironments_->clear();
+}
+
+void
+JSCompartment::sweepAfterMinorGC(JSTracer* trc)
+{
+ globalWriteBarriered = 0;
+
+ if (innerViews.needsSweepAfterMinorGC())
+ innerViews.sweepAfterMinorGC();
+
+ crossCompartmentWrappers.sweepAfterMinorGC(trc);
+}
+
+void
+JSCompartment::sweepSavedStacks()
+{
+ savedStacks_.sweep();
+}
+
+void
+JSCompartment::sweepGlobalObject(FreeOp* fop)
+{
+ if (global_ && IsAboutToBeFinalized(&global_)) {
+ if (isDebuggee())
+ Debugger::detachAllDebuggersFromGlobal(fop, global_.unbarrieredGet());
+ global_.set(nullptr);
+ }
+}
+
+void
+JSCompartment::sweepSelfHostingScriptSource()
+{
+ if (selfHostingScriptSource.unbarrieredGet() &&
+ IsAboutToBeFinalized(&selfHostingScriptSource))
+ {
+ selfHostingScriptSource.set(nullptr);
+ }
+}
+
+void
+JSCompartment::sweepJitCompartment(FreeOp* fop)
+{
+ if (jitCompartment_)
+ jitCompartment_->sweep(fop, this);
+}
+
+void
+JSCompartment::sweepRegExps()
+{
+ /*
+ * JIT code increments activeWarmUpCounter for any RegExpShared used by jit
+ * code for the lifetime of the JIT script. Thus, we must perform
+ * sweeping after clearing jit code.
+ */
+ regExps.sweep(runtimeFromAnyThread());
+}
+
+void
+JSCompartment::sweepDebugEnvironments()
+{
+ JSRuntime* rt = runtimeFromAnyThread();
+ if (debugEnvs)
+ debugEnvs->sweep(rt);
+}
+
+void
+JSCompartment::sweepNativeIterators()
+{
+ /* Sweep list of native iterators. */
+ NativeIterator* ni = enumerators->next();
+ while (ni != enumerators) {
+ JSObject* iterObj = ni->iterObj();
+ NativeIterator* next = ni->next();
+ if (gc::IsAboutToBeFinalizedUnbarriered(&iterObj))
+ ni->unlink();
+ ni = next;
+ }
+}
+
+/*
+ * Remove dead wrappers from the table. We must sweep all compartments, since
+ * string entries in the crossCompartmentWrappers table are not marked during
+ * markCrossCompartmentWrappers.
+ */
+void
+JSCompartment::sweepCrossCompartmentWrappers()
+{
+ crossCompartmentWrappers.sweep();
+}
+
+void
+JSCompartment::sweepVarNames()
+{
+ varNames_.sweep();
+}
+
+namespace {
+struct TraceRootFunctor {
+ JSTracer* trc;
+ const char* name;
+ TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {}
+ template <class T> void operator()(T* t) { return TraceRoot(trc, t, name); }
+};
+struct NeedsSweepUnbarrieredFunctor {
+ template <class T> bool operator()(T* t) const { return IsAboutToBeFinalizedUnbarriered(t); }
+};
+} // namespace (anonymous)
+
+void
+CrossCompartmentKey::trace(JSTracer* trc)
+{
+ applyToWrapped(TraceRootFunctor(trc, "CrossCompartmentKey::wrapped"));
+ applyToDebugger(TraceRootFunctor(trc, "CrossCompartmentKey::debugger"));
+}
+
+bool
+CrossCompartmentKey::needsSweep()
+{
+ return applyToWrapped(NeedsSweepUnbarrieredFunctor()) ||
+ applyToDebugger(NeedsSweepUnbarrieredFunctor());
+}
+
+void
+JSCompartment::sweepTemplateObjects()
+{
+ if (mappedArgumentsTemplate_ && IsAboutToBeFinalized(&mappedArgumentsTemplate_))
+ mappedArgumentsTemplate_.set(nullptr);
+
+ if (unmappedArgumentsTemplate_ && IsAboutToBeFinalized(&unmappedArgumentsTemplate_))
+ unmappedArgumentsTemplate_.set(nullptr);
+}
+
+/* static */ void
+JSCompartment::fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc)
+{
+ MOZ_ASSERT(trc->runtime()->gc.isHeapCompacting());
+
+ for (CompartmentsIter comp(trc->runtime(), SkipAtoms); !comp.done(); comp.next()) {
+ // Sweep the wrapper map to update its pointers to the wrappers.
+ comp->sweepCrossCompartmentWrappers();
+ // Trace the wrappers in the map to update their edges to their referents.
+ comp->traceOutgoingCrossCompartmentWrappers(trc);
+ }
+}
+
+void
+JSCompartment::fixupAfterMovingGC()
+{
+ purge();
+ fixupGlobal();
+ objectGroups.fixupTablesAfterMovingGC();
+ fixupScriptMapsAfterMovingGC();
+}
+
+void
+JSCompartment::fixupGlobal()
+{
+ GlobalObject* global = *global_.unsafeGet();
+ if (global)
+ global_.set(MaybeForwarded(global));
+}
+
+void
+JSCompartment::fixupScriptMapsAfterMovingGC()
+{
+ // Map entries are removed by JSScript::finalize, but we need to update the
+ // script pointers here in case they are moved by the GC.
+
+ if (scriptCountsMap) {
+ for (ScriptCountsMap::Enum e(*scriptCountsMap); !e.empty(); e.popFront()) {
+ JSScript* script = e.front().key();
+ if (!IsAboutToBeFinalizedUnbarriered(&script) && script != e.front().key())
+ e.rekeyFront(script);
+ }
+ }
+
+ if (debugScriptMap) {
+ for (DebugScriptMap::Enum e(*debugScriptMap); !e.empty(); e.popFront()) {
+ JSScript* script = e.front().key();
+ if (!IsAboutToBeFinalizedUnbarriered(&script) && script != e.front().key())
+ e.rekeyFront(script);
+ }
+ }
+}
+
+#ifdef JSGC_HASH_TABLE_CHECKS
+void
+JSCompartment::checkScriptMapsAfterMovingGC()
+{
+ if (scriptCountsMap) {
+ for (auto r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
+ JSScript* script = r.front().key();
+ CheckGCThingAfterMovingGC(script);
+ auto ptr = scriptCountsMap->lookup(script);
+ MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
+ }
+ }
+
+ if (debugScriptMap) {
+ for (auto r = debugScriptMap->all(); !r.empty(); r.popFront()) {
+ JSScript* script = r.front().key();
+ CheckGCThingAfterMovingGC(script);
+ DebugScript* ds = r.front().value();
+ for (uint32_t i = 0; i < ds->numSites; i++) {
+ BreakpointSite* site = ds->breakpoints[i];
+ if (site)
+ CheckGCThingAfterMovingGC(site->script);
+ }
+ auto ptr = debugScriptMap->lookup(script);
+ MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front());
+ }
+ }
+}
+#endif
+
+void
+JSCompartment::purge()
+{
+ dtoaCache.purge();
+ lastCachedNativeIterator = nullptr;
+}
+
+void
+JSCompartment::clearTables()
+{
+ global_.set(nullptr);
+
+ // No scripts should have run in this compartment. This is used when
+ // merging a compartment that has been used off thread into another
+ // compartment and zone.
+ MOZ_ASSERT(crossCompartmentWrappers.empty());
+ MOZ_ASSERT(!jitCompartment_);
+ MOZ_ASSERT(!debugEnvs);
+ MOZ_ASSERT(enumerators->next() == enumerators);
+ MOZ_ASSERT(regExps.empty());
+
+ objectGroups.clearTables();
+ if (savedStacks_.initialized())
+ savedStacks_.clear();
+ if (varNames_.initialized())
+ varNames_.clear();
+}
+
+void
+JSCompartment::setAllocationMetadataBuilder(const js::AllocationMetadataBuilder *builder)
+{
+ // Clear any jitcode in the runtime, which behaves differently depending on
+ // whether there is a creation callback.
+ ReleaseAllJITCode(runtime_->defaultFreeOp());
+
+ allocationMetadataBuilder = builder;
+}
+
+void
+JSCompartment::clearObjectMetadata()
+{
+ js_delete(objectMetadataTable);
+ objectMetadataTable = nullptr;
+}
+
+void
+JSCompartment::setNewObjectMetadata(JSContext* cx, HandleObject obj)
+{
+ assertSameCompartment(cx, this, obj);
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (JSObject* metadata = allocationMetadataBuilder->build(cx, obj, oomUnsafe)) {
+ assertSameCompartment(cx, metadata);
+ if (!objectMetadataTable) {
+ objectMetadataTable = cx->new_<ObjectWeakMap>(cx);
+ if (!objectMetadataTable || !objectMetadataTable->init())
+ oomUnsafe.crash("setNewObjectMetadata");
+ }
+ if (!objectMetadataTable->add(cx, obj, metadata))
+ oomUnsafe.crash("setNewObjectMetadata");
+ }
+}
+
+static bool
+AddInnerLazyFunctionsFromScript(JSScript* script, AutoObjectVector& lazyFunctions)
+{
+ if (!script->hasObjects())
+ return true;
+ ObjectArray* objects = script->objects();
+ for (size_t i = 0; i < objects->length; i++) {
+ JSObject* obj = objects->vector[i];
+ if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
+ if (!lazyFunctions.append(obj))
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+AddLazyFunctionsForCompartment(JSContext* cx, AutoObjectVector& lazyFunctions, AllocKind kind)
+{
+ // Find all live root lazy functions in the compartment: those which have a
+ // source object, indicating that they have a parent, and which do not have
+ // an uncompiled enclosing script. The last condition is so that we don't
+ // compile lazy scripts whose enclosing scripts failed to compile,
+ // indicating that the lazy script did not escape the script.
+ //
+ // Some LazyScripts have a non-null |JSScript* script| pointer. We still
+ // want to delazify in that case: this pointer is weak so the JSScript
+ // could be destroyed at the next GC.
+
+ for (auto i = cx->zone()->cellIter<JSObject>(kind); !i.done(); i.next()) {
+ JSFunction* fun = &i->as<JSFunction>();
+
+ // Sweeping is incremental; take care to not delazify functions that
+ // are about to be finalized. GC things referenced by objects that are
+ // about to be finalized (e.g., in slots) may already be freed.
+ if (gc::IsAboutToBeFinalizedUnbarriered(&fun) ||
+ fun->compartment() != cx->compartment())
+ {
+ continue;
+ }
+
+ if (fun->isInterpretedLazy()) {
+ LazyScript* lazy = fun->lazyScriptOrNull();
+ if (lazy && lazy->sourceObject() && !lazy->hasUncompiledEnclosingScript()) {
+ if (!lazyFunctions.append(fun))
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static bool
+CreateLazyScriptsForCompartment(JSContext* cx)
+{
+ AutoObjectVector lazyFunctions(cx);
+
+ if (!AddLazyFunctionsForCompartment(cx, lazyFunctions, AllocKind::FUNCTION))
+ return false;
+
+ // Methods, for instance {get method() {}}, are extended functions that can
+ // be relazified, so we need to handle those as well.
+ if (!AddLazyFunctionsForCompartment(cx, lazyFunctions, AllocKind::FUNCTION_EXTENDED))
+ return false;
+
+ // Create scripts for each lazy function, updating the list of functions to
+ // process with any newly exposed inner functions in created scripts.
+ // A function cannot be delazified until its outer script exists.
+ for (size_t i = 0; i < lazyFunctions.length(); i++) {
+ JSFunction* fun = &lazyFunctions[i]->as<JSFunction>();
+
+ // lazyFunctions may have been populated with multiple functions for
+ // a lazy script.
+ if (!fun->isInterpretedLazy())
+ continue;
+
+ LazyScript* lazy = fun->lazyScript();
+ bool lazyScriptHadNoScript = !lazy->maybeScript();
+
+ JSScript* script = fun->getOrCreateScript(cx);
+ if (!script)
+ return false;
+ if (lazyScriptHadNoScript && !AddInnerLazyFunctionsFromScript(script, lazyFunctions))
+ return false;
+ }
+
+ return true;
+}
+
+bool
+JSCompartment::ensureDelazifyScriptsForDebugger(JSContext* cx)
+{
+ MOZ_ASSERT(cx->compartment() == this);
+ if (needsDelazificationForDebugger() && !CreateLazyScriptsForCompartment(cx))
+ return false;
+ debugModeBits &= ~DebuggerNeedsDelazification;
+ return true;
+}
+
+void
+JSCompartment::updateDebuggerObservesFlag(unsigned flag)
+{
+ MOZ_ASSERT(isDebuggee());
+ MOZ_ASSERT(flag == DebuggerObservesAllExecution ||
+ flag == DebuggerObservesCoverage ||
+ flag == DebuggerObservesAsmJS);
+
+ GlobalObject* global = zone()->runtimeFromMainThread()->gc.isForegroundSweeping()
+ ? unsafeUnbarrieredMaybeGlobal()
+ : maybeGlobal();
+ const GlobalObject::DebuggerVector* v = global->getDebuggers();
+ for (auto p = v->begin(); p != v->end(); p++) {
+ Debugger* dbg = *p;
+ if (flag == DebuggerObservesAllExecution ? dbg->observesAllExecution() :
+ flag == DebuggerObservesCoverage ? dbg->observesCoverage() :
+ dbg->observesAsmJS())
+ {
+ debugModeBits |= flag;
+ return;
+ }
+ }
+
+ debugModeBits &= ~flag;
+}
+
+void
+JSCompartment::unsetIsDebuggee()
+{
+ if (isDebuggee()) {
+ debugModeBits &= ~DebuggerObservesMask;
+ DebugEnvironments::onCompartmentUnsetIsDebuggee(this);
+ }
+}
+
+void
+JSCompartment::updateDebuggerObservesCoverage()
+{
+ bool previousState = debuggerObservesCoverage();
+ updateDebuggerObservesFlag(DebuggerObservesCoverage);
+ if (previousState == debuggerObservesCoverage())
+ return;
+
+ if (debuggerObservesCoverage()) {
+ // Interrupt any running interpreter frame. The scriptCounts are
+ // allocated on demand when a script resume its execution.
+ for (ActivationIterator iter(runtimeFromMainThread()); !iter.done(); ++iter) {
+ if (iter->isInterpreter())
+ iter->asInterpreter()->enableInterruptsUnconditionally();
+ }
+ return;
+ }
+
+ // If code coverage is enabled by any other means, keep it.
+ if (collectCoverage())
+ return;
+
+ clearScriptCounts();
+}
+
+bool
+JSCompartment::collectCoverage() const
+{
+ return collectCoverageForPGO() ||
+ collectCoverageForDebug();
+}
+
+bool
+JSCompartment::collectCoverageForPGO() const
+{
+ return !JitOptions.disablePgo;
+}
+
+bool
+JSCompartment::collectCoverageForDebug() const
+{
+ return debuggerObservesCoverage() ||
+ runtimeFromAnyThread()->profilingScripts ||
+ runtimeFromAnyThread()->lcovOutput.isEnabled();
+}
+
+void
+JSCompartment::clearScriptCounts()
+{
+ if (!scriptCountsMap)
+ return;
+
+ // Clear all hasScriptCounts_ flags of JSScript, in order to release all
+ // ScriptCounts entry of the current compartment.
+ for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) {
+ ScriptCounts* value = r.front().value();
+ r.front().key()->takeOverScriptCountsMapEntry(value);
+ js_delete(value);
+ }
+
+ js_delete(scriptCountsMap);
+ scriptCountsMap = nullptr;
+}
+
+void
+JSCompartment::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, HandleObject handler)
+{
+ for (auto script = zone()->cellIter<JSScript>(); !script.done(); script.next()) {
+ if (script->compartment() == this && script->hasAnyBreakpointsOrStepMode())
+ script->clearBreakpointsIn(fop, dbg, handler);
+ }
+}
+
+void
+JSCompartment::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* varNamesSet,
+ size_t* nonSyntacticLexicalEnvironmentsArg,
+ size_t* jitCompartment,
+ size_t* privateData)
+{
+ *compartmentObject += mallocSizeOf(this);
+ objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
+ tiArrayTypeTables, tiObjectTypeTables,
+ compartmentTables);
+ wasm.addSizeOfExcludingThis(mallocSizeOf, compartmentTables);
+ *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
+ if (lazyArrayBuffers)
+ *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf);
+ if (objectMetadataTable)
+ *objectMetadataTablesArg += objectMetadataTable->sizeOfIncludingThis(mallocSizeOf);
+ *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
+ *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf);
+ *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
+ *varNamesSet += varNames_.sizeOfExcludingThis(mallocSizeOf);
+ if (nonSyntacticLexicalEnvironments_)
+ *nonSyntacticLexicalEnvironmentsArg +=
+ nonSyntacticLexicalEnvironments_->sizeOfIncludingThis(mallocSizeOf);
+ if (jitCompartment_)
+ *jitCompartment += jitCompartment_->sizeOfIncludingThis(mallocSizeOf);
+
+ auto callback = runtime_->sizeOfIncludingThisCompartmentCallback;
+ if (callback)
+ *privateData += callback(mallocSizeOf, this);
+}
+
+void
+JSCompartment::reportTelemetry()
+{
+ // Only report telemetry for web content and add-ons, not chrome JS.
+ if (isSystem_)
+ return;
+
+ // Hazard analysis can't tell that the telemetry callbacks don't GC.
+ JS::AutoSuppressGCAnalysis nogc;
+
+ int id = creationOptions_.addonIdOrNull()
+ ? JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS
+ : JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT;
+
+ // Call back into Firefox's Telemetry reporter.
+ for (size_t i = 0; i < DeprecatedLanguageExtensionCount; i++) {
+ if (sawDeprecatedLanguageExtension[i])
+ runtime_->addTelemetry(id, i);
+ }
+}
+
+void
+JSCompartment::addTelemetry(const char* filename, DeprecatedLanguageExtension e)
+{
+ // Only report telemetry for web content and add-ons, not chrome JS.
+ if (isSystem_)
+ return;
+ if (!creationOptions_.addonIdOrNull() && (!filename || strncmp(filename, "http", 4) != 0))
+ return;
+
+ sawDeprecatedLanguageExtension[e] = true;
+}
+
+HashNumber
+JSCompartment::randomHashCode()
+{
+ ensureRandomNumberGenerator();
+ return HashNumber(randomNumberGenerator.ref().next());
+}
+
+mozilla::HashCodeScrambler
+JSCompartment::randomHashCodeScrambler()
+{
+ return mozilla::HashCodeScrambler(randomKeyGenerator_.next(),
+ randomKeyGenerator_.next());
+}
+
+AutoSetNewObjectMetadata::AutoSetNewObjectMetadata(ExclusiveContext* ecx
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+ : CustomAutoRooter(ecx)
+ , cx_(ecx->maybeJSContext())
+ , prevState_(ecx->compartment()->objectMetadataState)
+{
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ if (cx_)
+ cx_->compartment()->objectMetadataState = NewObjectMetadataState(DelayMetadata());
+}
+
+AutoSetNewObjectMetadata::~AutoSetNewObjectMetadata()
+{
+ // If we don't have a cx, we didn't change the metadata state, so no need to
+ // reset it here.
+ if (!cx_)
+ return;
+
+ if (!cx_->isExceptionPending() && cx_->compartment()->hasObjectPendingMetadata()) {
+ // This destructor often runs upon exit from a function that is
+ // returning an unrooted pointer to a Cell. The allocation metadata
+ // callback often allocates; if it causes a GC, then the Cell pointer
+ // being returned won't be traced or relocated.
+ //
+ // The only extant callbacks are those internal to SpiderMonkey that
+ // capture the JS stack. In fact, we're considering removing general
+ // callbacks altogther in bug 1236748. Since it's not running arbitrary
+ // code, it's adequate to simply suppress GC while we run the callback.
+ AutoSuppressGC autoSuppressGC(cx_);
+
+ JSObject* obj = cx_->compartment()->objectMetadataState.as<PendingMetadata>();
+
+ // Make sure to restore the previous state before setting the object's
+ // metadata. SetNewObjectMetadata asserts that the state is not
+ // PendingMetadata in order to ensure that metadata callbacks are called
+ // in order.
+ cx_->compartment()->objectMetadataState = prevState_;
+
+ obj = SetNewObjectMetadata(cx_, obj);
+ } else {
+ cx_->compartment()->objectMetadataState = prevState_;
+ }
+}
+