Bug 982974 - Be paranoid when neutering ArrayBuffers. r=sfink, a=1.1.x+ B2G_1_1_20140317_MERGEDAY
authorJeff Walden <jwalden@mit.edu>
Sat, 15 Mar 2014 01:11:57 -0700
changeset 120027 2c70ef07c5b3
parent 120026 ed12abe9e118
child 120028 856d63e0708f
push id1154
push usersfink@mozilla.com
push date2014-03-16 02:38 +0000
reviewerssfink, 1
bugs982974
milestone18.1
Bug 982974 - Be paranoid when neutering ArrayBuffers. r=sfink, a=1.1.x+
js/src/jstypedarray.cpp
js/src/jstypedarray.h
js/src/jstypedarrayinlines.h
js/src/vm/ObjectImpl.h
--- a/js/src/jstypedarray.cpp
+++ b/js/src/jstypedarray.cpp
@@ -258,17 +258,17 @@ GetViewList(ArrayBufferObject *obj)
 #else
     // The list of views must be stored somewhere in the ArrayBufferObject, but
     // the slots are already being used for the element storage and the private
     // field is used for a delegate object. The ObjectElements header has space
     // for it, but I don't want to mess around with adding unions to it with
     // USE_NEW_OBJECT_REPRESENTATION pending, since it will solve this much
     // more cleanly.
     struct OldObjectRepresentationHack {
-            uint32_t capacity;
+            uint32_t flags;
             uint32_t initializedLength;
             JSObject *views;
     };
     return &reinterpret_cast<OldObjectRepresentationHack*>(obj->getElementsHeader())->views;
 #endif
 }
 
 bool
@@ -457,16 +457,18 @@ ArrayBufferObject::createDataViewForThis
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
 }
 
 bool
 ArrayBufferObject::stealContents(JSContext *cx, JSObject *obj, void **contents,
                                  uint8_t **data)
 {
+    MOZ_ASSERT(cx);
+
     ArrayBufferObject &buffer = obj->asArrayBuffer();
     JSObject *views = *GetViewList(&buffer);
 
     // remove buffer from the list of buffers with > 1 view
     if (views && NextView(views) && BufferLink(*GetViewList(&buffer)) != UNSET_BUFFER_LINK)
     {
         ArrayBufferObject *prev = &cx->runtime->liveArrayBuffers->asArrayBuffer();
         if (prev == &buffer) {
@@ -480,44 +482,57 @@ ArrayBufferObject::stealContents(JSConte
                     SetBufferLink(*GetViewList(prev), BufferLink(*GetViewList(buf)));
                     break;
                 }
                 prev = buf;
             }
         }
     }
 
-    js::ObjectElements *header = js::ObjectElements::fromElements((js::HeapSlot*)buffer.dataPointer());
-    if (buffer.hasDynamicElements()) {
+    uint32_t byteLen = buffer.byteLength();
+
+    js::ObjectElements *oldHeader = buffer.getElementsHeader();
+    js::ObjectElements *newHeader;
+
+    // If the ArrayBuffer's elements are transferrable, transfer ownership
+    // directly.  Otherwise we have to copy the data into new elements.
+    bool stolen = buffer.hasStealableContents();
+    if (stolen) {
+        newHeader = AllocateArrayBufferContents(cx, byteLen, NULL);
+        if (!newHeader)
+            return false;
+
         *GetViewList(&buffer) = NULL;
-        *contents = header;
+        *contents = oldHeader;
         *data = buffer.dataPointer();
 
-        buffer.setFixedElements();
-        header = js::ObjectElements::fromElements((js::HeapSlot*)buffer.dataPointer());
+        buffer.elements = newHeader->elements();
     } else {
-        uint32_t length = buffer.byteLength();
-        js::ObjectElements *newheader =
-            AllocateArrayBufferContents(cx, length, buffer.dataPointer());
-        if (!newheader) {
-            js_ReportOutOfMemory(cx);
+        js::ObjectElements *headerCopy =
+            AllocateArrayBufferContents(cx, byteLen, buffer.dataPointer());
+        if (!headerCopy)
             return false;
-        }
-
-        ArrayBufferObject::setElementsHeader(newheader, length);
-        *contents = newheader;
-        *data = reinterpret_cast<uint8_t *>(newheader + 1);
+
+        ArrayBufferObject::setElementsHeader(headerCopy, byteLen);
+        *contents = headerCopy;
+        *data = reinterpret_cast<uint8_t *>(headerCopy + 1);
+
+        // Keep using the current elements.
+        newHeader = oldHeader;
     }
 
     // Neuter the donor ArrayBuffer and all views of it
-    ArrayBufferObject::setElementsHeader(header, 0);
+    uint32_t flags = newHeader->flags;
+    ArrayBufferObject::setElementsHeader(newHeader, 0);
+    newHeader->flags = flags;
     *GetViewList(&buffer) = views;
     for (JSObject *view = views; view; view = NextView(view))
         TypedArray::neuter(view);
 
+    newHeader->setIsNeuteredBuffer();
     return true;
 }
 
 void
 ArrayBufferObject::obj_trace(JSTracer *trc, RawObject obj)
 {
     /*
      * If this object changes, it will get marked via the private data barrier,
--- a/js/src/jstypedarray.h
+++ b/js/src/jstypedarray.h
@@ -127,16 +127,29 @@ class ArrayBufferObject : public JSObjec
                                     HandleSpecialId sid, MutableHandleValue rval,
                                     JSBool strict);
 
     static JSBool obj_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op,
                                 MutableHandleValue statep, MutableHandleId idp);
 
     static void sweepAll(JSRuntime *rt);
 
+    bool hasStealableContents() const {
+        // Inline elements strictly adhere to the corresponding buffer.
+        if (!hasDynamicElements())
+            return false;
+
+        // Neutered contents aren't transferrable because we want a neutered
+        // array's contents to be backed by zeroed memory equal in length to
+        // the original buffer contents.  Transferring these contents would
+        // allocate new ones based on the current byteLength, which is 0 for a
+        // neutered array -- not the original byteLength.
+        return !isNeutered();
+    }
+
     static bool stealContents(JSContext *cx, JSObject *obj, void **contents,
                               uint8_t **data);
 
     static inline void setElementsHeader(js::ObjectElements *header, uint32_t bytes);
 
     void addView(RawObject view);
 
     bool allocateSlots(JSContext *cx, uint32_t size, uint8_t *contents = NULL);
@@ -152,16 +165,19 @@ class ArrayBufferObject : public JSObjec
     inline uint8_t * dataPointer() const;
 
    /*
      * Check if the arrayBuffer contains any data. This will return false for
      * ArrayBuffer.prototype and neutered ArrayBuffers.
      */
     inline bool hasData() const;
 
+    bool isNeutered() const {
+        return getElementsHeader()->isNeuteredBuffer();
+    }
 };
 
 /*
  * BufferView
  *
  * Common definitions shared by all ArrayBufferViews. (The name ArrayBufferView
  * is currently being used for a namespace in jsfriendapi.h.)
  */
--- a/js/src/jstypedarrayinlines.h
+++ b/js/src/jstypedarrayinlines.h
@@ -15,25 +15,23 @@
 
 // Sentinel value used to initialize ArrayBufferViews' NEXT_BUFFER_SLOTs to
 // show that they have not yet been added to any ArrayBuffer list
 JSObject * const UNSET_BUFFER_LINK = (JSObject*)0x2;
 
 inline void
 js::ArrayBufferObject::setElementsHeader(js::ObjectElements *header, uint32_t bytes)
 {
-    /*
-     * Note that |bytes| may not be a multiple of |sizeof(Value)|, so
-     * |capacity * sizeof(Value)| may underestimate the size by up to
-     * |sizeof(Value) - 1| bytes.
-     */
-    header->capacity = bytes / sizeof(js::Value);
+    header->flags = 0;
     header->initializedLength = bytes;
+
+    // NB: one or both of these fields is clobbered by GetViewList to store the
+    // 'views' link. Set them to 0 to effectively initialize 'views' to NULL.
+    header->capacity = 0;
     header->length = 0;
-    header->unused = 0;
 }
 
 inline uint32_t
 js::ArrayBufferObject::byteLength() const
 {
     JS_ASSERT(isArrayBuffer());
     return getElementsHeader()->initializedLength;
 }
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -857,42 +857,53 @@ ElementsHeader::asArrayBufferElements()
  */
 class ArrayBufferObject;
 class ObjectElements
 {
     friend struct ::JSObject;
     friend class ObjectImpl;
     friend class ArrayBufferObject;
 
-    /* Number of allocated slots. */
-    uint32_t capacity;
+    enum Flags {
+        NEUTERED_BUFFER = 0x1
+    };
+
+    /* See Flags enum above. */
+    uint32_t flags;
 
     /*
      * Number of initialized elements. This is <= the capacity, and for arrays
      * is <= the length. Memory for elements above the initialized length is
      * uninitialized, but values between the initialized length and the proper
      * length are conceptually holes.
+     *
+     * If this pairs with an ArrayBuffer, it instead stores byteLength.
      */
     uint32_t initializedLength;
 
+    /*
+     * Beware!  The next one or two fields (32-bit or 64-bit) are punned by
+     * ArrayBuffers to store their view list.  Overwrite with care!
+     */
+
+    /* Number of allocated slots. */
+    uint32_t capacity;
+
     /* 'length' property of array objects, unused for other objects. */
     uint32_t length;
 
-    /* :XXX: bug 586842 store state about sparse slots. */
-    uint32_t unused;
-
     void staticAsserts() {
         MOZ_STATIC_ASSERT(sizeof(ObjectElements) == VALUES_PER_HEADER * sizeof(Value),
                           "Elements size and values-per-Elements mismatch");
     }
 
   public:
 
     ObjectElements(uint32_t capacity, uint32_t length)
-      : capacity(capacity), initializedLength(0), length(length)
+      : flags(0), initializedLength(0), capacity(capacity), length(length)
     {}
 
     HeapSlot *elements() { return (HeapSlot *)(uintptr_t(this) + sizeof(ObjectElements)); }
     static ObjectElements * fromElements(HeapSlot *elems) {
         return (ObjectElements *)(uintptr_t(elems) - sizeof(ObjectElements));
     }
 
     static int offsetOfCapacity() {
@@ -901,16 +912,23 @@ class ObjectElements
     static int offsetOfInitializedLength() {
         return (int)offsetof(ObjectElements, initializedLength) - (int)sizeof(ObjectElements);
     }
     static int offsetOfLength() {
         return (int)offsetof(ObjectElements, length) - (int)sizeof(ObjectElements);
     }
 
     static const size_t VALUES_PER_HEADER = 2;
+
+    bool isNeuteredBuffer() const {
+        return flags & NEUTERED_BUFFER;
+    }
+    void setIsNeuteredBuffer() {
+        flags |= NEUTERED_BUFFER;
+    }
 };
 
 /* Shared singleton for objects with no elements. */
 extern HeapSlot *emptyObjectElements;
 
 struct Class;
 struct GCMarker;
 struct ObjectOps;