| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "jstypedarray.h" |
| |
| #include <string.h> |
| |
| #include "mozilla/FloatingPoint.h" |
| #include "mozilla/PodOperations.h" |
| |
| #include "jstypes.h" |
| #include "jsutil.h" |
| #include "jsapi.h" |
| #include "jsarray.h" |
| #include "jscntxt.h" |
| #include "jscpucfg.h" |
| #include "jsversion.h" |
| #include "jsgc.h" |
| #include "jsnum.h" |
| #include "jsobj.h" |
| |
| #include "gc/Barrier.h" |
| #include "gc/Marking.h" |
| #include "vm/GlobalObject.h" |
| #include "vm/Interpreter.h" |
| #include "vm/NumericConversions.h" |
| |
| #include "jsatominlines.h" |
| #include "jsinferinlines.h" |
| #include "jstypedarrayinlines.h" |
| |
| #include "vm/GlobalObject-inl.h" |
| |
| #if defined(STARBOARD) |
| // empty |
| # elif defined(XP_WIN) |
| # include "jswin.h" |
| # else |
| # include <sys/mman.h> |
| # endif |
| |
| using namespace js; |
| using namespace js::gc; |
| using namespace js::types; |
| |
| using mozilla::IsNaN; |
| using mozilla::PodCopy; |
| using JS::CanonicalizeNaN; |
| |
| /* |
| * Allocate array buffers with the maximum number of fixed slots marked as |
| * reserved, so that the fixed slots may be used for the buffer's contents. |
| * The last fixed slot is kept for the object's private data. |
| */ |
| static const uint8_t ARRAYBUFFER_RESERVED_SLOTS = JSObject::MAX_FIXED_SLOTS - 1; |
| |
| static bool |
| ValueIsLength(const Value &v, uint32_t *len) |
| { |
| if (v.isInt32()) { |
| int32_t i = v.toInt32(); |
| if (i < 0) |
| return false; |
| *len = i; |
| return true; |
| } |
| |
| if (v.isDouble()) { |
| double d = v.toDouble(); |
| if (IsNaN(d)) |
| return false; |
| |
| uint32_t length = uint32_t(d); |
| if (d != double(length)) |
| return false; |
| |
| *len = length; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* |
| * Convert |v| to an array index for an array of length |length| per |
| * the Typed Array Specification section 7.0, |subarray|. If successful, |
| * the output value is in the range [0, length]. |
| */ |
| static bool |
| ToClampedIndex(JSContext *cx, const Value &v, uint32_t length, uint32_t *out) |
| { |
| int32_t result; |
| if (!ToInt32(cx, v, &result)) |
| return false; |
| if (result < 0) { |
| result += length; |
| if (result < 0) |
| result = 0; |
| } else if (uint32_t(result) > length) { |
| result = length; |
| } |
| *out = uint32_t(result); |
| return true; |
| } |
| |
| /* |
| * ArrayBuffer |
| * |
| * This class holds the underlying raw buffer that the TypedArray classes |
| * access. It can be created explicitly and passed to a TypedArray, or |
| * can be created implicitly by constructing a TypedArray with a size. |
| */ |
| |
| JS_ALWAYS_INLINE bool |
| IsArrayBuffer(const Value &v) |
| { |
| return v.isObject() && v.toObject().hasClass(&ArrayBufferObject::class_); |
| } |
| |
| JS_ALWAYS_INLINE bool |
| ArrayBufferObject::byteLengthGetterImpl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsArrayBuffer(args.thisv())); |
| args.rval().setInt32(args.thisv().toObject().as<ArrayBufferObject>().byteLength()); |
| return true; |
| } |
| |
| JSBool |
| ArrayBufferObject::byteLengthGetter(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args); |
| } |
| |
| bool |
| ArrayBufferObject::fun_slice_impl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsArrayBuffer(args.thisv())); |
| |
| Rooted<JSObject*> thisObj(cx, &args.thisv().toObject()); |
| |
| // these are the default values |
| uint32_t length = thisObj->as<ArrayBufferObject>().byteLength(); |
| uint32_t begin = 0, end = length; |
| |
| if (args.length() > 0) { |
| if (!ToClampedIndex(cx, args[0], length, &begin)) |
| return false; |
| |
| if (args.length() > 1) { |
| if (!ToClampedIndex(cx, args[1], length, &end)) |
| return false; |
| } |
| } |
| |
| if (begin > end) |
| begin = end; |
| |
| JSObject *nobj = createSlice(cx, thisObj->as<ArrayBufferObject>(), begin, end); |
| if (!nobj) |
| return false; |
| args.rval().setObject(*nobj); |
| return true; |
| } |
| |
| JSBool |
| ArrayBufferObject::fun_slice(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsArrayBuffer, fun_slice_impl>(cx, args); |
| } |
| |
| /* |
| * new ArrayBuffer(byteLength) |
| */ |
| JSBool |
| ArrayBufferObject::class_constructor(JSContext *cx, unsigned argc, Value *vp) |
| { |
| int32_t nbytes = 0; |
| if (argc > 0 && !ToInt32(cx, vp[2], &nbytes)) |
| return false; |
| |
| if (nbytes < 0) { |
| /* |
| * We're just not going to support arrays that are bigger than what will fit |
| * as an integer value; if someone actually ever complains (validly), then we |
| * can fix. |
| */ |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ARRAY_LENGTH); |
| return false; |
| } |
| |
| JSObject *bufobj = create(cx, uint32_t(nbytes)); |
| if (!bufobj) |
| return false; |
| vp->setObject(*bufobj); |
| return true; |
| } |
| |
| /* |
| * Note that some callers are allowed to pass in a NULL cx, so we allocate with |
| * the cx if available and fall back to the runtime. If oldptr is given, |
| * it's expected to be a previously-allocated ObjectElements* pointer that we |
| * then realloc. |
| */ |
| static ObjectElements * |
| AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, uint8_t *initdata, void *oldptr = NULL) |
| { |
| uint32_t size = nbytes + sizeof(ObjectElements); |
| ObjectElements *newheader; |
| |
| // if oldptr is given, then we need to do a realloc |
| if (oldptr) { |
| ObjectElements *oldheader = static_cast<ObjectElements *>(oldptr); |
| uint32_t oldnbytes = ArrayBufferObject::getElementsHeaderInitializedLength(oldheader); |
| newheader = static_cast<ObjectElements *>(maybecx ? maybecx->realloc_(oldptr, size) : js_realloc(oldptr, size)); |
| |
| // if we grew the array, we need to set the new bytes to 0 |
| if (newheader && nbytes > oldnbytes) |
| memset(reinterpret_cast<uint8_t*>(newheader->elements()) + oldnbytes, 0, nbytes - oldnbytes); |
| } else { |
| newheader = static_cast<ObjectElements *>(maybecx ? maybecx->calloc_(size) : js_calloc(size)); |
| } |
| if (!newheader) { |
| if (maybecx) |
| js_ReportOutOfMemory(maybecx); |
| return NULL; |
| } |
| |
| if (initdata) |
| memcpy(newheader->elements(), initdata, nbytes); |
| |
| // we rely on this being correct |
| ArrayBufferObject::setElementsHeader(newheader, nbytes); |
| |
| return newheader; |
| } |
| |
| bool |
| ArrayBufferObject::allocateSlots(JSContext *maybecx, uint32_t bytes, uint8_t *contents) |
| { |
| /* |
| * ArrayBufferObjects delegate added properties to another JSObject, so |
| * their internal layout can use the object's fixed slots for storage. |
| * Set up the object to look like an array with an elements header. |
| */ |
| JS_ASSERT(is<ArrayBufferObject>() && !hasDynamicSlots() && !hasDynamicElements()); |
| |
| size_t usableSlots = ARRAYBUFFER_RESERVED_SLOTS - ObjectElements::VALUES_PER_HEADER; |
| |
| if (bytes > sizeof(Value) * usableSlots) { |
| ObjectElements *header = AllocateArrayBufferContents(maybecx, bytes, contents); |
| if (!header) |
| return false; |
| elements = header->elements(); |
| } else { |
| elements = fixedElements(); |
| if (contents) |
| memcpy(elements, contents, bytes); |
| else |
| memset(elements, 0, bytes); |
| } |
| |
| setElementsHeader(getElementsHeader(), bytes); |
| |
| return true; |
| } |
| |
| static JSObject * |
| NextView(JSObject *obj) |
| { |
| return static_cast<JSObject*>(obj->getFixedSlot(BufferView::NEXT_VIEW_SLOT).toPrivate()); |
| } |
| |
| static HeapPtrObject * |
| GetViewList(ArrayBufferObject *obj) |
| { |
| #if USE_NEW_OBJECT_REPRESENTATION |
| // untested |
| return obj->getElementsHeader()->asArrayBufferElements().viewList(); |
| #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 initializedLength; |
| HeapPtrObject views; |
| }; |
| return &reinterpret_cast<OldObjectRepresentationHack*>(obj->getElementsHeader())->views; |
| #endif |
| } |
| |
| void |
| ArrayBufferObject::changeContents(JSContext *maybecx, ObjectElements *newHeader) |
| { |
| // Grab out data before invalidating it. |
| uint32_t byteLengthCopy = byteLength(); |
| uintptr_t oldDataPointer = uintptr_t(dataPointer()); |
| JSObject *viewListHead = *GetViewList(this); |
| |
| // Update all views. |
| uintptr_t newDataPointer = uintptr_t(newHeader->elements()); |
| for (JSObject *view = viewListHead; view; view = NextView(view)) { |
| uintptr_t newDataPtr = uintptr_t(view->getPrivate()) - oldDataPointer + newDataPointer; |
| view->setPrivate(reinterpret_cast<uint8_t*>(newDataPtr)); |
| |
| // Notify compiled jit code that the base pointer has moved. |
| if (maybecx) |
| MarkObjectStateChange(maybecx, view); |
| } |
| |
| // Change to the new header (now, so we can use GetViewList). |
| elements = newHeader->elements(); |
| |
| // Initialize 'newHeader'. |
| ArrayBufferObject::setElementsHeader(newHeader, byteLengthCopy); |
| *GetViewList(this) = viewListHead; |
| } |
| |
| bool |
| ArrayBufferObject::uninlineData(JSContext *maybecx) |
| { |
| if (hasDynamicElements()) |
| return true; |
| |
| ObjectElements *newHeader = AllocateArrayBufferContents(maybecx, byteLength(), dataPointer()); |
| if (!newHeader) |
| return false; |
| |
| changeContents(maybecx, newHeader); |
| return true; |
| } |
| |
| #if defined(JS_ION) && defined(JS_CPU_X64) && !defined(STARBOARD) |
| // To avoid dynamically checking bounds on each load/store, asm.js code relies |
| // on the SIGSEGV handler in AsmJSSignalHandlers.cpp. However, this only works |
| // if we can guarantee that *any* out-of-bounds access generates a fault. This |
| // isn't generally true since an out-of-bounds access could land on other |
| // Mozilla data. To overcome this on x64, we reserve an entire 4GB space, |
| // making only the range [0, byteLength) accessible, and use a 32-bit unsigned |
| // index into this space. (x86 and ARM require different tricks.) |
| // |
| // One complication is that we need to put an ObjectElements struct immediately |
| // before the data array (as required by the general JSObject data structure). |
| // Thus, we must stick a page before the elements to hold ObjectElements. |
| // |
| // |<------------------------------ 4GB + 1 pages --------------------->| |
| // |<--- sizeof --->|<------------------- 4GB ----------------->| |
| // |
| // | waste | ObjectElements | data array | inaccessible reserved memory | |
| // ^ ^ ^ |
| // | \ / |
| // obj->elements required to be page boundaries |
| // |
| JS_STATIC_ASSERT(sizeof(ObjectElements) < AsmJSPageSize); |
| JS_STATIC_ASSERT(AsmJSAllocationGranularity == AsmJSPageSize); |
| static const size_t AsmJSMappedSize = AsmJSPageSize + AsmJSBufferProtectedSize; |
| |
| bool |
| ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer) |
| { |
| if (buffer->isAsmJSArrayBuffer()) |
| return true; |
| |
| // Get the entire reserved region (with all pages inaccessible). |
| void *p; |
| # ifdef XP_WIN |
| p = VirtualAlloc(NULL, AsmJSMappedSize, MEM_RESERVE, PAGE_NOACCESS); |
| if (!p) |
| return false; |
| # else |
| p = mmap(NULL, AsmJSMappedSize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); |
| if (p == MAP_FAILED) |
| return false; |
| # endif |
| |
| // Enable access to the valid region. |
| JS_ASSERT(buffer->byteLength() % AsmJSAllocationGranularity == 0); |
| # ifdef XP_WIN |
| if (!VirtualAlloc(p, AsmJSPageSize + buffer->byteLength(), MEM_COMMIT, PAGE_READWRITE)) { |
| VirtualFree(p, 0, MEM_RELEASE); |
| return false; |
| } |
| # else |
| if (mprotect(p, AsmJSPageSize + buffer->byteLength(), PROT_READ | PROT_WRITE)) { |
| munmap(p, AsmJSMappedSize); |
| return false; |
| } |
| # endif |
| |
| // Copy over the current contents of the typed array. |
| uint8_t *data = reinterpret_cast<uint8_t*>(p) + AsmJSPageSize; |
| memcpy(data, buffer->dataPointer(), buffer->byteLength()); |
| |
| // Swap the new elements into the ArrayBufferObject. |
| ObjectElements *newHeader = reinterpret_cast<ObjectElements*>(data - sizeof(ObjectElements)); |
| ObjectElements *oldHeader = buffer->hasDynamicElements() ? buffer->getElementsHeader() : NULL; |
| buffer->changeContents(cx, newHeader); |
| js_free(oldHeader); |
| |
| // Mark the ArrayBufferObject so (1) we don't do this again, (2) we know not |
| // to js_free the header in the normal way. |
| newHeader->setIsAsmJSArrayBuffer(); |
| JS_ASSERT(data == buffer->dataPointer()); |
| return true; |
| } |
| |
| void |
| ArrayBufferObject::releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj) |
| { |
| ArrayBufferObject &buffer = obj->as<ArrayBufferObject>(); |
| JS_ASSERT(buffer.isAsmJSArrayBuffer()); |
| |
| uint8_t *p = buffer.dataPointer() - AsmJSPageSize ; |
| JS_ASSERT(uintptr_t(p) % AsmJSPageSize == 0); |
| # ifdef XP_WIN |
| VirtualFree(p, 0, MEM_RELEASE); |
| # else |
| munmap(p, AsmJSMappedSize); |
| # endif |
| } |
| |
| void |
| ArrayBufferObject::neuterAsmJSArrayBuffer(ArrayBufferObject &buffer) |
| { |
| // Protect all the pages so that any read/write will generate a fault which |
| // the AsmJSMemoryFaultHandler will turn into the expected result value. |
| JS_ASSERT(buffer.isAsmJSArrayBuffer()); |
| JS_ASSERT(buffer.byteLength() % AsmJSAllocationGranularity == 0); |
| #ifdef XP_WIN |
| if (!VirtualAlloc(buffer.dataPointer(), buffer.byteLength(), MEM_RESERVE, PAGE_NOACCESS)) |
| MOZ_CRASH(); |
| #else |
| if (mprotect(buffer.dataPointer(), buffer.byteLength(), PROT_NONE)) |
| MOZ_CRASH(); |
| #endif |
| } |
| #else /* defined(JS_ION) && defined(JS_CPU_X64) && !defined(STARBOARD)*/ |
| bool |
| ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer) |
| { |
| if (!buffer->uninlineData(cx)) |
| return false; |
| |
| buffer->getElementsHeader()->setIsAsmJSArrayBuffer(); |
| return true; |
| } |
| |
| void |
| ArrayBufferObject::releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj) |
| { |
| fop->free_(obj->as<ArrayBufferObject>().getElementsHeader()); |
| } |
| |
| void |
| ArrayBufferObject::neuterAsmJSArrayBuffer(ArrayBufferObject &buffer) |
| { |
| // TODO: be ever-so-slightly unsound (but safe) for now. |
| } |
| #endif |
| |
| #ifdef JSGC_GENERATIONAL |
| class WeakObjectSlotRef : public js::gc::BufferableRef |
| { |
| JSObject *owner; |
| size_t slot; |
| const char *desc; |
| |
| public: |
| explicit WeakObjectSlotRef(JSObject *owner, size_t slot, const char desc[]) |
| : owner(owner), slot(slot), desc(desc) |
| { |
| } |
| |
| virtual void mark(JSTracer *trc) { |
| MarkObjectUnbarriered(trc, &owner, "weak TypeArrayView ref"); |
| JSObject *obj = static_cast<JSObject*>(owner->getFixedSlot(slot).toPrivate()); |
| if (obj && obj != UNSET_BUFFER_LINK) { |
| JS_SET_TRACING_LOCATION(trc, (void*)&owner->getFixedSlotRef(slot)); |
| MarkObjectUnbarriered(trc, &obj, desc); |
| } |
| owner->setFixedSlot(slot, PrivateValue(obj)); |
| } |
| }; |
| #endif |
| |
| // Custom barrier is necessary for PrivateValues because they are not traced by |
| // default. |
| static void |
| WeakObjectSlotBarrierPost(JSObject *obj, size_t slot, const char *desc) |
| { |
| #ifdef JSGC_GENERATIONAL |
| obj->runtime()->gcStoreBuffer.putGeneric(WeakObjectSlotRef(obj, slot, desc)); |
| #endif |
| } |
| |
| static JSObject * |
| BufferLink(JSObject *view) |
| { |
| return static_cast<JSObject*>(view->getFixedSlot(BufferView::NEXT_BUFFER_SLOT).toPrivate()); |
| } |
| |
| static void |
| SetBufferLink(JSObject *view, JSObject *buffer) |
| { |
| view->setFixedSlot(BufferView::NEXT_BUFFER_SLOT, PrivateValue(buffer)); |
| } |
| |
| void |
| ArrayBufferObject::addView(JSObject *view) |
| { |
| // This view should never have been associated with a buffer before |
| JS_ASSERT(BufferLink(view) == UNSET_BUFFER_LINK); |
| |
| // Note that pre-barriers are not needed here because either the list was |
| // previously empty, in which case no pointer is being overwritten, or the |
| // list was nonempty and will be made weak during this call (and weak |
| // pointers cannot violate the snapshot-at-the-beginning invariant.) |
| |
| HeapPtrObject *views = GetViewList(this); |
| if (*views == NULL) { |
| // This ArrayBuffer will have a single view at this point, so it is a |
| // strong pointer (it will be marked during tracing.) |
| JS_ASSERT(NextView(view) == NULL); |
| } else { |
| view->setFixedSlot(BufferView::NEXT_VIEW_SLOT, PrivateValue(*views)); |
| WeakObjectSlotBarrierPost(view, BufferView::NEXT_VIEW_SLOT, "arraybuffer.nextview"); |
| |
| // Move the multiview buffer list link into this view since we're |
| // prepending it to the list. |
| SetBufferLink(view, BufferLink(*views)); |
| SetBufferLink(*views, UNSET_BUFFER_LINK); |
| WeakObjectSlotBarrierPost(view, BufferView::NEXT_BUFFER_SLOT, "view.nextbuffer"); |
| } |
| |
| *views = view; |
| |
| // The view list is not stored in the private slot, but it needs the same |
| // post barrier implementation |
| privateWriteBarrierPost((void**)views); |
| } |
| |
| JSObject * |
| ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, uint8_t *contents) |
| { |
| SkipRoot skip(cx, &contents); |
| |
| RootedObject obj(cx, NewBuiltinClassInstance(cx, &class_)); |
| if (!obj) |
| return NULL; |
| JS_ASSERT_IF(obj->isTenured(), obj->tenuredGetAllocKind() == gc::FINALIZE_OBJECT16_BACKGROUND); |
| JS_ASSERT(obj->getClass() == &class_); |
| |
| js::Shape *empty = EmptyShape::getInitialShape(cx, &class_, |
| obj->getProto(), obj->getParent(), obj->getMetadata(), |
| gc::FINALIZE_OBJECT16_BACKGROUND); |
| if (!empty) |
| return NULL; |
| obj->setLastPropertyInfallible(empty); |
| |
| /* |
| * The beginning stores an ObjectElements header structure holding the |
| * length. The rest of it is a flat data store for the array buffer. |
| */ |
| if (!obj->as<ArrayBufferObject>().allocateSlots(cx, nbytes, contents)) |
| return NULL; |
| |
| return obj; |
| } |
| |
| JSObject * |
| ArrayBufferObject::createSlice(JSContext *cx, ArrayBufferObject &arrayBuffer, |
| uint32_t begin, uint32_t end) |
| { |
| JS_ASSERT(begin <= arrayBuffer.byteLength()); |
| JS_ASSERT(end <= arrayBuffer.byteLength()); |
| JS_ASSERT(begin <= end); |
| uint32_t length = end - begin; |
| |
| if (arrayBuffer.hasData()) |
| return create(cx, length, arrayBuffer.dataPointer() + begin); |
| |
| return create(cx, 0); |
| } |
| |
| bool |
| ArrayBufferObject::createDataViewForThisImpl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsArrayBuffer(args.thisv())); |
| |
| /* |
| * This method is only called for |DataView(alienBuf, ...)| which calls |
| * this as |createDataViewForThis.call(alienBuf, ..., DataView.prototype)|, |
| * ergo there must be at least two arguments. |
| */ |
| JS_ASSERT(args.length() >= 2); |
| |
| Rooted<JSObject*> proto(cx, &args[args.length() - 1].toObject()); |
| |
| Rooted<JSObject*> buffer(cx, &args.thisv().toObject()); |
| |
| /* |
| * Pop off the passed-along prototype and delegate to normal DataView |
| * object construction. |
| */ |
| CallArgs frobbedArgs = CallArgsFromVp(args.length() - 1, args.base()); |
| return DataViewObject::construct(cx, buffer, frobbedArgs, proto); |
| } |
| |
| JSBool |
| ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp) |
| { |
| 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->as<ArrayBufferObject>(); |
| JSObject *views = *GetViewList(&buffer); |
| |
| 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 = oldHeader; |
| *data = buffer.dataPointer(); |
| |
| MOZ_ASSERT(!buffer.isAsmJSArrayBuffer(), |
| "buffer won't be neutered by neuterAsmJSArrayBuffer"); |
| |
| buffer.elements = newHeader->elements(); |
| } else { |
| js::ObjectElements *headerCopy = |
| AllocateArrayBufferContents(cx, byteLen, buffer.dataPointer()); |
| if (!headerCopy) |
| return false; |
| |
| *contents = headerCopy; |
| *data = reinterpret_cast<uint8_t *>(headerCopy + 1); |
| if (buffer.isAsmJSArrayBuffer()) |
| ArrayBufferObject::neuterAsmJSArrayBuffer(buffer); |
| |
| // Keep using the current elements. |
| newHeader = oldHeader; |
| } |
| |
| // Neuter the donor ArrayBuffer and all views of it |
| uint32_t flags = newHeader->flags; |
| ArrayBufferObject::setElementsHeader(newHeader, 0); |
| newHeader->flags = flags; |
| GetViewList(&buffer)->init(views); |
| for (JSObject *view = views; view; view = NextView(view)) |
| TypedArray::neuter(view); |
| |
| newHeader->setIsNeuteredBuffer(); |
| return true; |
| } |
| |
| void |
| ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj) |
| { |
| /* |
| * If this object changes, it will get marked via the private data barrier, |
| * so it's safe to leave it Unbarriered. |
| */ |
| JSObject *delegate = static_cast<JSObject*>(obj->getPrivate()); |
| if (delegate) { |
| JS_SET_TRACING_LOCATION(trc, &obj->privateRef(obj->numFixedSlots())); |
| MarkObjectUnbarriered(trc, &delegate, "arraybuffer.delegate"); |
| obj->setPrivateUnbarriered(delegate); |
| } |
| |
| // ArrayBuffers need to maintain a list of possibly-weak pointers to their |
| // views. The straightforward way to update the weak pointers would be in |
| // the views' finalizers, but giving views finalizers means they cannot be |
| // swept in the background. This results in a very high performance cost. |
| // Instead, ArrayBuffers with a single view hold a strong pointer to the |
| // view. This can entrain garbage when the single view becomes otherwise |
| // unreachable while the buffer is still live, but this is expected to be |
| // rare. ArrayBuffers with 0-1 views are expected to be by far the most |
| // common cases. ArrayBuffers with multiple views are collected into a |
| // linked list during collection, and then swept to prune out their dead |
| // views. |
| |
| HeapPtrObject *views = GetViewList(&obj->as<ArrayBufferObject>()); |
| if (!*views) |
| return; |
| |
| // During minor collections, these edges are normally kept alive by the |
| // store buffer. If the store buffer overflows, fallback marking should |
| // just treat these as strong references for simplicity. |
| if (trc->runtime->isHeapMinorCollecting()) { |
| MarkObject(trc, views, "arraybuffer.viewlist"); |
| JSObject *prior = views->get(); |
| for (JSObject *view = NextView(prior); view; prior = view, view = NextView(view)) { |
| MarkObjectUnbarriered(trc, &view, "arraybuffer.views"); |
| prior->setFixedSlot(BufferView::NEXT_VIEW_SLOT, PrivateValue(view)); |
| } |
| return; |
| } |
| |
| JSObject *firstView = *views; |
| if (NextView(firstView) == NULL) { |
| // Single view: mark it, but only if we're actually doing a GC pass |
| // right now. Otherwise, the tracing pass for barrier verification will |
| // fail if we add another view and the pointer becomes weak. |
| if (IS_GC_MARKING_TRACER(trc)) |
| MarkObject(trc, views, "arraybuffer.singleview"); |
| } else { |
| // Multiple views: do not mark, but append buffer to list. |
| if (IS_GC_MARKING_TRACER(trc)) { |
| // obj_trace may be called multiple times before sweep(), so avoid |
| // adding this buffer to the list multiple times. |
| if (BufferLink(firstView) == UNSET_BUFFER_LINK) { |
| JS_ASSERT(obj->compartment() == firstView->compartment()); |
| JSObject **bufList = &obj->compartment()->gcLiveArrayBuffers; |
| SetBufferLink(firstView, *bufList); |
| *bufList = obj; |
| } else { |
| #ifdef DEBUG |
| bool found = false; |
| for (JSObject *p = obj->compartment()->gcLiveArrayBuffers; p; p = BufferLink(p)) { |
| if (p == obj) |
| found = true; |
| } |
| JS_ASSERT(found); |
| #endif |
| } |
| } |
| } |
| } |
| |
| void |
| ArrayBufferObject::sweep(JSCompartment *compartment) |
| { |
| JSObject *buffer = compartment->gcLiveArrayBuffers; |
| JS_ASSERT(buffer != UNSET_BUFFER_LINK); |
| compartment->gcLiveArrayBuffers = NULL; |
| |
| while (buffer) { |
| HeapPtrObject *views = GetViewList(&buffer->as<ArrayBufferObject>()); |
| JS_ASSERT(*views); |
| |
| JSObject *nextBuffer = BufferLink(*views); |
| JS_ASSERT(nextBuffer != UNSET_BUFFER_LINK); |
| SetBufferLink(*views, UNSET_BUFFER_LINK); |
| |
| // Rebuild the list of views of the ArrayBuffer, discarding dead views. |
| // If there is only one view, it will have already been marked. |
| JSObject *prevLiveView = NULL; |
| JSObject *view = *views; |
| while (view) { |
| JS_ASSERT(buffer->compartment() == view->compartment()); |
| JSObject *nextView = NextView(view); |
| if (!IsObjectAboutToBeFinalized(&view)) { |
| view->setFixedSlot(BufferView::NEXT_VIEW_SLOT, PrivateValue(prevLiveView)); |
| prevLiveView = view; |
| } |
| view = nextView; |
| } |
| *(views->unsafeGet()) = prevLiveView; |
| |
| buffer = nextBuffer; |
| } |
| } |
| |
| void |
| ArrayBufferObject::resetArrayBufferList(JSCompartment *comp) |
| { |
| JSObject *buffer = comp->gcLiveArrayBuffers; |
| JS_ASSERT(buffer != UNSET_BUFFER_LINK); |
| comp->gcLiveArrayBuffers = NULL; |
| |
| while (buffer) { |
| JSObject *view = *GetViewList(&buffer->as<ArrayBufferObject>()); |
| JS_ASSERT(view); |
| |
| JSObject *nextBuffer = BufferLink(view); |
| JS_ASSERT(nextBuffer != UNSET_BUFFER_LINK); |
| |
| SetBufferLink(view, UNSET_BUFFER_LINK); |
| buffer = nextBuffer; |
| } |
| } |
| |
| /* static */ bool |
| ArrayBufferObject::saveArrayBufferList(JSCompartment *comp, ArrayBufferVector &vector) |
| { |
| JSObject *obj = comp->gcLiveArrayBuffers; |
| while (obj) { |
| JS_ASSERT(obj != UNSET_BUFFER_LINK); |
| ArrayBufferObject *buffer = &obj->as<ArrayBufferObject>(); |
| if (!vector.append(buffer)) |
| return false; |
| |
| JSObject *view = *GetViewList(buffer); |
| JS_ASSERT(view); |
| obj = BufferLink(view); |
| } |
| return true; |
| } |
| |
| /* static */ void |
| ArrayBufferObject::restoreArrayBufferLists(ArrayBufferVector &vector) |
| { |
| for (ArrayBufferObject **p = vector.begin(); p != vector.end(); p++) { |
| ArrayBufferObject *buffer = *p; |
| JSCompartment *comp = buffer->compartment(); |
| JSObject *firstView = *GetViewList(&buffer->as<ArrayBufferObject>()); |
| JS_ASSERT(firstView); |
| JS_ASSERT(firstView->compartment() == comp); |
| JS_ASSERT(BufferLink(firstView) == UNSET_BUFFER_LINK); |
| SetBufferLink(firstView, comp->gcLiveArrayBuffers); |
| comp->gcLiveArrayBuffers = buffer; |
| } |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_lookupGeneric(JSContext *cx, HandleObject obj, HandleId id, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| |
| JSBool delegateResult = JSObject::lookupGeneric(cx, delegate, id, objp, propp); |
| |
| /* If false, there was an error, so propagate it. |
| * Otherwise, if propp is non-null, the property |
| * was found. Otherwise it was not |
| * found so look in the prototype chain. |
| */ |
| if (!delegateResult) |
| return false; |
| |
| if (propp) { |
| if (objp == delegate) |
| objp.set(obj); |
| return true; |
| } |
| |
| RootedObject proto(cx, obj->getProto()); |
| if (!proto) { |
| objp.set(NULL); |
| propp.set(NULL); |
| return true; |
| } |
| |
| return JSObject::lookupGeneric(cx, proto, id, objp, propp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_lookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_lookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_lookupElement(JSContext *cx, HandleObject obj, uint32_t index, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| |
| /* |
| * If false, there was an error, so propagate it. |
| * Otherwise, if propp is non-null, the property |
| * was found. Otherwise it was not |
| * found so look in the prototype chain. |
| */ |
| if (!JSObject::lookupElement(cx, delegate, index, objp, propp)) |
| return false; |
| |
| if (propp) { |
| if (objp == delegate) |
| objp.set(obj); |
| return true; |
| } |
| |
| RootedObject proto(cx, obj->getProto()); |
| if (proto) |
| return JSObject::lookupElement(cx, proto, index, objp, propp); |
| |
| objp.set(NULL); |
| propp.set(NULL); |
| return true; |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_lookupSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_lookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
| |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::DefineGeneric(cx, delegate, id, v, getter, setter, attrs); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_defineProperty(JSContext *cx, HandleObject obj, |
| HandlePropertyName name, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); |
| |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::DefineElement(cx, delegate, index, v, getter, setter, attrs); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_defineSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, |
| HandleId id, MutableHandleValue vp) |
| { |
| JS_ASSERT(obj->is<ArrayBufferObject>()); |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::GetProperty(cx, delegate, receiver, id, vp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getProperty(JSContext *cx, HandleObject obj, |
| HandleObject receiver, HandlePropertyName name, MutableHandleValue vp) |
| { |
| JS_ASSERT(obj->is<ArrayBufferObject>()); |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| Rooted<jsid> id(cx, NameToId(name)); |
| return baseops::GetProperty(cx, delegate, receiver, id, vp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getElement(JSContext *cx, HandleObject obj, |
| HandleObject receiver, uint32_t index, MutableHandleValue vp) |
| { |
| JS_ASSERT(obj->is<ArrayBufferObject>()); |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::GetElement(cx, delegate, receiver, index, vp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getElementIfPresent(JSContext *cx, HandleObject obj, HandleObject receiver, |
| uint32_t index, MutableHandleValue vp, bool *present) |
| { |
| JS_ASSERT(obj->is<ArrayBufferObject>()); |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return JSObject::getElementIfPresent(cx, delegate, receiver, index, vp, present); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getSpecial(JSContext *cx, HandleObject obj, |
| HandleObject receiver, HandleSpecialId sid, |
| MutableHandleValue vp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_getGeneric(cx, obj, receiver, id, vp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id, |
| MutableHandleValue vp, JSBool strict) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| |
| return baseops::SetPropertyHelper(cx, delegate, obj, id, 0, vp, strict); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setProperty(JSContext *cx, HandleObject obj, |
| HandlePropertyName name, MutableHandleValue vp, JSBool strict) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_setGeneric(cx, obj, id, vp, strict); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setElement(JSContext *cx, HandleObject obj, |
| uint32_t index, MutableHandleValue vp, JSBool strict) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| |
| return baseops::SetElementHelper(cx, delegate, obj, index, 0, vp, strict); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setSpecial(JSContext *cx, HandleObject obj, |
| HandleSpecialId sid, MutableHandleValue vp, JSBool strict) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_setGeneric(cx, obj, id, vp, strict); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getGenericAttributes(JSContext *cx, HandleObject obj, |
| HandleId id, unsigned *attrsp) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::GetAttributes(cx, delegate, id, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getPropertyAttributes(JSContext *cx, HandleObject obj, |
| HandlePropertyName name, unsigned *attrsp) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_getGenericAttributes(cx, obj, id, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getElementAttributes(JSContext *cx, HandleObject obj, |
| uint32_t index, unsigned *attrsp) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::GetElementAttributes(cx, delegate, index, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_getSpecialAttributes(JSContext *cx, HandleObject obj, |
| HandleSpecialId sid, unsigned *attrsp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_getGenericAttributes(cx, obj, id, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setGenericAttributes(JSContext *cx, HandleObject obj, |
| HandleId id, unsigned *attrsp) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::SetAttributes(cx, delegate, id, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setPropertyAttributes(JSContext *cx, HandleObject obj, |
| HandlePropertyName name, unsigned *attrsp) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_setGenericAttributes(cx, obj, id, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setElementAttributes(JSContext *cx, HandleObject obj, |
| uint32_t index, unsigned *attrsp) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::SetElementAttributes(cx, delegate, index, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_setSpecialAttributes(JSContext *cx, HandleObject obj, |
| HandleSpecialId sid, unsigned *attrsp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_setGenericAttributes(cx, obj, id, attrsp); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_deleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| JSBool *succeeded) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::DeleteProperty(cx, delegate, name, succeeded); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_deleteElement(JSContext *cx, HandleObject obj, uint32_t index, |
| JSBool *succeeded) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::DeleteElement(cx, delegate, index, succeeded); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_deleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| JSBool *succeeded) |
| { |
| RootedObject delegate(cx, ArrayBufferDelegate(cx, obj)); |
| if (!delegate) |
| return false; |
| return baseops::DeleteSpecial(cx, delegate, sid, succeeded); |
| } |
| |
| JSBool |
| ArrayBufferObject::obj_enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op, |
| MutableHandleValue statep, MutableHandleId idp) |
| { |
| statep.setNull(); |
| return true; |
| } |
| |
| /* |
| * TypedArray |
| * |
| * The non-templated base class for the specific typed implementations. |
| * This class holds all the member variables that are used by |
| * the subclasses. |
| */ |
| |
| inline bool |
| TypedArray::isArrayIndex(JSObject *obj, jsid id, uint32_t *ip) |
| { |
| uint32_t index; |
| if (js_IdIsIndex(id, &index) && index < length(obj)) { |
| if (ip) |
| *ip = index; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool |
| js::IsDataView(JSObject* obj) |
| { |
| JS_ASSERT(obj); |
| return obj->is<DataViewObject>(); |
| } |
| |
| void |
| TypedArray::neuter(JSObject *tarray) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| tarray->setSlot(LENGTH_SLOT, Int32Value(0)); |
| tarray->setSlot(BYTELENGTH_SLOT, Int32Value(0)); |
| tarray->setSlot(BYTEOFFSET_SLOT, Int32Value(0)); |
| tarray->setPrivate(NULL); |
| } |
| |
| JSBool |
| TypedArray::obj_lookupGeneric(JSContext *cx, HandleObject tarray, HandleId id, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| if (isArrayIndex(tarray, id)) { |
| MarkNonNativePropertyFound(propp); |
| objp.set(tarray); |
| return true; |
| } |
| |
| RootedObject proto(cx, tarray->getProto()); |
| if (!proto) { |
| objp.set(NULL); |
| propp.set(NULL); |
| return true; |
| } |
| |
| return JSObject::lookupGeneric(cx, proto, id, objp, propp); |
| } |
| |
| JSBool |
| TypedArray::obj_lookupProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_lookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| JSBool |
| TypedArray::obj_lookupElement(JSContext *cx, HandleObject tarray, uint32_t index, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| if (index < length(tarray)) { |
| MarkNonNativePropertyFound(propp); |
| objp.set(tarray); |
| return true; |
| } |
| |
| RootedObject proto(cx, tarray->getProto()); |
| if (proto) |
| return JSObject::lookupElement(cx, proto, index, objp, propp); |
| |
| objp.set(NULL); |
| propp.set(NULL); |
| return true; |
| } |
| |
| JSBool |
| TypedArray::obj_lookupSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| MutableHandleObject objp, MutableHandleShape propp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_lookupGeneric(cx, obj, id, objp, propp); |
| } |
| |
| JSBool |
| TypedArray::obj_getGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
| { |
| *attrsp = JSPROP_PERMANENT | JSPROP_ENUMERATE; |
| return true; |
| } |
| |
| JSBool |
| TypedArray::obj_getPropertyAttributes(JSContext *cx, HandleObject obj, HandlePropertyName name, unsigned *attrsp) |
| { |
| *attrsp = JSPROP_PERMANENT | JSPROP_ENUMERATE; |
| return true; |
| } |
| |
| JSBool |
| TypedArray::obj_getElementAttributes(JSContext *cx, HandleObject obj, uint32_t index, unsigned *attrsp) |
| { |
| *attrsp = JSPROP_PERMANENT | JSPROP_ENUMERATE; |
| return true; |
| } |
| |
| JSBool |
| TypedArray::obj_getSpecialAttributes(JSContext *cx, HandleObject obj, HandleSpecialId sid, unsigned *attrsp) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_getGenericAttributes(cx, obj, id, attrsp); |
| } |
| |
| JSBool |
| TypedArray::obj_setGenericAttributes(JSContext *cx, HandleObject obj, HandleId id, unsigned *attrsp) |
| { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SET_ARRAY_ATTRS); |
| return false; |
| } |
| |
| JSBool |
| TypedArray::obj_setPropertyAttributes(JSContext *cx, HandleObject obj, HandlePropertyName name, unsigned *attrsp) |
| { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SET_ARRAY_ATTRS); |
| return false; |
| } |
| |
| JSBool |
| TypedArray::obj_setElementAttributes(JSContext *cx, HandleObject obj, uint32_t index, unsigned *attrsp) |
| { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SET_ARRAY_ATTRS); |
| return false; |
| } |
| |
| JSBool |
| TypedArray::obj_setSpecialAttributes(JSContext *cx, HandleObject obj, HandleSpecialId sid, unsigned *attrsp) |
| { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_SET_ARRAY_ATTRS); |
| return false; |
| } |
| |
| /* static */ int |
| TypedArray::lengthOffset() |
| { |
| return JSObject::getFixedSlotOffset(LENGTH_SLOT); |
| } |
| |
| /* static */ int |
| TypedArray::dataOffset() |
| { |
| return JSObject::getPrivateDataOffset(DATA_SLOT); |
| } |
| |
| /* Helper clamped uint8_t type */ |
| |
| uint32_t JS_FASTCALL |
| js::ClampDoubleToUint8(const double x) |
| { |
| // Not < so that NaN coerces to 0 |
| if (!(x >= 0)) |
| return 0; |
| |
| if (x > 255) |
| return 255; |
| |
| double toTruncate = x + 0.5; |
| uint8_t y = uint8_t(toTruncate); |
| |
| /* |
| * now val is rounded to nearest, ties rounded up. We want |
| * rounded to nearest ties to even, so check whether we had a |
| * tie. |
| */ |
| if (y == toTruncate) { |
| /* |
| * It was a tie (since adding 0.5 gave us the exact integer |
| * we want). Since we rounded up, we either already have an |
| * even number or we have an odd number but the number we |
| * want is one less. So just unconditionally masking out the |
| * ones bit should do the trick to get us the value we |
| * want. |
| */ |
| return (y & ~1); |
| } |
| |
| return y; |
| } |
| |
| template<typename NativeType> static inline const int TypeIDOfType(); |
| template<> inline const int TypeIDOfType<int8_t>() { return TypedArray::TYPE_INT8; } |
| template<> inline const int TypeIDOfType<uint8_t>() { return TypedArray::TYPE_UINT8; } |
| template<> inline const int TypeIDOfType<int16_t>() { return TypedArray::TYPE_INT16; } |
| template<> inline const int TypeIDOfType<uint16_t>() { return TypedArray::TYPE_UINT16; } |
| template<> inline const int TypeIDOfType<int32_t>() { return TypedArray::TYPE_INT32; } |
| template<> inline const int TypeIDOfType<uint32_t>() { return TypedArray::TYPE_UINT32; } |
| template<> inline const int TypeIDOfType<float>() { return TypedArray::TYPE_FLOAT32; } |
| template<> inline const int TypeIDOfType<double>() { return TypedArray::TYPE_FLOAT64; } |
| template<> inline const int TypeIDOfType<uint8_clamped>() { return TypedArray::TYPE_UINT8_CLAMPED; } |
| |
| template<typename NativeType> static inline const bool ElementTypeMayBeDouble() { return false; } |
| template<> inline const bool ElementTypeMayBeDouble<uint32_t>() { return true; } |
| template<> inline const bool ElementTypeMayBeDouble<float>() { return true; } |
| template<> inline const bool ElementTypeMayBeDouble<double>() { return true; } |
| |
| template<typename NativeType> class TypedArrayTemplate; |
| |
| template<typename ElementType> |
| static inline JSObject * |
| NewArray(JSContext *cx, uint32_t nelements); |
| |
| template<typename NativeType> |
| class TypedArrayTemplate |
| : public TypedArray |
| { |
| public: |
| typedef NativeType ThisType; |
| typedef TypedArrayTemplate<NativeType> ThisTypeArray; |
| static const int ArrayTypeID() { return TypeIDOfType<NativeType>(); } |
| static const bool ArrayTypeIsUnsigned() { return TypeIsUnsigned<NativeType>(); } |
| static const bool ArrayTypeIsFloatingPoint() { return TypeIsFloatingPoint<NativeType>(); } |
| static const bool ArrayElementTypeMayBeDouble() { return ElementTypeMayBeDouble<NativeType>(); } |
| |
| static const size_t BYTES_PER_ELEMENT = sizeof(ThisType); |
| |
| static inline Class *protoClass() |
| { |
| return &TypedArray::protoClasses[ArrayTypeID()]; |
| } |
| |
| static inline Class *fastClass() |
| { |
| return &TypedArray::classes[ArrayTypeID()]; |
| } |
| |
| static bool is(const Value &v) { |
| return v.isObject() && v.toObject().hasClass(fastClass()); |
| } |
| |
| static void |
| obj_trace(JSTracer *trc, JSObject *obj) |
| { |
| MarkSlot(trc, &obj->getFixedSlotRef(BUFFER_SLOT), "typedarray.buffer"); |
| } |
| |
| static JSBool |
| obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver, HandlePropertyName name, |
| MutableHandleValue vp) |
| { |
| RootedObject proto(cx, obj->getProto()); |
| if (!proto) { |
| vp.setUndefined(); |
| return true; |
| } |
| |
| return JSObject::getProperty(cx, proto, receiver, name, vp); |
| } |
| |
| static JSBool |
| obj_getElement(JSContext *cx, HandleObject tarray, HandleObject receiver, uint32_t index, |
| MutableHandleValue vp) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| if (index < length(tarray)) { |
| copyIndexToValue(tarray, index, vp); |
| return true; |
| } |
| |
| RootedObject proto(cx, tarray->getProto()); |
| if (!proto) { |
| vp.setUndefined(); |
| return true; |
| } |
| |
| return JSObject::getElement(cx, proto, receiver, index, vp); |
| } |
| |
| static JSBool |
| obj_getSpecial(JSContext *cx, HandleObject obj, HandleObject receiver, HandleSpecialId sid, |
| MutableHandleValue vp) |
| { |
| RootedObject proto(cx, obj->getProto()); |
| if (!proto) { |
| vp.setUndefined(); |
| return true; |
| } |
| |
| return JSObject::getSpecial(cx, proto, receiver, sid, vp); |
| } |
| |
| static JSBool |
| obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, HandleId id, |
| MutableHandleValue vp) |
| { |
| RootedValue idval(cx, IdToValue(id)); |
| |
| uint32_t index; |
| if (IsDefinitelyIndex(idval, &index)) |
| return obj_getElement(cx, obj, receiver, index, vp); |
| |
| Rooted<SpecialId> sid(cx); |
| if (ValueIsSpecial(obj, &idval, &sid, cx)) |
| return obj_getSpecial(cx, obj, receiver, sid, vp); |
| |
| JSAtom *atom = ToAtom<CanGC>(cx, idval); |
| if (!atom) |
| return false; |
| |
| if (atom->isIndex(&index)) |
| return obj_getElement(cx, obj, receiver, index, vp); |
| |
| Rooted<PropertyName*> name(cx, atom->asPropertyName()); |
| return obj_getProperty(cx, obj, receiver, name, vp); |
| } |
| |
| static JSBool |
| obj_getElementIfPresent(JSContext *cx, HandleObject tarray, HandleObject receiver, uint32_t index, |
| MutableHandleValue vp, bool *present) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| // Fast-path the common case of index < length |
| if (index < length(tarray)) { |
| // this inline function is specialized for each type |
| copyIndexToValue(tarray, index, vp); |
| *present = true; |
| return true; |
| } |
| |
| RootedObject proto(cx, tarray->getProto()); |
| if (!proto) { |
| vp.setUndefined(); |
| return true; |
| } |
| |
| return JSObject::getElementIfPresent(cx, proto, receiver, index, vp, present); |
| } |
| |
| static bool |
| toDoubleForTypedArray(JSContext *cx, HandleValue vp, double *d) |
| { |
| if (vp.isDouble()) { |
| *d = vp.toDouble(); |
| } else if (vp.isNull()) { |
| *d = 0.0; |
| } else if (vp.isPrimitive()) { |
| JS_ASSERT(vp.isString() || vp.isUndefined() || vp.isBoolean()); |
| if (vp.isString()) { |
| if (!ToNumber(cx, vp, d)) |
| return false; |
| } else if (vp.isUndefined()) { |
| *d = js_NaN; |
| } else { |
| *d = double(vp.toBoolean()); |
| } |
| } else { |
| // non-primitive assignments become NaN or 0 (for float/int arrays) |
| *d = js_NaN; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| setElementTail(JSContext *cx, HandleObject tarray, uint32_t index, |
| MutableHandleValue vp, JSBool strict) |
| { |
| JS_ASSERT(tarray); |
| JS_ASSERT(index < length(tarray)); |
| |
| if (vp.isInt32()) { |
| setIndex(tarray, index, NativeType(vp.toInt32())); |
| return true; |
| } |
| |
| double d; |
| if (!toDoubleForTypedArray(cx, vp, &d)) |
| return false; |
| |
| // If the array is an integer array, we only handle up to |
| // 32-bit ints from this point on. if we want to handle |
| // 64-bit ints, we'll need some changes. |
| |
| // Assign based on characteristics of the destination type |
| if (ArrayTypeIsFloatingPoint()) { |
| setIndex(tarray, index, NativeType(d)); |
| } else if (ArrayTypeIsUnsigned()) { |
| JS_ASSERT(sizeof(NativeType) <= 4); |
| uint32_t n = ToUint32(d); |
| setIndex(tarray, index, NativeType(n)); |
| } else if (ArrayTypeID() == TypedArray::TYPE_UINT8_CLAMPED) { |
| // The uint8_clamped type has a special rounding converter |
| // for doubles. |
| setIndex(tarray, index, NativeType(d)); |
| } else { |
| JS_ASSERT(sizeof(NativeType) <= 4); |
| int32_t n = ToInt32(d); |
| setIndex(tarray, index, NativeType(n)); |
| } |
| |
| return true; |
| } |
| |
| static JSBool |
| obj_setGeneric(JSContext *cx, HandleObject tarray, HandleId id, |
| MutableHandleValue vp, JSBool strict) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| uint32_t index; |
| // We can't just chain to js_SetPropertyHelper, because we're not a normal object. |
| if (!isArrayIndex(tarray, id, &index)) { |
| // Silent ignore is better than an exception here, because |
| // at some point we may want to support other properties on |
| // these objects. This is especially true when these arrays |
| // are used to implement HTML Canvas 2D's PixelArray objects, |
| // which used to be plain old arrays. |
| vp.setUndefined(); |
| return true; |
| } |
| |
| return setElementTail(cx, tarray, index, vp, strict); |
| } |
| |
| static JSBool |
| obj_setProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, |
| MutableHandleValue vp, JSBool strict) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_setGeneric(cx, obj, id, vp, strict); |
| } |
| |
| static JSBool |
| obj_setElement(JSContext *cx, HandleObject tarray, uint32_t index, |
| MutableHandleValue vp, JSBool strict) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| if (index >= length(tarray)) { |
| // Silent ignore is better than an exception here, because |
| // at some point we may want to support other properties on |
| // these objects. This is especially true when these arrays |
| // are used to implement HTML Canvas 2D's PixelArray objects, |
| // which used to be plain old arrays. |
| vp.setUndefined(); |
| return true; |
| } |
| |
| return setElementTail(cx, tarray, index, vp, strict); |
| } |
| |
| static JSBool |
| obj_setSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, |
| MutableHandleValue vp, JSBool strict) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_setGeneric(cx, obj, id, vp, strict); |
| } |
| |
| static JSBool |
| obj_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| RootedValue tmp(cx, v); |
| return obj_setGeneric(cx, obj, id, &tmp, false); |
| } |
| |
| static JSBool |
| obj_defineProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| Rooted<jsid> id(cx, NameToId(name)); |
| return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs); |
| } |
| |
| static JSBool |
| obj_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| RootedValue tmp(cx, v); |
| return obj_setElement(cx, obj, index, &tmp, false); |
| } |
| |
| static JSBool |
| obj_defineSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid, HandleValue v, |
| PropertyOp getter, StrictPropertyOp setter, unsigned attrs) |
| { |
| Rooted<jsid> id(cx, SPECIALID_TO_JSID(sid)); |
| return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs); |
| } |
| |
| static JSBool |
| obj_deleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, JSBool *succeeded) |
| { |
| *succeeded = true; |
| return true; |
| } |
| |
| static JSBool |
| obj_deleteElement(JSContext *cx, HandleObject tarray, uint32_t index, JSBool *succeeded) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| if (index < length(tarray)) { |
| *succeeded = false; |
| return true; |
| } |
| |
| *succeeded = true; |
| return true; |
| } |
| |
| static JSBool |
| obj_deleteSpecial(JSContext *cx, HandleObject tarray, HandleSpecialId sid, JSBool *succeeded) |
| { |
| *succeeded = true; |
| return true; |
| } |
| |
| static JSBool |
| obj_enumerate(JSContext *cx, HandleObject tarray, JSIterateOp enum_op, |
| MutableHandleValue statep, MutableHandleId idp) |
| { |
| JS_ASSERT(tarray->isTypedArray()); |
| |
| uint32_t index; |
| switch (enum_op) { |
| case JSENUMERATE_INIT_ALL: |
| case JSENUMERATE_INIT: |
| statep.setInt32(0); |
| idp.set(::INT_TO_JSID(length(tarray))); |
| break; |
| |
| case JSENUMERATE_NEXT: |
| index = static_cast<uint32_t>(statep.toInt32()); |
| if (index < length(tarray)) { |
| idp.set(::INT_TO_JSID(index)); |
| statep.setInt32(index + 1); |
| } else { |
| JS_ASSERT(index == length(tarray)); |
| statep.setNull(); |
| } |
| break; |
| |
| case JSENUMERATE_DESTROY: |
| statep.setNull(); |
| break; |
| } |
| |
| return true; |
| } |
| |
| static JSObject * |
| makeProtoInstance(JSContext *cx, HandleObject proto) |
| { |
| JS_ASSERT(proto); |
| |
| RootedObject obj(cx, NewBuiltinClassInstance(cx, fastClass())); |
| if (!obj) |
| return NULL; |
| |
| types::TypeObject *type = proto->getNewType(cx, obj->getClass()); |
| if (!type) |
| return NULL; |
| obj->setType(type); |
| |
| return obj; |
| } |
| |
| static JSObject * |
| makeTypedInstance(JSContext *cx, uint32_t len) |
| { |
| if (len * sizeof(NativeType) >= TypedArray::SINGLETON_TYPE_BYTE_LENGTH) |
| return NewBuiltinClassInstance(cx, fastClass(), SingletonObject); |
| |
| jsbytecode *pc; |
| RootedScript script(cx, cx->currentScript(&pc)); |
| NewObjectKind newKind = script |
| ? UseNewTypeForInitializer(cx, script, pc, fastClass()) |
| : GenericObject; |
| RootedObject obj(cx, NewBuiltinClassInstance(cx, fastClass(), newKind)); |
| if (!obj) |
| return NULL; |
| |
| if (script) { |
| if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind)) |
| return NULL; |
| } |
| |
| return obj; |
| } |
| |
| static JSObject * |
| makeInstance(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, uint32_t len, |
| HandleObject proto) |
| { |
| RootedObject obj(cx); |
| if (proto) |
| obj = makeProtoInstance(cx, proto); |
| else if (cx->typeInferenceEnabled()) |
| obj = makeTypedInstance(cx, len); |
| else |
| obj = NewBuiltinClassInstance(cx, fastClass()); |
| if (!obj) |
| return NULL; |
| JS_ASSERT_IF(obj->isTenured(), |
| obj->tenuredGetAllocKind() == gc::FINALIZE_OBJECT8_BACKGROUND); |
| |
| obj->setSlot(TYPE_SLOT, Int32Value(ArrayTypeID())); |
| obj->setSlot(BUFFER_SLOT, ObjectValue(*bufobj)); |
| |
| JS_ASSERT(bufobj->is<ArrayBufferObject>()); |
| Rooted<ArrayBufferObject *> buffer(cx, &bufobj->as<ArrayBufferObject>()); |
| |
| InitArrayBufferViewDataPointer(obj, buffer, byteOffset); |
| obj->setSlot(LENGTH_SLOT, Int32Value(len)); |
| obj->setSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset)); |
| obj->setSlot(BYTELENGTH_SLOT, Int32Value(len * sizeof(NativeType))); |
| obj->setSlot(NEXT_VIEW_SLOT, PrivateValue(NULL)); |
| obj->setSlot(NEXT_BUFFER_SLOT, PrivateValue(UNSET_BUFFER_LINK)); |
| |
| // Mark the object as non-extensible. We cannot simply call |
| // obj->preventExtensions() because that has to iterate through all |
| // properties, and on long arrays that is much too slow. We could |
| // initialize the length fields to zero to avoid that, but then it |
| // would just boil down to a slightly slower wrapper around the |
| // following code anyway: |
| js::Shape *empty = EmptyShape::getInitialShape(cx, fastClass(), |
| obj->getProto(), obj->getParent(), obj->getMetadata(), |
| gc::FINALIZE_OBJECT8_BACKGROUND, |
| BaseShape::NOT_EXTENSIBLE); |
| if (!empty) |
| return NULL; |
| obj->setLastPropertyInfallible(empty); |
| |
| #ifdef DEBUG |
| uint32_t bufferByteLength = buffer->byteLength(); |
| uint32_t arrayByteLength = static_cast<uint32_t>(byteLengthValue(obj).toInt32()); |
| uint32_t arrayByteOffset = static_cast<uint32_t>(byteOffsetValue(obj).toInt32()); |
| JS_ASSERT(buffer->dataPointer() <= viewData(obj)); |
| JS_ASSERT(bufferByteLength - byteOffsetValue(obj).toInt32() >= arrayByteLength); |
| JS_ASSERT(arrayByteOffset <= bufferByteLength); |
| |
| // Verify that the private slot is at the expected place |
| JS_ASSERT(obj->numFixedSlots() == DATA_SLOT); |
| #endif |
| |
| buffer->addView(obj); |
| |
| return obj; |
| } |
| |
| static JSObject * |
| makeInstance(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, uint32_t len) |
| { |
| RootedObject nullproto(cx, NULL); |
| return makeInstance(cx, bufobj, byteOffset, len, nullproto); |
| } |
| |
| /* |
| * new [Type]Array(length) |
| * new [Type]Array(otherTypedArray) |
| * new [Type]Array(JSArray) |
| * new [Type]Array(ArrayBuffer, [optional] byteOffset, [optional] length) |
| */ |
| static JSBool |
| class_constructor(JSContext *cx, unsigned argc, Value *vp) |
| { |
| /* N.B. this is a constructor for protoClass, not fastClass! */ |
| JSObject *obj = create(cx, argc, JS_ARGV(cx, vp)); |
| if (!obj) |
| return false; |
| vp->setObject(*obj); |
| return true; |
| } |
| |
| static JSObject * |
| create(JSContext *cx, unsigned argc, Value *argv) |
| { |
| /* N.B. there may not be an argv[-2]/argv[-1]. */ |
| |
| /* () or (number) */ |
| uint32_t len = 0; |
| if (argc == 0 || ValueIsLength(argv[0], &len)) |
| return fromLength(cx, len); |
| |
| /* (not an object) */ |
| if (!argv[0].isObject()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; |
| } |
| |
| RootedObject dataObj(cx, &argv[0].toObject()); |
| |
| /* |
| * (typedArray) |
| * (type[] array) |
| * |
| * Otherwise create a new typed array and copy elements 0..len-1 |
| * properties from the object, treating it as some sort of array. |
| * Note that offset and length will be ignored |
| */ |
| if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObject>()) |
| return fromArray(cx, dataObj); |
| |
| /* (ArrayBuffer, [byteOffset, [length]]) */ |
| int32_t byteOffset = 0; |
| int32_t length = -1; |
| |
| if (argc > 1) { |
| if (!ToInt32(cx, argv[1], &byteOffset)) |
| return NULL; |
| if (byteOffset < 0) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "1"); |
| return NULL; |
| } |
| |
| if (argc > 2) { |
| if (!ToInt32(cx, argv[2], &length)) |
| return NULL; |
| if (length < 0) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_TYPED_ARRAY_NEGATIVE_ARG, "2"); |
| return NULL; |
| } |
| } |
| } |
| |
| Rooted<JSObject*> proto(cx, NULL); |
| return fromBuffer(cx, dataObj, byteOffset, length, proto); |
| } |
| |
| static bool IsThisClass(const Value &v) { |
| return v.isObject() && v.toObject().hasClass(fastClass()); |
| } |
| |
| template<Value ValueGetter(JSObject *obj)> |
| static bool |
| GetterImpl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsThisClass(args.thisv())); |
| args.rval().set(ValueGetter(&args.thisv().toObject())); |
| return true; |
| } |
| |
| // ValueGetter is a function that takes an unwrapped typed array object and |
| // returns a Value. Given such a function, Getter<> is a native that |
| // retrieves a given Value, probably from a slot on the object. |
| template<Value ValueGetter(JSObject *obj)> |
| static JSBool |
| Getter(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<ThisTypeArray::IsThisClass, |
| ThisTypeArray::GetterImpl<ValueGetter> >(cx, args); |
| } |
| |
| // Define an accessor for a read-only property that invokes a native getter |
| template<Value ValueGetter(JSObject *obj)> |
| static bool |
| DefineGetter(JSContext *cx, PropertyName *name, HandleObject proto) |
| { |
| RootedId id(cx, NameToId(name)); |
| unsigned flags = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT; |
| |
| Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal()); |
| JSObject *getter = NewFunction(cx, NullPtr(), Getter<ValueGetter>, 0, |
| JSFunction::NATIVE_FUN, global, NullPtr()); |
| if (!getter) |
| return false; |
| |
| RootedValue value(cx, UndefinedValue()); |
| return DefineNativeProperty(cx, proto, id, value, |
| JS_DATA_TO_FUNC_PTR(PropertyOp, getter), NULL, |
| flags, 0, 0); |
| } |
| |
| static |
| bool defineGetters(JSContext *cx, HandleObject proto) |
| { |
| if (!DefineGetter<lengthValue>(cx, cx->names().length, proto)) |
| return false; |
| |
| if (!DefineGetter<bufferValue>(cx, cx->names().buffer, proto)) |
| return false; |
| |
| if (!DefineGetter<byteLengthValue>(cx, cx->names().byteLength, proto)) |
| return false; |
| |
| if (!DefineGetter<byteOffsetValue>(cx, cx->names().byteOffset, proto)) |
| return false; |
| |
| return true; |
| } |
| |
| /* subarray(start[, end]) */ |
| static bool |
| fun_subarray_impl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsThisClass(args.thisv())); |
| RootedObject tarray(cx, &args.thisv().toObject()); |
| |
| // these are the default values |
| uint32_t begin = 0, end = length(tarray); |
| uint32_t length = TypedArray::length(tarray); |
| |
| if (args.length() > 0) { |
| if (!ToClampedIndex(cx, args[0], length, &begin)) |
| return false; |
| |
| if (args.length() > 1) { |
| if (!ToClampedIndex(cx, args[1], length, &end)) |
| return false; |
| } |
| } |
| |
| if (begin > end) |
| begin = end; |
| |
| JSObject *nobj = createSubarray(cx, tarray, begin, end); |
| if (!nobj) |
| return false; |
| args.rval().setObject(*nobj); |
| return true; |
| } |
| |
| static JSBool |
| fun_subarray(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<ThisTypeArray::IsThisClass, ThisTypeArray::fun_subarray_impl>(cx, args); |
| } |
| |
| /* move(begin, end, dest) */ |
| static bool |
| fun_move_impl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsThisClass(args.thisv())); |
| RootedObject tarray(cx, &args.thisv().toObject()); |
| |
| if (args.length() < 3) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return false; |
| } |
| |
| uint32_t srcBegin; |
| uint32_t srcEnd; |
| uint32_t dest; |
| |
| uint32_t length = TypedArray::length(tarray); |
| if (!ToClampedIndex(cx, args[0], length, &srcBegin) || |
| !ToClampedIndex(cx, args[1], length, &srcEnd) || |
| !ToClampedIndex(cx, args[2], length, &dest) || |
| srcBegin > srcEnd) |
| { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return false; |
| } |
| |
| uint32_t nelts = srcEnd - srcBegin; |
| |
| JS_ASSERT(dest + nelts >= dest); |
| if (dest + nelts > length) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return false; |
| } |
| |
| uint32_t byteDest = dest * sizeof(NativeType); |
| uint32_t byteSrc = srcBegin * sizeof(NativeType); |
| uint32_t byteSize = nelts * sizeof(NativeType); |
| |
| #ifdef DEBUG |
| uint32_t viewByteLength = byteLengthValue(tarray).toInt32(); |
| JS_ASSERT(byteDest <= viewByteLength); |
| JS_ASSERT(byteSrc <= viewByteLength); |
| JS_ASSERT(byteDest + byteSize <= viewByteLength); |
| JS_ASSERT(byteSrc + byteSize <= viewByteLength); |
| |
| // Should not overflow because size is limited to 2^31 |
| JS_ASSERT(byteDest + byteSize >= byteDest); |
| JS_ASSERT(byteSrc + byteSize >= byteSrc); |
| #endif |
| |
| uint8_t *data = static_cast<uint8_t*>(viewData(tarray)); |
| memmove(&data[byteDest], &data[byteSrc], byteSize); |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| fun_move(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<ThisTypeArray::IsThisClass, ThisTypeArray::fun_move_impl>(cx, args); |
| } |
| |
| /* set(array[, offset]) */ |
| static bool |
| fun_set_impl(JSContext *cx, CallArgs args) |
| { |
| JS_ASSERT(IsThisClass(args.thisv())); |
| RootedObject tarray(cx, &args.thisv().toObject()); |
| |
| // first arg must be either a typed array or a JS array |
| if (args.length() == 0 || !args[0].isObject()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return false; |
| } |
| |
| int32_t offset = 0; |
| if (args.length() > 1) { |
| if (!ToInt32(cx, args[1], &offset)) |
| return false; |
| |
| if (offset < 0 || uint32_t(offset) > length(tarray)) { |
| // the given offset is bogus |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_INDEX, "2"); |
| return false; |
| } |
| } |
| |
| if (!args[0].isObject()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return false; |
| } |
| |
| RootedObject arg0(cx, args[0].toObjectOrNull()); |
| if (arg0->isTypedArray()) { |
| if (length(arg0) > length(tarray) - offset) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ARRAY_LENGTH); |
| return false; |
| } |
| |
| if (!copyFromTypedArray(cx, tarray, arg0, offset)) |
| return false; |
| } else { |
| uint32_t len; |
| if (!GetLengthProperty(cx, arg0, &len)) |
| return false; |
| |
| // avoid overflow; we know that offset <= length |
| if (len > length(tarray) - offset) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_ARRAY_LENGTH); |
| return false; |
| } |
| |
| if (!copyFromArray(cx, tarray, arg0, len, offset)) |
| return false; |
| } |
| |
| args.rval().setUndefined(); |
| return true; |
| } |
| |
| static JSBool |
| fun_set(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<ThisTypeArray::IsThisClass, ThisTypeArray::fun_set_impl>(cx, args); |
| } |
| |
| public: |
| static JSObject * |
| fromBuffer(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, int32_t lengthInt, |
| HandleObject proto) |
| { |
| if (!ObjectClassIs(bufobj, ESClass_ArrayBuffer, cx)) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; // must be arrayBuffer |
| } |
| |
| JS_ASSERT(bufobj->is<ArrayBufferObject>() || bufobj->isProxy()); |
| if (bufobj->isProxy()) { |
| /* |
| * Normally, NonGenericMethodGuard handles the case of transparent |
| * wrappers. However, we have a peculiar situation: we want to |
| * construct the new typed array in the compartment of the buffer, |
| * so that the typed array can point directly at their buffer's |
| * data without crossing compartment boundaries. So we use the |
| * machinery underlying NonGenericMethodGuard directly to proxy the |
| * native call. We will end up with a wrapper in the origin |
| * compartment for a view in the target compartment referencing the |
| * ArrayBuffer in that same compartment. |
| */ |
| JSObject *wrapped = CheckedUnwrap(bufobj); |
| if (!wrapped) { |
| JS_ReportError(cx, "Permission denied to access object"); |
| return NULL; |
| } |
| if (wrapped->is<ArrayBufferObject>()) { |
| /* |
| * And for even more fun, the new view's prototype should be |
| * set to the origin compartment's prototype object, not the |
| * target's (specifically, the actual view in the target |
| * compartment will use as its prototype a wrapper around the |
| * origin compartment's view.prototype object). |
| * |
| * Rather than hack some crazy solution together, implement |
| * this all using a private helper function, created when |
| * ArrayBuffer was initialized and cached in the global. This |
| * reuses all the existing cross-compartment crazy so we don't |
| * have to do anything *uniquely* crazy here. |
| */ |
| |
| Rooted<JSObject*> proto(cx); |
| if (!FindProto(cx, fastClass(), &proto)) |
| return NULL; |
| |
| InvokeArgs args(cx); |
| if (!args.init(3)) |
| return NULL; |
| |
| args.setCallee(cx->compartment()->maybeGlobal()->createArrayFromBuffer<NativeType>()); |
| args.setThis(ObjectValue(*bufobj)); |
| args[0] = NumberValue(byteOffset); |
| args[1] = Int32Value(lengthInt); |
| args[2] = ObjectValue(*proto); |
| |
| if (!Invoke(cx, args)) |
| return NULL; |
| return &args.rval().toObject(); |
| } |
| } |
| |
| if (!bufobj->is<ArrayBufferObject>()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; // must be arrayBuffer |
| } |
| |
| ArrayBufferObject &buffer = bufobj->as<ArrayBufferObject>(); |
| |
| if (byteOffset > buffer.byteLength() || byteOffset % sizeof(NativeType) != 0) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; // invalid byteOffset |
| } |
| |
| uint32_t len; |
| if (lengthInt == -1) { |
| len = (buffer.byteLength() - byteOffset) / sizeof(NativeType); |
| if (len * sizeof(NativeType) != buffer.byteLength() - byteOffset) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; // given byte array doesn't map exactly to sizeof(NativeType) * N |
| } |
| } else { |
| len = uint32_t(lengthInt); |
| } |
| |
| // Go slowly and check for overflow. |
| uint32_t arrayByteLength = len * sizeof(NativeType); |
| if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; // overflow when calculating byteOffset + len * sizeof(NativeType) |
| } |
| |
| if (arrayByteLength + byteOffset > buffer.byteLength()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPED_ARRAY_BAD_ARGS); |
| return NULL; // byteOffset + len is too big for the arraybuffer |
| } |
| |
| return makeInstance(cx, bufobj, byteOffset, len, proto); |
| } |
| |
| static JSObject * |
| fromLength(JSContext *cx, uint32_t nelements) |
| { |
| RootedObject buffer(cx, createBufferWithSizeAndCount(cx, nelements)); |
| if (!buffer) |
| return NULL; |
| return makeInstance(cx, buffer, 0, nelements); |
| } |
| |
| static JSObject * |
| fromArray(JSContext *cx, HandleObject other) |
| { |
| uint32_t len; |
| if (other->isTypedArray()) { |
| len = length(other); |
| } else if (!GetLengthProperty(cx, other, &len)) { |
| return NULL; |
| } |
| |
| RootedObject bufobj(cx, createBufferWithSizeAndCount(cx, len)); |
| if (!bufobj) |
| return NULL; |
| |
| RootedObject obj(cx, makeInstance(cx, bufobj, 0, len)); |
| if (!obj || !copyFromArray(cx, obj, other, len)) |
| return NULL; |
| return obj; |
| } |
| |
| static const NativeType |
| getIndex(JSObject *obj, uint32_t index) |
| { |
| return *(static_cast<const NativeType*>(viewData(obj)) + index); |
| } |
| |
| static void |
| setIndex(JSObject *obj, uint32_t index, NativeType val) |
| { |
| *(static_cast<NativeType*>(viewData(obj)) + index) = val; |
| } |
| |
| static void copyIndexToValue(JSObject *tarray, uint32_t index, MutableHandleValue vp); |
| |
| static JSObject * |
| createSubarray(JSContext *cx, HandleObject tarray, uint32_t begin, uint32_t end) |
| { |
| JS_ASSERT(tarray); |
| |
| JS_ASSERT(begin <= length(tarray)); |
| JS_ASSERT(end <= length(tarray)); |
| |
| RootedObject bufobj(cx, buffer(tarray)); |
| JS_ASSERT(bufobj); |
| |
| JS_ASSERT(begin <= end); |
| uint32_t length = end - begin; |
| |
| JS_ASSERT(begin < UINT32_MAX / sizeof(NativeType)); |
| uint32_t arrayByteOffset = byteOffsetValue(tarray).toInt32(); |
| JS_ASSERT(UINT32_MAX - begin * sizeof(NativeType) >= arrayByteOffset); |
| uint32_t byteOffset = arrayByteOffset + begin * sizeof(NativeType); |
| |
| return makeInstance(cx, bufobj, byteOffset, length); |
| } |
| |
| protected: |
| static NativeType |
| nativeFromDouble(double d) |
| { |
| if (!ArrayTypeIsFloatingPoint() && JS_UNLIKELY(IsNaN(d))) |
| return NativeType(int32_t(0)); |
| if (TypeIsFloatingPoint<NativeType>()) |
| return NativeType(d); |
| if (TypeIsUnsigned<NativeType>()) |
| return NativeType(ToUint32(d)); |
| return NativeType(ToInt32(d)); |
| } |
| |
| static bool |
| nativeFromValue(JSContext *cx, const Value &v, NativeType *result) |
| { |
| if (v.isInt32()) { |
| *result = v.toInt32(); |
| return true; |
| } |
| |
| if (v.isDouble()) { |
| *result = nativeFromDouble(v.toDouble()); |
| return true; |
| } |
| |
| /* |
| * The condition guarantees that holes and undefined values |
| * are treated identically. |
| */ |
| if (v.isPrimitive() && !v.isMagic() && !v.isUndefined()) { |
| RootedValue primitive(cx, v); |
| double dval; |
| // ToNumber will only fail from OOM |
| if (!ToNumber(cx, primitive, &dval)) |
| return false; |
| *result = nativeFromDouble(dval); |
| return true; |
| } |
| |
| *result = ArrayTypeIsFloatingPoint() |
| ? NativeType(js_NaN) |
| : NativeType(int32_t(0)); |
| return true; |
| } |
| |
| static bool |
| copyFromArray(JSContext *cx, HandleObject thisTypedArrayObj, |
| HandleObject ar, uint32_t len, uint32_t offset = 0) |
| { |
| JS_ASSERT(thisTypedArrayObj->isTypedArray()); |
| JS_ASSERT(offset <= length(thisTypedArrayObj)); |
| JS_ASSERT(len <= length(thisTypedArrayObj) - offset); |
| if (ar->isTypedArray()) |
| return copyFromTypedArray(cx, thisTypedArrayObj, ar, offset); |
| |
| const Value *src = NULL; |
| NativeType *dest = static_cast<NativeType*>(viewData(thisTypedArrayObj)) + offset; |
| |
| // The only way the code below can GC is if nativeFromValue fails, but |
| // in that case we return false immediately, so we do not need to root |
| // |src| and |dest|. These SkipRoots are to protect from the |
| // unconditional MaybeCheckStackRoots done by ToNumber. |
| SkipRoot skipDest(cx, &dest); |
| SkipRoot skipSrc(cx, &src); |
| |
| if (ar->isArray() && !ar->isIndexed() && ar->getDenseInitializedLength() >= len) { |
| JS_ASSERT(ar->getArrayLength() == len); |
| |
| src = ar->getDenseElements(); |
| for (uint32_t i = 0; i < len; ++i) { |
| NativeType n; |
| if (!nativeFromValue(cx, src[i], &n)) |
| return false; |
| dest[i] = n; |
| } |
| } else { |
| RootedValue v(cx); |
| |
| for (uint32_t i = 0; i < len; ++i) { |
| if (!JSObject::getElement(cx, ar, ar, i, &v)) |
| return false; |
| NativeType n; |
| if (!nativeFromValue(cx, v, &n)) |
| return false; |
| dest[i] = n; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool |
| copyFromTypedArray(JSContext *cx, JSObject *thisTypedArrayObj, JSObject *tarray, uint32_t offset) |
| { |
| JS_ASSERT(thisTypedArrayObj->isTypedArray()); |
| JS_ASSERT(offset <= length(thisTypedArrayObj)); |
| JS_ASSERT(length(tarray) <= length(thisTypedArrayObj) - offset); |
| if (buffer(tarray) == buffer(thisTypedArrayObj)) |
| return copyFromWithOverlap(cx, thisTypedArrayObj, tarray, offset); |
| |
| NativeType *dest = static_cast<NativeType*>(viewData(thisTypedArrayObj)) + offset; |
| |
| if (type(tarray) == type(thisTypedArrayObj)) { |
| js_memcpy(dest, viewData(tarray), byteLengthValue(tarray).toInt32()); |
| return true; |
| } |
| |
| unsigned srclen = length(tarray); |
| switch (type(tarray)) { |
| case TypedArray::TYPE_INT8: { |
| int8_t *src = static_cast<int8_t*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_UINT8: |
| case TypedArray::TYPE_UINT8_CLAMPED: { |
| uint8_t *src = static_cast<uint8_t*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_INT16: { |
| int16_t *src = static_cast<int16_t*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_UINT16: { |
| uint16_t *src = static_cast<uint16_t*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_INT32: { |
| int32_t *src = static_cast<int32_t*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_UINT32: { |
| uint32_t *src = static_cast<uint32_t*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_FLOAT32: { |
| float *src = static_cast<float*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_FLOAT64: { |
| double *src = static_cast<double*>(viewData(tarray)); |
| for (unsigned i = 0; i < srclen; ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| default: |
| JS_NOT_REACHED("copyFrom with a TypedArray of unknown type"); |
| break; |
| } |
| |
| return true; |
| } |
| |
| static bool |
| copyFromWithOverlap(JSContext *cx, JSObject *self, JSObject *tarray, uint32_t offset) |
| { |
| JS_ASSERT(offset <= length(self)); |
| |
| NativeType *dest = static_cast<NativeType*>(viewData(self)) + offset; |
| uint32_t byteLength = byteLengthValue(tarray).toInt32(); |
| |
| if (type(tarray) == type(self)) { |
| memmove(dest, viewData(tarray), byteLength); |
| return true; |
| } |
| |
| // We have to make a copy of the source array here, since |
| // there's overlap, and we have to convert types. |
| void *srcbuf = cx->malloc_(byteLength); |
| if (!srcbuf) |
| return false; |
| js_memcpy(srcbuf, viewData(tarray), byteLength); |
| |
| switch (type(tarray)) { |
| case TypedArray::TYPE_INT8: { |
| int8_t *src = (int8_t*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_UINT8: |
| case TypedArray::TYPE_UINT8_CLAMPED: { |
| uint8_t *src = (uint8_t*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_INT16: { |
| int16_t *src = (int16_t*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_UINT16: { |
| uint16_t *src = (uint16_t*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_INT32: { |
| int32_t *src = (int32_t*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_UINT32: { |
| uint32_t *src = (uint32_t*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_FLOAT32: { |
| float *src = (float*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| case TypedArray::TYPE_FLOAT64: { |
| double *src = (double*) srcbuf; |
| for (unsigned i = 0; i < length(tarray); ++i) |
| *dest++ = NativeType(*src++); |
| break; |
| } |
| default: |
| JS_NOT_REACHED("copyFromWithOverlap with a TypedArray of unknown type"); |
| break; |
| } |
| |
| js_free(srcbuf); |
| return true; |
| } |
| |
| static JSObject * |
| createBufferWithSizeAndCount(JSContext *cx, uint32_t count) |
| { |
| size_t size = sizeof(NativeType); |
| if (size != 0 && count >= INT32_MAX / size) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_NEED_DIET, "size and count"); |
| return NULL; |
| } |
| |
| uint32_t bytelen = size * count; |
| return ArrayBufferObject::create(cx, bytelen); |
| } |
| }; |
| |
| class Int8Array : public TypedArrayTemplate<int8_t> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_INT8 }; |
| static const JSProtoKey key = JSProto_Int8Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Uint8Array : public TypedArrayTemplate<uint8_t> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_UINT8 }; |
| static const JSProtoKey key = JSProto_Uint8Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Int16Array : public TypedArrayTemplate<int16_t> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_INT16 }; |
| static const JSProtoKey key = JSProto_Int16Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Uint16Array : public TypedArrayTemplate<uint16_t> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_UINT16 }; |
| static const JSProtoKey key = JSProto_Uint16Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Int32Array : public TypedArrayTemplate<int32_t> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_INT32 }; |
| static const JSProtoKey key = JSProto_Int32Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Uint32Array : public TypedArrayTemplate<uint32_t> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_UINT32 }; |
| static const JSProtoKey key = JSProto_Uint32Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Float32Array : public TypedArrayTemplate<float> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_FLOAT32 }; |
| static const JSProtoKey key = JSProto_Float32Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Float64Array : public TypedArrayTemplate<double> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_FLOAT64 }; |
| static const JSProtoKey key = JSProto_Float64Array; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| class Uint8ClampedArray : public TypedArrayTemplate<uint8_clamped> { |
| public: |
| enum { ACTUAL_TYPE = TYPE_UINT8_CLAMPED }; |
| static const JSProtoKey key = JSProto_Uint8ClampedArray; |
| static const JSFunctionSpec jsfuncs[]; |
| }; |
| |
| template<typename T> |
| bool |
| ArrayBufferObject::createTypedArrayFromBufferImpl(JSContext *cx, CallArgs args) |
| { |
| typedef TypedArrayTemplate<T> ArrayType; |
| JS_ASSERT(IsArrayBuffer(args.thisv())); |
| JS_ASSERT(args.length() == 3); |
| |
| Rooted<JSObject*> buffer(cx, &args.thisv().toObject()); |
| Rooted<JSObject*> proto(cx, &args[2].toObject()); |
| |
| Rooted<JSObject*> obj(cx); |
| double byteOffset = args[0].toNumber(); |
| MOZ_ASSERT(0 <= byteOffset); |
| MOZ_ASSERT(byteOffset <= UINT32_MAX); |
| MOZ_ASSERT(byteOffset == uint32_t(byteOffset)); |
| obj = ArrayType::fromBuffer(cx, buffer, uint32_t(byteOffset), args[1].toInt32(), proto); |
| if (!obj) |
| return false; |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| template<typename T> |
| JSBool |
| ArrayBufferObject::createTypedArrayFromBuffer(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| return CallNonGenericMethod<IsArrayBuffer, createTypedArrayFromBufferImpl<T> >(cx, args); |
| } |
| |
| // this default implementation is only valid for integer types |
| // less than 32-bits in size. |
| template<typename NativeType> |
| void |
| TypedArrayTemplate<NativeType>::copyIndexToValue(JSObject *tarray, uint32_t index, |
| MutableHandleValue vp) |
| { |
| JS_STATIC_ASSERT(sizeof(NativeType) < 4); |
| |
| vp.setInt32(getIndex(tarray, index)); |
| } |
| |
| // and we need to specialize for 32-bit integers and floats |
| template<> |
| void |
| TypedArrayTemplate<int32_t>::copyIndexToValue(JSObject *tarray, uint32_t index, |
| MutableHandleValue vp) |
| { |
| int32_t val = getIndex(tarray, index); |
| vp.setInt32(val); |
| } |
| |
| template<> |
| void |
| TypedArrayTemplate<uint32_t>::copyIndexToValue(JSObject *tarray, uint32_t index, |
| MutableHandleValue vp) |
| { |
| uint32_t val = getIndex(tarray, index); |
| vp.setNumber(val); |
| } |
| |
| template<> |
| void |
| TypedArrayTemplate<float>::copyIndexToValue(JSObject *tarray, uint32_t index, |
| MutableHandleValue vp) |
| { |
| float val = getIndex(tarray, index); |
| double dval = val; |
| |
| /* |
| * Doubles in typed arrays could be typed-punned arrays of integers. This |
| * could allow user code to break the engine-wide invariant that only |
| * canonical nans are stored into jsvals, which means user code could |
| * confuse the engine into interpreting a double-typed jsval as an |
| * object-typed jsval. |
| * |
| * This could be removed for platforms/compilers known to convert a 32-bit |
| * non-canonical nan to a 64-bit canonical nan. |
| */ |
| vp.setDouble(CanonicalizeNaN(dval)); |
| } |
| |
| template<> |
| void |
| TypedArrayTemplate<double>::copyIndexToValue(JSObject *tarray, uint32_t index, |
| MutableHandleValue vp) |
| { |
| double val = getIndex(tarray, index); |
| |
| /* |
| * Doubles in typed arrays could be typed-punned arrays of integers. This |
| * could allow user code to break the engine-wide invariant that only |
| * canonical nans are stored into jsvals, which means user code could |
| * confuse the engine into interpreting a double-typed jsval as an |
| * object-typed jsval. |
| */ |
| vp.setDouble(CanonicalizeNaN(val)); |
| } |
| |
| JSBool |
| DataViewObject::construct(JSContext *cx, JSObject *bufobj, const CallArgs &args, HandleObject proto) |
| { |
| if (!bufobj->is<ArrayBufferObject>()) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPECTED_TYPE, |
| "DataView", "ArrayBuffer", bufobj->getClass()->name); |
| return false; |
| } |
| |
| Rooted<ArrayBufferObject*> buffer(cx, &bufobj->as<ArrayBufferObject>()); |
| uint32_t bufferLength = buffer->byteLength(); |
| uint32_t byteOffset = 0; |
| uint32_t byteLength = bufferLength; |
| |
| if (args.length() > 1) { |
| if (!ToUint32(cx, args[1], &byteOffset)) |
| return false; |
| if (byteOffset > INT32_MAX) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); |
| return false; |
| } |
| |
| if (args.length() > 2) { |
| if (!ToUint32(cx, args[2], &byteLength)) |
| return false; |
| if (byteLength > INT32_MAX) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, |
| JSMSG_ARG_INDEX_OUT_OF_RANGE, "2"); |
| return false; |
| } |
| } else { |
| if (byteOffset > bufferLength) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); |
| return false; |
| } |
| |
| byteLength = bufferLength - byteOffset; |
| } |
| } |
| |
| /* The sum of these cannot overflow a uint32_t */ |
| JS_ASSERT(byteOffset <= INT32_MAX); |
| JS_ASSERT(byteLength <= INT32_MAX); |
| |
| if (byteOffset + byteLength > bufferLength) { |
| JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_ARG_INDEX_OUT_OF_RANGE, "1"); |
| return false; |
| } |
| |
| JSObject *obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, proto); |
| if (!obj) |
| return false; |
| args.rval().setObject(*obj); |
| return true; |
| } |
| |
| JSBool |
| DataViewObject::class_constructor(JSContext *cx, unsigned argc, Value *vp) |
| { |
| CallArgs args = CallArgsFromVp(argc, vp); |
| |
| RootedObject bufobj(cx); |
| if (!GetFirstArgumentAsObject(cx, args, "DataView constructor", &bufobj)) |
| return false; |
| |
| if (bufobj->isWrapper() && UncheckedUnwrap(bufobj)->is<ArrayBufferObject>()) { |
| Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal()); |
| Rooted<JSObject*> proto(cx, global->getOrCreateDataViewPrototype(cx)); |
| if (!proto) |
| return false; |
| |
| InvokeArgs args2(cx); |
| if (!args2.init(args.length() + 1)) |
| return false; |
| args2.setCallee(global->createDataViewForThis()); |
| args2.setThis(ObjectValue(*bufobj)); |
| PodCopy(args2.array(), args.array(), args.length()); |
| args2[argc] = ObjectValue(*proto); |
| if (!Invoke(cx, args2)) |
| return false; |
| args.rval().set(args2.rval()); |
| return true; |
| } |
| |
| return construct(cx, bufobj, args, NullPtr()); |
| } |
| |
| /* static */ bool |
| |