Bug 1482215: Make Debugger visibility per-Compartment, and ensure realms match their compartments. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Mon, 17 Dec 2018 18:37:29 +0000
changeset 451023 7e30c215f294ff5ffdf3b314bd1f49fc92d726de
parent 451022 bc0c590fd29f9ad34bbad86d0fad8d06bb9c7968
child 451024 3a3522cd4bb7dbeafe1fca1edaba3d4f587195d0
push id110593
push useraiakab@mozilla.com
push dateTue, 18 Dec 2018 05:37:03 +0000
treeherdermozilla-inbound@0f29d352d926 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1482215
milestone66.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 1482215: Make Debugger visibility per-Compartment, and ensure realms match their compartments. r=jorendorff Debugger invisibility is only practical to enforce on compartment boundaries, and for its proper uses, that's good enough. Unfortunately, at present, debugger invisibility is a flag on realms. This misfit is the reason for the sole remaining code that assumes that every object is associated with a particular realm: Debugger.Object.prototype.unwrap consults the unwrapped object's global to see whether it is about to reveal an object that it must not. We would like to remove this code. This patch: - adds an `invisibleToDebugger` flag to JS::Compartment, and sets it from the Realm options (since there is no API for creating compartments directly; only the act of creating a Realm can create a compartment to hold it); - changes Debugger.Object.prototype.unwrap to check the compartment's flag, thus removing the final use of JSObject::deprecatedRealm; - asserts that new realms added to a compartment have a compatible visibility; and - changes the shell primitive for creating realms to throw an error in case of incompatible requested visibilities, rather than crashing. Differential Revision: https://phabricator.services.mozilla.com/D14625
js/src/gc/GC.cpp
js/src/jsapi.h
js/src/shell/js.cpp
js/src/vm/Compartment.cpp
js/src/vm/Compartment.h
js/src/vm/Debugger.cpp
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -7878,18 +7878,23 @@ Realm* js::NewRealm(JSContext* cx, JSPri
     if (!zoneHolder->init(isSystem)) {
       ReportOutOfMemory(cx);
       return nullptr;
     }
 
     zone = zoneHolder.get();
   }
 
-  if (!comp) {
-    compHolder = cx->make_unique<JS::Compartment>(zone);
+  bool invisibleToDebugger = options.creationOptions().invisibleToDebugger();
+  if (comp) {
+    // Debugger visibility is per-compartment, not per-realm, so make sure the
+    // new realm's visibility matches its compartment's.
+    MOZ_ASSERT(comp->invisibleToDebugger() == invisibleToDebugger);
+  } else {
+    compHolder = cx->make_unique<JS::Compartment>(zone, invisibleToDebugger);
     if (!compHolder) {
       return nullptr;
     }
 
     comp = compHolder.get();
   }
 
   UniquePtr<Realm> realm(cx->new_<Realm>(comp, options));
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1544,20 +1544,25 @@ class JS_PUBLIC_API RealmCreationOptions
 
   // Set the compartment/zone to use for the realm. See CompartmentSpecifier
   // above.
   RealmCreationOptions& setNewCompartmentInSystemZone();
   RealmCreationOptions& setNewCompartmentInExistingZone(JSObject* obj);
   RealmCreationOptions& setNewCompartmentAndZone();
   RealmCreationOptions& setExistingCompartment(JSObject* obj);
 
-  // Certain scopes (i.e. XBL compilation scopes) are implementation details
-  // of the embedding, and references to them should never leak out to script.
-  // This flag causes the this realm to skip firing onNewGlobalObject and
-  // makes addDebuggee a no-op for this global.
+  // Certain compartments are implementation details of the embedding, and
+  // references to them should never leak out to script. This flag causes this
+  // realm to skip firing onNewGlobalObject and makes addDebuggee a no-op for
+  // this global.
+  //
+  // Debugger visibility is per-compartment, not per-realm (it's only practical
+  // to enforce visibility on compartment boundaries), so if a realm is being
+  // created in an extant compartment, its requested visibility must match that
+  // of the compartment.
   bool invisibleToDebugger() const { return invisibleToDebugger_; }
   RealmCreationOptions& setInvisibleToDebugger(bool flag) {
     invisibleToDebugger_ = flag;
     return *this;
   }
 
   // Realms used for off-thread compilation have their contents merged into a
   // target realm when the compilation is finished. This is only allowed if
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -6183,16 +6183,25 @@ static bool NewGlobal(JSContext* cx, uns
     bool isSystem =
         principals && principals == cx->runtime()->trustedPrincipals();
     if (isSystem != IsSystemCompartment(comp)) {
       JS_ReportErrorASCII(cx,
                           "Cannot create system and non-system realms in the "
                           "same compartment");
       return false;
     }
+
+    // Debugger visibility is per-compartment, not per-realm, so make sure the
+    // requested visibility matches the existing compartment's.
+    if (creationOptions.invisibleToDebugger() != comp->invisibleToDebugger()) {
+      JS_ReportErrorASCII(cx,
+                          "All the realms in a compartment must have "
+                          "the same debugger visibility");
+      return false;
+    }
   }
 
   RootedObject global(cx, NewGlobalObject(cx, options, principals));
   if (principals) {
     JS_DropPrincipals(cx, principals);
   }
   if (!global) {
     return false;
--- a/js/src/vm/Compartment.cpp
+++ b/js/src/vm/Compartment.cpp
@@ -33,19 +33,20 @@
 #include "vm/JSScript-inl.h"
 #include "vm/NativeObject-inl.h"
 #include "vm/UnboxedObject-inl.h"
 
 using namespace js;
 
 using JS::AutoStableStringChars;
 
-Compartment::Compartment(Zone* zone)
+Compartment::Compartment(Zone* zone, bool invisibleToDebugger)
     : zone_(zone),
       runtime_(zone->runtimeFromAnyThread()),
+      invisibleToDebugger_(invisibleToDebugger),
       crossCompartmentWrappers(0) {}
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 namespace {
 struct CheckGCThingAfterMovingGCFunctor {
   template <class T>
   void operator()(T* t) {
--- a/js/src/vm/Compartment.h
+++ b/js/src/vm/Compartment.h
@@ -405,16 +405,17 @@ class WrapperMap {
   }
 };
 
 }  // namespace js
 
 class JS::Compartment {
   JS::Zone* zone_;
   JSRuntime* runtime_;
+  bool invisibleToDebugger_;
 
   js::WrapperMap crossCompartmentWrappers;
 
   using RealmVector = js::Vector<JS::Realm*, 1, js::SystemAllocPolicy>;
   RealmVector realms_;
 
  public:
   /*
@@ -459,16 +460,22 @@ class JS::Compartment {
     MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime_));
     return runtime_;
   }
 
   // Note: Unrestricted access to the zone's runtime from an arbitrary
   // thread can easily lead to races. Use this method very carefully.
   JSRuntime* runtimeFromAnyThread() const { return runtime_; }
 
+  // Certain compartments are implementation details of the embedding, and
+  // references to them should never leak out to script. For realms belonging to
+  // this compartment, onNewGlobalObject does not fire, and addDebuggee is a
+  // no-op.
+  bool invisibleToDebugger() const { return invisibleToDebugger_; }
+
   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* realmForNewCCW() const { return realms_[0]; }
@@ -488,17 +495,17 @@ class JS::Compartment {
 
  private:
   bool getNonWrapperObjectForCurrentCompartment(JSContext* cx,
                                                 js::MutableHandleObject obj);
   bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing,
                           js::MutableHandleObject obj);
 
  public:
-  explicit Compartment(JS::Zone* zone);
+  explicit Compartment(JS::Zone* zone, bool invisibleToDebugger);
 
   void destroy(js::FreeOp* fop);
 
   MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
 
   MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp);
 #ifdef ENABLE_BIGINT
   MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -11428,19 +11428,19 @@ double DebuggerObject::promiseTimeToReso
 
   RootedObject unwrapped(cx, UnwrapOneChecked(referent));
   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->deprecatedRealm()->creationOptions().invisibleToDebugger()) {
+  // invisible-to-Debugger compartment. (If our referent is a *wrapper* to such,
+  // and the wrapper is in a visible compartment, that's fine.)
+  if (unwrapped->compartment()->invisibleToDebugger()) {
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_DEBUG_INVISIBLE_COMPARTMENT);
     return false;
   }
 
   return dbg->wrapDebuggeeObject(cx, unwrapped, result);
 }