From 713936e41b2bbdb7da75dd715fff5ca19b5615ee Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Fri, 24 Jul 2015 13:05:01 -0700 Subject: [PATCH] Bug 789589 - Implement JS_NewDataView, r=Waldo --HG-- extra : commitid : b5iH1lEe6N extra : rebase_source : 81579a6e32a33acb08850dd7b8d6d3b900e02b3b --- js/src/jsapi-tests/testArrayBufferView.cpp | 55 +++++++++++++++------- js/src/jsfriendapi.h | 10 ++++ js/src/vm/TypedArrayObject.cpp | 49 ++++++++++++++----- 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/js/src/jsapi-tests/testArrayBufferView.cpp b/js/src/jsapi-tests/testArrayBufferView.cpp index 34afbf7b33dd..853d55780d02 100644 --- a/js/src/jsapi-tests/testArrayBufferView.cpp +++ b/js/src/jsapi-tests/testArrayBufferView.cpp @@ -86,23 +86,10 @@ BEGIN_TEST(testArrayBufferView_type) static JSObject* CreateDataView(JSContext* cx) { - JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); - if (!global) + JS::Rooted buffer(cx, JS_NewArrayBuffer(cx, 8)); + if (!buffer) return nullptr; - - static const char code[] = "new DataView(new ArrayBuffer(8))"; - - JS::Rooted val(cx); - JS::CompileOptions opts(cx); - if (!JS::Evaluate(cx, opts.setFileAndLine(__FILE__, __LINE__), - code, strlen(code), &val)) - return nullptr; - - JS::Rooted dv(cx, &val.toObject()); - if (!JS_IsDataViewObject(dv)) - return nullptr; - - return dv; + return JS_NewDataView(cx, buffer, 0, 8); } template buffer(cx); + { + AutoCompartment ac(cx, otherGlobal); + buffer = JS_NewArrayBuffer(cx, 8); + CHECK(buffer); + CHECK(buffer->as().byteLength() == 8); + } + CHECK(buffer->compartment() == otherGlobal->compartment()); + CHECK(JS_WrapObject(cx, &buffer)); + CHECK(buffer->compartment() == global->compartment()); + + JS::Rooted dataview(cx, JS_NewDataView(cx, buffer, 4, 4)); + CHECK(dataview); + CHECK(dataview->is()); + + JS::Rooted val(cx); + + val = ObjectValue(*dataview); + CHECK(JS_SetProperty(cx, global, "view", val)); + + EVAL("view.buffer", &val); + CHECK(val.toObject().is()); + + CHECK(dataview->compartment() == global->compartment()); + JS::Rooted otherView(cx, js::UncheckedUnwrap(dataview)); + CHECK(otherView->compartment() == otherGlobal->compartment()); + JS::Rooted otherBuffer(cx, js::UncheckedUnwrap(&val.toObject())); + CHECK(otherBuffer->compartment() == otherGlobal->compartment()); + + EVAL("Object.getPrototypeOf(view) === DataView.prototype", &val); + CHECK(val.toBoolean() == true); + return true; } diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 5bd2c786ea25..c14cd030ca15 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -2127,6 +2127,16 @@ JS_IsNeuteredArrayBufferObject(JSObject* obj); JS_FRIEND_API(bool) JS_IsDataViewObject(JSObject* obj); +/* + * Create a new DataView using the given ArrayBuffer for storage. The given + * buffer must be an ArrayBuffer (or a cross-compartment wrapper of an + * ArrayBuffer), and the offset and length must fit within the bounds of the + * arrayBuffer. Currently, nullptr will be returned and an exception will be + * thrown if these conditions do not hold, but do not depend on that behavior. + */ +JS_FRIEND_API(JSObject*) +JS_NewDataView(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, int32_t byteLength); + /* * Return the byte offset of a data view into its array buffer. |obj| must be a * DataView. diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index c5a8a2e205e4..2e1fdf05fa77 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -481,8 +481,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject public: static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt) { - RootedObject proto(cx, nullptr); - return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, proto); + return fromBufferWithProto(cx, bufobj, byteOffset, lengthInt, nullptr); } static JSObject* @@ -969,17 +968,11 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, { MOZ_ASSERT(byteOffset <= INT32_MAX); MOZ_ASSERT(byteLength <= INT32_MAX); + MOZ_ASSERT(byteOffset + byteLength < UINT32_MAX); RootedObject proto(cx, protoArg); RootedObject obj(cx); - // This is overflow-safe: 2 * INT32_MAX is still a valid uint32_t. - if (byteOffset + byteLength > arrayBuffer->byteLength()) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); - return nullptr; - - } - NewObjectKind newKind = DataViewNewObjectKind(cx, byteLength, proto); obj = NewBuiltinClassInstance(cx, &class_, newKind); if (!obj) @@ -1002,12 +995,17 @@ DataViewObject::create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, } } + // Caller should have established these preconditions, and no + // (non-self-hosted) JS code has had an opportunity to run so nothing can + // have invalidated them. + MOZ_ASSERT(byteOffset <= arrayBuffer->byteLength()); + MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength()); + DataViewObject& dvobj = obj->as(); dvobj.setFixedSlot(TypedArrayLayout::BYTEOFFSET_SLOT, Int32Value(byteOffset)); dvobj.setFixedSlot(TypedArrayLayout::LENGTH_SLOT, Int32Value(byteLength)); dvobj.setFixedSlot(TypedArrayLayout::BUFFER_SLOT, ObjectValue(*arrayBuffer)); dvobj.initPrivate(arrayBuffer->dataPointer() + byteOffset); - MOZ_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength()); // Include a barrier if the data view's data pointer is in the nursery, as // is done for typed arrays. @@ -1033,9 +1031,8 @@ DataViewObject::construct(JSContext* cx, JSObject* bufobj, const CallArgs& args, } Rooted buffer(cx, &AsArrayBuffer(bufobj)); - uint32_t bufferLength = buffer->byteLength(); uint32_t byteOffset = 0; - uint32_t byteLength = bufferLength; + uint32_t byteLength = buffer->byteLength(); if (args.length() > 1) { if (!ToUint32(cx, args[1], &byteOffset)) @@ -1055,6 +1052,8 @@ DataViewObject::construct(JSContext* cx, JSObject* bufobj, const CallArgs& args, return false; } } else { + uint32_t bufferLength = buffer->byteLength(); + if (byteOffset > bufferLength) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); @@ -1069,7 +1068,7 @@ DataViewObject::construct(JSContext* cx, JSObject* bufobj, const CallArgs& args, MOZ_ASSERT(byteOffset <= INT32_MAX); MOZ_ASSERT(byteLength <= INT32_MAX); - if (byteOffset + byteLength > bufferLength) { + if (byteOffset + byteLength > buffer->byteLength()) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); return false; } @@ -2348,3 +2347,27 @@ JS_GetDataViewByteLength(JSObject* obj) return 0; return obj->as().byteLength(); } + +JS_FRIEND_API(JSObject*) +JS_NewDataView(JSContext* cx, HandleObject arrayBuffer, uint32_t byteOffset, int32_t byteLength) +{ + ConstructArgs cargs(cx); + if (!cargs.init(3)) + return nullptr; + + RootedObject constructor(cx); + JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(&DataViewObject::class_); + if (!GetBuiltinConstructor(cx, key, &constructor)) + return nullptr; + + cargs[0].setObject(*arrayBuffer); + cargs[1].setNumber(byteOffset); + cargs[2].setInt32(byteLength); + + RootedValue fun(cx, ObjectValue(*constructor)); + RootedValue rval(cx); + if (!Construct(cx, fun, cargs, fun, &rval)) + return nullptr; + MOZ_ASSERT(rval.isObject()); + return &rval.toObject(); +}