Bug 861925 - Steal and neuter ArrayBuffers at end of structured clone, r=jorendorff

--HG--
extra : rebase_source : 3378a06b47e7ee2f45da841b3604077b4c2b64e6
This commit is contained in:
Steve Fink 2013-10-15 23:48:20 -07:00
Родитель f1b0439945
Коммит 63e2453e93
6 изменённых файлов: 289 добавлений и 71 удалений

Просмотреть файл

@ -48,7 +48,9 @@ typedef bool (*WriteStructuredCloneOp)(JSContext *cx, JSStructuredCloneWriter *w
// with error set to one of the JS_SCERR_* values.
typedef void (*StructuredCloneErrorOp)(JSContext *cx, uint32_t errorid);
// The maximum supported structured-clone serialization format version.
// The maximum supported structured-clone serialization format version. Note
// that this does not need to be bumped for Transferable-only changes, since
// they are never saved to persistent storage.
#define JS_STRUCTURED_CLONE_VERSION 2
struct JSStructuredCloneCallbacks {

Просмотреть файл

@ -234,7 +234,7 @@ MSG_DEF(JSMSG_UNUSED180, 180, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_UNUSED181, 181, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_BAD_GENERATOR_SEND, 182, 1, JSEXN_TYPEERR, "attempt to send {0} to newborn generator")
MSG_DEF(JSMSG_SC_NOT_TRANSFERABLE, 183, 0, JSEXN_TYPEERR, "invalid transferable array for structured clone")
MSG_DEF(JSMSG_UNUSED184, 184, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_SC_DUP_TRANSFERABLE, 184, 0, JSEXN_TYPEERR, "duplicate transferable for structured clone")
MSG_DEF(JSMSG_CANT_REPORT_AS_NON_EXTENSIBLE, 185, 0, JSEXN_TYPEERR, "proxy can't report an extensible object as non-extensible")
MSG_DEF(JSMSG_UNUSED186, 186, 0, JSEXN_NONE, "")
MSG_DEF(JSMSG_UNUSED187, 187, 0, JSEXN_NONE, "")

Просмотреть файл

@ -224,6 +224,12 @@ class AutoArrayRooter : private AutoGCRooter {
template<class T>
class AutoVectorRooter : protected AutoGCRooter
{
typedef js::Vector<T, 8> VectorImpl;
VectorImpl vector;
/* Prevent overwriting of inline elements in vector. */
js::SkipRoot vectorRoot;
public:
explicit AutoVectorRooter(JSContext *cx, ptrdiff_t tag
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
@ -240,6 +246,7 @@ class AutoVectorRooter : protected AutoGCRooter
}
typedef T ElementType;
typedef typename VectorImpl::Range Range;
size_t length() const { return vector.length(); }
bool empty() const { return vector.empty(); }
@ -299,6 +306,8 @@ class AutoVectorRooter : protected AutoGCRooter
const T *end() const { return vector.end(); }
T *end() { return vector.end(); }
Range all() { return vector.all(); }
const T &back() const { return vector.back(); }
friend void AutoGCRooter::trace(JSTracer *trc);
@ -310,12 +319,6 @@ class AutoVectorRooter : protected AutoGCRooter
memset(t, 0, sizeof(T));
}
typedef js::Vector<T, 8> VectorImpl;
VectorImpl vector;
/* Prevent overwriting of inline elements in vector. */
js::SkipRoot vectorRoot;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
@ -335,6 +338,7 @@ class AutoHashMapRooter : protected AutoGCRooter
typedef Key KeyType;
typedef Value ValueType;
typedef typename HashMapImpl::Entry Entry;
typedef typename HashMapImpl::Lookup Lookup;
typedef typename HashMapImpl::Ptr Ptr;
typedef typename HashMapImpl::AddPtr AddPtr;

Просмотреть файл

@ -0,0 +1,87 @@
// |reftest| skip-if(!xulRuntime.shell)
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/licenses/publicdomain/
function test() {
// Note: -8 and -200 will trigger asm.js link failures because 8 and 200
// bytes are below the minimum allowed size, and the buffer will not
// actually be converted to an asm.js buffer.
for (var size of [0, 8, 16, 200, 1000, 4096, -8, -200, -8192, -65536]) {
var buffer_ctor = (size < 0) ? AsmJSArrayBuffer : ArrayBuffer;
size = Math.abs(size);
var old = buffer_ctor(size);
var copy = deserialize(serialize(old, [old]));
assertEq(old.byteLength, 0);
assertEq(copy.byteLength, size);
var constructors = [ Int8Array,
Uint8Array,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array,
Uint8ClampedArray ];
for (var ctor of constructors) {
var buf = buffer_ctor(size);
var old_arr = ctor(buf);
assertEq(buf.byteLength, size);
assertEq(buf, old_arr.buffer);
assertEq(old_arr.length, size / old_arr.BYTES_PER_ELEMENT);
var copy_arr = deserialize(serialize(old_arr, [ buf ]));
assertEq(buf.byteLength, 0, "donor array buffer should be neutered");
assertEq(old_arr.length, 0, "donor typed array should be neutered");
assertEq(copy_arr.buffer.byteLength == size, true);
assertEq(copy_arr.length, size / old_arr.BYTES_PER_ELEMENT);
buf = null;
old_arr = null;
gc(); // Tickle the ArrayBuffer -> view management
}
for (var ctor of constructors) {
var buf = buffer_ctor(size);
var old_arr = ctor(buf);
var dv = DataView(buf); // Second view
var copy_arr = deserialize(serialize(old_arr, [ buf ]));
assertEq(buf.byteLength, 0, "donor array buffer should be neutered");
assertEq(old_arr.length, 0, "donor typed array should be neutered");
assertEq(dv.byteLength, 0, "all views of donor array buffer should be neutered");
buf = null;
old_arr = null;
gc(); // Tickle the ArrayBuffer -> view management
}
// Mutate the buffer during the clone operation. The modifications should be visible.
if (size >= 4) {
old = buffer_ctor(size);
var view = Int32Array(old);
view[0] = 1;
var mutator = { get foo() { view[0] = 2; } };
var copy = deserialize(serialize([ old, mutator ], [old]));
var viewCopy = Int32Array(copy[0]);
assertEq(view.length, 0); // Neutered
assertEq(viewCopy[0], 2);
}
// Neuter the buffer during the clone operation. Should throw an exception.
if (size >= 4) {
old = buffer_ctor(size);
var mutator = {
get foo() {
deserialize(serialize(old, [old]));
}
};
// The throw is not yet implemented, bug 919259.
//var copy = deserialize(serialize([ old, mutator ], [old]));
}
}
}
test();
reportCompare(0, 0, 'ok');

Просмотреть файл

@ -197,3 +197,16 @@ function referencesVia(from, edge, to) {
print(e);
return false;
}
// Note that AsmJS ArrayBuffers have a minimum size, currently 4096 bytes. If a
// smaller size is given, a regular ArrayBuffer will be returned instead.
function AsmJSArrayBuffer(size) {
var ab = new ArrayBuffer(size);
(new Function('global', 'foreign', 'buffer', '' +
' "use asm";' +
' var i32 = new global.Int32Array(buffer);' +
' function g() {};' +
' return g;' +
''))(this,null,ab);
return ab;
}

Просмотреть файл

@ -69,8 +69,8 @@ enum StructuredDataType {
SCTAG_STRING_OBJECT,
SCTAG_NUMBER_OBJECT,
SCTAG_BACK_REFERENCE_OBJECT,
SCTAG_TRANSFER_MAP_HEADER,
SCTAG_TRANSFER_MAP,
SCTAG_DO_NOT_USE_1,
SCTAG_DO_NOT_USE_2,
SCTAG_TYPED_ARRAY_OBJECT,
SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100,
SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_INT8,
@ -83,12 +83,37 @@ enum StructuredDataType {
SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_FLOAT64,
SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_UINT8_CLAMPED,
SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + ScalarTypeRepresentation::TYPE_MAX - 1,
/*
* Define a separate range of numbers for Transferable-only tags, since
* they are not used for persistent clone buffers and therefore do not
* require bumping JS_STRUCTURED_CLONE_VERSION.
*/
SCTAG_TRANSFER_MAP_HEADER = 0xFFFF0200,
SCTAG_TRANSFER_MAP_ENTRY,
SCTAG_END_OF_BUILTIN_TYPES
};
// Data associated with an SCTAG_TRANSFER_MAP_HEADER that tells whether the
// contents have been read out yet or not.
enum TransferableMapHeader {
SCTAG_TM_NOT_MARKED = 0,
SCTAG_TM_MARKED
SCTAG_TM_UNREAD = 0,
SCTAG_TM_TRANSFERRED
};
enum TransferableObjectType {
// Transferable data has not been filled in yet
SCTAG_TM_UNFILLED = 0,
// Structured clone buffer does not yet own the data
SCTAG_TM_UNOWNED = 1,
// All values at least this large are owned by the clone buffer
SCTAG_TM_FIRST_OWNED = 2,
// Data is a pointer that can be freed
SCTAG_TM_ALLOC_DATA = 2,
};
namespace js {
@ -96,6 +121,7 @@ namespace js {
struct SCOutput {
public:
explicit SCOutput(JSContext *cx);
~SCOutput();
JSContext *context() const { return cx; }
@ -111,7 +137,8 @@ struct SCOutput {
bool extractBuffer(uint64_t **datap, size_t *sizep);
uint64_t count() { return buf.length(); }
uint64_t count() const { return buf.length(); }
uint64_t *rawBuffer() { return buf.begin(); }
private:
JSContext *cx;
@ -137,6 +164,12 @@ struct SCInput {
bool replace(uint64_t u);
bool replacePair(uint32_t tag, uint32_t data);
uint64_t *tell() const { return point; }
void seek(uint64_t *pos) {
JS_ASSERT(pos <= end);
point = pos;
}
template <class T>
bool readArray(T *p, size_t nelems);
@ -207,8 +240,7 @@ struct JSStructuredCloneWriter {
memory(out.context()), callbacks(cb), closure(cbClosure),
transferable(out.context(), tVal), transferableObjects(out.context()) { }
bool init() { return transferableObjects.init() && parseTransferable() &&
memory.init() && writeTransferMap(); }
bool init() { return parseTransferable() && memory.init() && writeTransferMap(); }
bool write(const js::Value &v);
@ -229,6 +261,7 @@ struct JSStructuredCloneWriter {
bool parseTransferable();
void reportErrorTransferable();
bool transferOwnership();
inline void checkStack();
@ -261,7 +294,7 @@ struct JSStructuredCloneWriter {
// List of transferable objects
JS::RootedValue transferable;
js::AutoObjectHashSet transferableObjects;
JS::AutoObjectVector transferableObjects;
friend bool JS_WriteTypedArray(JSStructuredCloneWriter *w, JS::Value v);
};
@ -298,35 +331,47 @@ ReadStructuredClone(JSContext *cx, uint64_t *data, size_t nbytes, Value *vp,
return r.read(vp);
}
bool
ClearStructuredClone(const uint64_t *data, size_t nbytes)
// This may acquire new ways of discarding transfer map entries as new
// Transferables are implemented.
static void
DiscardEntry(uint32_t mapEntryDescriptor, const uint64_t *ptr)
{
const uint64_t *point = data;
const uint64_t *end = data + nbytes / 8;
JS_ASSERT(mapEntryDescriptor == SCTAG_TM_ALLOC_DATA);
uint64_t u = LittleEndian::readUint64(ptr);
js_free(reinterpret_cast<void*>(u));
}
static void
Discard(const uint64_t *begin, const uint64_t *end)
{
const uint64_t *point = begin;
uint64_t u = LittleEndian::readUint64(point++);
uint32_t tag = uint32_t(u >> 32);
if (tag == SCTAG_TRANSFER_MAP_HEADER) {
if ((TransferableMapHeader)uint32_t(u) == SCTAG_TM_NOT_MARKED) {
while (point != end) {
uint64_t u = LittleEndian::readUint64(point++);
uint32_t tag = uint32_t(u >> 32);
if (tag == SCTAG_TRANSFER_MAP) {
u = LittleEndian::readUint64(point++);
js_free(reinterpret_cast<void*>(u));
} else {
// The only things in the transfer map should be
// SCTAG_TRANSFER_MAP tags paired with pointers. If we find
// any other tag, we've walked off the end of the transfer
// map.
break;
}
}
if (tag != SCTAG_TRANSFER_MAP_HEADER)
return;
if (TransferableMapHeader(u) == SCTAG_TM_TRANSFERRED)
return;
uint64_t numTransferables = LittleEndian::readUint64(point++);
while (numTransferables--) {
uint64_t u = LittleEndian::readUint64(point++);
JS_ASSERT(uint32_t(u >> 32) == SCTAG_TRANSFER_MAP_ENTRY);
uint32_t mapEntryDescriptor = uint32_t(u);
if (mapEntryDescriptor >= SCTAG_TM_FIRST_OWNED) {
DiscardEntry(mapEntryDescriptor, point);
point += 2; // Pointer and userdata
}
}
}
js_free((void *)data);
return true;
static void
ClearStructuredClone(const uint64_t *data, size_t nbytes)
{
JS_ASSERT(nbytes % 8 == 0);
Discard(data, data + nbytes / 8);
js_free(const_cast<uint64_t*>(data));
}
bool
@ -337,15 +382,14 @@ StructuredCloneHasTransferObjects(const uint64_t *data, size_t nbytes, bool *has
if (data) {
uint64_t u = LittleEndian::readUint64(data);
uint32_t tag = uint32_t(u >> 32);
if (tag == SCTAG_TRANSFER_MAP_HEADER) {
if (tag == SCTAG_TRANSFER_MAP_HEADER)
*hasTransferable = true;
}
}
return true;
}
}
} /* namespace js */
static inline uint64_t
PairToUInt64(uint32_t tag, uint32_t data)
@ -500,6 +544,12 @@ SCInput::readPtr(void **p)
SCOutput::SCOutput(JSContext *cx) : cx(cx), buf(cx) {}
SCOutput::~SCOutput()
{
// Free any transferable data left lying around in the buffer
Discard(rawBuffer(), rawBuffer() + count());
}
bool
SCOutput::write(uint64_t u)
{
@ -627,7 +677,7 @@ JS_STATIC_ASSERT(JSString::MAX_LENGTH < UINT32_MAX);
bool
JSStructuredCloneWriter::parseTransferable()
{
transferableObjects.clear();
MOZ_ASSERT(transferableObjects.empty(), "parseTransferable called with stale data");
if (JSVAL_IS_NULL(transferable) || JSVAL_IS_VOID(transferable))
return true;
@ -663,7 +713,7 @@ JSStructuredCloneWriter::parseTransferable()
JSObject* tObj = CheckedUnwrap(&v.toObject());
if (!tObj) {
JS_ReportError(context(), "Permission denied to access object");
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_UNWRAP_DENIED);
return false;
}
if (!tObj->is<ArrayBufferObject>()) {
@ -671,13 +721,13 @@ JSStructuredCloneWriter::parseTransferable()
return false;
}
// No duplicate:
if (transferableObjects.has(tObj)) {
reportErrorTransferable();
// No duplicates allowed
if (std::find(transferableObjects.begin(), transferableObjects.end(), tObj) != transferableObjects.end()) {
JS_ReportErrorNumber(context(), js_GetErrorMessage, NULL, JSMSG_SC_DUP_TRANSFERABLE);
return false;
}
if (!transferableObjects.putNew(tObj))
if (!transferableObjects.append(tObj))
return false;
}
@ -895,25 +945,67 @@ JSStructuredCloneWriter::writeTransferMap()
if (transferableObjects.empty())
return true;
if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_NOT_MARKED))
if (!out.writePair(SCTAG_TRANSFER_MAP_HEADER, (uint32_t)SCTAG_TM_UNREAD))
return false;
for (HashSet<JSObject*>::Range r = transferableObjects.all();
!r.empty(); r.popFront()) {
JSObject *obj = r.front();
if (!out.write(transferableObjects.length()))
return false;
for (JS::AutoObjectVector::Range tr = transferableObjects.all();
!tr.empty(); tr.popFront())
{
JSObject *obj = tr.front();
if (!memory.put(obj, memory.count()))
return false;
// Emit a placeholder pointer. We will steal the data and neuter the
// transferable later.
if (!out.writePair(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_UNFILLED) ||
!out.writePtr(NULL) ||
!out.write(0))
{
return false;
}
}
return true;
}
bool
JSStructuredCloneWriter::transferOwnership()
{
if (transferableObjects.empty())
return true;
// Walk along the transferables and the transfer map at the same time,
// grabbing out pointers from the transferables and stuffing them into the
// transfer map.
uint64_t *point = out.rawBuffer();
JS_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_HEADER);
point++;
JS_ASSERT(LittleEndian::readUint64(point) == transferableObjects.length());
point++;
for (JS::AutoObjectVector::Range tr = transferableObjects.all();
!tr.empty();
tr.popFront())
{
void *content;
uint8_t *data;
if (!JS_StealArrayBufferContents(context(), obj, &content, &data))
return false;
if (!JS_StealArrayBufferContents(context(), tr.front(), &content, &data))
return false; // Destructor will clean up the already-transferred data
if (!out.writePair(SCTAG_TRANSFER_MAP, 0) || !out.writePtr(content))
return false;
MOZ_ASSERT(uint32_t(LittleEndian::readUint64(point) >> 32) == SCTAG_TRANSFER_MAP_ENTRY);
LittleEndian::writeUint64(point++, PairToUInt64(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_ALLOC_DATA));
LittleEndian::writeUint64(point++, reinterpret_cast<uint64_t>(content));
LittleEndian::writeUint64(point++, 0);
}
JS_ASSERT(point <= out.rawBuffer() + out.count());
JS_ASSERT_IF(point < out.rawBuffer() + out.count(),
uint32_t(LittleEndian::readUint64(point) >> 32) != SCTAG_TRANSFER_MAP_ENTRY);
return true;
}
@ -960,8 +1052,7 @@ JSStructuredCloneWriter::write(const Value &v)
}
memory.clear();
return true;
return transferOwnership();
}
bool
@ -1287,7 +1378,7 @@ JSStructuredCloneReader::startRead(Value *vp)
"invalid input");
return false;
case SCTAG_TRANSFER_MAP:
case SCTAG_TRANSFER_MAP_ENTRY:
// A map cannot be here but just at the beginning of the buffer.
JS_ReportErrorNumber(context(), js_GetErrorMessage, nullptr,
JSMSG_SC_BAD_SERIALIZED_DATA,
@ -1373,37 +1464,57 @@ JSStructuredCloneReader::readId(jsid *idp)
bool
JSStructuredCloneReader::readTransferMap()
{
uint64_t *headerPos = in.tell();
uint32_t tag, data;
if (!in.getPair(&tag, &data))
return false;
if (tag != SCTAG_TRANSFER_MAP_HEADER ||
(TransferableMapHeader)data == SCTAG_TM_MARKED)
if (tag != SCTAG_TRANSFER_MAP_HEADER || TransferableMapHeader(data) == SCTAG_TM_TRANSFERRED)
return true;
if (!in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_MARKED))
uint64_t numTransferables;
MOZ_ALWAYS_TRUE(in.readPair(&tag, &data));
if (!in.read(&numTransferables))
return false;
if (!in.readPair(&tag, &data))
return false;
for (uint64_t i = 0; i < numTransferables; i++) {
uint64_t *pos = in.tell();
while (1) {
if (!in.getPair(&tag, &data))
if (!in.readPair(&tag, &data))
return false;
if (tag != SCTAG_TRANSFER_MAP)
break;
JS_ASSERT(tag == SCTAG_TRANSFER_MAP_ENTRY);
JS_ASSERT(data == SCTAG_TM_ALLOC_DATA);
void *content;
if (!in.readPtr(&content))
return false;
if (!in.readPair(&tag, &data) || !in.readPtr(&content))
uint64_t userdata;
if (!in.read(&userdata))
return false;
JSObject *obj = JS_NewArrayBufferWithContents(context(), content);
if (!obj || !allObjs.append(ObjectValue(*obj)))
if (!obj)
return false;
// Rewind to the SCTAG_TRANSFER_MAP_ENTRY and mark this entry as unowned by
// the input buffer.
uint64_t *next = in.tell();
in.seek(pos);
MOZ_ALWAYS_TRUE(in.replacePair(SCTAG_TRANSFER_MAP_ENTRY, SCTAG_TM_UNOWNED));
in.seek(next);
if (!allObjs.append(ObjectValue(*obj)))
return false;
}
// Mark the whole transfer map as consumed
uint64_t *endPos = in.tell();
in.seek(headerPos);
MOZ_ALWAYS_TRUE(in.replacePair(SCTAG_TRANSFER_MAP_HEADER, SCTAG_TM_TRANSFERRED));
in.seek(endPos);
return true;
}
@ -1478,7 +1589,8 @@ JS_WriteStructuredClone(JSContext *cx, JS::Value valueArg, uint64_t **bufp, size
JS_PUBLIC_API(bool)
JS_ClearStructuredClone(const uint64_t *data, size_t nbytes)
{
return ClearStructuredClone(data, nbytes);
ClearStructuredClone(data, nbytes);
return true;
}
JS_PUBLIC_API(bool)