Bug 1465163 - Some UbiNode changes for same-compartment realms. r=fitzgen
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 01 Jun 2018 19:23:06 +0200
changeset 420928 ee68c73f661babf8bbbf499387e8f044b434ed38
parent 420927 739202297d05e2cf3724e67a2a4969ca52113f6a
child 420929 8733db9b64b090b34f9e44139739299d2c8903b8
push id34083
push userapavel@mozilla.com
push dateSat, 02 Jun 2018 23:03:25 +0000
treeherdermozilla-central@1f62ecdf59b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfitzgen
bugs1465163
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 1465163 - Some UbiNode changes for same-compartment realms. r=fitzgen
js/public/UbiNode.h
js/src/jsapi-tests/testUbiNode.cpp
js/src/vm/Debugger.cpp
js/src/vm/UbiNode.cpp
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -598,16 +598,23 @@ class JS_PUBLIC_API(Base) {
     virtual JS::Zone* zone() const { return nullptr; }
 
     // Return the compartment for this node. Some ubi::Node referents are not
     // associated with JSCompartments, such as JSStrings (which are associated
     // with Zones). When the referent is not associated with a compartment,
     // nullptr is returned.
     virtual JSCompartment* compartment() const { return nullptr; }
 
+    // Return the realm for this node. Some ubi::Node referents are not
+    // associated with Realms, such as JSStrings (which are associated
+    // with Zones) or cross-compartment wrappers (which are associated with
+    // compartments). When the referent is not associated with a realm,
+    // nullptr is returned.
+    virtual JS::Realm* realm() const { return nullptr; }
+
     // Return whether this node's referent's allocation stack was captured.
     virtual bool hasAllocationStack() const { return false; }
 
     // Get the stack recorded at the time this node's referent was
     // allocated. This must only be called when hasAllocationStack() is true.
     virtual StackFrame allocationStack() const {
         MOZ_CRASH("Concrete classes that have an allocation stack must override both "
                   "hasAllocationStack and allocationStack.");
@@ -771,16 +778,17 @@ class Node {
     // Otherwise return UndefinedValue(). JSStrings, JS::Symbols, and some (but
     // not all!) JSObjects can be exposed.
     JS::Value exposeToJS() const;
 
     CoarseType coarseType()         const { return base()->coarseType(); }
     const char16_t* typeName()      const { return base()->typeName(); }
     JS::Zone* zone()                const { return base()->zone(); }
     JSCompartment* compartment()    const { return base()->compartment(); }
+    JS::Realm* realm()              const { return base()->realm(); }
     const char* jsObjectClassName() const { return base()->jsObjectClassName(); }
     MOZ_MUST_USE bool jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName) const {
         return base()->jsObjectConstructorName(cx, outName);
     }
 
     const char* scriptFilename() const { return base()->scriptFilename(); }
 
     using Size = Base::Size;
@@ -976,17 +984,20 @@ class MOZ_STACK_CLASS JS_PUBLIC_API(Root
     JSContext* cx;
     EdgeVector edges;
     bool       wantNames;
 
     RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false);
 
     // Find all GC roots.
     MOZ_MUST_USE bool init();
-    // Find only GC roots in the provided set of |JSCompartment|s.
+    // Find only GC roots in the provided set of |JSCompartment|s. Note: it's
+    // important to take a CompartmentSet and not a RealmSet: objects in
+    // same-compartment realms can reference each other directly, without going
+    // through CCWs, so if we used a RealmSet here we would miss edges.
     MOZ_MUST_USE bool init(CompartmentSet& debuggees);
     // Find only GC roots in the given Debugger object's set of debuggee
     // compartments.
     MOZ_MUST_USE bool init(HandleObject debuggees);
 
     // Returns true if the RootList has been initialized successfully, false
     // otherwise.
     bool initialized() { return noGC.isSome(); }
@@ -1022,24 +1033,26 @@ class JS_PUBLIC_API(TracerConcrete) : pu
     js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
     JS::Zone* zone() const override;
 
   protected:
     explicit TracerConcrete(Referent* ptr) : Base(ptr) { }
     Referent& get() const { return *static_cast<Referent*>(ptr); }
 };
 
-// For JS::TraceChildren-based types that have a 'compartment' method.
+// For JS::TraceChildren-based types that have 'realm' and 'compartment'
+// methods.
 template<typename Referent>
-class JS_PUBLIC_API(TracerConcreteWithCompartment) : public TracerConcrete<Referent> {
+class JS_PUBLIC_API(TracerConcreteWithRealm) : public TracerConcrete<Referent> {
     typedef TracerConcrete<Referent> TracerBase;
     JSCompartment* compartment() const override;
+    JS::Realm* realm() const override;
 
   protected:
-    explicit TracerConcreteWithCompartment(Referent* ptr) : TracerBase(ptr) { }
+    explicit TracerConcreteWithRealm(Referent* ptr) : TracerBase(ptr) { }
 };
 
 // Define specializations for some commonly-used public JSAPI types.
 // These can use the generic templates above.
 template<>
 class JS_PUBLIC_API(Concrete<JS::Symbol>) : TracerConcrete<JS::Symbol> {
   protected:
     explicit Concrete(JS::Symbol* ptr) : TracerConcrete(ptr) { }
@@ -1069,42 +1082,45 @@ class JS_PUBLIC_API(Concrete<JS::BigInt>
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
 
     const char16_t* typeName() const override { return concreteTypeName; }
     static const char16_t concreteTypeName[];
 };
 #endif
 
 template<>
-class JS_PUBLIC_API(Concrete<JSScript>) : TracerConcreteWithCompartment<JSScript> {
+class JS_PUBLIC_API(Concrete<JSScript>) : TracerConcreteWithRealm<JSScript> {
   protected:
-    explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment<JSScript>(ptr) { }
+    explicit Concrete(JSScript *ptr) : TracerConcreteWithRealm<JSScript>(ptr) { }
 
   public:
     static void construct(void *storage, JSScript *ptr) { new (storage) Concrete(ptr); }
 
     CoarseType coarseType() const final { return CoarseType::Script; }
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
     const char* scriptFilename() const final;
 
     const char16_t* typeName() const override { return concreteTypeName; }
     static const char16_t concreteTypeName[];
 };
 
 // The JSObject specialization.
 template<>
-class JS_PUBLIC_API(Concrete<JSObject>) : public TracerConcreteWithCompartment<JSObject> {
+class JS_PUBLIC_API(Concrete<JSObject>) : public TracerConcrete<JSObject> {
   protected:
-    explicit Concrete(JSObject* ptr) : TracerConcreteWithCompartment(ptr) { }
+    explicit Concrete(JSObject* ptr) : TracerConcrete<JSObject>(ptr) { }
 
   public:
     static void construct(void* storage, JSObject* ptr) {
         new (storage) Concrete(ptr);
     }
 
+    JSCompartment* compartment() const override;
+    JS::Realm* realm() const override;
+
     const char* jsObjectClassName() const override;
     MOZ_MUST_USE bool jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& outName)
         const override;
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
 
     bool hasAllocationStack() const override;
     StackFrame allocationStack() const override;
 
@@ -1134,16 +1150,17 @@ class JS_PUBLIC_API(Concrete<JSString>) 
 // The ubi::Node null pointer. Any attempt to operate on a null ubi::Node asserts.
 template<>
 class JS_PUBLIC_API(Concrete<void>) : public Base {
     const char16_t* typeName() const override;
     Size size(mozilla::MallocSizeOf mallocSizeOf) const override;
     js::UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override;
     JS::Zone* zone() const override;
     JSCompartment* compartment() const override;
+    JS::Realm* realm() const override;
     CoarseType coarseType() const final;
 
     explicit Concrete(void* ptr) : Base(ptr) { }
 
   public:
     static void construct(void* storage, void* ptr) { new (storage) Concrete(ptr); }
 };
 
--- a/js/src/jsapi-tests/testUbiNode.cpp
+++ b/js/src/jsapi-tests/testUbiNode.cpp
@@ -4,16 +4,17 @@
 
 #include "builtin/TestingFunctions.h"
 #include "js/UbiNode.h"
 #include "js/UbiNodeDominatorTree.h"
 #include "js/UbiNodePostOrder.h"
 #include "js/UbiNodeShortestPaths.h"
 #include "jsapi-tests/tests.h"
 #include "util/Text.h"
+#include "vm/JSCompartment.h"
 #include "vm/SavedFrame.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
@@ -113,24 +114,27 @@ BEGIN_TEST(test_ubiNodeZone)
 END_TEST(test_ubiNodeZone)
 
 // ubi::Node::compartment works
 BEGIN_TEST(test_ubiNodeCompartment)
 {
     RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
     CHECK(global1);
     CHECK(JS::ubi::Node(global1).compartment() == cx->compartment());
+    CHECK(JS::ubi::Node(global1).realm() == cx->realm());
 
     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());
 
     JS::CompileOptions options(cx);
 
     // Create a script in the original realm...
     RootedScript script1(cx);
     CHECK(JS::Compile(cx, options, "", 0, &script1));
 
     {
@@ -138,16 +142,27 @@ 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());
+
+        // 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());
+        CHECK(JS::ubi::Node(wrappedGlobal1).realm() == nullptr);
     }
 
     return true;
 }
 END_TEST(test_ubiNodeCompartment)
 
 BEGIN_TEST(test_ubiNodeJSObjectConstructorName)
 {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4776,16 +4776,19 @@ class MOZ_STACK_CLASS Debugger::ObjectQu
     /* Construct an ObjectQuery to use matching scripts for |dbg|. */
     ObjectQuery(JSContext* cx, Debugger* dbg) :
         objects(cx), cx(cx), dbg(dbg), className(cx)
     { }
 
     /* The vector that we are accumulating results in. */
     AutoObjectVector objects;
 
+    /* The set of debuggee compartments. */
+    JS::CompartmentSet debuggeeCompartments;
+
     /*
      * Parse the query object |query|, and prepare to match only the objects it
      * specifies.
      */
     bool parseQuery(HandleObject query) {
         /* Check for the 'class' property */
         RootedValue cls(cx);
         if (!GetProperty(cx, query, query, cx->names().class_, &cls))
@@ -4810,16 +4813,28 @@ class MOZ_STACK_CLASS Debugger::ObjectQu
     /*
      * Traverse the heap to find all relevant objects and add them to the
      * provided vector.
      */
     bool findObjects() {
         if (!prepareQuery())
             return false;
 
+        if (!debuggeeCompartments.init()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+
+        for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
+            if (!debuggeeCompartments.put(r.front()->compartment())) {
+                ReportOutOfMemory(cx);
+                return false;
+            }
+        }
+
         {
             /*
              * We can't tolerate the GC moving things around while we're
              * searching the heap. Check that nothing we do causes a GC.
              */
             Maybe<JS::AutoCheckCannotGC> maybeNoGC;
             RootedObject dbgObj(cx, dbg->object);
             JS::ubi::RootList rootList(cx, maybeNoGC);
@@ -4861,22 +4876,32 @@ class MOZ_STACK_CLASS Debugger::ObjectQu
          * exist some path from this non-debuggee node back to a node in our
          * debuggee compartments. However, if that were true, then the incoming
          * cross compartment edge back into a debuggee compartment is already
          * listed as an edge in the RootList we started traversal with, and
          * therefore we don't need to follow edges to or from this non-debuggee
          * node.
          */
         JSCompartment* comp = referent.compartment();
-        if (comp && !dbg->isDebuggeeUnbarriered(JS::GetRealmForCompartment(comp))) {
+        if (comp && !debuggeeCompartments.has(comp)) {
             traversal.abandonReferent();
             return true;
         }
 
         /*
+         * If the referent has an associated realm and it's not a debuggee
+         * realm, skip it. Don't abandonReferent() here like above: realms
+         * within a compartment can reference each other without going through
+         * cross-compartment wrappers.
+         */
+        Realm* realm = referent.realm();
+        if (realm && !dbg->isDebuggeeUnbarriered(realm))
+            return true;
+
+        /*
          * If the referent is an object and matches our query's restrictions,
          * add it to the vector accumulating results. Skip objects that should
          * never be exposed to JS, like EnvironmentObjects and internal
          * functions.
          */
 
         if (!referent.is<JSObject>() || referent.exposeToJS().isUndefined())
             return true;
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -50,17 +50,17 @@ using JS::ubi::AtomOrTwoByteChars;
 using JS::ubi::CoarseType;
 using JS::ubi::Concrete;
 using JS::ubi::Edge;
 using JS::ubi::EdgeRange;
 using JS::ubi::Node;
 using JS::ubi::EdgeVector;
 using JS::ubi::StackFrame;
 using JS::ubi::TracerConcrete;
-using JS::ubi::TracerConcreteWithCompartment;
+using JS::ubi::TracerConcreteWithRealm;
 
 struct CopyToBufferMatcher
 {
     RangedPtr<char16_t> destination;
     size_t              maxLength;
 
     CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
       : destination(destination)
@@ -156,16 +156,17 @@ StackFrame::functionDisplayNameLength()
     return functionDisplayName().length();
 }
 
 // All operations on null ubi::Nodes crash.
 CoarseType Concrete<void>::coarseType() const      { MOZ_CRASH("null ubi::Node"); }
 const char16_t* Concrete<void>::typeName() const   { MOZ_CRASH("null ubi::Node"); }
 JS::Zone* Concrete<void>::zone() const             { MOZ_CRASH("null ubi::Node"); }
 JSCompartment* Concrete<void>::compartment() const { MOZ_CRASH("null ubi::Node"); }
+JS::Realm* Concrete<void>::realm() const           { MOZ_CRASH("null ubi::Node"); }
 
 UniquePtr<EdgeRange>
 Concrete<void>::edges(JSContext*, bool) const {
     MOZ_CRASH("null ubi::Node");
 }
 
 Node::Size
 Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const
@@ -352,22 +353,30 @@ template UniquePtr<EdgeRange> TracerConc
 template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(JSContext* cx, bool wantNames) const;
 #ifdef ENABLE_BIGINT
 template UniquePtr<EdgeRange> TracerConcrete<BigInt>::edges(JSContext* cx, bool wantNames) const;
 #endif
 template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(JSContext* cx, bool wantNames) const;
 
 template<typename Referent>
 JSCompartment*
-TracerConcreteWithCompartment<Referent>::compartment() const
+TracerConcreteWithRealm<Referent>::compartment() const
 {
     return TracerBase::get().compartment();
 }
 
-template JSCompartment* TracerConcreteWithCompartment<JSScript>::compartment() const;
+template<typename Referent>
+Realm*
+TracerConcreteWithRealm<Referent>::realm() const
+{
+    return TracerBase::get().realm();
+}
+
+template Realm* TracerConcreteWithRealm<JSScript>::realm() const;
+template JSCompartment* TracerConcreteWithRealm<JSScript>::compartment() const;
 
 bool
 Concrete<JSObject>::hasAllocationStack() const
 {
     return !!js::Debugger::getObjectAllocationSite(get());
 }
 
 StackFrame
@@ -402,16 +411,30 @@ Concrete<JSObject>::jsObjectConstructorN
     mozilla::Range<char16_t> chars(outName.get(), size);
     if (!JS_CopyStringChars(cx, chars, name))
         return false;
 
     outName[len] = '\0';
     return true;
 }
 
+JSCompartment*
+Concrete<JSObject>::compartment() const
+{
+    return Concrete::get().compartment();
+}
+
+Realm*
+Concrete<JSObject>::realm() const
+{
+    // Cross-compartment wrappers are shared by all realms in the compartment,
+    // so we return nullptr in that case.
+    return JS::GetObjectRealmOrNull(&Concrete::get());
+}
+
 const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol";
 #ifdef ENABLE_BIGINT
 const char16_t Concrete<BigInt>::concreteTypeName[] = u"JS::BigInt";
 #endif
 const char16_t Concrete<JSScript>::concreteTypeName[] = u"JSScript";
 const char16_t Concrete<js::LazyScript>::concreteTypeName[] = u"js::LazyScript";
 const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] = u"js::jit::JitCode";
 const char16_t Concrete<js::Shape>::concreteTypeName[] = u"js::Shape";