| /* | 
 |  * Copyright (C) 2013-2019 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.  | 
 |  */ | 
 |  | 
 | #include "config.h" | 
 | #include "JSArrayBufferView.h" | 
 |  | 
 | #include "GenericTypedArrayViewInlines.h" | 
 | #include "JSArrayBuffer.h" | 
 | #include "JSCInlines.h" | 
 | #include "JSGenericTypedArrayViewInlines.h" | 
 | #include "JSTypedArrays.h" | 
 | #include "TypeError.h" | 
 | #include "TypedArrayController.h" | 
 | #include "TypedArrays.h" | 
 | #include <wtf/Gigacage.h> | 
 |  | 
 | namespace JSC { | 
 |  | 
 | const ClassInfo JSArrayBufferView::s_info = { | 
 |     "ArrayBufferView", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSArrayBufferView) | 
 | }; | 
 |  | 
 | String JSArrayBufferView::toStringName(const JSObject*, JSGlobalObject*) | 
 | { | 
 |     return "Object"_s; | 
 | } | 
 |  | 
 | JSArrayBufferView::ConstructionContext::ConstructionContext( | 
 |     Structure* structure, uint32_t length, void* vector) | 
 |     : m_structure(structure) | 
 |     , m_vector(vector, length) | 
 |     , m_length(length) | 
 |     , m_mode(FastTypedArray) | 
 |     , m_butterfly(nullptr) | 
 | { | 
 |     ASSERT(vector == removeArrayPtrTag(vector)); | 
 |     RELEASE_ASSERT(length <= fastSizeLimit); | 
 | } | 
 |  | 
 | JSArrayBufferView::ConstructionContext::ConstructionContext( | 
 |     VM& vm, Structure* structure, uint32_t length, uint32_t elementSize, | 
 |     InitializationMode mode) | 
 |     : m_structure(0) | 
 |     , m_length(length) | 
 |     , m_butterfly(0) | 
 | { | 
 |     if (length <= fastSizeLimit) { | 
 |         // Attempt GC allocation. | 
 |         void* temp; | 
 |         size_t size = sizeOf(length, elementSize); | 
 |         temp = vm.primitiveGigacageAuxiliarySpace.allocateNonVirtual(vm, size, nullptr, AllocationFailureMode::ReturnNull); | 
 |         if (!temp) | 
 |             return; | 
 |  | 
 |         m_structure = structure; | 
 |         m_vector = VectorType(temp, length); | 
 |         m_mode = FastTypedArray; | 
 |  | 
 |         if (mode == ZeroFill) { | 
 |             uint64_t* asWords = static_cast<uint64_t*>(vector()); | 
 |             for (unsigned i = size / sizeof(uint64_t); i--;) | 
 |                 asWords[i] = 0; | 
 |         } | 
 |          | 
 |         return; | 
 |     } | 
 |  | 
 |     // Don't allow a typed array to use more than 2GB. | 
 |     if (length > static_cast<unsigned>(INT_MAX) / elementSize) | 
 |         return; | 
 |      | 
 |     size_t size = static_cast<size_t>(length) * static_cast<size_t>(elementSize); | 
 |     m_vector = VectorType(Gigacage::tryMalloc(Gigacage::Primitive, size), length); | 
 |     if (!m_vector) | 
 |         return; | 
 |     if (mode == ZeroFill) | 
 |         memset(vector(), 0, size); | 
 |      | 
 |     vm.heap.reportExtraMemoryAllocated(static_cast<size_t>(length) * elementSize); | 
 |      | 
 |     m_structure = structure; | 
 |     m_mode = OversizeTypedArray; | 
 | } | 
 |  | 
 | JSArrayBufferView::ConstructionContext::ConstructionContext( | 
 |     VM& vm, Structure* structure, RefPtr<ArrayBuffer>&& arrayBuffer, | 
 |     unsigned byteOffset, unsigned length) | 
 |     : m_structure(structure) | 
 |     , m_length(length) | 
 |     , m_mode(WastefulTypedArray) | 
 | { | 
 |     ASSERT(arrayBuffer->data() == removeArrayPtrTag(arrayBuffer->data())); | 
 |     m_vector = VectorType(static_cast<uint8_t*>(arrayBuffer->data()) + byteOffset, length); | 
 |     IndexingHeader indexingHeader; | 
 |     indexingHeader.setArrayBuffer(arrayBuffer.get()); | 
 |     m_butterfly = Butterfly::create(vm, 0, 0, 0, true, indexingHeader, 0); | 
 | } | 
 |  | 
 | JSArrayBufferView::ConstructionContext::ConstructionContext( | 
 |     Structure* structure, RefPtr<ArrayBuffer>&& arrayBuffer, | 
 |     unsigned byteOffset, unsigned length, DataViewTag) | 
 |     : m_structure(structure) | 
 |     , m_length(length) | 
 |     , m_mode(DataViewMode) | 
 |     , m_butterfly(0) | 
 | { | 
 |     ASSERT(arrayBuffer->data() == removeArrayPtrTag(arrayBuffer->data())); | 
 |     m_vector = VectorType(static_cast<uint8_t*>(arrayBuffer->data()) + byteOffset, length); | 
 | } | 
 |  | 
 | JSArrayBufferView::JSArrayBufferView(VM& vm, ConstructionContext& context) | 
 |     : Base(vm, context.structure(), nullptr) | 
 |     , m_length(context.length()) | 
 |     , m_mode(context.mode()) | 
 | { | 
 |     setButterfly(vm, context.butterfly()); | 
 |     ASSERT(context.vector() == removeArrayPtrTag(context.vector())); | 
 |     m_vector.setWithoutBarrier(context.vector(), m_length); | 
 | } | 
 |  | 
 | void JSArrayBufferView::finishCreation(VM& vm) | 
 | { | 
 |     Base::finishCreation(vm); | 
 |     ASSERT(jsDynamicCast<JSArrayBufferView*>(vm, this)); | 
 |     switch (m_mode) { | 
 |     case FastTypedArray: | 
 |         return; | 
 |     case OversizeTypedArray: | 
 |         vm.heap.addFinalizer(this, finalize); | 
 |         return; | 
 |     case WastefulTypedArray: | 
 |         vm.heap.addReference(this, butterfly()->indexingHeader()->arrayBuffer()); | 
 |         return; | 
 |     case DataViewMode: | 
 |         ASSERT(!butterfly()); | 
 |         vm.heap.addReference(this, jsCast<JSDataView*>(this)->possiblySharedBuffer()); | 
 |         return; | 
 |     } | 
 |     RELEASE_ASSERT_NOT_REACHED(); | 
 | } | 
 |  | 
 | void JSArrayBufferView::visitChildren(JSCell* cell, SlotVisitor& visitor) | 
 | { | 
 |     JSArrayBufferView* thisObject = jsCast<JSArrayBufferView*>(cell); | 
 |     ASSERT_GC_OBJECT_INHERITS(thisObject, info()); | 
 |     Base::visitChildren(cell, visitor); | 
 |  | 
 |     if (thisObject->hasArrayBuffer()) { | 
 |         WTF::loadLoadFence(); | 
 |         ArrayBuffer* buffer = thisObject->possiblySharedBuffer(); | 
 |         RELEASE_ASSERT(buffer); | 
 |         visitor.addOpaqueRoot(buffer); | 
 |     } | 
 | } | 
 |  | 
 | bool JSArrayBufferView::put( | 
 |     JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, | 
 |     PutPropertySlot& slot) | 
 | { | 
 |     JSArrayBufferView* thisObject = jsCast<JSArrayBufferView*>(cell); | 
 |  | 
 |     if (UNLIKELY(isThisValueAltered(slot, thisObject))) | 
 |         return ordinarySetSlow(globalObject, thisObject, propertyName, value, slot.thisValue(), slot.isStrictMode()); | 
 |      | 
 |     return Base::put(thisObject, globalObject, propertyName, value, slot); | 
 | } | 
 |  | 
 | ArrayBuffer* JSArrayBufferView::unsharedBuffer() | 
 | { | 
 |     ArrayBuffer* result = possiblySharedBuffer(); | 
 |     RELEASE_ASSERT(!result->isShared()); | 
 |     return result; | 
 | } | 
 |      | 
 | void JSArrayBufferView::finalize(JSCell* cell) | 
 | { | 
 |     JSArrayBufferView* thisObject = static_cast<JSArrayBufferView*>(cell); | 
 |     ASSERT(thisObject->m_mode == OversizeTypedArray || thisObject->m_mode == WastefulTypedArray); | 
 |     if (thisObject->m_mode == OversizeTypedArray) | 
 |         Gigacage::free(Gigacage::Primitive, thisObject->vector()); | 
 | } | 
 |  | 
 | JSArrayBuffer* JSArrayBufferView::unsharedJSBuffer(JSGlobalObject* globalObject) | 
 | { | 
 |     VM& vm = globalObject->vm(); | 
 |     return vm.m_typedArrayController->toJS(globalObject, this->globalObject(vm), unsharedBuffer()); | 
 | } | 
 |  | 
 | JSArrayBuffer* JSArrayBufferView::possiblySharedJSBuffer(JSGlobalObject* globalObject) | 
 | { | 
 |     VM& vm = globalObject->vm(); | 
 |     return vm.m_typedArrayController->toJS(globalObject, this->globalObject(vm), possiblySharedBuffer()); | 
 | } | 
 |  | 
 | void JSArrayBufferView::neuter() | 
 | { | 
 |     auto locker = holdLock(cellLock()); | 
 |     RELEASE_ASSERT(hasArrayBuffer()); | 
 |     RELEASE_ASSERT(!isShared()); | 
 |     m_length = 0; | 
 |     m_vector.clear(); | 
 | } | 
 |  | 
 | static const constexpr size_t ElementSizeData[] = { | 
 | #define FACTORY(type) sizeof(typename type ## Adaptor::Type), | 
 |     FOR_EACH_TYPED_ARRAY_TYPE_EXCLUDING_DATA_VIEW(FACTORY) | 
 | #undef FACTORY | 
 | }; | 
 |  | 
 | #define FACTORY(type) static_assert(std::is_final<JS ## type ## Array>::value, ""); | 
 | FOR_EACH_TYPED_ARRAY_TYPE_EXCLUDING_DATA_VIEW(FACTORY) | 
 | #undef FACTORY | 
 |  | 
 | static inline size_t elementSize(JSType type) | 
 | { | 
 |     ASSERT(type >= Int8ArrayType && type <= Float64ArrayType); | 
 |     return ElementSizeData[type - Int8ArrayType]; | 
 | } | 
 |  | 
 | ArrayBuffer* JSArrayBufferView::slowDownAndWasteMemory() | 
 | { | 
 |     ASSERT(m_mode == FastTypedArray || m_mode == OversizeTypedArray); | 
 |  | 
 |     // We play this game because we want this to be callable even from places that | 
 |     // don't have access to CallFrame* 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 bizarre 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(this); | 
 |     VM& vm = heap->vm(); | 
 |     DeferGCForAWhile deferGC(*heap); | 
 |  | 
 |     RELEASE_ASSERT(!hasIndexingHeader(vm)); | 
 |     Structure* structure = this->structure(vm); | 
 |     setButterfly(vm, Butterfly::createOrGrowArrayRight( | 
 |         butterfly(), vm, this, structure, | 
 |         structure->outOfLineCapacity(), false, 0, 0)); | 
 |  | 
 |     RefPtr<ArrayBuffer> buffer; | 
 |     unsigned byteLength = m_length * elementSize(type()); | 
 |  | 
 |     switch (m_mode) { | 
 |     case FastTypedArray: | 
 |         buffer = ArrayBuffer::create(vector(), 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(vector(), byteLength); | 
 |         break; | 
 |  | 
 |     default: | 
 |         RELEASE_ASSERT_NOT_REACHED(); | 
 |         break; | 
 |     } | 
 |  | 
 |     { | 
 |         auto locker = holdLock(cellLock()); | 
 |         butterfly()->indexingHeader()->setArrayBuffer(buffer.get()); | 
 |         m_vector.setWithoutBarrier(buffer->data(), m_length); | 
 |         WTF::storeStoreFence(); | 
 |         m_mode = WastefulTypedArray; | 
 |     } | 
 |     heap->addReference(this, buffer.get()); | 
 |  | 
 |     return buffer.get(); | 
 | } | 
 |  | 
 | // Allocates the full-on native buffer and moves data into the C heap if | 
 | // necessary. Note that this never allocates in the GC heap. | 
 | RefPtr<ArrayBufferView> JSArrayBufferView::possiblySharedImpl() | 
 | { | 
 |     ArrayBuffer* buffer = possiblySharedBuffer(); | 
 |     unsigned byteOffset = this->byteOffset(); | 
 |     unsigned length = this->length(); | 
 |     switch (type()) { | 
 | #define FACTORY(type) \ | 
 |     case type ## ArrayType: \ | 
 |         return type ## Array::tryCreate(buffer, byteOffset, length); | 
 |     FOR_EACH_TYPED_ARRAY_TYPE_EXCLUDING_DATA_VIEW(FACTORY) | 
 | #undef FACTORY | 
 |     case DataViewType: | 
 |         return DataView::create(buffer, byteOffset, length); | 
 |     default: | 
 |         RELEASE_ASSERT_NOT_REACHED(); | 
 |         return nullptr; | 
 |     } | 
 | } | 
 |  | 
 | } // namespace JSC | 
 |  | 
 | namespace WTF { | 
 |  | 
 | using namespace JSC; | 
 |  | 
 | void printInternal(PrintStream& out, TypedArrayMode mode) | 
 | { | 
 |     switch (mode) { | 
 |     case FastTypedArray: | 
 |         out.print("FastTypedArray"); | 
 |         return; | 
 |     case OversizeTypedArray: | 
 |         out.print("OversizeTypedArray"); | 
 |         return; | 
 |     case WastefulTypedArray: | 
 |         out.print("WastefulTypedArray"); | 
 |         return; | 
 |     case DataViewMode: | 
 |         out.print("DataViewMode"); | 
 |         return; | 
 |     } | 
 |     RELEASE_ASSERT_NOT_REACHED(); | 
 | } | 
 |  | 
 | } // namespace WTF | 
 |  |