Bug 1498095 - Only poison the portion of the nursery we used r=jonco
authorPaul Bone <pbone@mozilla.com>
Thu, 11 Oct 2018 16:29:55 +1100
changeset 489663 a49f77c868163991c7d1bee5d73dd6716bbca577
parent 489662 414fe669452a2203910fb8da59fcdf211a249ca9
child 489664 9c6bfeb0e0dbbf7cccb4bbe7b17fabd37c80f55b
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersjonco
bugs1498095
milestone64.0a1
Bug 1498095 - Only poison the portion of the nursery we used r=jonco We have to do this both for sweeping and when setting the current chunk. We set the current chunk after sweeping so to have an effect when there's only one chunk (the main reason for this code) we need to do this correctly then also.
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -69,45 +69,47 @@ struct js::Nursery::Canary
 };
 #endif
 
 namespace js {
 struct NurseryChunk {
     char data[Nursery::NurseryChunkUsableSize];
     gc::ChunkTrailer trailer;
     static NurseryChunk* fromChunk(gc::Chunk* chunk);
-    void poisonAndInit(JSRuntime* rt);
-    void poisonAfterSweep();
+    void poisonAndInit(JSRuntime* rt, size_t extent = ChunkSize);
+    void poisonAfterSweep(size_t extent = ChunkSize);
     uintptr_t start() const { return uintptr_t(&data); }
     uintptr_t end() const { return uintptr_t(&trailer); }
     gc::Chunk* toChunk(JSRuntime* rt);
 };
 static_assert(sizeof(js::NurseryChunk) == gc::ChunkSize,
               "Nursery chunk size must match gc::Chunk size.");
 
 } /* namespace js */
 
 inline void
-js::NurseryChunk::poisonAndInit(JSRuntime* rt)
+js::NurseryChunk::poisonAndInit(JSRuntime* rt, size_t extent)
 {
-    MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
+    MOZ_ASSERT(extent <= ChunkSize);
+    MOZ_MAKE_MEM_UNDEFINED(this, extent);
 
-    JS_POISON(this, JS_FRESH_NURSERY_PATTERN, ChunkSize, MemCheckKind::MakeUndefined);
+    JS_POISON(this, JS_FRESH_NURSERY_PATTERN, extent, MemCheckKind::MakeUndefined);
 
     new (&trailer) gc::ChunkTrailer(rt, &rt->gc.storeBuffer());
 }
 
 inline void
-js::NurseryChunk::poisonAfterSweep()
+js::NurseryChunk::poisonAfterSweep(size_t extent)
 {
+    MOZ_ASSERT(extent <= ChunkSize);
     // We can poison the same chunk more than once, so first make sure memory
     // sanitizers will let us poison it.
-    MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
+    MOZ_MAKE_MEM_UNDEFINED(this, extent);
 
-    JS_POISON(this, JS_SWEPT_NURSERY_PATTERN, ChunkSize, MemCheckKind::MakeNoAccess);
+    JS_POISON(this, JS_SWEPT_NURSERY_PATTERN, extent, MemCheckKind::MakeNoAccess);
 }
 
 /* static */ inline js::NurseryChunk*
 js::NurseryChunk::fromChunk(Chunk* chunk)
 {
     return reinterpret_cast<NurseryChunk*>(chunk);
 }
 
@@ -170,17 +172,17 @@ js::Nursery::init(uint32_t maxNurseryByt
 
     maxChunkCount_ = 1;
     if (!allocateNextChunk(0, lock)) {
         maxChunkCount_ = 0;
         return false;
     }
     /* After this point the Nursery has been enabled */
 
-    setCurrentChunk(0);
+    setCurrentChunk(0, true);
     setStartPosition();
 
     char* env = getenv("JS_GC_PROFILE_NURSERY");
     if (env) {
         if (0 == strcmp(env, "help")) {
             fprintf(stderr, "JS_GC_PROFILE_NURSERY=N\n"
                     "\tReport minor GC's taking at least N microseconds.\n");
             exit(0);
@@ -226,17 +228,17 @@ js::Nursery::enable()
         AutoLockGCBgAlloc lock(runtime());
         maxChunkCount_ = 1;
         if (!allocateNextChunk(0, lock)) {
             maxChunkCount_ = 0;
             return;
         }
     }
 
-    setCurrentChunk(0);
+    setCurrentChunk(0, true);
     setStartPosition();
 #ifdef JS_GC_ZEAL
     if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
         enterZealMode();
     }
 #endif
 
     MOZ_ALWAYS_TRUE(runtime()->gc.storeBuffer().enable());
@@ -297,17 +299,17 @@ js::Nursery::enterZealMode() {
         maxChunkCount_ = chunkCountLimit();
     }
 }
 
 void
 js::Nursery::leaveZealMode() {
     if (isEnabled()) {
         MOZ_ASSERT(isEmpty());
-        setCurrentChunk(0);
+        setCurrentChunk(0, true);
         setStartPosition();
     }
 }
 #endif // JS_GC_ZEAL
 
 JSObject*
 js::Nursery::allocateObject(JSContext* cx, size_t size, size_t nDynamicSlots, const js::Class* clasp)
 {
@@ -1100,25 +1102,27 @@ js::Nursery::sweep(JSTracer* trc)
     sweepMapAndSetObjects();
 }
 
 void
 js::Nursery::clear()
 {
 #if defined(JS_GC_ZEAL) || defined(JS_CRASH_DIAGNOSTICS)
     /* Poison the nursery contents so touching a freed object will crash. */
-    for (unsigned i = currentStartChunk_; i < allocatedChunkCount(); ++i) {
+    for (unsigned i = currentStartChunk_; i < currentChunk_; ++i) {
         chunk(i).poisonAfterSweep();
     }
+    MOZ_ASSERT(maxChunkCount() > 0);
+    chunk(currentChunk_).poisonAfterSweep(position() - chunk(currentChunk_).start());
 #endif
 
     if (runtime()->hasZealMode(ZealMode::GenerationalGC)) {
         /* Only reset the alloc point when we are close to the end. */
         if (currentChunk_ + 1 == maxChunkCount()) {
-            setCurrentChunk(0);
+            setCurrentChunk(0, true);
         } else {
             // poisonAfterSweep poisons the chunk trailer. Ensure it's
             // initialized.
             chunk(currentChunk_).poisonAndInit(runtime());
         }
     } else {
         setCurrentChunk(0);
     }
@@ -1139,27 +1143,44 @@ js::Nursery::spaceToEnd(unsigned chunkCo
                    ((lastChunk - currentStartChunk_) * NurseryChunkUsableSize);
 
     MOZ_ASSERT(bytes <= maxChunkCount() * NurseryChunkUsableSize);
 
     return bytes;
 }
 
 MOZ_ALWAYS_INLINE void
-js::Nursery::setCurrentChunk(unsigned chunkno)
+js::Nursery::setCurrentChunk(unsigned chunkno, bool fullPoison)
 {
     MOZ_ASSERT(chunkno < chunkCountLimit());
     MOZ_ASSERT(chunkno < allocatedChunkCount());
+
+    if (!fullPoison &&
+        chunkno == currentChunk_ &&
+        position_ < chunk(chunkno).end() &&
+        position_ >= chunk(chunkno).start())
+    {
+        // When we setup a new chunk the whole chunk must be poisoned with the
+        // correct value (JS_FRESH_NURSERY_PATTERN).
+        //  1. The first time it was used it was fully poisoned with the
+        //     correct value.
+        //  2. When it is swept, only the used part is poisoned with the sept
+        //     value.
+        //  3. We repoison the swept part here, with the correct value.
+        chunk(chunkno).poisonAndInit(runtime(), position_ - chunk(chunkno).start());
+    } else {
+        chunk(chunkno).poisonAndInit(runtime());
+    }
+
     currentChunk_ = chunkno;
     position_ = chunk(chunkno).start();
     currentEnd_ = chunk(chunkno).end();
     if (canAllocateStrings_) {
         currentStringEnd_ = currentEnd_;
     }
-    chunk(chunkno).poisonAndInit(runtime());
 }
 
 bool
 js::Nursery::allocateNextChunk(const unsigned chunkno,
     AutoLockGCBgAlloc& lock)
 {
     const unsigned priorCount = allocatedChunkCount();
     const unsigned newCount = priorCount + 1;
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -504,17 +504,24 @@ class Nursery
     struct Canary;
     Canary* lastCanary_;
 #endif
 
     NurseryChunk& chunk(unsigned index) const {
         return *chunks_[index];
     }
 
-    void setCurrentChunk(unsigned chunkno);
+    /*
+     * Set the current chunk. This updates the currentChunk_, position_
+     * currentEnd_ and currentStringEnd_ values as approprite. It'll also
+     * poison the chunk, either a portion of the chunk if it is already the
+     * current chunk, or the whole chunk if fullPoison is true or it is not
+     * the current chunk.
+     */
+    void setCurrentChunk(unsigned chunkno, bool fullPoison = false);
     void setStartPosition();
 
     /*
      * Allocate the next chunk, or the first chunk for initialization.
      * Callers will probably want to call setCurrentChunk(0) next.
      */
     MOZ_MUST_USE bool allocateNextChunk(unsigned chunkno,
         AutoLockGCBgAlloc& lock);