Bug 810560 - Fix scheduledForDestruction assertion (r=luke)
authorBill McCloskey <wmccloskey@mozilla.com>
Mon, 12 Nov 2012 14:57:53 -0800
changeset 113029 a10481c78d8e4d4a179b25f4aab47c3dec9c2a4c
parent 113026 5ea36ae3e9f00aa0107e50e7e518ddc8bbf75376
child 113030 866e9c7d656d7a95c564fbd24b40a6da0ef5d442
push id23848
push useremorley@mozilla.com
push dateTue, 13 Nov 2012 16:29:34 +0000
treeherdermozilla-central@d56d537a1843 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs810560
milestone19.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 810560 - Fix scheduledForDestruction assertion (r=luke)
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/xpconnect/src/XPCWrappedNative.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -799,17 +799,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     gcSweepPhase(0),
     gcSweepCompartmentIndex(0),
     gcSweepKindIndex(0),
     gcArenasAllocatedDuringSweep(NULL),
     gcInterFrameGC(0),
     gcSliceBudget(SliceBudget::Unlimited),
     gcIncrementalEnabled(true),
     gcExactScanningEnabled(true),
-    gcInTransplant(false),
+    gcManipulatingDeadCompartments(false),
     gcObjectsMarkedInDeadCompartments(0),
     gcPoke(false),
     heapState(Idle),
 #ifdef JS_GC_ZEAL
     gcZeal_(0),
     gcZealFrequency(0),
     gcNextScheduled(0),
     gcDeterministicOnly(false),
@@ -1553,17 +1553,17 @@ JS_TransplantObject(JSContext *cx, JSObj
 {
     RootedObject origobj(cx, origobjArg);
     RootedObject target(cx, targetArg);
     AssertHeapIsIdle(cx);
     JS_ASSERT(origobj != target);
     JS_ASSERT(!IsCrossCompartmentWrapper(origobj));
     JS_ASSERT(!IsCrossCompartmentWrapper(target));
 
-    AutoTransplantGC agc(cx);
+    AutoMaybeTouchDeadCompartments agc(cx);
 
     JSCompartment *destination = target->compartment();
     WrapperMap &map = destination->crossCompartmentWrappers;
     Value origv = ObjectValue(*origobj);
     JSObject *newIdentity;
 
     if (origobj->compartment() == destination) {
         // If the original object is in the same compartment as the
@@ -1627,17 +1627,17 @@ js_TransplantObjectWithWrapper(JSContext
                                JSObject *targetobjArg,
                                JSObject *targetwrapperArg)
 {
     RootedObject origobj(cx, origobjArg);
     RootedObject origwrapper(cx, origwrapperArg);
     RootedObject targetobj(cx, targetobjArg);
     RootedObject targetwrapper(cx, targetwrapperArg);
 
-    AutoTransplantGC agc(cx);
+    AutoMaybeTouchDeadCompartments agc(cx);
 
     AssertHeapIsIdle(cx);
     JS_ASSERT(!IsCrossCompartmentWrapper(origobj));
     JS_ASSERT(!IsCrossCompartmentWrapper(origwrapper));
     JS_ASSERT(!IsCrossCompartmentWrapper(targetobj));
     JS_ASSERT(!IsCrossCompartmentWrapper(targetwrapper));
 
     JSObject *newWrapper;
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -714,19 +714,20 @@ struct JSRuntime : js::RuntimeFriendFiel
      * currently only used for dynamic root analysis. Exact scanning starts out
      * enabled, and is disabled if e4x has been used.
      */
     bool                gcExactScanningEnabled;
 
 
     /*
      * This is true if we are in the middle of a brain transplant (e.g.,
-     * JS_TransplantObject).
+     * JS_TransplantObject) or some other operation that can manipulate
+     * dead compartments.
      */
-    bool                gcInTransplant;
+    bool                gcManipulatingDeadCompartments;
 
     /*
      * This field is incremented each time we mark an object inside a
      * compartment with no incoming cross-compartment pointers. Typically if
      * this happens it signals that an incremental GC is marking too much
      * stuff. At various times we check this counter and, if it has changed, we
      * run an immediate, non-incremental GC to clean up the dead
      * compartments. This should happen very rarely.
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3420,17 +3420,17 @@ BeginMarkPhase(JSRuntime *rt)
      * JS_TransplantObject and the like. The code here ensures that we don't
      * regress.
      *
      * Note that there are certain cases where allocations or read barriers in
      * dead compartments are difficult to avoid. We detect such cases (via the
      * gcObjectsMarkedInDeadCompartment counter) and redo any ongoing GCs after
      * the JS_TransplantObject function has finished. This ensures that the dead
      * compartments will be cleaned up. See AutoMarkInDeadCompartment and
-     * AutoTransplantGC for details.
+     * AutoMaybeTouchDeadCompartments for details.
      */
 
     /* Set the maybeAlive flag based on cross-compartment edges. */
     for (CompartmentsIter c(rt); !c.done(); c.next()) {
         for (WrapperMap::Enum e(c->crossCompartmentWrappers); !e.empty(); e.popFront()) {
             Cell *dst = e.front().key.wrapped;
             dst->compartment()->maybeAlive = true;
         }
@@ -5878,33 +5878,42 @@ PurgeJITCaches(JSCompartment *c)
         if (script->hasIonScript())
             script->ion->purgeCaches(c);
 
 #endif
     }
 #endif
 }
 
-AutoTransplantGC::AutoTransplantGC(JSContext *cx)
+AutoMaybeTouchDeadCompartments::AutoMaybeTouchDeadCompartments(JSContext *cx)
   : runtime(cx->runtime),
     markCount(runtime->gcObjectsMarkedInDeadCompartments),
     inIncremental(IsIncrementalGCInProgress(runtime)),
-    inTransplant(runtime->gcInTransplant)
-{
-    runtime->gcInTransplant = true;
-}
-
-AutoTransplantGC::~AutoTransplantGC()
+    manipulatingDeadCompartments(runtime->gcManipulatingDeadCompartments)
+{
+    runtime->gcManipulatingDeadCompartments = true;
+}
+
+AutoMaybeTouchDeadCompartments::AutoMaybeTouchDeadCompartments(JSObject *obj)
+  : runtime(obj->compartment()->rt),
+    markCount(runtime->gcObjectsMarkedInDeadCompartments),
+    inIncremental(IsIncrementalGCInProgress(runtime)),
+    manipulatingDeadCompartments(runtime->gcManipulatingDeadCompartments)
+{
+    runtime->gcManipulatingDeadCompartments = true;
+}
+
+AutoMaybeTouchDeadCompartments::~AutoMaybeTouchDeadCompartments()
 {
     if (inIncremental && runtime->gcObjectsMarkedInDeadCompartments != markCount) {
         PrepareForFullGC(runtime);
         js::GC(runtime, GC_NORMAL, gcreason::TRANSPLANT);
     }
 
-    runtime->gcInTransplant = inTransplant;
+    runtime->gcManipulatingDeadCompartments = manipulatingDeadCompartments;
 }
 
 } /* namespace js */
 
 JS_PUBLIC_API(void)
 JS_IterateCompartments(JSRuntime *rt, void *data,
                        JSIterateCompartmentCallback compartmentCallback)
 {
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1168,35 +1168,11 @@ MaybeVerifyBarriers(JSContext *cx, bool 
 
 #endif
 
 } /* namespace gc */
 
 void
 PurgeJITCaches(JSCompartment *c);
 
-/*
- * This auto class should be used around any code that does brain
- * transplants. Brain transplants can cause problems because they operate on all
- * compartments, whether live or dead. A brain transplant can cause a formerly
- * dead object to be "reanimated" by causing a read or write barrier to be
- * invoked on it during the transplant.
- *
- * To work around this issue, we observe when mark bits are set on objects in
- * dead compartments. If this happens during a brain transplant, we do a full,
- * non-incremental GC at the end of the brain transplant. This will clean up any
- * objects that were improperly marked.
- */
-struct AutoTransplantGC
-{
-    AutoTransplantGC(JSContext *cx);
-    ~AutoTransplantGC();
-
-  private:
-    JSRuntime *runtime;
-    unsigned markCount;
-    bool inIncremental;
-    bool inTransplant;
-};
-
 } /* namespace js */
 
 #endif /* jsgc_h___ */
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -21,26 +21,26 @@
 using JS::AssertCanGC;
 
 namespace js {
 
 struct Shape;
 
 /*
  * This auto class should be used around any code that might cause a mark bit to
- * be set on an object in a dead compartment. See AutoTransplantGC for more
- * details.
+ * be set on an object in a dead compartment. See AutoMaybeTouchDeadCompartments
+ * for more details.
  */
 struct AutoMarkInDeadCompartment
 {
     AutoMarkInDeadCompartment(JSCompartment *comp)
       : compartment(comp),
         scheduled(comp->scheduledForDestruction)
     {
-        if (comp->rt->gcInTransplant && comp->scheduledForDestruction) {
+        if (comp->rt->gcManipulatingDeadCompartments && comp->scheduledForDestruction) {
             comp->rt->gcObjectsMarkedInDeadCompartments++;
             comp->scheduledForDestruction = false;
         }
     }
 
     ~AutoMarkInDeadCompartment() {
         compartment->scheduledForDestruction = scheduled;
     }
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -1159,17 +1159,17 @@ js::RemapAllWrappersForObject(JSContext 
 
     return true;
 }
 
 JS_FRIEND_API(bool)
 js::RecomputeWrappers(JSContext *cx, const CompartmentFilter &sourceFilter,
                       const CompartmentFilter &targetFilter)
 {
-    AutoTransplantGC agc(cx);
+    AutoMaybeTouchDeadCompartments agc(cx);
 
     AutoWrapperVector toRecompute(cx);
 
     for (CompartmentsIter c(cx->runtime); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -290,11 +290,39 @@ RemapAllWrappersForObject(JSContext *cx,
                           JSObject *newTarget);
 
 // API to recompute all cross-compartment wrappers whose source and target
 // match the given filters.
 JS_FRIEND_API(bool)
 RecomputeWrappers(JSContext *cx, const CompartmentFilter &sourceFilter,
                   const CompartmentFilter &targetFilter);
 
+/*
+ * This auto class should be used around any code, such as brain transplants,
+ * that may touch dead compartments. Brain transplants can cause problems
+ * because they operate on all compartments, whether live or dead. A brain
+ * transplant can cause a formerly dead object to be "reanimated" by causing a
+ * read or write barrier to be invoked on it during the transplant. In this way,
+ * a compartment becomes a zombie, kept alive by repeatedly consuming
+ * (transplanted) brains.
+ *
+ * To work around this issue, we observe when mark bits are set on objects in
+ * dead compartments. If this happens during a brain transplant, we do a full,
+ * non-incremental GC at the end of the brain transplant. This will clean up any
+ * objects that were improperly marked.
+ */
+struct JS_FRIEND_API(AutoMaybeTouchDeadCompartments)
+{
+    // The version that takes an object just uses it for its runtime.
+    AutoMaybeTouchDeadCompartments(JSContext *cx);
+    AutoMaybeTouchDeadCompartments(JSObject *obj);
+    ~AutoMaybeTouchDeadCompartments();
+
+  private:
+    JSRuntime *runtime;
+    unsigned markCount;
+    bool inIncremental;
+    bool manipulatingDeadCompartments;
+};
+
 } /* namespace js */
 
 #endif
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -505,16 +505,19 @@ XPCWrappedNative::GetNewOrUsed(XPCCallCo
     XPCMarkableJSVal newParentVal_markable(&newParentVal);
     AutoMarkingJSVal newParentVal_automarker(ccx, &newParentVal_markable);
     JSBool needsSOW = false;
     JSBool needsCOW = false;
 
     mozilla::Maybe<JSAutoCompartment> ac;
 
     if (sciWrapper.GetFlags().WantPreCreate()) {
+        // PreCreate may touch dead compartments.
+        js::AutoDeadCompartmentGC agc(parent);
+
         JSObject* plannedParent = parent;
         nsresult rv = sciWrapper.GetCallback()->PreCreate(identity, ccx,
                                                           parent, &parent);
         if (NS_FAILED(rv))
             return rv;
 
         if (rv == NS_SUCCESS_CHROME_ACCESS_ONLY)
             needsSOW = true;
@@ -1748,16 +1751,19 @@ XPCWrappedNative::RescueOrphans(XPCCallC
     // NB: We pass stopAtOuter=false during the unwrap because Location objects
     // are parented to outer window proxies.
     nsresult rv;
     JSObject *parentObj = js::GetObjectParent(mFlatJSObject);
     if (!parentObj)
         return NS_OK; // Global object. We're done.
     parentObj = js::UnwrapObject(parentObj, /* stopAtOuter = */ false);
 
+    // PreCreate may touch dead compartments.
+    js::AutoDeadCompartmentGC agc(parentobj);
+
     // There's one little nasty twist here. For reasons described in bug 752764,
     // we nuke SOW-ed objects after transplanting them. This means that nodes
     // parented to an element (such as XUL elements), can end up with a nuked proxy
     // in the parent chain, depending on the order of fixup. Because the proxy is
     // nuked, we can't follow it anywhere. But we _can_ find the new wrapper for
     // the underlying native parent, which is exactly what PreCreate does.
     // So do that here.
     if (MOZ_UNLIKELY(JS_IsDeadWrapper(parentObj))) {
@@ -3805,16 +3811,19 @@ ConstructSlimWrapper(XPCCallContext &ccx
     JSObject* parent = xpcScope->GetGlobalJSObject();
     if (!flags.WantPreCreate()) {
         SLIM_LOG_NOT_CREATED(ccx, identityObj,
                              "scriptable helper has no PreCreate hook");
 
         return false;
     }
 
+    // PreCreate may touch dead compartments.
+    js::AutoDeadCompartmentGC agc(parent);
+
     JSObject* plannedParent = parent;
     nsresult rv = classInfoHelper->PreCreate(identityObj, ccx, parent, &parent);
     if (rv != NS_SUCCESS_ALLOW_SLIM_WRAPPERS) {
         SLIM_LOG_NOT_CREATED(ccx, identityObj, "PreCreate hook refused");
 
         return false;
     }