Bug 667388 - Handle wrappers during structured clone. r=mrbkap
authorBobby Holley <bobbyholley@gmail.com>
Fri, 23 Mar 2012 14:59:27 -0700
changeset 90214 1fb77c1bb7425855a005ed125ae33c8a712d8f02
parent 90213 419581bb1b9054e3e3429c6fe4f327d9d17f2641
child 90215 66223f04fb5505123f5469bb53f5df79b33c16cc
push id22323
push userbmo@edmorley.co.uk
push dateSat, 24 Mar 2012 16:06:07 +0000
treeherdermozilla-central@20a01901480f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs667388
milestone14.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 667388 - Handle wrappers during structured clone. r=mrbkap We also remove the declared-but-never-implemented JSObject::getWrapperHandler.
js/src/jsclone.cpp
js/src/jsclone.h
js/src/jsobj.h
js/src/jsscope.cpp
js/src/jswrapper.cpp
js/src/jswrapper.h
--- a/js/src/jsclone.cpp
+++ b/js/src/jsclone.cpp
@@ -508,28 +508,43 @@ JSStructuredCloneWriter::startObject(JSO
 
     /* Write the header for obj. */
     return out.writePair(obj->isArray() ? SCTAG_ARRAY_OBJECT : SCTAG_OBJECT_OBJECT, 0);
 }
 
 bool
 JSStructuredCloneWriter::startWrite(const js::Value &v)
 {
+    assertSameCompartment(context(), v);
+
     if (v.isString()) {
         return writeString(SCTAG_STRING, v.toString());
     } else if (v.isNumber()) {
         return out.writeDouble(v.toNumber());
     } else if (v.isBoolean()) {
         return out.writePair(SCTAG_BOOLEAN, v.toBoolean());
     } else if (v.isNull()) {
         return out.writePair(SCTAG_NULL, 0);
     } else if (v.isUndefined()) {
         return out.writePair(SCTAG_UNDEFINED, 0);
     } else if (v.isObject()) {
         JSObject *obj = &v.toObject();
+
+        // The object might be a security wrapper. See if we can clone what's
+        // behind it. If we can, unwrap the object.
+        obj = UnwrapObjectChecked(context(), obj);
+        if (!obj)
+            return false;
+
+        // If we unwrapped above, we'll need to enter the underlying compartment.
+        // Let the AutoEnterCompartment do the right thing for us.
+        JSAutoEnterCompartment ac;
+        if (!ac.enter(context(), obj))
+            return false;
+
         if (obj->isRegExp()) {
             RegExpObject &reobj = obj->asRegExp();
             return out.writePair(SCTAG_REGEXP_OBJECT, reobj.getFlags()) &&
                    writeString(SCTAG_STRING, reobj.getSource());
         } else if (obj->isDate()) {
             double d = js_DateGetMsecSinceEpoch(context(), obj);
             return out.writePair(SCTAG_DATE_OBJECT, 0) && out.writeDouble(d);
         } else if (obj->isObject() || obj->isArray()) {
@@ -559,16 +574,22 @@ JSStructuredCloneWriter::startWrite(cons
 bool
 JSStructuredCloneWriter::write(const Value &v)
 {
     if (!startWrite(v))
         return false;
 
     while (!counts.empty()) {
         JSObject *obj = &objs.back().toObject();
+
+        // The objects in |obj| can live in other compartments.
+        JSAutoEnterCompartment ac;
+        if (!ac.enter(context(), obj))
+            return false;
+
         if (counts.back()) {
             counts.back()--;
             jsid id = ids.back();
             ids.popBack();
             checkStack();
             if (JSID_IS_STRING(id) || JSID_IS_INT(id)) {
                 /*
                  * If obj still has an own property named id, write it out.
--- a/js/src/jsclone.h
+++ b/js/src/jsclone.h
@@ -168,16 +168,19 @@ struct JSStructuredCloneWriter {
     bool startObject(JSObject *obj);
     bool startWrite(const js::Value &v);
 
     inline void checkStack();
 
     js::SCOutput &out;
 
     // Vector of objects with properties remaining to be written.
+    //
+    // NB: These can span multiple compartments, so the compartment must be
+    // entered before any manipulation is performed.
     js::AutoValueVector objs;
 
     // counts[i] is the number of properties of objs[i] remaining to be written.
     // counts.length() == objs.length() and sum(counts) == ids.length().
     js::Vector<size_t> counts;
 
     // Ids of properties remaining to be written.
     js::AutoIdVector ids;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -861,21 +861,16 @@ struct JSObject : public js::ObjectImpl
     inline jsval getNamespaceDeclared() const;
     inline void setNamespaceDeclared(jsval decl);
 
     inline JSAtom *getQNameLocalName() const;
     inline jsval getQNameLocalNameVal() const;
     inline void setQNameLocalName(JSAtom *name);
 
     /*
-     * Proxy-specific getters and setters.
-     */
-    inline js::Wrapper *getWrapperHandler() const;
-
-    /*
      * Back to generic stuff.
      */
     inline bool isCallable();
 
     inline void finish(JSContext *cx);
     JS_ALWAYS_INLINE void finalize(JSContext *cx, bool background);
 
     inline bool hasProperty(JSContext *cx, jsid id, bool *foundp, unsigned flags = 0);
--- a/js/src/jsscope.cpp
+++ b/js/src/jsscope.cpp
@@ -103,16 +103,17 @@ PropertyTable::init(JSRuntime *rt, Shape
     }
     return true;
 }
 
 bool
 Shape::makeOwnBaseShape(JSContext *cx)
 {
     JS_ASSERT(!base()->isOwned());
+    assertSameCompartment(cx, compartment());
 
     RootedVarShape self(cx, this);
 
     BaseShape *nbase = js_NewGCBaseShape(cx);
     if (!nbase)
         return false;
 
     new (nbase) BaseShape(*self->base());
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -81,16 +81,35 @@ js::UnwrapObject(JSObject *wrapped, bool
         if (stopAtOuter && wrapped->getClass()->ext.innerObject)
             break;
     }
     if (flagsp)
         *flagsp = flags;
     return wrapped;
 }
 
+JS_FRIEND_API(JSObject *)
+js::UnwrapObjectChecked(JSContext *cx, JSObject *obj)
+{
+    while (obj->isWrapper()) {
+        JSObject *wrapper = obj;
+        Wrapper *handler = Wrapper::wrapperHandler(obj);
+        bool rvOnFailure;
+        if (!handler->enter(cx, wrapper, JSID_VOID,
+                            Wrapper::PUNCTURE, &rvOnFailure))
+            return rvOnFailure ? obj : NULL;
+        obj = Wrapper::wrappedObject(obj);
+        JS_ASSERT(obj);
+        handler->leave(cx, wrapper);
+        if (obj->getClass()->ext.innerObject)
+            break;
+    }
+    return obj;
+}
+
 bool
 js::IsCrossCompartmentWrapper(const JSObject *wrapper)
 {
     return wrapper->isWrapper() &&
            !!(Wrapper::wrapperHandler(wrapper)->flags() & Wrapper::CROSS_COMPARTMENT);
 }
 
 Wrapper::Wrapper(unsigned flags) : ProxyHandler(&sWrapperFamily), mFlags(flags)
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -248,13 +248,19 @@ IsWrapper(const JSObject *obj)
 
 // Given a JSObject, returns that object stripped of wrappers. If
 // stopAtOuter is true, then this returns the outer window if it was
 // previously wrapped. Otherwise, this returns the first object for
 // which JSObject::isWrapper returns false.
 JS_FRIEND_API(JSObject *) UnwrapObject(JSObject *obj, bool stopAtOuter = true,
                                        unsigned *flagsp = NULL);
 
+// Given a JSObject, returns that object stripped of wrappers. At each stage,
+// the security wrapper has the opportunity to veto the unwrap. Since checked
+// code should never be unwrapping outer window wrappers, we always stop at
+// outer windows.
+JS_FRIEND_API(JSObject *) UnwrapObjectChecked(JSContext *cx, JSObject *obj);
+
 bool IsCrossCompartmentWrapper(const JSObject *obj);
 
 } /* namespace js */
 
 #endif