Bug 1016738 - Back out for webrtc crashes. a=backout
authorBill McCloskey <wmccloskey@mozilla.com>
Mon, 09 Jun 2014 12:30:38 -0700
changeset 206859 1f9bc5814f5ff23f8b3cf6766cda2d4e484c0b4c
parent 206858 c9048768c6c79a7b410405c3e2cea06dd82bb680
child 206860 a48d663e71b285b7ff6a2795f87c6e3214def8f2
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1016738
milestone32.0a2
Bug 1016738 - Back out for webrtc crashes. a=backout
js/public/GCAPI.h
js/src/gc/Marking.cpp
js/src/gc/Tracer.cpp
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/jsapi.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfriendapi.cpp
js/src/jsgc.cpp
js/src/jsgcinlines.h
js/src/jsobj.cpp
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/vm/Debugger.cpp
js/src/vm/ProxyObject.cpp
js/xpconnect/src/XPCWrappedNative.cpp
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -40,17 +40,17 @@ namespace JS {
     D(API)                                      \
     D(MAYBEGC)                                  \
     D(DESTROY_RUNTIME)                          \
     D(DESTROY_CONTEXT)                          \
     D(LAST_DITCH)                               \
     D(TOO_MUCH_MALLOC)                          \
     D(ALLOC_TRIGGER)                            \
     D(DEBUG_GC)                                 \
-    D(COMPARTMENT_REVIVED)                      \
+    D(TRANSPLANT)                               \
     D(RESET)                                    \
     D(OUT_OF_NURSERY)                           \
     D(EVICT_NURSERY)                            \
     D(FULL_STORE_BUFFER)                        \
                                                 \
     /* These are reserved for future use. */    \
     D(RESERVED0)                                \
     D(RESERVED1)                                \
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -183,16 +183,19 @@ CheckMarkedThing(JSTracer *trc, T **thin
         return;
 
     JS_ASSERT(thing->zone());
     JS_ASSERT(thing->zone()->runtimeFromMainThread() == trc->runtime());
     JS_ASSERT(trc->hasTracingDetails());
 
     DebugOnly<JSRuntime *> rt = trc->runtime();
 
+    JS_ASSERT_IF(IS_GC_MARKING_TRACER(trc) && rt->gc.manipulatingDeadZones,
+                 !thing->zone()->scheduledForDestruction);
+
     JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     JS_ASSERT_IF(thing->zone()->requireGCTracer(),
                  IS_GC_MARKING_TRACER(trc));
 
     JS_ASSERT(thing->isAligned());
 
     JS_ASSERT(MapTypeToTraceKind<T>::kind == GetGCThingTraceKind(thing));
@@ -213,43 +216,16 @@ CheckMarkedThing(JSTracer *trc, T **thin
      * ArenaHeader may not be synced with the real one in ArenaLists.
      */
     JS_ASSERT_IF(IsThingPoisoned(thing) && rt->isHeapBusy(),
                  !InFreeList(thing->arenaHeader(), thing));
 #endif
 
 }
 
-/*
- * We only set the maybeAlive flag for objects and scripts. It's assumed that,
- * if a compartment is alive, then it will have at least some live object or
- * script it in. Even if we get this wrong, the worst that will happen is that
- * scheduledForDestruction will be set on the compartment, which will cause some
- * extra GC activity to try to free the compartment.
- */
-template<typename T>
-static inline void
-SetMaybeAliveFlag(T *thing)
-{
-}
-
-template<>
-void
-SetMaybeAliveFlag(JSObject *thing)
-{
-    thing->compartment()->maybeAlive = true;
-}
-
-template<>
-void
-SetMaybeAliveFlag(JSScript *thing)
-{
-    thing->compartment()->maybeAlive = true;
-}
-
 template<typename T>
 static void
 MarkInternal(JSTracer *trc, T **thingp)
 {
     CheckMarkedThing(trc, thingp);
     T *thing = *thingp;
 
     if (!trc->callback) {
@@ -273,17 +249,17 @@ MarkInternal(JSTracer *trc, T **thingp)
         /*
          * Don't mark things outside a compartment if we are in a
          * per-compartment GC.
          */
         if (!thing->zone()->isGCMarking())
             return;
 
         PushMarkStack(AsGCMarker(trc), thing);
-        SetMaybeAliveFlag(thing);
+        thing->zone()->maybeAlive = true;
     } else {
         trc->callback(trc, (void **)thingp, MapTypeToTraceKind<T>::kind);
         trc->unsetTracingLocation();
     }
 
     trc->clearTracingDetails();
 }
 
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -633,30 +633,17 @@ GCMarker::appendGrayRoot(void *thing, JS
 #ifdef DEBUG
     root.debugPrinter = debugPrinter();
     root.debugPrintArg = debugPrintArg();
     root.debugPrintIndex = debugPrintIndex();
 #endif
 
     Zone *zone = static_cast<Cell *>(thing)->tenuredZone();
     if (zone->isCollecting()) {
-        // See the comment on SetMaybeAliveFlag to see why we only do this for
-        // objects and scripts. We rely on gray root buffering for this to work,
-        // but we only need to worry about uncollected dead compartments during
-        // incremental GCs (when we do gray root buffering).
-        switch (kind) {
-          case JSTRACE_OBJECT:
-            static_cast<JSObject *>(thing)->compartment()->maybeAlive = true;
-            break;
-          case JSTRACE_SCRIPT:
-            static_cast<JSScript *>(thing)->compartment()->maybeAlive = true;
-            break;
-          default:
-            break;
-        }
+        zone->maybeAlive = true;
         if (!zone->gcGrayRoots.append(root)) {
             resetBufferedGrayRoots();
             grayBufferState = GRAY_BUFFER_FAILED;
         }
     }
 }
 
 void
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -30,16 +30,18 @@ JS::Zone::Zone(JSRuntime *rt)
     gcHeapGrowthFactor(3.0),
     gcMallocBytes(0),
     gcMallocGCTriggered(false),
     gcBytes(0),
     gcTriggerBytes(0),
     data(nullptr),
     isSystem(false),
     usedByExclusiveThread(false),
+    scheduledForDestruction(false),
+    maybeAlive(true),
     active(false),
     jitZone_(nullptr),
     gcState_(NoGC),
     gcScheduled_(false),
     gcPreserveCode_(false),
     ionUsingBarriers_(false)
 {
     /* Ensure that there are no vtables to mess us up here. */
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -253,16 +253,21 @@ struct Zone : public JS::shadow::Zone,
 
     // Per-zone data for use by an embedder.
     void *data;
 
     bool isSystem;
 
     bool usedByExclusiveThread;
 
+    // These flags help us to discover if a compartment that shouldn't be alive
+    // manages to outlive a GC.
+    bool scheduledForDestruction;
+    bool maybeAlive;
+
     // True when there are active frames.
     bool active;
 
     mozilla::DebugOnly<unsigned> gcLastZoneGroupIndex;
 
   private:
     js::jit::JitZone *jitZone_;
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1083,16 +1083,17 @@ JS_WrapId(JSContext *cx, JS::MutableHand
 JS_PUBLIC_API(JSObject *)
 JS_TransplantObject(JSContext *cx, HandleObject origobj, HandleObject target)
 {
     AssertHeapIsIdle(cx);
     JS_ASSERT(origobj != target);
     JS_ASSERT(!origobj->is<CrossCompartmentWrapperObject>());
     JS_ASSERT(!target->is<CrossCompartmentWrapperObject>());
 
+    AutoMaybeTouchDeadZones agc(cx);
     AutoDisableProxyCheck adpc(cx->runtime());
 
     JSCompartment *destination = target->compartment();
     RootedValue origv(cx, ObjectValue(*origobj));
     RootedObject newIdentity(cx);
 
     if (origobj->compartment() == destination) {
         // If the original object is in the same compartment as the
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -60,19 +60,17 @@ JSCompartment::JSCompartment(Zone *zone,
     gcWeakMapList(nullptr),
     debugModeBits(runtime_->debugMode ? DebugFromC : 0),
     rngState(0),
     watchpointMap(nullptr),
     scriptCountsMap(nullptr),
     debugScriptMap(nullptr),
     debugScopes(nullptr),
     enumerators(nullptr),
-    compartmentStats(nullptr),
-    scheduledForDestruction(false),
-    maybeAlive(true)
+    compartmentStats(nullptr)
 #ifdef JS_ION
     , jitCompartment_(nullptr)
 #endif
 {
     runtime_->numCompartments++;
     JS_ASSERT_IF(options.mergeable(), options.invisibleToDebugger());
 }
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -443,21 +443,16 @@ struct JSCompartment
      * List of potentially active iterators that may need deleted property
      * suppression.
      */
     js::NativeIterator *enumerators;
 
     /* Used by memory reporters and invalid otherwise. */
     void               *compartmentStats;
 
-    // These flags help us to discover if a compartment that shouldn't be alive
-    // manages to outlive a GC.
-    bool scheduledForDestruction;
-    bool maybeAlive;
-
 #ifdef JS_ION
   private:
     js::jit::JitCompartment *jitCompartment_;
 
   public:
     bool ensureJitCompartmentExists(JSContext *cx);
     js::jit::JitCompartment *jitCompartment() {
         return jitCompartment_;
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -973,33 +973,35 @@ JS::IsIncrementalBarrierNeeded(JSContext
 JS_FRIEND_API(void)
 JS::IncrementalObjectBarrier(JSObject *obj)
 {
     if (!obj)
         return;
 
     JS_ASSERT(!obj->zone()->runtimeFromMainThread()->isHeapMajorCollecting());
 
+    AutoMarkInDeadZone amn(obj->zone());
+
     JSObject::writeBarrierPre(obj);
 }
 
 JS_FRIEND_API(void)
 JS::IncrementalReferenceBarrier(void *ptr, JSGCTraceKind kind)
 {
     if (!ptr)
         return;
 
     gc::Cell *cell = static_cast<gc::Cell *>(ptr);
-
-#ifdef DEBUG
     Zone *zone = kind == JSTRACE_OBJECT
                  ? static_cast<JSObject *>(cell)->zone()
                  : cell->tenuredZone();
+
     JS_ASSERT(!zone->runtimeFromMainThread()->isHeapMajorCollecting());
-#endif
+
+    AutoMarkInDeadZone amn(zone);
 
     if (kind == JSTRACE_OBJECT)
         JSObject::writeBarrierPre(static_cast<JSObject*>(cell));
     else if (kind == JSTRACE_STRING)
         JSString::writeBarrierPre(static_cast<JSString*>(cell));
     else if (kind == JSTRACE_SCRIPT)
         JSScript::writeBarrierPre(static_cast<JSScript*>(cell));
     else if (kind == JSTRACE_LAZY_SCRIPT)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2977,24 +2977,24 @@ GCRuntime::beginMarkPhase()
             if (!rt->isAtomsZone(zone)) {
                 any = true;
                 zone->setGCState(Zone::Mark);
             }
         } else {
             isFull = false;
         }
 
+        zone->scheduledForDestruction = false;
+        zone->maybeAlive = false;
         zone->setPreservingCode(false);
     }
 
     for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
         JS_ASSERT(c->gcLiveArrayBuffers.empty());
         c->marked = false;
-        c->scheduledForDestruction = false;
-        c->maybeAlive = false;
         if (shouldPreserveJITCode(c, currentTime))
             c->zone()->setPreservingCode(true);
     }
 
     if (!rt->gc.shouldCleanUpEverything) {
 #ifdef JS_ION
         if (JSCompartment *comp = jit::TopmostIonActivationCompartment(rt))
             comp->zone()->setPreservingCode(true);
@@ -3085,71 +3085,61 @@ GCRuntime::beginMarkPhase()
     if (isFull)
         UnmarkScriptData(rt);
 
     markRuntime(gcmarker);
     if (isIncremental)
         bufferGrayRoots();
 
     /*
-     * This code ensures that if a compartment is "dead", then it will be
-     * collected in this GC. A compartment is considered dead if its maybeAlive
+     * This code ensures that if a zone is "dead", then it will be
+     * collected in this GC. A zone is considered dead if its maybeAlive
      * flag is false. The maybeAlive flag is set if:
-     *   (1) the compartment has incoming cross-compartment edges, or
-     *   (2) an object in the compartment was marked during root marking, either
+     *   (1) the zone has incoming cross-compartment edges, or
+     *   (2) an object in the zone was marked during root marking, either
      *       as a black root or a gray root.
      * If the maybeAlive is false, then we set the scheduledForDestruction flag.
-     * At the end of the GC, we look for compartments where
-     * scheduledForDestruction is true. These are compartments that were somehow
-     * "revived" during the incremental GC. If any are found, we do a special,
-     * non-incremental GC of those compartments to try to collect them.
+     * At any time later in the GC, if we try to mark an object whose
+     * zone is scheduled for destruction, we will assert.
+     * NOTE: Due to bug 811587, we only assert if gcManipulatingDeadCompartments
+     * is true (e.g., if we're doing a brain transplant).
      *
-     * Compartments can be revived for a variety of reasons. On reason is bug
-     * 811587, where a reflector that was dead can be revived by DOM code that
-     * still refers to the underlying DOM node.
+     * The purpose of this check is to ensure that a zone that we would
+     * normally destroy is not resurrected by a read barrier or an
+     * allocation. This might happen during a function like JS_TransplantObject,
+     * which iterates over all compartments, live or dead, and operates on their
+     * objects. See bug 803376 for details on this problem. To avoid the
+     * problem, we are very careful to avoid allocation and read barriers during
+     * JS_TransplantObject and the like. The code here ensures that we don't
+     * regress.
      *
-     * Read barriers and allocations can also cause revival. This might happen
-     * during a function like JS_TransplantObject, which iterates over all
-     * compartments, live or dead, and operates on their objects. See bug 803376
-     * for details on this problem. To avoid the problem, we try to avoid
-     * allocation and read barriers during JS_TransplantObject and the like.
+     * Note that there are certain cases where allocations or read barriers in
+     * dead zone 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
+     * zones will be cleaned up. See AutoMarkInDeadZone and
+     * AutoMaybeTouchDeadZones for details.
      */
 
     /* Set the maybeAlive flag based on cross-compartment edges. */
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
-            const CrossCompartmentKey &key = e.front().key();
-            JSCompartment *dest;
-            switch (key.kind) {
-              case CrossCompartmentKey::ObjectWrapper:
-              case CrossCompartmentKey::DebuggerObject:
-              case CrossCompartmentKey::DebuggerSource:
-              case CrossCompartmentKey::DebuggerEnvironment:
-                dest = static_cast<JSObject *>(key.wrapped)->compartment();
-                break;
-              case CrossCompartmentKey::DebuggerScript:
-                dest = static_cast<JSScript *>(key.wrapped)->compartment();
-                break;
-              default:
-                dest = nullptr;
-                break;
-            }
-            if (dest)
-                dest->maybeAlive = true;
+            Cell *dst = e.front().key().wrapped;
+            dst->tenuredZone()->maybeAlive = true;
         }
     }
 
     /*
      * For black roots, code in gc/Marking.cpp will already have set maybeAlive
      * during MarkRuntime.
      */
 
-    for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
-        if (!c->maybeAlive && !rt->isAtomsCompartment(c))
-            c->scheduledForDestruction = true;
+    for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
+        if (!zone->maybeAlive && !rt->isAtomsZone(zone))
+            zone->scheduledForDestruction = true;
     }
     foundBlackGrayEdges = false;
 
     return true;
 }
 
 template <class CompartmentIterT>
 void
@@ -4515,18 +4505,18 @@ GCRuntime::resetIncrementalGC(const char
         JS_ASSERT(!strictCompartmentChecking);
 
         break;
       }
 
       case SWEEP:
         marker.reset();
 
-        for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
-            c->scheduledForDestruction = false;
+        for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
+            zone->scheduledForDestruction = false;
 
         /* Finish sweeping the current zone group, then abort. */
         abortSweepAfterCurrentGroup = true;
         incrementalCollectSlice(SliceBudget::Unlimited, JS::gcreason::RESET, GC_NORMAL);
 
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_WAIT_BACKGROUND_THREAD);
             rt->gc.waitBackgroundSweepOrAllocEnd();
@@ -4995,39 +4985,22 @@ GCRuntime::collect(bool incremental, int
                 gcCallback.op(rt, JSGC_END, gcCallback.data);
         }
 
         /* Need to re-schedule all zones for GC. */
         if (poked && shouldCleanUpEverything)
             JS::PrepareForFullGC(rt);
 
         /*
-         * This code makes an extra effort to collect compartments that we
-         * thought were dead at the start of the GC. See the large comment in
-         * beginMarkPhase.
-         */
-        bool repeatForDeadZone = false;
-        if (incremental && incrementalState == NO_INCREMENTAL) {
-            for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
-                if (c->scheduledForDestruction) {
-                    incremental = false;
-                    repeatForDeadZone = true;
-                    reason = JS::gcreason::COMPARTMENT_REVIVED;
-                    c->zone()->scheduleGC();
-                }
-            }
-        }
-
-        /*
          * If we reset an existing GC, we need to start a new one. Also, we
          * repeat GCs that happen during shutdown (the gcShouldCleanUpEverything
          * case) until we can be sure that no additional garbage is created
          * (which typically happens if roots are dropped during finalizers).
          */
-        repeat = (poked && shouldCleanUpEverything) || wasReset || repeatForDeadZone;
+        repeat = (poked && shouldCleanUpEverything) || wasReset;
     } while (repeat);
 
     if (incrementalState == NO_INCREMENTAL) {
 #ifdef JS_THREADSAFE
         EnqueuePendingParseTasksAfterGC(rt);
 #endif
     }
 }
@@ -5554,16 +5527,44 @@ ArenaLists::containsArena(JSRuntime *rt,
     for (ArenaHeader *aheader = arenaLists[allocKind].head(); aheader; aheader = aheader->next) {
         if (aheader == needle)
             return true;
     }
     return false;
 }
 
 
+AutoMaybeTouchDeadZones::AutoMaybeTouchDeadZones(JSContext *cx)
+  : runtime(cx->runtime()),
+    markCount(runtime->gc.objectsMarkedInDeadZones),
+    inIncremental(JS::IsIncrementalGCInProgress(runtime)),
+    manipulatingDeadZones(runtime->gc.manipulatingDeadZones)
+{
+    runtime->gc.manipulatingDeadZones = true;
+}
+
+AutoMaybeTouchDeadZones::AutoMaybeTouchDeadZones(JSObject *obj)
+  : runtime(obj->compartment()->runtimeFromMainThread()),
+    markCount(runtime->gc.objectsMarkedInDeadZones),
+    inIncremental(JS::IsIncrementalGCInProgress(runtime)),
+    manipulatingDeadZones(runtime->gc.manipulatingDeadZones)
+{
+    runtime->gc.manipulatingDeadZones = true;
+}
+
+AutoMaybeTouchDeadZones::~AutoMaybeTouchDeadZones()
+{
+    runtime->gc.manipulatingDeadZones = manipulatingDeadZones;
+
+    if (inIncremental && runtime->gc.objectsMarkedInDeadZones != markCount) {
+        JS::PrepareForFullGC(runtime);
+        js::GC(runtime, GC_NORMAL, JS::gcreason::TRANSPLANT);
+    }
+}
+
 AutoSuppressGC::AutoSuppressGC(ExclusiveContext *cx)
   : suppressGC_(cx->perThreadData->suppressGC)
 {
     suppressGC_++;
 }
 
 AutoSuppressGC::AutoSuppressGC(JSCompartment *comp)
   : suppressGC_(comp->runtimeFromMainThread()->mainThread.suppressGC)
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -10,16 +10,43 @@
 #include "jsgc.h"
 
 #include "gc/Zone.h"
 
 namespace js {
 
 class 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 zone. See AutoMaybeTouchDeadZones
+ * for more details.
+ */
+struct AutoMarkInDeadZone
+{
+    explicit AutoMarkInDeadZone(JS::Zone *zone)
+      : zone(zone),
+        scheduled(zone->scheduledForDestruction)
+    {
+        JSRuntime *rt = zone->runtimeFromMainThread();
+        if (rt->gc.manipulatingDeadZones && zone->scheduledForDestruction) {
+            rt->gc.objectsMarkedInDeadZones++;
+            zone->scheduledForDestruction = false;
+        }
+    }
+
+    ~AutoMarkInDeadZone() {
+        zone->scheduledForDestruction = scheduled;
+    }
+
+  private:
+    JS::Zone *zone;
+    bool scheduled;
+};
+
 inline Allocator *
 ThreadSafeContext::allocator() const
 {
     JS_ASSERT_IF(isJSContext(), &asJSContext()->zone()->allocator == allocator_);
     return allocator_;
 }
 
 template <typename T>
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2500,16 +2500,19 @@ JSObject::TradeGuts(JSContext *cx, JSObj
     }
 #endif
 }
 
 /* Use this method with extreme caution. It trades the guts of two objects. */
 bool
 JSObject::swap(JSContext *cx, HandleObject a, HandleObject b)
 {
+    AutoMarkInDeadZone adc1(a->zone());
+    AutoMarkInDeadZone adc2(b->zone());
+
     // Ensure swap doesn't cause a finalizer to not be run.
     JS_ASSERT(IsBackgroundFinalized(a->tenuredGetAllocKind()) ==
               IsBackgroundFinalized(b->tenuredGetAllocKind()));
     JS_ASSERT(a->compartment() == b->compartment());
 
     unsigned r = NotifyGCPreSwap(a, b);
 
     TradeGutsReserved reserved(cx);
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -39,16 +39,18 @@ Wrapper::defaultValue(JSContext *cx, Han
 }
 
 JSObject *
 Wrapper::New(JSContext *cx, JSObject *obj, JSObject *parent, Wrapper *handler,
              const WrapperOptions *options)
 {
     JS_ASSERT(parent);
 
+    AutoMarkInDeadZone amd(cx->zone());
+
     RootedValue priv(cx, ObjectValue(*obj));
     mozilla::Maybe<WrapperOptions> opts;
     if (!options) {
         opts.construct();
         opts.ref().selectDefaultClass(obj->isCallable());
         options = opts.addr();
     }
     return NewProxyObject(cx, handler, priv, options->proto(), parent, *options);
@@ -1038,16 +1040,18 @@ js::RemapAllWrappersForObject(JSContext 
 
     return true;
 }
 
 JS_FRIEND_API(bool)
 js::RecomputeWrappers(JSContext *cx, const CompartmentFilter &sourceFilter,
                       const CompartmentFilter &targetFilter)
 {
+    AutoMaybeTouchDeadZones agc(cx);
+
     AutoWrapperVector toRecompute(cx);
 
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate over the wrappers, filtering appropriately.
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -299,11 +299,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 zones. 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 zone 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 zones. 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(AutoMaybeTouchDeadZones)
+{
+    // The version that takes an object just uses it for its runtime.
+    explicit AutoMaybeTouchDeadZones(JSContext *cx);
+    explicit AutoMaybeTouchDeadZones(JSObject *obj);
+    ~AutoMaybeTouchDeadZones();
+
+  private:
+    JSRuntime *runtime;
+    unsigned markCount;
+    bool inIncremental;
+    bool manipulatingDeadZones;
+};
+
 } /* namespace js */
 
 #endif /* jswrapper_h */
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2088,17 +2088,17 @@ Debugger::addAllGlobalsAsDebuggees(JSCon
     for (ZonesIter zone(cx->runtime(), SkipAtoms); !zone.done(); zone.next()) {
         // Invalidate a zone at a time to avoid doing a ZoneCellIter
         // per compartment.
         AutoDebugModeInvalidation invalidate(zone);
 
         for (CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
             if (c == dbg->object->compartment() || c->options().invisibleToDebugger())
                 continue;
-            c->scheduledForDestruction = false;
+            c->zone()->scheduledForDestruction = false;
             GlobalObject *global = c->maybeGlobal();
             if (global) {
                 Rooted<GlobalObject*> rg(cx, global);
                 if (!dbg->addDebuggeeGlobal(cx, rg, invalidate))
                     return false;
             }
         }
     }
@@ -2885,17 +2885,17 @@ Debugger::findAllGlobals(JSContext *cx, 
     RootedObject result(cx, NewDenseEmptyArray(cx));
     if (!result)
         return false;
 
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         if (c->options().invisibleToDebugger())
             continue;
 
-        c->scheduledForDestruction = false;
+        c->zone()->scheduledForDestruction = false;
 
         GlobalObject *global = c->maybeGlobal();
 
         if (cx->runtime()->isSelfHostingGlobal(global))
             continue;
 
         if (global) {
             /*
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -72,17 +72,24 @@ void
 ProxyObject::initHandler(BaseProxyHandler *handler)
 {
     initSlot(HANDLER_SLOT, PrivateValue(handler));
 }
 
 static void
 NukeSlot(ProxyObject *proxy, uint32_t slot)
 {
-    proxy->setReservedSlot(slot, NullValue());
+    Value old = proxy->getSlot(slot);
+    if (old.isMarkable()) {
+        Zone *zone = ZoneOfValue(old);
+        AutoMarkInDeadZone amd(zone);
+        proxy->setReservedSlot(slot, NullValue());
+    } else {
+        proxy->setReservedSlot(slot, NullValue());
+    }
 }
 
 void
 ProxyObject::nuke(BaseProxyHandler *handler)
 {
     /* Allow people to add their own number of reserved slots beyond the expected 4 */
     unsigned numSlots = JSCLASS_RESERVED_SLOTS(getClass());
     for (unsigned i = 0; i < numSlots; i++)
--- a/js/xpconnect/src/XPCWrappedNative.cpp
+++ b/js/xpconnect/src/XPCWrappedNative.cpp
@@ -351,16 +351,19 @@ XPCWrappedNative::GetNewOrUsed(xpcObject
 
     RootedObject parent(cx, Scope->GetGlobalJSObject());
 
     RootedValue newParentVal(cx, NullValue());
 
     mozilla::Maybe<JSAutoCompartment> ac;
 
     if (sciWrapper.GetFlags().WantPreCreate()) {
+        // PreCreate may touch dead compartments.
+        js::AutoMaybeTouchDeadZones agc(parent);
+
         RootedObject plannedParent(cx, parent);
         nsresult rv = sciWrapper.GetCallback()->PreCreate(identity, cx,
                                                           parent, parent.address());
         if (NS_FAILED(rv))
             return rv;
         rv = NS_OK;
 
         MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent),
@@ -1277,16 +1280,19 @@ RescueOrphans(HandleObject obj)
     // NB: We pass stopAtOuter=false during the unwrap because Location objects
     // are parented to outer window proxies.
     nsresult rv;
     RootedObject parentObj(cx, js::GetObjectParent(obj));
     if (!parentObj)
         return NS_OK; // Global object. We're done.
     parentObj = js::UncheckedUnwrap(parentObj, /* stopAtOuter = */ false);
 
+    // PreCreate may touch dead compartments.
+    js::AutoMaybeTouchDeadZones agc(parentObj);
+
     // Recursively fix up orphans on the parent chain.
     rv = RescueOrphans(parentObj);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Now that we know our parent is in the right place, determine if we've
     // been orphaned. If not, we have nothing to do.
     if (!js::IsCrossCompartmentWrapper(parentObj))
         return NS_OK;