Bug 1526588 - Fix some issues with js::GetFirstGlobalInCompartment and XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC. r=bzbarsky
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 13 Feb 2019 06:41:44 +0000
changeset 516733 8c306186cbd7672259b3a730471cfcbe44a9b0d0
parent 516732 e7603feb22527e3eb390a218643a3d786a68ef93
child 516734 571569056837b1b7f7a234fd29a59aed1dee168a
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1526588
milestone67.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 1526588 - Fix some issues with js::GetFirstGlobalInCompartment and XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC. r=bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D19493
js/public/Realm.h
js/src/builtin/TestingFunctions.cpp
js/src/jit-test/tests/realms/first-global.js
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/proxy/Wrapper.cpp
js/src/vm/Compartment.cpp
js/src/vm/Compartment.h
js/src/vm/ProxyObject.cpp
js/src/vm/Realm-inl.h
js/src/vm/Realm.cpp
js/src/vm/Realm.h
js/xpconnect/src/XPCWrappedNativeScope.cpp
--- a/js/public/Realm.h
+++ b/js/public/Realm.h
@@ -11,17 +11,16 @@
 #include "js/GCPolicyAPI.h"
 #include "js/TypeDecls.h"  // forward-declaration of JS::Realm
 
 namespace js {
 namespace gc {
 JS_PUBLIC_API void TraceRealm(JSTracer* trc, JS::Realm* realm,
                               const char* name);
 JS_PUBLIC_API bool RealmNeedsSweep(JS::Realm* realm);
-JS_PUBLIC_API bool AllRealmsNeedSweep(JS::Compartment* comp);
 }  // namespace gc
 }  // namespace js
 
 namespace JS {
 
 // Each Realm holds a strong reference to its GlobalObject, and vice versa.
 template <>
 struct GCPolicy<Realm*> : public NonGCPointerPolicy<Realm*> {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -5330,16 +5330,36 @@ static bool ObjectGlobal(JSContext* cx, 
   }
 
   obj = ToWindowProxyIfWindow(&obj->nonCCWGlobal());
 
   args.rval().setObject(*obj);
   return true;
 }
 
+static bool FirstGlobalInCompartment(JSContext* cx, unsigned argc, Value* vp) {
+  CallArgs args = CallArgsFromVp(argc, vp);
+  RootedObject callee(cx, &args.callee());
+
+  if (!args.get(0).isObject()) {
+    ReportUsageErrorASCII(cx, callee, "Argument must be an object");
+    return false;
+  }
+
+  RootedObject obj(cx, UncheckedUnwrap(&args[0].toObject()));
+  obj = ToWindowProxyIfWindow(GetFirstGlobalInCompartment(obj->compartment()));
+
+  if (!cx->compartment()->wrap(cx, &obj)) {
+    return false;
+  }
+
+  args.rval().setObject(*obj);
+  return true;
+}
+
 static bool AssertCorrectRealm(JSContext* cx, unsigned argc, Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_RELEASE_ASSERT(cx->realm() == args.callee().as<JSFunction>().realm());
   args.rval().setUndefined();
   return true;
 }
 
 static bool GlobalLexicals(JSContext* cx, unsigned argc, Value* vp) {
@@ -6318,16 +6338,20 @@ gc::ZealModeHelpText),
     JS_FN_HELP("scriptedCallerGlobal", ScriptedCallerGlobal, 0, 0,
 "scriptedCallerGlobal()",
 "  Get the caller's global (or null). See JS::GetScriptedCallerGlobal.\n"),
 
     JS_FN_HELP("objectGlobal", ObjectGlobal, 1, 0,
 "objectGlobal(obj)",
 "  Returns the object's global object or null if the object is a wrapper.\n"),
 
+    JS_FN_HELP("firstGlobalInCompartment", FirstGlobalInCompartment, 1, 0,
+"firstGlobalInCompartment(obj)",
+"  Returns the first global in obj's compartment.\n"),
+
     JS_FN_HELP("assertCorrectRealm", AssertCorrectRealm, 0, 0,
 "assertCorrectRealm()",
 "  Asserts cx->realm matches callee->realm.\n"),
 
     JS_FN_HELP("globalLexicals", GlobalLexicals, 0, 0,
 "globalLexicals()",
 "  Returns an object containing a copy of all global lexical bindings.\n"
 "  Example use: let x = 1; assertEq(globalLexicals().x, 1);\n"),
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/realms/first-global.js
@@ -0,0 +1,5 @@
+var g1 = newGlobal({sameCompartmentAs: this});
+var g2 = newGlobal({newCompartment: true});
+assertEq(firstGlobalInCompartment(this), this);
+assertEq(firstGlobalInCompartment(g1), this);
+assertEq(firstGlobalInCompartment(g2), g2);
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1130,22 +1130,16 @@ JS_FRIEND_API JS::Realm* js::GetAnyRealm
     return nullptr;
   }
 
   RealmsInZoneIter realm(zone);
   MOZ_ASSERT(!realm.done());
   return realm.get();
 }
 
-JS_FRIEND_API JSObject* js::GetFirstGlobalInCompartment(JS::Compartment* comp) {
-  JSObject* global = comp->firstRealm()->maybeGlobal();
-  MOZ_ASSERT(global);
-  return global;
-}
-
 void JS::ObjectPtr::finalize(JSRuntime* rt) {
   if (IsIncrementalBarrierNeeded(rt->mainContextFromOwnThread())) {
     IncrementalPreWriteBarrier(value);
   }
   value = nullptr;
 }
 
 void JS::ObjectPtr::finalize(JSContext* cx) { finalize(cx->runtime()); }
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -547,16 +547,20 @@ SizeOfDataIfCDataObject(mozilla::MallocS
 extern JS_FRIEND_API JS::Realm* GetAnyRealmInZone(JS::Zone* zone);
 
 // Returns the first realm's global in a compartment. Note: this is not
 // guaranteed to always be the same realm because individual realms can be
 // collected by the GC.
 extern JS_FRIEND_API JSObject* GetFirstGlobalInCompartment(
     JS::Compartment* comp);
 
+// Returns true if the compartment contains a global object and this global is
+// not being collected.
+extern JS_FRIEND_API bool CompartmentHasLiveGlobal(JS::Compartment* comp);
+
 /*
  * Shadow declarations of JS internal structures, for access by inline access
  * functions below. Do not use these structures in any other way. When adding
  * new fields for access by inline methods, make sure to add static asserts to
  * the original header file to ensure that offsets are consistent.
  */
 namespace shadow {
 
--- a/js/src/proxy/Wrapper.cpp
+++ b/js/src/proxy/Wrapper.cpp
@@ -268,20 +268,20 @@ bool ForwardingProxyHandler::isCallable(
 bool ForwardingProxyHandler::isConstructor(JSObject* obj) const {
   JSObject* target = obj->as<ProxyObject>().target();
   return target->isConstructor();
 }
 
 JSObject* Wrapper::New(JSContext* cx, JSObject* obj, const Wrapper* handler,
                        const WrapperOptions& options) {
   // If this is a cross-compartment wrapper allocate it in the compartment's
-  // first realm. See Realm::realmForNewCCW.
-  mozilla::Maybe<AutoRealmUnchecked> ar;
+  // first global. See Compartment::globalForNewCCW.
+  mozilla::Maybe<AutoRealm> ar;
   if (handler->isCrossCompartmentWrapper()) {
-    ar.emplace(cx, cx->compartment()->realmForNewCCW());
+    ar.emplace(cx, &cx->compartment()->globalForNewCCW());
   }
   RootedValue priv(cx, ObjectValue(*obj));
   return NewProxyObject(cx, handler, priv, options.proto(), options);
 }
 
 JSObject* Wrapper::Renew(JSObject* existing, JSObject* obj,
                          const Wrapper* handler) {
   existing->as<ProxyObject>().renew(handler, ObjectValue(*obj));
--- a/js/src/vm/Compartment.cpp
+++ b/js/src/vm/Compartment.cpp
@@ -483,8 +483,34 @@ void Compartment::addSizeOfIncludingThis
   *compartmentObjects += mallocSizeOf(this);
   *crossCompartmentWrappersTables +=
       crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
 
   if (auto callback = runtime_->sizeOfIncludingThisCompartmentCallback) {
     *compartmentsPrivateData += callback(mallocSizeOf, this);
   }
 }
+
+GlobalObject& Compartment::firstGlobal() const {
+  for (Realm* realm : realms_) {
+    if (!realm->hasLiveGlobal()) {
+      continue;
+    }
+    GlobalObject* global = realm->maybeGlobal();
+    ExposeObjectToActiveJS(global);
+    return *global;
+  }
+  MOZ_CRASH("If all our globals are dead, why is someone expecting a global?");
+}
+
+JS_FRIEND_API JSObject* js::GetFirstGlobalInCompartment(JS::Compartment* comp) {
+  return &comp->firstGlobal();
+}
+
+JS_FRIEND_API bool js::CompartmentHasLiveGlobal(JS::Compartment* comp) {
+  MOZ_ASSERT(comp);
+  for (Realm* r : comp->realms()) {
+    if (r->hasLiveGlobal()) {
+      return true;
+    }
+  }
+  return false;
+}
--- a/js/src/vm/Compartment.h
+++ b/js/src/vm/Compartment.h
@@ -448,18 +448,18 @@ class JS::Compartment {
 
   RealmVector& realms() { return realms_; }
 
   // Cross-compartment wrappers are shared by all realms in the compartment, but
   // they still have a per-realm ObjectGroup etc. To prevent us from having
   // multiple realms, each with some cross-compartment wrappers potentially
   // keeping the realm alive longer than necessary, we always allocate CCWs in
   // the first realm.
-  Realm* firstRealm() const { return realms_[0]; }
-  Realm* realmForNewCCW() const { return firstRealm(); }
+  js::GlobalObject& firstGlobal() const;
+  js::GlobalObject& globalForNewCCW() const { return firstGlobal(); }
 
   void assertNoCrossCompartmentWrappers() {
     MOZ_ASSERT(crossCompartmentWrappers.empty());
   }
 
   void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                               size_t* compartmentObjects,
                               size_t* crossCompartmentWrappersTables,
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -102,17 +102,17 @@ static gc::AllocKind GetProxyGCObjectKin
 
   proxy->setInlineValueArray();
 
   detail::ProxyValueArray* values = detail::GetProxyDataLayout(proxy)->values();
   values->init(proxy->numReservedSlots());
 
   proxy->data.handler = handler;
   if (IsCrossCompartmentWrapper(proxy)) {
-    MOZ_ASSERT(cx->realm() == cx->compartment()->realmForNewCCW());
+    MOZ_ASSERT(cx->global() == &cx->compartment()->globalForNewCCW());
     proxy->setCrossCompartmentPrivate(priv);
   } else {
     proxy->setSameCompartmentPrivate(priv);
   }
 
   /* Don't track types of properties of non-DOM and non-singleton proxies. */
   if (newKind != SingletonObject && !clasp->isDOMClass()) {
     MarkObjectGroupUnknownProperties(cx, proxy->group());
--- a/js/src/vm/Realm-inl.h
+++ b/js/src/vm/Realm-inl.h
@@ -32,16 +32,21 @@ js::GlobalObject* JS::Realm::unsafeUnbar
 }
 
 inline bool JS::Realm::globalIsAboutToBeFinalized() {
   MOZ_ASSERT(zone_->isGCSweeping());
   return global_ &&
          js::gc::IsAboutToBeFinalizedUnbarriered(global_.unsafeGet());
 }
 
+inline bool JS::Realm::hasLiveGlobal() {
+  js::GlobalObject* global = unsafeUnbarrieredMaybeGlobal();
+  return global && !js::gc::IsAboutToBeFinalizedUnbarriered(&global);
+}
+
 /* static */ inline js::ObjectRealm& js::ObjectRealm::get(const JSObject* obj) {
   // 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>
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -985,29 +985,16 @@ JS_PUBLIC_API void gc::TraceRealm(JSTrac
   // 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();
 }
 
--- a/js/src/vm/Realm.h
+++ b/js/src/vm/Realm.h
@@ -508,16 +508,19 @@ class JS::Realm : public JS::shadow::Rea
   inline js::GlobalObject* maybeGlobal() const;
 
   /* An unbarriered getter for use while tracing. */
   inline js::GlobalObject* unsafeUnbarrieredMaybeGlobal() const;
 
   /* True if a global object exists, but it's being collected. */
   inline bool globalIsAboutToBeFinalized();
 
+  /* True if a global exists and it's not being collected. */
+  inline bool hasLiveGlobal();
+
   inline void initGlobal(js::GlobalObject& global);
 
   /*
    * This method traces data that is live iff we know that this realm's
    * global is still live.
    */
   void traceGlobal(JSTracer* trc);
 
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -345,17 +345,17 @@ void XPCWrappedNativeScope::UpdateWeakPo
     mWaiverWrapperMap->Sweep();
   }
 
   if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) {
     return;
   }
 
   // Update our pointer to the compartment in case we finalized all globals.
-  if (js::gc::AllRealmsNeedSweep(mCompartment)) {
+  if (!js::CompartmentHasLiveGlobal(mCompartment)) {
     mCompartment = nullptr;
     GetWrappedNativeMap()->Clear();
     mWrappedNativeProtoMap->Clear();
     return;
   }
 
   // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
   // been handled by XPCWrappedNative::FlatJSObjectMoved.