/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef JSGenericTypedArrayViewInlines_h #define JSGenericTypedArrayViewInlines_h #include "ArrayBufferView.h" #include "DeferGC.h" #include "Error.h" #include "ExceptionHelpers.h" #include "JSGenericTypedArrayView.h" #include "Reject.h" #include "TypedArrays.h" namespace JSC { template JSGenericTypedArrayView::JSGenericTypedArrayView( VM& vm, ConstructionContext& context) : Base(vm, context) { } template JSGenericTypedArrayView* JSGenericTypedArrayView::create( ExecState* exec, Structure* structure, unsigned length) { ConstructionContext context(exec->vm(), structure, length, sizeof(typename Adaptor::Type)); if (!context) { exec->vm().throwException(exec, createOutOfMemoryError(structure->globalObject())); return 0; } JSGenericTypedArrayView* result = new (NotNull, allocateCell(exec->vm().heap)) JSGenericTypedArrayView(exec->vm(), context); result->finishCreation(exec->vm()); return result; } template JSGenericTypedArrayView* JSGenericTypedArrayView::createUninitialized( ExecState* exec, Structure* structure, unsigned length) { ConstructionContext context( exec->vm(), structure, length, sizeof(typename Adaptor::Type), ConstructionContext::DontInitialize); if (!context) { exec->vm().throwException(exec, createOutOfMemoryError(structure->globalObject())); return 0; } JSGenericTypedArrayView* result = new (NotNull, allocateCell(exec->vm().heap)) JSGenericTypedArrayView(exec->vm(), context); result->finishCreation(exec->vm()); return result; } template JSGenericTypedArrayView* JSGenericTypedArrayView::create( ExecState* exec, Structure* structure, PassRefPtr passedBuffer, unsigned byteOffset, unsigned length) { RefPtr buffer = passedBuffer; if (!ArrayBufferView::verifySubRange(buffer, byteOffset, length)) { exec->vm().throwException( exec, createRangeError(exec, "Byte offset and length out of range of buffer")); return 0; } ConstructionContext context(exec->vm(), structure, buffer, byteOffset, length); ASSERT(context); JSGenericTypedArrayView* result = new (NotNull, allocateCell(exec->vm().heap)) JSGenericTypedArrayView(exec->vm(), context); result->finishCreation(exec->vm()); return result; } template JSGenericTypedArrayView* JSGenericTypedArrayView::create( VM& vm, Structure* structure, PassRefPtr impl) { RefPtr buffer = impl->buffer(); ConstructionContext context(vm, structure, buffer, impl->byteOffset(), impl->length()); ASSERT(context); JSGenericTypedArrayView* result = new (NotNull, allocateCell(vm.heap)) JSGenericTypedArrayView(vm, context); result->finishCreation(vm); return result; } template JSGenericTypedArrayView* JSGenericTypedArrayView::create( Structure* structure, JSGlobalObject* globalObject, PassRefPtr impl) { return create(globalObject->vm(), structure, impl); } template bool JSGenericTypedArrayView::validateRange( ExecState* exec, unsigned offset, unsigned length) { if (canAccessRangeQuickly(offset, length)) return true; exec->vm().throwException(exec, createRangeError(exec, "Range consisting of offset and length are out of bounds")); return false; } template template bool JSGenericTypedArrayView::setWithSpecificType( ExecState* exec, JSGenericTypedArrayView* other, unsigned offset, unsigned length) { // Handle the hilarious case: the act of getting the length could have resulted // in neutering. Well, no. That'll never happen because there cannot be // side-effects on getting the length of a typed array. But predicting where there // are, or aren't, side-effects is a fool's game so we resort to this cheap // check. Worst case, if we're wrong, people start seeing less things get copied // but we won't have a security vulnerability. length = std::min(length, other->length()); if (!validateRange(exec, offset, length)) return false; if (other->length() != length) { exec->vm().throwException(exec, createRangeError(exec, "Length of incoming array changed unexpectedly.")); return false; } // This method doesn't support copying between the same array. Note that // set() will only call this if the types differ, which implicitly guarantees // that we can't be the same array. This is relevant because the way we detect // non-overlapping is by checking if either (a) either array doesn't have a // backing buffer or (b) the backing buffers are different, but that doesn't // catch the case where it's the *same* array - fortunately though, this code // path never needs to worry about that case. ASSERT(static_cast(this) != static_cast(other)); // 1) If the two arrays are non-overlapping, we can copy in any order we like // and we don't need an intermediate buffer. Arrays are definitely // non-overlapping if either one of them has no backing buffer (that means // that it *owns* its philosophical backing buffer) or if they have // different backing buffers. // 2) If the two arrays overlap but have the same element size, we can do a // memmove-like copy where we flip-flop direction based on which vector // starts before the other: // A) If the destination vector is before the source vector, then a forward // copy is in order. // B) If the destination vector is after the source vector, then a backward // copy is in order. // 3) If we have different element sizes and there is a chance of overlap then // we need an intermediate vector. // NB. Comparisons involving elementSize will be constant-folded by template // specialization. unsigned otherElementSize = sizeof(typename OtherAdaptor::Type); // Handle cases (1) and (2B). if (!hasArrayBuffer() || !other->hasArrayBuffer() || existingBuffer() != other->existingBuffer() || (elementSize == otherElementSize && vector() > other->vector())) { for (unsigned i = length; i--;) { setIndexQuicklyToNativeValue( offset + i, OtherAdaptor::template convertTo( other->getIndexQuicklyAsNativeValue(i))); } return true; } // Now we either have (2A) or (3) - so first we try to cover (2A). if (elementSize == otherElementSize) { for (unsigned i = 0; i < length; ++i) { setIndexQuicklyToNativeValue( offset + i, OtherAdaptor::template convertTo( other->getIndexQuicklyAsNativeValue(i))); } return true; } // Fail: we need an intermediate transfer buffer (i.e. case (3)). Vector transferBuffer(length); for (unsigned i = length; i--;) { transferBuffer[i] = OtherAdaptor::template convertTo( other->getIndexQuicklyAsNativeValue(i)); } for (unsigned i = length; i--;) setIndexQuicklyToNativeValue(offset + i, transferBuffer[i]); return true; } template bool JSGenericTypedArrayView::set( ExecState* exec, JSObject* object, unsigned offset, unsigned length) { const ClassInfo* ci = object->classInfo(); if (ci->typedArrayStorageType == Adaptor::typeValue) { // The super fast case: we can just memcpy since we're the same type. JSGenericTypedArrayView* other = jsCast(object); length = std::min(length, other->length()); if (!validateRange(exec, offset, length)) return false; memmove(typedVector() + offset, other->typedVector(), other->byteLength()); return true; } switch (ci->typedArrayStorageType) { case TypeInt8: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeInt16: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeInt32: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeUint8: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeUint8Clamped: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeUint16: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeUint32: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeFloat32: return setWithSpecificType( exec, jsCast(object), offset, length); case TypeFloat64: return setWithSpecificType( exec, jsCast(object), offset, length); case NotTypedArray: case TypeDataView: { if (!validateRange(exec, offset, length)) return false; // We could optimize this case. But right now, we don't. for (unsigned i = 0; i < length; ++i) { JSValue value = object->get(exec, i); if (!setIndex(exec, offset + i, value)) return false; } return true; } } RELEASE_ASSERT_NOT_REACHED(); return false; } template ArrayBuffer* JSGenericTypedArrayView::existingBuffer() { return existingBufferInButterfly(); } template bool JSGenericTypedArrayView::getOwnPropertySlot( JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) { JSGenericTypedArrayView* thisObject = jsCast(object); if (propertyName == exec->propertyNames().length) { slot.setValue(thisObject, DontDelete | ReadOnly, jsNumber(thisObject->length())); return true; } if (propertyName == exec->propertyNames().byteLength) { slot.setValue(thisObject, DontDelete | ReadOnly, jsNumber(thisObject->byteLength())); return true; } unsigned index = propertyName.asIndex(); if (index != PropertyName::NotAnIndex && thisObject->canGetIndexQuickly(index)) { slot.setValue(thisObject, DontDelete | ReadOnly, thisObject->getIndexQuickly(index)); return true; } return Base::getOwnPropertySlot(thisObject, exec, propertyName, slot); } template void JSGenericTypedArrayView::put( JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) { JSGenericTypedArrayView* thisObject = jsCast(cell); if (propertyName == exec->propertyNames().length) { // Firefox appears to simply ignore attempts to store to the length property. // Even in strict mode. I will do the same. return; } unsigned index = propertyName.asIndex(); if (index != PropertyName::NotAnIndex) { putByIndex(thisObject, exec, index, value, slot.isStrictMode()); return; } Base::put(thisObject, exec, propertyName, value, slot); } template bool JSGenericTypedArrayView::defineOwnProperty( JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) { JSGenericTypedArrayView* thisObject = jsCast(object); // This is matching Firefox behavior. In particular, it rejects all attempts to // defineOwnProperty for indexed properties on typed arrays, even if they're out // of bounds. if (propertyName == exec->propertyNames().length || propertyName.asIndex() != PropertyName::NotAnIndex) return reject(exec, shouldThrow, "Attempting to write to a read-only typed array property."); return Base::defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow); } template bool JSGenericTypedArrayView::deleteProperty( JSCell* cell, ExecState* exec, PropertyName propertyName) { JSGenericTypedArrayView* thisObject = jsCast(cell); if (propertyName == exec->propertyNames().length || propertyName.asIndex() != PropertyName::NotAnIndex) return false; return Base::deleteProperty(thisObject, exec, propertyName); } template bool JSGenericTypedArrayView::getOwnPropertySlotByIndex( JSObject* object, ExecState* exec, unsigned propertyName, PropertySlot& slot) { JSGenericTypedArrayView* thisObject = jsCast(object); if (propertyName > MAX_ARRAY_INDEX) { return thisObject->methodTable()->getOwnPropertySlot( thisObject, exec, Identifier::from(exec, propertyName), slot); } if (!thisObject->canGetIndexQuickly(propertyName)) return false; slot.setValue(thisObject, None, thisObject->getIndexQuickly(propertyName)); return true; } template void JSGenericTypedArrayView::putByIndex( JSCell* cell, ExecState* exec, unsigned propertyName, JSValue value, bool shouldThrow) { JSGenericTypedArrayView* thisObject = jsCast(cell); if (propertyName > MAX_ARRAY_INDEX) { PutPropertySlot slot(JSValue(thisObject), shouldThrow); thisObject->methodTable()->put( thisObject, exec, Identifier::from(exec, propertyName), value, slot); return; } thisObject->setIndex(exec, propertyName, value); } template bool JSGenericTypedArrayView::deletePropertyByIndex( JSCell* cell, ExecState* exec, unsigned propertyName) { if (propertyName > MAX_ARRAY_INDEX) { return cell->methodTable()->deleteProperty( cell, exec, Identifier::from(exec, propertyName)); } return false; } template void JSGenericTypedArrayView::getOwnNonIndexPropertyNames( JSObject* object, ExecState* exec, PropertyNameArray& array, EnumerationMode mode) { JSGenericTypedArrayView* thisObject = jsCast(object); if (mode == IncludeDontEnumProperties) array.add(exec->propertyNames().length); Base::getOwnNonIndexPropertyNames(thisObject, exec, array, mode); } template void JSGenericTypedArrayView::getOwnPropertyNames( JSObject* object, ExecState* exec, PropertyNameArray& array, EnumerationMode mode) { JSGenericTypedArrayView* thisObject = jsCast(object); for (unsigned i = 0; i < thisObject->m_length; ++i) array.add(Identifier::from(exec, i)); return Base::getOwnPropertyNames(object, exec, array, mode); } template void JSGenericTypedArrayView::visitChildren(JSCell* cell, SlotVisitor& visitor) { JSGenericTypedArrayView* thisObject = jsCast(cell); switch (thisObject->m_mode) { case FastTypedArray: { if (thisObject->m_vector) visitor.copyLater(thisObject, TypedArrayVectorCopyToken, thisObject->m_vector, thisObject->byteSize()); break; } case OversizeTypedArray: { visitor.reportExtraMemoryUsage(thisObject, thisObject->byteSize()); break; } case WastefulTypedArray: break; case DataViewMode: RELEASE_ASSERT_NOT_REACHED(); break; } Base::visitChildren(thisObject, visitor); } template void JSGenericTypedArrayView::copyBackingStore( JSCell* cell, CopyVisitor& visitor, CopyToken token) { JSGenericTypedArrayView* thisObject = jsCast(cell); if (token == TypedArrayVectorCopyToken && visitor.checkIfShouldCopy(thisObject->m_vector)) { ASSERT(thisObject->m_vector); void* oldVector = thisObject->m_vector; void* newVector = visitor.allocateNewSpace(thisObject->byteSize()); memcpy(newVector, oldVector, thisObject->byteSize()); thisObject->m_vector = newVector; visitor.didCopy(oldVector, thisObject->byteSize()); } Base::copyBackingStore(thisObject, visitor, token); } template ArrayBuffer* JSGenericTypedArrayView::slowDownAndWasteMemory(JSArrayBufferView* object) { JSGenericTypedArrayView* thisObject = jsCast(object); // We play this game because we want this to be callable even from places that // don't have access to ExecState* or the VM, and we only allocate so little // memory here that it's not necessary to trigger a GC - just accounting what // we have done is good enough. The sort of bizarro exception to the "allocating // little memory" is when we transfer a backing buffer into the C heap; this // will temporarily get counted towards heap footprint (incorrectly, in the case // of adopting an oversize typed array) but we don't GC here anyway. That's // almost certainly fine. The worst case is if you created a ton of fast typed // arrays, and did nothing but caused all of them to slow down and waste memory. // In that case, your memory footprint will double before the GC realizes what's // up. But if you do *anything* to trigger a GC watermark check, it will know // that you *had* done those allocations and it will GC appropriately. Heap* heap = Heap::heap(thisObject); DeferGCForAWhile deferGC(*heap); ASSERT(!thisObject->hasIndexingHeader()); size_t size = thisObject->byteSize(); if (thisObject->m_mode == FastTypedArray && !thisObject->butterfly() && size >= sizeof(IndexingHeader)) { ASSERT(thisObject->m_vector); // Reuse already allocated memory if at all possible. thisObject->m_butterfly.setWithoutWriteBarrier( static_cast(thisObject->m_vector)->butterfly()); } else { VM& vm = *heap->vm(); thisObject->m_butterfly.set(vm, thisObject, Butterfly::createOrGrowArrayRight( thisObject->butterfly(), vm, thisObject, thisObject->structure(), thisObject->structure()->outOfLineCapacity(), false, 0, 0)); } RefPtr buffer; switch (thisObject->m_mode) { case FastTypedArray: buffer = ArrayBuffer::create(thisObject->m_vector, thisObject->byteLength()); break; case OversizeTypedArray: // FIXME: consider doing something like "subtracting" from extra memory // cost, since right now this case will cause the GC to think that we reallocated // the whole buffer. buffer = ArrayBuffer::createAdopted(thisObject->m_vector, thisObject->byteLength()); break; default: RELEASE_ASSERT_NOT_REACHED(); break; } thisObject->butterfly()->indexingHeader()->setArrayBuffer(buffer.get()); thisObject->m_vector = buffer->data(); thisObject->m_mode = WastefulTypedArray; heap->addReference(thisObject, buffer.get()); return buffer.get(); } template PassRefPtr JSGenericTypedArrayView::getTypedArrayImpl(JSArrayBufferView* object) { JSGenericTypedArrayView* thisObject = jsCast(object); return thisObject->typedImpl(); } } // namespace JSC #endif // JSGenericTypedArrayViewInlines_h