Bug 1468406 part 4 - Remove remaining JSObject::realm() calls. r=luke
☠☠ backed out by 54f8630a5669 ☠ ☠
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 20 Jun 2018 11:04:02 +0200
changeset 423137 49cbcdeaa8bb951088c7e313fcd18fbb4a787cfc
parent 423136 3dc86f7109f69530d31231ca3f58057cb11521bc
child 423138 37d2fca3693a6be63d44a8fa2df1369e142116ab
push id34164
push usercsabou@mozilla.com
push dateThu, 21 Jun 2018 01:17:13 +0000
treeherdermozilla-central@d231a3231680 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1468406
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1468406 part 4 - Remove remaining JSObject::realm() calls. r=luke
js/src/builtin/TestingFunctions.cpp
js/src/jit/CacheIR.cpp
js/src/jsapi-tests/testPreserveJitCode.cpp
js/src/jsapi-tests/testUbiNode.cpp
js/src/jsapi.cpp
js/src/proxy/CrossCompartmentWrapper.cpp
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/EnvironmentObject.cpp
js/src/vm/JSContext-inl.h
js/src/vm/JSContext.h
js/src/vm/JSObject-inl.h
js/src/vm/JSObject.h
js/src/vm/MemoryMetrics.cpp
js/src/vm/NativeObject.h
js/src/vm/Realm-inl.h
js/src/vm/Realm.cpp
js/src/vm/SavedStacks.cpp
js/src/vm/Stack-inl.h
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1355,17 +1355,17 @@ CaptureFirstSubsumedFrame(JSContext* cx,
 
     RootedObject obj(cx, &args[0].toObject());
     obj = CheckedUnwrap(obj);
     if (!obj) {
         JS_ReportErrorASCII(cx, "Denied permission to object.");
         return false;
     }
 
-    JS::StackCapture capture(JS::FirstSubsumedFrame(cx, obj->realm()->principals()));
+    JS::StackCapture capture(JS::FirstSubsumedFrame(cx, obj->nonCCWRealm()->principals()));
     if (args.length() > 1)
         capture.as<JS::FirstSubsumedFrame>().ignoreSelfHosted = JS::ToBoolean(args[1]);
 
     JS::RootedObject capturedStack(cx);
     if (!JS::CaptureCurrentStack(cx, &capturedStack, std::move(capture)))
         return false;
 
     args.rval().setObjectOrNull(capturedStack);
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -1123,17 +1123,17 @@ GetPropIRGenerator::tryAttachCrossCompar
     // such as de-lazifying type info.
     {
         AutoRealm ar(cx_, unwrapped);
 
         // The first CCW for iframes is almost always wrapping another WindowProxy
         // so we optimize for that case as well.
         isWindowProxy = IsWindowProxy(unwrapped);
         if (isWindowProxy) {
-            MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == unwrapped->realm()->maybeGlobal());
+            MOZ_ASSERT(ToWindowIfWindowProxy(unwrapped) == &unwrapped->nonCCWGlobal());
             unwrapped = cx_->global();
             MOZ_ASSERT(unwrapped);
         }
 
         NativeGetPropCacheability canCache =
             CanAttachNativeGetProp(cx_, unwrapped, id, &holder, &shape, pc_,
                                 resultFlags_, isTemporarilyUnoptimizable_);
         if (canCache != CanAttachReadSlot)
--- a/js/src/jsapi-tests/testPreserveJitCode.cpp
+++ b/js/src/jsapi-tests/testPreserveJitCode.cpp
@@ -2,16 +2,18 @@
  * 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/. */
 
 // For js::jit::IsIonEnabled().
 #include "jit/Ion.h"
 
 #include "jsapi-tests/tests.h"
 
+#include "vm/JSObject-inl.h"
+
 using namespace JS;
 
 static void
 ScriptCallback(JSRuntime* rt, void* data, JSScript* script, const JS::AutoRequireNoGC& nogc)
 {
     unsigned& count = *static_cast<unsigned*>(data);
     if (script->hasIonScript())
         ++count;
@@ -23,17 +25,17 @@ BEGIN_TEST(test_PreserveJitCode)
     CHECK(testPreserveJitCode(true, 1));
     return true;
 }
 
 unsigned
 countIonScripts(JSObject* global)
 {
     unsigned count = 0;
-    js::IterateScripts(cx, global->realm(), &count, ScriptCallback);
+    js::IterateScripts(cx, global->nonCCWRealm(), &count, ScriptCallback);
     return count;
 }
 
 bool
 testPreserveJitCode(bool preserveJitCode, unsigned remainingIonScripts)
 {
     cx->options().setBaseline(true);
     cx->options().setIon(true);
--- a/js/src/jsapi-tests/testUbiNode.cpp
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -7,16 +7,18 @@
 #include "js/UbiNodeDominatorTree.h"
 #include "js/UbiNodePostOrder.h"
 #include "js/UbiNodeShortestPaths.h"
 #include "jsapi-tests/tests.h"
 #include "util/Text.h"
 #include "vm/Realm.h"
 #include "vm/SavedFrame.h"
 
+#include "vm/JSObject-inl.h"
+
 using JS::RootedObject;
 using JS::RootedScript;
 using JS::RootedString;
 using namespace js;
 
 // A helper JS::ubi::Node concrete implementation that can be used to make mock
 // graphs for testing traversals with.
 struct FakeNode
@@ -123,18 +125,18 @@ BEGIN_TEST(test_ubiNodeCompartment)
 
     JS::RealmOptions globalOptions;
     RootedObject global2(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
                                                 JS::FireOnNewGlobalHook, globalOptions));
     CHECK(global2);
     CHECK(global1->compartment() != global2->compartment());
     CHECK(JS::ubi::Node(global2).compartment() == global2->compartment());
     CHECK(JS::ubi::Node(global2).compartment() != global1->compartment());
-    CHECK(JS::ubi::Node(global2).realm() == global2->realm());
-    CHECK(JS::ubi::Node(global2).realm() != global1->realm());
+    CHECK(JS::ubi::Node(global2).realm() == global2->nonCCWRealm());
+    CHECK(JS::ubi::Node(global2).realm() != global1->nonCCWRealm());
 
     JS::CompileOptions options(cx);
 
     // Create a script in the original realm...
     RootedScript script1(cx);
     CHECK(JS::Compile(cx, options, "", 0, &script1));
 
     {
@@ -142,18 +144,18 @@ BEGIN_TEST(test_ubiNodeCompartment)
         // there, too.
         JSAutoRealm ar(cx, global2);
 
         RootedScript script2(cx);
         CHECK(JS::Compile(cx, options, "", 0, &script2));
 
         CHECK(JS::ubi::Node(script1).compartment() == global1->compartment());
         CHECK(JS::ubi::Node(script2).compartment() == global2->compartment());
-        CHECK(JS::ubi::Node(script1).realm() == global1->realm());
-        CHECK(JS::ubi::Node(script2).realm() == global2->realm());
+        CHECK(JS::ubi::Node(script1).realm() == global1->nonCCWRealm());
+        CHECK(JS::ubi::Node(script2).realm() == global2->nonCCWRealm());
 
         // Now create a wrapper for global1 in global2's compartment.
         RootedObject wrappedGlobal1(cx, global1);
         CHECK(cx->compartment()->wrap(cx, &wrappedGlobal1));
 
         // Cross-compartment wrappers have a compartment() but not a realm().
         CHECK(JS::ubi::Node(wrappedGlobal1).zone() == cx->zone());
         CHECK(JS::ubi::Node(wrappedGlobal1).compartment() == cx->compartment());
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -880,17 +880,17 @@ JS_TransplantObject(JSContext* cx, Handl
 
     JS::Compartment* destination = target->compartment();
 
     if (origobj->compartment() == destination) {
         // If the original object is in the same compartment as the
         // destination, then we know that we won't find a wrapper in the
         // destination's cross compartment map and that the same
         // object will continue to work.
-        AutoRealmUnchecked ar(cx, origobj->realm());
+        AutoRealmUnchecked ar(cx, origobj->deprecatedRealm());
         if (!JSObject::swap(cx, origobj, target))
             MOZ_CRASH();
         newIdentity = origobj;
     } else if (WrapperMap::Ptr p = destination->lookupWrapper(origv)) {
         // There might already be a wrapper for the original object in
         // the new compartment. If there is, we use its identity and swap
         // in the contents of |target|.
         newIdentity = &p->value().get().toObject();
@@ -914,17 +914,17 @@ JS_TransplantObject(JSContext* cx, Handl
     // `newIdentity == origobj`, because this process also clears out any
     // cached wrapper state.
     if (!RemapAllWrappersForObject(cx, origobj, newIdentity))
         MOZ_CRASH();
 
     // Lastly, update the original object to point to the new one.
     if (origobj->compartment() != destination) {
         RootedObject newIdentityWrapper(cx, newIdentity);
-        AutoRealmUnchecked ar(cx, origobj->realm());
+        AutoRealmUnchecked ar(cx, origobj->deprecatedRealm());
         if (!JS_WrapObject(cx, &newIdentityWrapper))
             MOZ_CRASH();
         MOZ_ASSERT(Wrapper::wrappedObject(newIdentityWrapper) == newIdentity);
         if (!JSObject::swap(cx, origobj, newIdentityWrapper))
             MOZ_CRASH();
         if (!origobj->compartment()->putWrapper(cx, CrossCompartmentKey(newIdentity), origv))
             MOZ_CRASH();
     }
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -609,17 +609,17 @@ js::RemapWrapper(JSContext* cx, JSObject
     RootedObject newTarget(cx, newTargetArg);
     MOZ_ASSERT(wobj->is<CrossCompartmentWrapperObject>());
     MOZ_ASSERT(!newTarget->is<CrossCompartmentWrapperObject>());
     JSObject* origTarget = Wrapper::wrappedObject(wobj);
     MOZ_ASSERT(origTarget);
     MOZ_ASSERT(!JS_IsDeadWrapper(origTarget),
                "We don't want a dead proxy in the wrapper map");
     Value origv = ObjectValue(*origTarget);
-    Realm* wrealm = wobj->realm();
+    Realm* wrealm = wobj->deprecatedRealm();
     JS::Compartment* wcompartment = wobj->compartment();
 
     AutoDisableProxyCheck adpc;
 
     // If we're mapping to a different target (as opposed to just recomputing
     // for the same target), we must not have an existing wrapper for the new
     // target, otherwise this will break.
     MOZ_ASSERT_IF(origTarget != newTarget,
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -10664,17 +10664,17 @@ DebuggerObject::unwrap(JSContext* cx, Ha
     if (!unwrapped) {
         result.set(nullptr);
         return true;
     }
 
     // Don't allow unwrapping to create a D.O whose referent is in an
     // invisible-to-Debugger global. (If our referent is a *wrapper* to such,
     // and the wrapper is in a visible realm, that's fine.)
-    if (unwrapped->realm()->creationOptions().invisibleToDebugger()) {
+    if (unwrapped->nonCCWRealm()->creationOptions().invisibleToDebugger()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
         return false;
     }
 
     return dbg->wrapDebuggeeObject(cx, unwrapped, result);
 }
 
 /* static */ bool
@@ -11540,17 +11540,37 @@ Debugger::isDebuggerCrossCompartmentEdge
     } else if (obj->is<DebuggerObject>()) {
         referent = static_cast<gc::Cell*>(obj->as<DebuggerObject>().getPrivate());
     } else if (obj->is<DebuggerEnvironment>()) {
         referent = static_cast<gc::Cell*>(obj->as<DebuggerEnvironment>().getPrivate());
     }
 
     return referent == target;
 }
-#endif
+
+static void
+CheckDebuggeeThingRealm(Realm* realm, bool invisibleOk)
+{
+    MOZ_ASSERT(!realm->creationOptions().mergeable());
+    MOZ_ASSERT_IF(!invisibleOk, !realm->creationOptions().invisibleToDebugger());
+}
+
+void
+js::CheckDebuggeeThing(JSScript* script, bool invisibleOk)
+{
+    CheckDebuggeeThingRealm(script->realm(), invisibleOk);
+}
+
+void
+js::CheckDebuggeeThing(JSObject* obj, bool invisibleOk)
+{
+    if (Realm* realm = JS::GetObjectRealmOrNull(obj))
+        CheckDebuggeeThingRealm(realm, invisibleOk);
+}
+#endif // DEBUG
 
 
 /*** JS::dbg::GarbageCollectionEvent **************************************************************/
 
 namespace JS {
 namespace dbg {
 
 /* static */ GarbageCollectionEvent::Ptr
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -84,16 +84,24 @@ class DebuggerMemory;
 class ScriptedOnStepHandler;
 class ScriptedOnPopHandler;
 class WasmInstanceObject;
 
 typedef HashSet<ReadBarrieredGlobalObject,
                 MovableCellHasher<ReadBarrieredGlobalObject>,
                 ZoneAllocPolicy> WeakGlobalObjectSet;
 
+#ifdef DEBUG
+extern void
+CheckDebuggeeThing(JSScript* script, bool invisibleOk);
+
+extern void
+CheckDebuggeeThing(JSObject* obj, bool invisibleOk);
+#endif
+
 /*
  * A weakmap from GC thing keys to JSObject values that supports the keys being
  * in different compartments to the values. All values must be in the same
  * compartment.
  *
  * The purpose of this is to allow the garbage collector to easily find edges
  * from debuggee object compartments to debugger compartments when calculating
  * the compartment groups.  Note that these edges are the inverse of the edges
@@ -156,19 +164,19 @@ class DebuggerWeakMap : private WeakMap<
 
     MOZ_MUST_USE bool init(uint32_t len = 16) {
         return Base::init(len) && zoneCounts.init();
     }
 
     template<typename KeyInput, typename ValueInput>
     bool relookupOrAdd(AddPtr& p, const KeyInput& k, const ValueInput& v) {
         MOZ_ASSERT(v->compartment() == this->compartment);
-        MOZ_ASSERT(!k->realm()->creationOptions().mergeable());
-        MOZ_ASSERT_IF(!InvisibleKeysOk,
-                      !k->realm()->creationOptions().invisibleToDebugger());
+#ifdef DEBUG
+        CheckDebuggeeThing(k, InvisibleKeysOk);
+#endif
         MOZ_ASSERT(!Base::has(k));
         if (!incZoneCount(k->zone()))
             return false;
         bool ok = Base::relookupOrAdd(p, k, v);
         if (!ok)
             decZoneCount(k->zone());
         return ok;
     }
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -2547,17 +2547,17 @@ DebugEnvironments::hasDebugEnvironment(J
     return nullptr;
 }
 
 /* static */ bool
 DebugEnvironments::addDebugEnvironment(JSContext* cx, Handle<EnvironmentObject*> env,
                                        Handle<DebugEnvironmentProxy*> debugEnv)
 {
     MOZ_ASSERT(cx->realm() == env->realm());
-    MOZ_ASSERT(cx->realm() == debugEnv->realm());
+    MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm());
 
     if (!CanUseDebugEnvironmentMaps(cx))
         return true;
 
     DebugEnvironments* envs = ensureRealmData(cx);
     if (!envs)
         return false;
 
@@ -2580,17 +2580,17 @@ DebugEnvironments::hasDebugEnvironment(J
     return nullptr;
 }
 
 /* static */ bool
 DebugEnvironments::addDebugEnvironment(JSContext* cx, const EnvironmentIter& ei,
                                        Handle<DebugEnvironmentProxy*> debugEnv)
 {
     MOZ_ASSERT(!ei.hasSyntacticEnvironment());
-    MOZ_ASSERT(cx->realm() == debugEnv->realm());
+    MOZ_ASSERT(cx->realm() == debugEnv->nonCCWRealm());
     // Generators should always have environments.
     MOZ_ASSERT_IF(ei.scope().is<FunctionScope>(),
                   !ei.scope().as<FunctionScope>().canonicalFunction()->isGenerator() &&
                   !ei.scope().as<FunctionScope>().canonicalFunction()->isAsync());
 
     if (!CanUseDebugEnvironmentMaps(cx))
         return true;
 
@@ -2862,17 +2862,17 @@ DebugEnvironments::updateLiveEnvironment
      * fp, simply popping fp effectively clears the flag for us, at exactly
      * the time when execution resumes fp->prev().
      */
     for (AllFramesIter i(cx); !i.done(); ++i) {
         if (!i.hasUsableAbstractFramePtr())
             continue;
 
         AbstractFramePtr frame = i.abstractFramePtr();
-        if (frame.environmentChain()->realm() != cx->realm())
+        if (frame.realm() != cx->realm())
             continue;
 
         if (frame.isFunctionFrame()) {
             if (frame.callee()->isGenerator() || frame.callee()->isAsync())
                 continue;
         }
 
         if (!frame.isDebuggee())
@@ -2891,17 +2891,17 @@ DebugEnvironments::updateLiveEnvironment
                     return false;
                 if (!envs->liveEnvs.put(&ei.environment(), LiveEnvironmentVal(ei)))
                     return false;
             }
         }
 
         if (frame.prevUpToDate())
             return true;
-        MOZ_ASSERT(frame.environmentChain()->realm()->isDebuggee());
+        MOZ_ASSERT(frame.realm()->isDebuggee());
         frame.setPrevUpToDate();
     }
 
     return true;
 }
 
 LiveEnvironmentVal*
 DebugEnvironments::hasLiveEnvironment(EnvironmentObject& env)
@@ -2930,17 +2930,17 @@ DebugEnvironments::unsetPrevUpToDateUnti
     for (AllFramesIter i(cx); !i.done(); ++i) {
         if (!i.hasUsableAbstractFramePtr())
             continue;
 
         AbstractFramePtr frame = i.abstractFramePtr();
         if (frame == until)
             return;
 
-        if (frame.environmentChain()->realm() != cx->realm())
+        if (frame.realm() != cx->realm())
             continue;
 
         frame.unsetPrevUpToDate();
     }
 }
 
 /* static */ void
 DebugEnvironments::forwardLiveFrame(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to)
--- a/js/src/vm/JSContext-inl.h
+++ b/js/src/vm/JSContext-inl.h
@@ -473,19 +473,32 @@ JSContext::enterAtomsZone(const js::Auto
     // Only one thread can be in the atoms zone at a time.
     MOZ_ASSERT(runtime_->currentThreadHasExclusiveAccess());
 
     realm_ = nullptr;
     zone_ = runtime_->atomsZone(lock);
     arenas_ = &zone_->arenas;
 }
 
-template <typename T>
+inline void
+JSContext::enterRealmOf(JSObject* target)
+{
+    MOZ_ASSERT(JS::CellIsNotGray(target));
+    enterRealm(target->deprecatedRealm());
+}
+
 inline void
-JSContext::enterRealmOf(const T& target)
+JSContext::enterRealmOf(JSScript* target)
+{
+    MOZ_ASSERT(JS::CellIsNotGray(target));
+    enterRealm(target->realm());
+}
+
+inline void
+JSContext::enterRealmOf(js::ObjectGroup* target)
 {
     MOZ_ASSERT(JS::CellIsNotGray(target));
     enterRealm(target->realm());
 }
 
 inline void
 JSContext::enterNullRealm()
 {
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -207,18 +207,19 @@ struct JSContext : public JS::RootingCon
     inline void setRealm(JS::Realm* realm);
     inline void enterRealm(JS::Realm* realm);
     inline void enterAtomsZone(const js::AutoLockForExclusiveAccess& lock);
 
     friend class js::AutoAtomsZone;
     friend class js::AutoRealm;
 
   public:
-    template <typename T>
-    inline void enterRealmOf(const T& target);
+    inline void enterRealmOf(JSObject* target);
+    inline void enterRealmOf(JSScript* target);
+    inline void enterRealmOf(js::ObjectGroup* target);
     inline void enterNullRealm();
 
     inline void leaveRealm(JS::Realm* oldRealm);
     inline void leaveAtomsZone(JS::Realm* oldRealm,
                                const js::AutoLockForExclusiveAccess& lock);
 
     void setHelperThread(js::HelperThread* helperThread);
     js::HelperThread* helperThread() const { return helperThread_; }
--- a/js/src/vm/JSObject-inl.h
+++ b/js/src/vm/JSObject-inl.h
@@ -388,31 +388,29 @@ SetNewObjectMetadata(JSContext* cx, T* o
     return obj;
 }
 
 } // namespace js
 
 inline js::GlobalObject&
 JSObject::deprecatedGlobal() const
 {
-    return *realm()->unsafeUnbarrieredMaybeGlobal();
+    return *deprecatedRealm()->unsafeUnbarrieredMaybeGlobal();
 }
 
 inline js::GlobalObject&
 JSObject::nonCCWGlobal() const
 {
-    MOZ_ASSERT(!js::IsCrossCompartmentWrapper(this));
-
     /*
      * The global is read-barriered so that it is kept live by access through
      * the Realm. When accessed through a JSObject, however, the global will be
      * already kept live by the black JSObject's group pointer, so does not
      * need to be read-barriered.
      */
-    return *realm()->unsafeUnbarrieredMaybeGlobal();
+    return *nonCCWRealm()->unsafeUnbarrieredMaybeGlobal();
 }
 
 inline bool
 JSObject::hasAllFlags(js::BaseShape::Flag flags) const
 {
     MOZ_ASSERT(flags);
     if (js::Shape* shape = maybeShape())
         return shape->hasAllObjectFlags(flags);
--- a/js/src/vm/JSObject.h
+++ b/js/src/vm/JSObject.h
@@ -157,17 +157,16 @@ class JSObject : public js::gc::Cell
      * might have a lazy group, use getGroup() below, otherwise group().
      */
     bool hasLazyGroup() const {
         return group_->lazy();
     }
 
     JS::Compartment* compartment() const { return group_->compartment(); }
     JS::Compartment* maybeCompartment() const { return compartment(); }
-    JS::Realm* realm() const { return group_->realm(); }
 
     inline js::Shape* maybeShape() const;
     inline js::Shape* ensureShape(JSContext* cx);
 
     enum GenerateShape {
         GENERATE_NONE,
         GENERATE_SHAPE
     };
@@ -428,19 +427,37 @@ class JSObject : public js::gc::Cell
      * non-EnvironmentObjects can be on the environment chain).
      */
     inline JSObject* enclosingEnvironment() const;
 
     // Deprecated: call nonCCWGlobal or NativeObject::global() instead!
     inline js::GlobalObject& deprecatedGlobal() const;
 
     // Cross-compartment wrappers are not associated with a single realm/global,
-    // so this method asserts the object is not a CCW.
+    // so these methods assert the object is not a CCW.
     inline js::GlobalObject& nonCCWGlobal() const;
 
+    JS::Realm* nonCCWRealm() const {
+        MOZ_ASSERT(!js::IsCrossCompartmentWrapper(this));
+        return group_->realm();
+    }
+
+    // Returns the object's realm even if the object is a CCW (be careful, in
+    // this case the realm is not very meaningful because wrappers are shared by
+    // all realms in the compartment).
+    JS::Realm* maybeCCWRealm() const {
+        return group_->realm();
+    }
+
+    // Deprecated: call nonCCWRealm(), maybeCCWRealm(), or NativeObject::realm()
+    // instead!
+    JS::Realm* deprecatedRealm() const {
+        return group_->realm();
+    }
+
     /*
      * ES5 meta-object properties and operations.
      */
 
   public:
     // Indicates whether a non-proxy is extensible.  Don't call on proxies!
     // This method really shouldn't exist -- but there are a few internal
     // places that want it (JITs and the like), and it'd be a pain to mark them
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -456,17 +456,17 @@ StatsCellCallback(JSRuntime* rt, void* d
                   size_t thingSize)
 {
     StatsClosure* closure = static_cast<StatsClosure*>(data);
     RuntimeStats* rtStats = closure->rtStats;
     ZoneStats* zStats = rtStats->currZoneStats;
     switch (traceKind) {
       case JS::TraceKind::Object: {
         JSObject* obj = static_cast<JSObject*>(thing);
-        RealmStats& realmStats = obj->realm()->realmStats();
+        RealmStats& realmStats = obj->maybeCCWRealm()->realmStats();
         JS::ClassInfo info;        // This zeroes all the sizes.
         info.objectsGCHeap += thingSize;
 
         obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &info);
 
         // These classes require special handling due to shared resources which
         // we must be careful not to report twice.
         if (obj->is<WasmModuleObject>()) {
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1469,18 +1469,20 @@ class NativeObject : public ShapedObject
     inline js::gc::AllocKind allocKindForTenure() const;
 
     void updateShapeAfterMovingGC();
     void sweepDictionaryListPointer();
     void updateDictionaryListPointerAfterMinorGC(NativeObject* old);
 
     // Native objects are never wrappers, so a native object always has a realm
     // and global.
+    JS::Realm* realm() const {
+        return nonCCWRealm();
+    }
     inline js::GlobalObject& global() const;
-    JS::Realm* realm() const { return JSObject::realm(); }
 
     /* JIT Accessors */
     static size_t offsetOfElements() { return offsetof(NativeObject, elements_); }
     static size_t offsetOfFixedElements() {
         return sizeof(NativeObject) + sizeof(ObjectElements);
     }
 
     static size_t getFixedSlotOffset(size_t slot) {
--- a/js/src/vm/Realm-inl.h
+++ b/js/src/vm/Realm-inl.h
@@ -41,17 +41,20 @@ JS::Realm::globalIsAboutToBeFinalized()
 {
     MOZ_ASSERT(zone_->isGCSweeping());
     return global_ && js::gc::IsAboutToBeFinalizedUnbarriered(global_.unsafeGet());
 }
 
 /* static */ inline js::ObjectRealm&
 js::ObjectRealm::get(const JSObject* obj)
 {
-    return obj->realm()->objects_;
+    // Note: obj might be a CCW if we're accessing ObjectRealm::enumerators.
+    // CCWs here are fine because we always return the same ObjectRealm for a
+    // particular (CCW) object.
+    return obj->maybeCCWRealm()->objects_;
 }
 
 template <typename T>
 js::AutoRealm::AutoRealm(JSContext* cx, const T& target)
   : cx_(cx),
     origin_(cx->realm())
 {
     cx_->enterRealmOf(target);
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -637,22 +637,22 @@ Realm::forgetAllocationMetadataBuilder()
     CancelOffThreadIonCompile(this);
 
     allocationMetadataBuilder_ = nullptr;
 }
 
 void
 Realm::setNewObjectMetadata(JSContext* cx, HandleObject obj)
 {
-    MOZ_ASSERT(obj->realm() == this);
+    MOZ_ASSERT(obj->maybeCCWRealm() == this);
     assertSameCompartment(cx, compartment(), obj);
 
     AutoEnterOOMUnsafeRegion oomUnsafe;
     if (JSObject* metadata = allocationMetadataBuilder_->build(cx, obj, oomUnsafe)) {
-        MOZ_ASSERT(metadata->realm() == obj->realm());
+        MOZ_ASSERT(metadata->maybeCCWRealm() == obj->maybeCCWRealm());
         assertSameCompartment(cx, metadata);
 
         if (!objects_.objectMetadataTable) {
             auto table = cx->make_unique<ObjectWeakMap>(cx);
             if (!table || !table->init())
                 oomUnsafe.crash("setNewObjectMetadata");
 
             objects_.objectMetadataTable = std::move(table);
@@ -1011,17 +1011,17 @@ 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->realm();
+    return IsCrossCompartmentWrapper(obj) ? nullptr : obj->nonCCWRealm();
 }
 
 JS_PUBLIC_API(void*)
 JS::GetRealmPrivate(JS::Realm* realm)
 {
     return realm->realmPrivate();
 }
 
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -779,25 +779,25 @@ public:
     AutoMaybeEnterFrameRealm(JSContext* cx,
                              HandleObject obj
                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
         MOZ_RELEASE_ASSERT(cx->realm());
         if (obj)
-            MOZ_RELEASE_ASSERT(obj->realm());
+            MOZ_RELEASE_ASSERT(obj->deprecatedRealm());
 
         // Note that obj might be null here, since we're doing this before
         // UnwrapSavedFrame.
-        if (obj && cx->realm() != obj->realm())
+        if (obj && cx->realm() != obj->deprecatedRealm())
         {
             JSSubsumesOp subsumes = cx->runtime()->securityCallbacks->subsumes;
             if (subsumes && subsumes(cx->realm()->principals(),
-                                     obj->realm()->principals()))
+                                     obj->deprecatedRealm()->principals()))
             {
                 ar_.emplace(cx, obj);
             }
         }
     }
 
  private:
     Maybe<JSAutoRealm> ar_;
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -492,17 +492,17 @@ inline bool
 AbstractFramePtr::pushVarEnvironment(JSContext* cx, HandleScope scope)
 {
     return js::PushVarEnvironmentObject(cx, scope, *this);
 }
 
 inline JS::Realm*
 AbstractFramePtr::realm() const
 {
-    return environmentChain()->realm();
+    return environmentChain()->nonCCWRealm();
 }
 
 inline unsigned
 AbstractFramePtr::numActualArgs() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->numActualArgs();
     if (isBaselineFrame())