/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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 "vm/Realm-inl.h" #include "mozilla/MemoryReporting.h" #include #include "jsfriendapi.h" #include "gc/Policy.h" #include "gc/PublicIterators.h" #include "jit/JitOptions.h" #include "jit/JitRealm.h" #include "js/Date.h" #include "js/Proxy.h" #include "js/RootingAPI.h" #include "js/Wrapper.h" #include "proxy/DeadObjectProxy.h" #include "vm/DateTime.h" #include "vm/Debugger.h" #include "vm/Iteration.h" #include "vm/JSContext.h" #include "vm/WrapperObject.h" #include "gc/GC-inl.h" #include "gc/Marking-inl.h" #include "vm/JSAtom-inl.h" #include "vm/JSFunction-inl.h" #include "vm/JSObject-inl.h" #include "vm/JSScript-inl.h" #include "vm/NativeObject-inl.h" #include "vm/UnboxedObject-inl.h" using namespace js; ObjectRealm::ObjectRealm(JS::Zone* zone) : innerViews(zone) {} ObjectRealm::~ObjectRealm() { MOZ_ASSERT(enumerators == iteratorSentinel_.get()); } Realm::Realm(Compartment* comp, const JS::RealmOptions& options) : JS::shadow::Realm(comp), zone_(comp->zone()), runtime_(comp->runtimeFromMainThread()), creationOptions_(options.creationOptions()), behaviors_(options.behaviors()), global_(nullptr), objects_(zone_), randomKeyGenerator_(runtime_->forkRandomKeyGenerator()), wasm(runtime_) { MOZ_ASSERT_IF(creationOptions_.mergeable(), creationOptions_.invisibleToDebugger()); runtime_->numRealms++; } Realm::~Realm() { MOZ_ASSERT(!hasBeenEnteredIgnoringJit()); // Write the code coverage information in a file. JSRuntime* rt = runtimeFromMainThread(); if (rt->lcovOutput().isEnabled()) { rt->lcovOutput().writeLCovResult(lcovOutput); } #ifdef DEBUG // Avoid assertion destroying the unboxed layouts list if the embedding // leaked GC things. if (!runtime_->gc.shutdownCollectedEverything()) { objectGroups_.unboxedLayouts.clear(); } #endif MOZ_ASSERT(runtime_->numRealms > 0); runtime_->numRealms--; } bool ObjectRealm::init(JSContext* cx) { NativeIteratorSentinel sentinel(NativeIterator::allocateSentinel(cx)); if (!sentinel) { return false; } iteratorSentinel_ = std::move(sentinel); enumerators = iteratorSentinel_.get(); return true; } bool Realm::init(JSContext* cx, JSPrincipals* principals) { /* * As a hack, we clear our timezone cache every time we create a new realm. * 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::ResetTimeZoneInternal(ResetTimeZoneMode::DontResetIfOffsetUnchanged); if (!objects_.init(cx)) { return false; } if (principals) { // Any realm with the trusted principals -- and there can be // multiple -- is a system realm. isSystem_ = (principals == cx->runtime()->trustedPrincipals()); JS_HoldPrincipals(principals); principals_ = principals; } return true; } bool JSRuntime::createJitRuntime(JSContext* cx) { using namespace js::jit; MOZ_ASSERT(!jitRuntime_); if (!CanLikelyAllocateMoreExecutableMemory()) { // Report OOM instead of potentially hitting the MOZ_CRASH below, but first // try to release memory. if (OnLargeAllocationFailure) { OnLargeAllocationFailure(); } if (!CanLikelyAllocateMoreExecutableMemory()) { ReportOutOfMemory(cx); return false; } } jit::JitRuntime* jrt = cx->new_(); if (!jrt) { return false; } // Unfortunately, initialization depends on jitRuntime_ being non-null, so // we can't just wait to assign jitRuntime_. jitRuntime_ = jrt; AutoEnterOOMUnsafeRegion noOOM; if (!jitRuntime_->initialize(cx)) { // 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 true; } bool Realm::ensureJitRealmExists(JSContext* cx) { using namespace js::jit; if (jitRealm_) { return true; } if (!zone()->getJitZone(cx)) { return false; } UniquePtr jitRealm = cx->make_unique(); if (!jitRealm) { return false; } if (!jitRealm->initialize(cx)) { return false; } jitRealm_ = std::move(jitRealm); return true; } #ifdef JSGC_HASH_TABLE_CHECKS void js::DtoaCache::checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); } namespace { struct CheckGCThingAfterMovingGCFunctor { template void operator()(T* t) { CheckGCThingAfterMovingGC(*t); } }; } // namespace #endif // JSGC_HASH_TABLE_CHECKS LexicalEnvironmentObject* ObjectRealm::getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx, HandleObject enclosing, HandleObject key, HandleObject thisv) { MOZ_ASSERT(&ObjectRealm::get(enclosing) == this); if (!nonSyntacticLexicalEnvironments_) { auto map = cx->make_unique(cx); if (!map) { return nullptr; } nonSyntacticLexicalEnvironments_ = std::move(map); } RootedObject lexicalEnv(cx, nonSyntacticLexicalEnvironments_->lookup(key)); if (!lexicalEnv) { MOZ_ASSERT(key->is() || !key->is()); lexicalEnv = LexicalEnvironmentObject::createNonSyntactic(cx, enclosing, thisv); if (!lexicalEnv) { return nullptr; } if (!nonSyntacticLexicalEnvironments_->add(cx, key, lexicalEnv)) { return nullptr; } } return &lexicalEnv->as(); } LexicalEnvironmentObject* ObjectRealm::getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx, HandleObject enclosing) { // 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()) { MOZ_ASSERT(!enclosing->as().isSyntactic()); key = &enclosing->as().object(); } // NOTE: The default global |this| value is set to key for compatibility // with existing users of the lexical environment cache. // - When used by shared-global JSM loader, |this| must be the // NonSyntacticVariablesObject passed as enclosing. // - When used by SubscriptLoader, |this| must be the target object of // the WithEnvironmentObject wrapper. // - When used by XBL/DOM Events, we execute directly as a function and // do not access the |this| value. // See js::GetFunctionThis / js::GetNonSyntacticGlobalThis return getOrCreateNonSyntacticLexicalEnvironment(cx, enclosing, key, /*thisv = */ key); } LexicalEnvironmentObject* ObjectRealm::getNonSyntacticLexicalEnvironment( JSObject* key) const { MOZ_ASSERT(&ObjectRealm::get(key) == this); if (!nonSyntacticLexicalEnvironments_) { return nullptr; } // If a wrapped WithEnvironmentObject was passed in, unwrap it as in // getOrCreateNonSyntacticLexicalEnvironment. if (key->is()) { MOZ_ASSERT(!key->as().isSyntactic()); key = &key->as().object(); } JSObject* lexicalEnv = nonSyntacticLexicalEnvironments_->lookup(key); if (!lexicalEnv) { return nullptr; } return &lexicalEnv->as(); } bool Realm::addToVarNames(JSContext* cx, JS::Handle name) { MOZ_ASSERT(name); if (varNames_.put(name)) { return true; } ReportOutOfMemory(cx); return false; } void Realm::traceGlobal(JSTracer* trc) { // Trace things reachable from the realm's global. Note that these edges // must be swept too in case the realm is live but the global is not. savedStacks_.trace(trc); // Atoms are always tenured. if (!JS::RuntimeHeapIsMinorCollecting()) { varNames_.trace(trc); } } void ObjectRealm::trace(JSTracer* trc) { if (lazyArrayBuffers) { lazyArrayBuffers->trace(trc); } if (objectMetadataTable) { objectMetadataTable->trace(trc); } if (nonSyntacticLexicalEnvironments_) { nonSyntacticLexicalEnvironments_->trace(trc); } } void Realm::traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark) { if (objectMetadataState_.is()) { TraceRoot(trc, &objectMetadataState_.as(), "on-stack object pending metadata"); } if (!JS::RuntimeHeapIsMinorCollecting()) { // The global is never nursery allocated, so we don't need to // trace it when doing a minor collection. // // If a compartment is on-stack, we mark its global so that // JSContext::global() remains valid. if (shouldTraceGlobal() && 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()->isCollectingFromAnyThread()) { return; } /* Mark debug scopes, if present */ if (debugEnvs_) { debugEnvs_->trace(trc); } objects_.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 && !JS::RuntimeHeapIsMinorCollecting()) { MOZ_ASSERT_IF(!trc->runtime()->isBeingDestroyed(), collectCoverage()); for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) { JSScript* script = const_cast(r.front().key()); MOZ_ASSERT(script->hasScriptCounts()); TraceRoot(trc, &script, "profilingScripts"); MOZ_ASSERT(script == r.front().key(), "const_cast is only a work-around"); } } } void ObjectRealm::finishRoots() { if (lazyArrayBuffers) { lazyArrayBuffers->clear(); } if (objectMetadataTable) { objectMetadataTable->clear(); } if (nonSyntacticLexicalEnvironments_) { nonSyntacticLexicalEnvironments_->clear(); } } void Realm::finishRoots() { if (debugEnvs_) { debugEnvs_->finish(); } objects_.finishRoots(); clearScriptCounts(); clearScriptNames(); #ifdef MOZ_VTUNE scriptVTuneIdMap.reset(); #endif } void ObjectRealm::sweepAfterMinorGC() { InnerViewTable& table = innerViews.get(); if (table.needsSweepAfterMinorGC()) { table.sweepAfterMinorGC(); } } void Realm::sweepAfterMinorGC() { globalWriteBarriered = 0; dtoaCache.purge(); objects_.sweepAfterMinorGC(); } void Realm::sweepSavedStacks() { savedStacks_.sweep(); } void Realm::sweepGlobalObject() { if (global_ && IsAboutToBeFinalized(&global_)) { global_.set(nullptr); } } void Realm::sweepSelfHostingScriptSource() { if (selfHostingScriptSource.unbarrieredGet() && IsAboutToBeFinalized(&selfHostingScriptSource)) { selfHostingScriptSource.set(nullptr); } } void Realm::sweepJitRealm() { if (jitRealm_) { jitRealm_->sweep(this); } } void Realm::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(); } void Realm::sweepDebugEnvironments() { if (debugEnvs_) { debugEnvs_->sweep(); } } void ObjectRealm::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(); } MOZ_ASSERT_IF(ni->objectBeingIterated(), &ObjectRealm::get(ni->objectBeingIterated()) == this); ni = next; } } void Realm::sweepObjectRealm() { objects_.sweepNativeIterators(); } void Realm::sweepVarNames() { varNames_.sweep(); } namespace { struct TraceRootFunctor { JSTracer* trc; const char* name; TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {} template void operator()(T* t) { return TraceRoot(trc, t, name); } }; struct NeedsSweepUnbarrieredFunctor { template bool operator()(T* t) const { return IsAboutToBeFinalizedUnbarriered(t); } }; } // namespace void Realm::sweepTemplateObjects() { if (mappedArgumentsTemplate_ && IsAboutToBeFinalized(&mappedArgumentsTemplate_)) { mappedArgumentsTemplate_.set(nullptr); } if (unmappedArgumentsTemplate_ && IsAboutToBeFinalized(&unmappedArgumentsTemplate_)) { unmappedArgumentsTemplate_.set(nullptr); } if (iterResultTemplate_ && IsAboutToBeFinalized(&iterResultTemplate_)) { iterResultTemplate_.set(nullptr); } if (iterResultWithoutPrototypeTemplate_ && IsAboutToBeFinalized(&iterResultWithoutPrototypeTemplate_)) { iterResultWithoutPrototypeTemplate_.set(nullptr); } } void Realm::fixupAfterMovingGC() { purge(); fixupGlobal(); objectGroups_.fixupTablesAfterMovingGC(); fixupScriptMapsAfterMovingGC(); } void Realm::fixupGlobal() { GlobalObject* global = *global_.unsafeGet(); if (global) { global_.set(MaybeForwarded(global)); } } void Realm::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 (scriptNameMap) { for (ScriptNameMap::Enum e(*scriptNameMap); !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 MOZ_VTUNE if (scriptVTuneIdMap) { for (ScriptVTuneIdMap::Enum e(*scriptVTuneIdMap); !e.empty(); e.popFront()) { JSScript* script = e.front().key(); if (!IsAboutToBeFinalizedUnbarriered(&script) && script != e.front().key()) { e.rekeyFront(script); } } } #endif } #ifdef JSGC_HASH_TABLE_CHECKS void Realm::checkScriptMapsAfterMovingGC() { if (scriptCountsMap) { for (auto r = scriptCountsMap->all(); !r.empty(); r.popFront()) { JSScript* script = r.front().key(); MOZ_ASSERT(script->realm() == this); CheckGCThingAfterMovingGC(script); auto ptr = scriptCountsMap->lookup(script); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); } } if (scriptNameMap) { for (auto r = scriptNameMap->all(); !r.empty(); r.popFront()) { JSScript* script = r.front().key(); MOZ_ASSERT(script->realm() == this); CheckGCThingAfterMovingGC(script); auto ptr = scriptNameMap->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(); MOZ_ASSERT(script->realm() == this); CheckGCThingAfterMovingGC(script); DebugScript* ds = r.front().value().get(); for (uint32_t i = 0; i < ds->numSites; i++) { BreakpointSite* site = ds->breakpoints[i]; if (site && site->type() == BreakpointSite::Type::JS) { CheckGCThingAfterMovingGC(site->asJS()->script); } } auto ptr = debugScriptMap->lookup(script); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); } } #ifdef MOZ_VTUNE if (scriptVTuneIdMap) { for (auto r = scriptVTuneIdMap->all(); !r.empty(); r.popFront()) { JSScript* script = r.front().key(); MOZ_ASSERT(script->realm() == this); CheckGCThingAfterMovingGC(script); auto ptr = scriptVTuneIdMap->lookup(script); MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &r.front()); } } #endif // MOZ_VTUNE } #endif void Realm::purge() { dtoaCache.purge(); newProxyCache.purge(); objectGroups_.purge(); objects_.iteratorCache.clearAndCompact(); arraySpeciesLookup.purge(); promiseLookup.purge(); } void Realm::clearTables() { global_.set(nullptr); // No scripts should have run in this realm. This is used when merging // a realm that has been used off thread into another realm and zone. compartment()->assertNoCrossCompartmentWrappers(); MOZ_ASSERT(!jitRealm_); MOZ_ASSERT(!debugEnvs_); MOZ_ASSERT(objects_.enumerators->next() == objects_.enumerators); objectGroups_.clearTables(); savedStacks_.clear(); varNames_.clear(); } void Realm::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 Realm::forgetAllocationMetadataBuilder() { // Unlike setAllocationMetadataBuilder, we don't have to discard all JIT // code here (code is still valid, just a bit slower because it doesn't do // inline GC allocations when a metadata builder is present), but we do want // to cancel off-thread Ion compilations to avoid races when Ion calls // hasAllocationMetadataBuilder off-thread. CancelOffThreadIonCompile(this); allocationMetadataBuilder_ = nullptr; } void Realm::setNewObjectMetadata(JSContext* cx, HandleObject obj) { MOZ_ASSERT(obj->maybeCCWRealm() == this); cx->check(compartment(), obj); AutoEnterOOMUnsafeRegion oomUnsafe; if (JSObject* metadata = allocationMetadataBuilder_->build(cx, obj, oomUnsafe)) { MOZ_ASSERT(metadata->maybeCCWRealm() == obj->maybeCCWRealm()); cx->check(metadata); if (!objects_.objectMetadataTable) { auto table = cx->make_unique(cx); if (!table) { oomUnsafe.crash("setNewObjectMetadata"); } objects_.objectMetadataTable = std::move(table); } if (!objects_.objectMetadataTable->add(cx, obj, metadata)) { oomUnsafe.crash("setNewObjectMetadata"); } } } static bool AddInnerLazyFunctionsFromScript(JSScript* script, AutoObjectVector& lazyFunctions) { if (!script->hasObjects()) { return true; } for (JSObject* obj : script->objects()) { if (obj->is() && obj->as().isInterpretedLazy()) { if (!lazyFunctions.append(obj)) { return false; } } } return true; } static bool AddLazyFunctionsForRealm(JSContext* cx, AutoObjectVector& lazyFunctions, gc::AllocKind kind) { // Find all live root lazy functions in the realm: those which have a // non-lazy enclosing script, 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(kind); !i.done(); i.next()) { JSFunction* fun = &i->as(); // 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->realm() != cx->realm()) { continue; } if (fun->isInterpretedLazy()) { LazyScript* lazy = fun->lazyScriptOrNull(); if (lazy && lazy->enclosingScriptHasEverBeenCompiled()) { if (!lazyFunctions.append(fun)) { return false; } } } } return true; } static bool CreateLazyScriptsForRealm(JSContext* cx) { AutoObjectVector lazyFunctions(cx); if (!AddLazyFunctionsForRealm(cx, lazyFunctions, gc::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 (!AddLazyFunctionsForRealm(cx, lazyFunctions, gc::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. RootedFunction fun(cx); for (size_t i = 0; i < lazyFunctions.length(); i++) { fun = &lazyFunctions[i]->as(); // lazyFunctions may have been populated with multiple functions for // a lazy script. if (!fun->isInterpretedLazy()) { continue; } bool lazyScriptHadNoScript = !fun->lazyScript()->maybeScript(); JSScript* script = JSFunction::getOrCreateScript(cx, fun); if (!script) { return false; } if (lazyScriptHadNoScript && !AddInnerLazyFunctionsFromScript(script, lazyFunctions)) { return false; } } return true; } bool Realm::ensureDelazifyScriptsForDebugger(JSContext* cx) { AutoRealmUnchecked ar(cx, this); if (needsDelazificationForDebugger() && !CreateLazyScriptsForRealm(cx)) { return false; } debugModeBits_ &= ~DebuggerNeedsDelazification; return true; } void Realm::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++) { // Use unbarrieredGet() to prevent triggering read barrier while collecting, // this is safe as long as dbg does not escape. Debugger* dbg = p->unbarrieredGet(); if (flag == DebuggerObservesAllExecution ? dbg->observesAllExecution() : flag == DebuggerObservesCoverage ? dbg->observesCoverage() : flag == DebuggerObservesAsmJS && dbg->observesAsmJS()) { debugModeBits_ |= flag; return; } } debugModeBits_ &= ~flag; } void Realm::unsetIsDebuggee() { if (isDebuggee()) { debugModeBits_ &= ~DebuggerObservesMask; DebugEnvironments::onRealmUnsetIsDebuggee(this); } } void Realm::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 resumes its execution. JSContext* cx = TlsContext.get(); for (ActivationIterator iter(cx); !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(); clearScriptNames(); } bool Realm::collectCoverage() const { return collectCoverageForPGO() || collectCoverageForDebug(); } bool Realm::collectCoverageForPGO() const { return !jit::JitOptions.disablePgo; } bool Realm::collectCoverageForDebug() const { return debuggerObservesCoverage() || runtimeFromAnyThread()->profilingScripts || runtimeFromAnyThread()->lcovOutput().isEnabled(); } void Realm::clearScriptCounts() { if (!scriptCountsMap) { return; } // Clear all hasScriptCounts_ flags of JSScript, in order to release all // ScriptCounts entries of the current realm. for (ScriptCountsMap::Range r = scriptCountsMap->all(); !r.empty(); r.popFront()) { r.front().key()->clearHasScriptCounts(); } scriptCountsMap.reset(); } void Realm::clearScriptNames() { scriptNameMap.reset(); } void Realm::clearBreakpointsIn(FreeOp* fop, js::Debugger* dbg, HandleObject handler) { for (auto script = zone()->cellIter(); !script.done(); script.next()) { if (script->realm() == this && script->hasAnyBreakpointsOrStepMode()) { script->clearBreakpointsIn(fop, dbg, handler); } } } void ObjectRealm::addSizeOfExcludingThis( mozilla::MallocSizeOf mallocSizeOf, size_t* innerViewsArg, size_t* lazyArrayBuffersArg, size_t* objectMetadataTablesArg, size_t* nonSyntacticLexicalEnvironmentsArg) { *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf); if (lazyArrayBuffers) { *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf); } if (objectMetadataTable) { *objectMetadataTablesArg += objectMetadataTable->sizeOfIncludingThis(mallocSizeOf); } if (auto& map = nonSyntacticLexicalEnvironments_) { *nonSyntacticLexicalEnvironmentsArg += map->sizeOfIncludingThis(mallocSizeOf); } } void Realm::addSizeOfIncludingThis( mozilla::MallocSizeOf mallocSizeOf, size_t* tiAllocationSiteTables, size_t* tiArrayTypeTables, size_t* tiObjectTypeTables, size_t* realmObject, size_t* realmTables, size_t* innerViewsArg, size_t* lazyArrayBuffersArg, size_t* objectMetadataTablesArg, size_t* savedStacksSet, size_t* varNamesSet, size_t* nonSyntacticLexicalEnvironmentsArg, size_t* jitRealm, size_t* scriptCountsMapArg) { *realmObject += mallocSizeOf(this); objectGroups_.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables, tiArrayTypeTables, tiObjectTypeTables, realmTables); wasm.addSizeOfExcludingThis(mallocSizeOf, realmTables); objects_.addSizeOfExcludingThis(mallocSizeOf, innerViewsArg, lazyArrayBuffersArg, objectMetadataTablesArg, nonSyntacticLexicalEnvironmentsArg); *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf); *varNamesSet += varNames_.shallowSizeOfExcludingThis(mallocSizeOf); if (jitRealm_) { *jitRealm += jitRealm_->sizeOfIncludingThis(mallocSizeOf); } if (scriptCountsMap) { *scriptCountsMapArg += scriptCountsMap->shallowSizeOfIncludingThis(mallocSizeOf); for (auto r = scriptCountsMap->all(); !r.empty(); r.popFront()) { *scriptCountsMapArg += r.front().value()->sizeOfIncludingThis(mallocSizeOf); } } } mozilla::HashCodeScrambler Realm::randomHashCodeScrambler() { return mozilla::HashCodeScrambler(randomKeyGenerator_.next(), randomKeyGenerator_.next()); } AutoSetNewObjectMetadata::AutoSetNewObjectMetadata( JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) : CustomAutoRooter(cx), cx_(cx->helperThread() ? nullptr : cx), prevState_(cx->realm()->objectMetadataState_) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; if (cx_) { cx_->realm()->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_->realm()->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. gc::AutoSuppressGC autoSuppressGC(cx_); JSObject* obj = cx_->realm()->objectMetadataState_.as(); // 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_->realm()->objectMetadataState_ = prevState_; obj = SetNewObjectMetadata(cx_, obj); } else { cx_->realm()->objectMetadataState_ = prevState_; } } JS_PUBLIC_API void gc::TraceRealm(JSTracer* trc, JS::Realm* realm, const char* name) { // The way GC works with compartments is basically incomprehensible. // For Realms, what we want is very simple: each Realm has a strong // reference to its GlobalObject, and vice versa. // // Here we simply trace our side of that edge. During GC, // GCRuntime::traceRuntimeCommon() marks all other realm roots, for // all realms. realm->traceGlobal(trc); } JS_PUBLIC_API bool gc::RealmNeedsSweep(JS::Realm* realm) { return realm->globalIsAboutToBeFinalized(); } JS_PUBLIC_API bool gc::AllRealmsNeedSweep(JS::Compartment* comp) { MOZ_ASSERT(comp); if (!comp->zone()->isGCSweeping()) { return false; } for (Realm* r : comp->realms()) { if (!gc::RealmNeedsSweep(r)) { return false; } } return true; } JS_PUBLIC_API JS::Realm* JS::GetCurrentRealmOrNull(JSContext* cx) { return cx->realm(); } JS_PUBLIC_API JS::Realm* JS::GetObjectRealmOrNull(JSObject* obj) { return IsCrossCompartmentWrapper(obj) ? nullptr : obj->nonCCWRealm(); } JS_PUBLIC_API void* JS::GetRealmPrivate(JS::Realm* realm) { return realm->realmPrivate(); } JS_PUBLIC_API void JS::SetRealmPrivate(JS::Realm* realm, void* data) { realm->setRealmPrivate(data); } JS_PUBLIC_API void JS::SetDestroyRealmCallback( JSContext* cx, JS::DestroyRealmCallback callback) { cx->runtime()->destroyRealmCallback = callback; } JS_PUBLIC_API void JS::SetRealmNameCallback(JSContext* cx, JS::RealmNameCallback callback) { cx->runtime()->realmNameCallback = callback; } JS_PUBLIC_API JSObject* JS::GetRealmGlobalOrNull(Handle realm) { return realm->maybeGlobal(); } JS_PUBLIC_API bool JS::InitRealmStandardClasses(JSContext* cx) { MOZ_ASSERT(!cx->zone()->isAtomsZone()); AssertHeapIsIdle(); CHECK_THREAD(cx); return GlobalObject::initStandardClasses(cx, cx->global()); } JS_PUBLIC_API JSObject* JS::GetRealmObjectPrototype(JSContext* cx) { CHECK_THREAD(cx); return GlobalObject::getOrCreateObjectPrototype(cx, cx->global()); } JS_PUBLIC_API JSObject* JS::GetRealmFunctionPrototype(JSContext* cx) { CHECK_THREAD(cx); return GlobalObject::getOrCreateFunctionPrototype(cx, cx->global()); } JS_PUBLIC_API JSObject* JS::GetRealmArrayPrototype(JSContext* cx) { CHECK_THREAD(cx); return GlobalObject::getOrCreateArrayPrototype(cx, cx->global()); } JS_PUBLIC_API JSObject* JS::GetRealmErrorPrototype(JSContext* cx) { CHECK_THREAD(cx); return GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(), JSEXN_ERR); } JS_PUBLIC_API JSObject* JS::GetRealmIteratorPrototype(JSContext* cx) { CHECK_THREAD(cx); return GlobalObject::getOrCreateIteratorPrototype(cx, cx->global()); }