Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "js/shadow/Realm.h" // JS::shadow::Realm
#include "vm/Realm-inl.h"
#include "mozilla/MemoryReporting.h"
#include <stddef.h>
#include "jsfriendapi.h"
#include "builtin/WrappedFunctionObject.h"
#include "debugger/DebugAPI.h"
#include "debugger/Debugger.h"
#include "gc/GC.h"
#include "jit/JitRuntime.h"
#include "js/CallAndConstruct.h" // JS::IsCallable
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
#include "js/GCVariant.h"
#include "js/Proxy.h"
#include "js/RootingAPI.h"
#include "js/Wrapper.h"
#include "vm/Compartment.h"
#include "vm/DateTime.h"
#include "vm/Iteration.h"
#include "vm/JSContext.h"
#include "vm/PIC.h"
#include "gc/Marking-inl.h"
#include "vm/JSObject-inl.h"
using namespace js;
Realm::DebuggerVectorEntry::DebuggerVectorEntry(js::Debugger* dbg_,
JSObject* link)
: dbg(dbg_), debuggerLink(link) {}
ObjectRealm::ObjectRealm(JS::Zone* zone)
: innerViews(zone, zone), iteratorCache(zone) {}
Realm::Realm(Compartment* comp, const JS::RealmOptions& options)
: JS::shadow::Realm(comp),
zone_(comp->zone()),
runtime_(comp->runtimeFromMainThread()),
creationOptions_(options.creationOptions()),
behaviors_(options.behaviors()),
objects_(zone_),
randomKeyGenerator_(runtime_->forkRandomKeyGenerator()),
debuggers_(zone_),
allocatedDuringIncrementalGC_(zone_->isGCMarkingOrSweeping() ||
zone_->isGCFinished()),
wasm(runtime_) {
runtime_->numRealms++;
}
Realm::~Realm() {
MOZ_ASSERT(!hasBeenEnteredIgnoringJit());
MOZ_ASSERT(!isDebuggee());
// Write the code coverage information in a file.
if (lcovRealm_) {
runtime_->lcovOutput().writeLCovResult(*lcovRealm_);
}
if (allocationMetadataBuilder_) {
forgetAllocationMetadataBuilder();
}
MOZ_ASSERT(runtime_->numRealms > 0);
runtime_->numRealms--;
}
void 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 (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;
}
}
bool JSRuntime::createJitRuntime(JSContext* cx) {
using namespace js::jit;
MOZ_ASSERT(!jitRuntime_);
if (!CanLikelyAllocateMoreExecutableMemory()) {
// Try to release memory first instead of potentially reporting OOM below.
if (OnLargeAllocationFailure) {
OnLargeAllocationFailure();
}
}
jit::JitRuntime* jrt = cx->new_<jit::JitRuntime>();
if (!jrt) {
return false;
}
// Unfortunately, initialization depends on jitRuntime_ being non-null, so
// we can't just wait to assign jitRuntime_.
jitRuntime_ = jrt;
if (!jitRuntime_->initialize(cx)) {
js_delete(jitRuntime_.ref());
jitRuntime_ = nullptr;
return false;
}
return true;
}
#ifdef JSGC_HASH_TABLE_CHECKS
void js::DtoaCache::checkCacheAfterMovingGC() {
MOZ_ASSERT(!str || !IsForwarded(str));
}
#endif // JSGC_HASH_TABLE_CHECKS
NonSyntacticLexicalEnvironmentObject*
ObjectRealm::getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx,
HandleObject enclosing,
HandleObject key,
HandleObject thisv) {
MOZ_ASSERT(&ObjectRealm::get(enclosing) == this);
if (!nonSyntacticLexicalEnvironments_) {
auto map = cx->make_unique<ObjectWeakMap>(cx);
if (!map) {
return nullptr;
}
nonSyntacticLexicalEnvironments_ = std::move(map);
}
RootedObject lexicalEnv(cx, nonSyntacticLexicalEnvironments_->lookup(key));
if (!lexicalEnv) {
MOZ_ASSERT(key->is<NonSyntacticVariablesObject>() ||
!key->is<EnvironmentObject>());
lexicalEnv =
NonSyntacticLexicalEnvironmentObject::create(cx, enclosing, thisv);
if (!lexicalEnv) {
return nullptr;
}
if (!nonSyntacticLexicalEnvironments_->add(cx, key, lexicalEnv)) {
return nullptr;
}
}
return &lexicalEnv->as<NonSyntacticLexicalEnvironmentObject>();
}
NonSyntacticLexicalEnvironmentObject*
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<WithEnvironmentObject>()) {
MOZ_ASSERT(!enclosing->as<WithEnvironmentObject>().isSyntactic());
key = &enclosing->as<WithEnvironmentObject>().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);
}
NonSyntacticLexicalEnvironmentObject*
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<WithEnvironmentObject>()) {
MOZ_ASSERT(!key->as<WithEnvironmentObject>().isSyntactic());
key = &key->as<WithEnvironmentObject>().object();
}
JSObject* lexicalEnv = nonSyntacticLexicalEnvironments_->lookup(key);
if (!lexicalEnv) {
return nullptr;
}
return &lexicalEnv->as<NonSyntacticLexicalEnvironmentObject>();
}
void Realm::traceGlobalData(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);
DebugAPI::traceFromRealm(trc, this);
}
void ObjectRealm::trace(JSTracer* trc) {
if (objectMetadataTable) {
objectMetadataTable->trace(trc);
}
if (nonSyntacticLexicalEnvironments_) {
nonSyntacticLexicalEnvironments_->trace(trc);
}
}
void Realm::traceRoots(JSTracer* trc,
js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark) {
// It's not possible to trigger a GC between allocating the pending object and
// setting its meta data in ~AutoSetNewObjectMetadata.
MOZ_RELEASE_ASSERT(!objectPendingMetadata_);
if (!JS::RuntimeHeapIsMinorCollecting()) {
// The global is never nursery allocated, so we don't need to
// trace it when doing a minor collection.
//
// If a realm is on-stack, we mark its global so that JSContext::global()
// remains valid.
if (shouldTraceGlobal() && global_) {
TraceRoot(trc, global_.unbarrieredAddress(), "on-stack realm global");
}
// If the realm is still being initialized we set a flag so that it doesn't
// get deleted, since there may be GC things that contain pointers to it.
if (shouldTraceGlobal() && initializingGlobal_) {
allocatedDuringIncrementalGC_ = true;
}
}
// 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);
}
void ObjectRealm::finishRoots() {
if (objectMetadataTable) {
objectMetadataTable->clear();
}
if (nonSyntacticLexicalEnvironments_) {
nonSyntacticLexicalEnvironments_->clear();
}
}
void Realm::finishRoots() {
if (debugEnvs_) {
debugEnvs_->finish();
}
objects_.finishRoots();
}
void ObjectRealm::sweepAfterMinorGC(JSTracer* trc) {
InnerViewTable& table = innerViews.get();
if (table.needsSweepAfterMinorGC()) {
table.sweepAfterMinorGC(trc);
}
}
void Realm::sweepAfterMinorGC(JSTracer* trc) {
globalWriteBarriered = 0;
dtoaCache.purge();
objects_.sweepAfterMinorGC(trc);
}
void Realm::traceWeakSavedStacks(JSTracer* trc) { savedStacks_.traceWeak(trc); }
void Realm::traceWeakGlobalEdge(JSTracer* trc) {
// If the global is dead, free its GlobalObjectData.
auto result = TraceWeakEdge(trc, &global_, "Realm::global_");
if (result.isDead()) {
result.initialTarget()->releaseData(runtime_->gcContext());
}
}
void Realm::traceWeakDebugEnvironmentEdges(JSTracer* trc) {
if (debugEnvs_) {
debugEnvs_->traceWeak(trc);
}
}
void Realm::fixupAfterMovingGC(JSTracer* trc) {
purge();
traceWeakGlobalEdge(trc);
}
void Realm::purge() {
dtoaCache.purge();
newProxyCache.purge();
newPlainObjectWithPropsCache.purge();
plainObjectAssignCache.purge();
objects_.iteratorCache.clearAndCompact();
arraySpeciesLookup.purge();
promiseLookup.purge();
if (zone()->isGCPreparing()) {
purgeForOfPicChain();
}
}
void Realm::purgeForOfPicChain() {
if (GlobalObject* global = global_.unbarrieredGet()) {
if (NativeObject* object = global->getForOfPICObject()) {
ForOfPIC::Chain* chain = ForOfPIC::fromJSObject(object);
chain->freeAllStubs(runtime_->gcContext());
}
}
}
// Check to see if this individual realm is recording allocations. Debuggers or
// runtimes can try and record allocations, so this method can check to see if
// any initialization is needed.
bool Realm::isRecordingAllocations() { return !!allocationMetadataBuilder_; }
void Realm::setAllocationMetadataBuilder(
const js::AllocationMetadataBuilder* builder) {
// Clear any jitcode in the runtime, which behaves differently depending on
// whether there is a creation callback.
if (bool(allocationMetadataBuilder_) != bool(builder)) {
ReleaseAllJITCode(runtime_->gcContext());
if (builder) {
zone()->incNumRealmsWithAllocMetadataBuilder();
} else {
zone()->decNumRealmsWithAllocMetadataBuilder();
}
}
allocationMetadataBuilder_ = builder;
}
void Realm::forgetAllocationMetadataBuilder() {
if (!allocationMetadataBuilder_) {
return;
}
// 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 accesses
// numRealmsWithAllocMetadataBuilder_ off-thread.
CancelOffThreadIonCompile(zone());
zone()->decNumRealmsWithAllocMetadataBuilder();
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<ObjectWeakMap>(cx);
if (!table) {
oomUnsafe.crash("setNewObjectMetadata");
}
objects_.objectMetadataTable = std::move(table);
}
if (!objects_.objectMetadataTable->add(cx, obj, metadata)) {
oomUnsafe.crash("setNewObjectMetadata");
}
}
}
void Realm::updateDebuggerObservesFlag(unsigned flag) {
MOZ_ASSERT(isDebuggee());
MOZ_ASSERT(flag == DebuggerObservesAllExecution ||
flag == DebuggerObservesCoverage ||
flag == DebuggerObservesAsmJS || flag == DebuggerObservesWasm ||
flag == DebuggerObservesNativeCall);
GlobalObject* global =
zone()->runtimeFromMainThread()->gc.isForegroundSweeping()
? unsafeUnbarrieredMaybeGlobal()
: maybeGlobal();
bool observes = false;
if (flag == DebuggerObservesAllExecution) {
observes = DebugAPI::debuggerObservesAllExecution(global);
} else if (flag == DebuggerObservesCoverage) {
observes = DebugAPI::debuggerObservesCoverage(global);
} else if (flag == DebuggerObservesAsmJS) {
observes = DebugAPI::debuggerObservesAsmJS(global);
} else if (flag == DebuggerObservesWasm) {
observes = DebugAPI::debuggerObservesWasm(global);
} else if (flag == DebuggerObservesNativeCall) {
observes = DebugAPI::debuggerObservesNativeCall(global);
}
if (observes) {
debugModeBits_ |= flag;
} else {
debugModeBits_ &= ~flag;
}
}
void Realm::setIsDebuggee() {
if (!isDebuggee()) {
debugModeBits_ |= IsDebuggee;
runtimeFromMainThread()->incrementNumDebuggeeRealms();
}
}
void Realm::unsetIsDebuggee() {
if (isDebuggee()) {
if (debuggerObservesCoverage()) {
runtime_->decrementNumDebuggeeRealmsObservingCoverage();
}
debugModeBits_ = 0;
DebugEnvironments::onRealmUnsetIsDebuggee(this);
runtimeFromMainThread()->decrementNumDebuggeeRealms();
}
}
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();
}
}
runtime_->incrementNumDebuggeeRealmsObservingCoverage();
return;
}
runtime_->decrementNumDebuggeeRealmsObservingCoverage();
// If code coverage is enabled by any other means, keep it.
if (collectCoverageForDebug()) {
return;
}
clearScriptCounts();
clearScriptLCov();
}
coverage::LCovRealm* Realm::lcovRealm() {
if (!lcovRealm_) {
lcovRealm_ = js::MakeUnique<coverage::LCovRealm>(this);
}
return lcovRealm_.get();
}
bool Realm::collectCoverageForDebug() const {
return debuggerObservesCoverage() || coverage::IsLCovEnabled();
}
void Realm::clearScriptCounts() { zone()->clearScriptCounts(this); }
void Realm::clearScriptLCov() { zone()->clearScriptLCov(this); }
const char* Realm::getLocale() const {
if (RefPtr<LocaleString> locale = creationOptions_.locale()) {
return locale->chars();
}
return runtime_->getDefaultLocale();
}
void ObjectRealm::addSizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf, size_t* innerViewsArg,
size_t* objectMetadataTablesArg,
size_t* nonSyntacticLexicalEnvironmentsArg) {
*innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
if (objectMetadataTable) {
*objectMetadataTablesArg +=
objectMetadataTable->sizeOfIncludingThis(mallocSizeOf);
}
if (auto& map = nonSyntacticLexicalEnvironments_) {
*nonSyntacticLexicalEnvironmentsArg +=
map->sizeOfIncludingThis(mallocSizeOf);
}
}
void Realm::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
size_t* realmObject, size_t* realmTables,
size_t* innerViewsArg,
size_t* objectMetadataTablesArg,
size_t* savedStacksSet,
size_t* nonSyntacticLexicalEnvironmentsArg) {
*realmObject += mallocSizeOf(this);
wasm.addSizeOfExcludingThis(mallocSizeOf, realmTables);
objects_.addSizeOfExcludingThis(mallocSizeOf, innerViewsArg,
objectMetadataTablesArg,
nonSyntacticLexicalEnvironmentsArg);
*savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
}
bool Realm::shouldCaptureStackForThrow() {
// Determine whether a stack trace should be captured for throw-statements (or
// similar) in JS code in this realm. We don't want to do this unconditionally
// because capturing stacks is slow and some scripts throw a lot of
// exceptions.
//
// Note: this is unrelated to Error.stack! That property is observable from
// JS code so we can't use these heuristics there. The code here is mostly
// relevant for uncaught exceptions that are not Error objects.
// To match other browsers, we always capture a stack trace if the realm is a
// debuggee (this includes the devtools console being open) or if unlimited
// stack traces have been enabled for this realm (used in automation).
if (isDebuggee() || isUnlimitedStacksCapturingEnabled) {
return true;
}
// Also always capture for chrome code. This is code we control and this helps
// debugging.
if (principals() &&
principals() == runtimeFromMainThread()->trustedPrincipals()) {
return true;
}
// Else, capture the stack only for the first N exceptions so that we can
// still show stack traces for scripts that don't throw a lot of exceptions
// (if the console is opened later).
static constexpr uint16_t MaxStacksCapturedForThrow = 50;
if (numStacksCapturedForThrow_ > MaxStacksCapturedForThrow) {
return false;
}
numStacksCapturedForThrow_++;
return true;
}
mozilla::HashCodeScrambler Realm::randomHashCodeScrambler() {
return mozilla::HashCodeScrambler(randomKeyGenerator_.next(),
randomKeyGenerator_.next());
}
void AutoSetNewObjectMetadata::setPendingMetadata() {
JSObject* obj = cx_->realm()->getAndClearObjectPendingMetadata();
if (!obj) {
return;
}
MOZ_ASSERT(obj->getClass()->shouldDelayMetadataBuilder());
if (cx_->isExceptionPending()) {
return;
}
// This function is called from a destructor that 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_);
(void)SetNewObjectMetadata(cx_, obj);
}
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->traceGlobalData(trc);
}
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(JS::Realm* 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 bool JS::MaybeFreezeCtorAndPrototype(JSContext* cx,
HandleObject ctor,
HandleObject maybeProto) {
if (MOZ_LIKELY(!cx->realm()->creationOptions().freezeBuiltins())) {
return true;
}
if (!SetIntegrityLevel(cx, ctor, IntegrityLevel::Frozen)) {
return false;
}
if (maybeProto) {
if (!SetIntegrityLevel(cx, maybeProto, IntegrityLevel::Sealed)) {
return false;
}
}
return true;
}
JS_PUBLIC_API JSObject* JS::GetRealmObjectPrototype(JSContext* cx) {
CHECK_THREAD(cx);
return &cx->global()->getObjectPrototype();
}
JS_PUBLIC_API JS::Handle<JSObject*> JS::GetRealmObjectPrototypeHandle(
JSContext* cx) {
return cx->global()->getObjectPrototypeHandle();
}
JS_PUBLIC_API JSObject* JS::GetRealmFunctionPrototype(JSContext* cx) {
CHECK_THREAD(cx);
return &cx->global()->getFunctionPrototype();
}
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());
}
JS_PUBLIC_API JSObject* JS::GetRealmAsyncIteratorPrototype(JSContext* cx) {
CHECK_THREAD(cx);
return GlobalObject::getOrCreateAsyncIteratorPrototype(cx, cx->global());
}
JS_PUBLIC_API JSObject* JS::GetRealmKeyObject(JSContext* cx) {
return GlobalObject::getOrCreateRealmKeyObject(cx, cx->global());
}
JS_PUBLIC_API Realm* JS::GetFunctionRealm(JSContext* cx, HandleObject objArg) {
// 7.3.22 GetFunctionRealm ( obj )
CHECK_THREAD(cx);
cx->check(objArg);
RootedObject obj(cx, objArg);
while (true) {
obj = CheckedUnwrapStatic(obj);
if (!obj) {
ReportAccessDenied(cx);
return nullptr;
}
// Step 1.
MOZ_ASSERT(IsCallable(obj));
// Steps 2 and 3. We use a loop instead of recursion to unwrap bound
// functions.
if (obj->is<JSFunction>()) {
return obj->as<JSFunction>().realm();
}
if (obj->is<BoundFunctionObject>()) {
obj = obj->as<BoundFunctionObject>().getTarget();
continue;
}
// WrappedFunctionObjects also have a [[Realm]] internal slot,
// which is the nonCCWRealm by construction.
if (obj->is<WrappedFunctionObject>()) {
return obj->nonCCWRealm();
}
// Step 4.
if (IsScriptedProxy(obj)) {
// Steps 4.a-b.
JSObject* proxyTarget = GetProxyTargetObject(obj);
if (!proxyTarget) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_PROXY_REVOKED);
return nullptr;
}
// Step 4.c.
obj = proxyTarget;
continue;
}
// Step 5.
return cx->realm();
}
}