/* * Copyright (C) 2013-2021 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 JSCInlines.h #include JSGenericTypedArrayViewInlines.h #include JSTypedArrays.h #include TypedArrayController.h #include TypedArrays.h #include <wtf/Gigacage.h> namespace JSC { const ClassInfo JSArrayBufferView::s_info = { ArrayBufferView_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSArrayBufferView) }; const ASCIILiteral typedArrayBufferHasBeenDetachedErrorMessage { Underlying ArrayBuffer has been detached from the view or out-of-bounds_s }; JSArrayBufferView::ConstructionContext::ConstructionContext(Structure* structure, size_t length, void* vector) : m_structure(structure) , m_vector(vector, length) , m_length(length) , m_byteOffset(0) , m_mode(FastTypedArray) , m_butterfly(nullptr) { ASSERT(!isResizableOrGrowableSharedTypedArrayIncludingDataView(structure->classInfoForCells())); ASSERT(!Gigacage::isEnabled() || (Gigacage::contains(vector) && Gigacage::contains(static_cast<const uint8_t*>(vector) + length – 1))); ASSERT(vector == removeArrayPtrTag(vector)); RELEASE_ASSERT(length <= fastSizeLimit); } JSArrayBufferView::ConstructionContext::ConstructionContext(VM& vm, Structure* structure, size_t length, unsigned elementSize, InitializationMode mode) : m_structure(nullptr) , m_length(length) , m_byteOffset(0) , m_butterfly(nullptr) { ASSERT(!isResizableOrGrowableSharedTypedArrayIncludingDataView(structure->classInfoForCells())); if (length <= fastSizeLimit) { // Attempt GC allocation. void* temp; size_t size = sizeOf(length, elementSize); temp = vm.primitiveGigacageAuxiliarySpace().allocate(vm, size, nullptr, AllocationFailureMode::ReturnNull); if (!temp) return; m_structure = structure; m_vector = VectorType(temp, m_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; } CheckedSize size = length; size *= elementSize; if (size.hasOverflowed() || size > MAX_ARRAY_BUFFER_SIZE) return; m_vector = VectorType(Gigacage::tryMalloc(Gigacage::Primitive, size.value()), m_length); if (!m_vector) return; if (mode == ZeroFill) memset(vector(), 0, size); vm.heap.reportExtraMemoryAllocated(size.value()); m_structure = structure; m_mode = OversizeTypedArray; } JSArrayBufferView::ConstructionContext::ConstructionContext(VM& vm, Structure* structure, RefPtr&& arrayBuffer, size_t byteOffset, std::optional<size_t> length) : m_structure(structure) , m_length(length.value_or(0)) , m_byteOffset(byteOffset) , m_mode(WastefulTypedArray) { if (!arrayBuffer->isResizableOrGrowableShared()) m_mode = WastefulTypedArray; else { if (arrayBuffer->isGrowableShared()) m_mode = length ? GrowableSharedWastefulTypedArray : GrowableSharedAutoLengthWastefulTypedArray; else m_mode = length ? ResizableNonSharedWastefulTypedArray : ResizableNonSharedAutoLengthWastefulTypedArray; } #if ASSERT_ENABLED if (!length) ASSERT(arrayBuffer->isResizableOrGrowableShared()); if (JSC::isResizableOrGrowableShared(m_mode)) ASSERT(isResizableOrGrowableSharedTypedArrayIncludingDataView(structure->classInfoForCells())); else ASSERT(!isResizableOrGrowableSharedTypedArrayIncludingDataView(structure->classInfoForCells())); #endif ASSERT(arrayBuffer->data() == removeArrayPtrTag(arrayBuffer->data())); m_vector = VectorType(static_cast<uint8_t*>(arrayBuffer->data()) + byteOffset, m_length); IndexingHeader indexingHeader; indexingHeader.setArrayBuffer(arrayBuffer.get()); m_butterfly = Butterfly::create(vm, nullptr, 0, 0, true, indexingHeader, 0); } JSArrayBufferView::ConstructionContext::ConstructionContext(Structure* structure, RefPtr&& arrayBuffer, size_t byteOffset, std::optional<size_t> length, DataViewTag) : m_structure(structure) , m_length(length.value_or(0)) , m_byteOffset(byteOffset) , m_mode(DataViewMode) , m_butterfly(nullptr) { if (!arrayBuffer->isResizableOrGrowableShared()) m_mode = DataViewMode; else { if (arrayBuffer->isGrowableShared()) m_mode = length ? GrowableSharedDataViewMode : GrowableSharedAutoLengthDataViewMode; else m_mode = length ? ResizableNonSharedDataViewMode : ResizableNonSharedAutoLengthDataViewMode; } #if ASSERT_ENABLED if (!length) ASSERT(arrayBuffer->isResizableOrGrowableShared()); if (JSC::isResizableOrGrowableShared(m_mode)) ASSERT(isResizableOrGrowableSharedTypedArrayIncludingDataView(structure->classInfoForCells())); else ASSERT(!isResizableOrGrowableSharedTypedArrayIncludingDataView(structure->classInfoForCells())); #endif ASSERT(arrayBuffer->data() == removeArrayPtrTag(arrayBuffer->data())); m_vector = VectorType(static_cast<uint8_t*>(arrayBuffer->data()) + byteOffset, m_length); } JSArrayBufferView::JSArrayBufferView(VM& vm, ConstructionContext& context) : Base(vm, context.structure(), nullptr) , m_length(context.length()) , m_byteOffset(context.byteOffset()) , 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(this)); switch (m_mode) { case FastTypedArray: return; case OversizeTypedArray: vm.heap.addFinalizer(this, finalize); return; case WastefulTypedArray: case ResizableNonSharedWastefulTypedArray: case ResizableNonSharedAutoLengthWastefulTypedArray: case GrowableSharedWastefulTypedArray: case GrowableSharedAutoLengthWastefulTypedArray: vm.heap.addReference(this, butterfly()->indexingHeader()->arrayBuffer()); return; case DataViewMode: case ResizableNonSharedDataViewMode: case ResizableNonSharedAutoLengthDataViewMode: case GrowableSharedDataViewMode: case GrowableSharedAutoLengthDataViewMode: ASSERT(!butterfly()); vm.heap.addReference(this, jsCast(this)->possiblySharedBuffer()); return; } RELEASE_ASSERT_NOT_REACHED(); } template<typename Visitor> void JSArrayBufferView::visitChildrenImpl(JSCell* cell, Visitor& visitor) { JSArrayBufferView* thisObject = jsCast(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); } } DEFINE_VISIT_CHILDREN(JSArrayBufferView); ArrayBuffer* JSArrayBufferView::unsharedBuffer() { ArrayBuffer* result = possiblySharedBuffer(); RELEASE_ASSERT(!result || !result->isShared()); return result; } void JSArrayBufferView::finalize(JSCell* cell) { JSArrayBufferView* thisObject = static_cast(cell); // This JSArrayBufferView could be an OversizeTypedArray that was converted // to a WastefulTypedArray via slowDownAndWasteMemory(). Hence, it is possible // to get to this finalizer and found the mode to be WastefulTypedArray. ASSERT(thisObject->m_mode == OversizeTypedArray || thisObject->hasArrayBuffer()); if (thisObject->m_mode == OversizeTypedArray) Gigacage::free(Gigacage::Primitive, thisObject->vector()); } JSArrayBuffer* JSArrayBufferView::unsharedJSBuffer(JSGlobalObject* globalObject) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (ArrayBuffer* buffer = unsharedBuffer()) return vm.m_typedArrayController->toJS(globalObject, this->globalObject(), buffer); scope.throwException(globalObject, createOutOfMemoryError(globalObject)); return nullptr; } JSArrayBuffer* JSArrayBufferView::possiblySharedJSBuffer(JSGlobalObject* globalObject) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (ArrayBuffer* buffer = possiblySharedBuffer()) return vm.m_typedArrayController->toJS(globalObject, this->globalObject(), buffer); scope.throwException(globalObject, createOutOfMemoryError(globalObject)); return nullptr; } void JSArrayBufferView::detach() { Locker locker { cellLock() }; RELEASE_ASSERT(hasArrayBuffer()); RELEASE_ASSERT(!isShared()); m_length = 0; m_byteOffset = 0; m_vector.clear(); } ArrayBuffer* JSArrayBufferView::slowDownAndWasteMemory() { ASSERT(!hasArrayBuffer()); // 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(vm); RELEASE_ASSERT(!hasIndexingHeader()); Structure* structure = this->structure(); RefPtr buffer; size_t byteLength = this->byteLength(); switch (m_mode) { case FastTypedArray: { buffer = ArrayBuffer::tryCreate(vector(), byteLength); if (!buffer) return nullptr; 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; } RELEASE_ASSERT(buffer); // Don’t create bufferfly until we know we have an ArrayBuffer. setButterfly(vm, Butterfly::createOrGrowArrayRight( butterfly(), vm, this, structure, structure->outOfLineCapacity(), false, 0, 0)); { Locker locker { cellLock() }; butterfly()->indexingHeader()->setArrayBuffer(buffer.get()); m_vector.setWithoutBarrier(buffer->data(), m_length); WTF::storeStoreFence(); m_mode = WastefulTypedArray; // There is no possibility that FastTypedArray or OversizeTypedArray becomes resizable ones since resizable ones do not start with FastTypedArray or OversizeTypedArray. } 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 JSArrayBufferView::possiblySharedImpl() { ArrayBuffer* buffer = possiblySharedBuffer(); if (!buffer) return nullptr; size_t byteOffset = this->byteOffsetRaw(); size_t length = this->lengthRaw(); switch (type()) { #define FACTORY(type) case type ## ArrayType: return type ## Array::wrappedAs(*buffer, byteOffset, isAutoLength() ? std::nullopt : std::optional { length }); FOR_EACH_TYPED_ARRAY_TYPE_EXCLUDING_DATA_VIEW(FACTORY) #undef FACTORY case DataViewType: return DataView::wrappedAs(*buffer, byteOffset, isAutoLength() ? std::nullopt : std::optional { length }); default: RELEASE_ASSERT_NOT_REACHED(); return nullptr; } } bool JSArrayBufferView::isIteratorProtocolFastAndNonObservable() { // Excluding DataView. if (!isTypedArrayType(type())) return false; JSGlobalObject* globalObject = this->globalObject(); TypedArrayType typedArrayType = JSC::typedArrayType(type()); if (!globalObject->isTypedArrayPrototypeIteratorProtocolFastAndNonObservable(typedArrayType)) return false; VM& vm = globalObject->vm(); Structure* structure = this->structure(); // This is the fast case. Many TypedArrays will be an original typed array structure. if (globalObject->isOriginalTypedArrayStructure(structure, true) || globalObject->isOriginalTypedArrayStructure(structure, false)) return true; if (getPrototypeDirect() != globalObject->typedArrayPrototype(typedArrayType)) return false; if (getDirectOffset(vm, vm.propertyNames->iteratorSymbol) != invalidOffset) return false; return true; } } // 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 ResizableNonSharedWastefulTypedArray: out.print(ResizableNonSharedWastefulTypedArray); return; case ResizableNonSharedAutoLengthWastefulTypedArray: out.print(ResizableNonSharedAutoLengthWastefulTypedArray); return; case GrowableSharedWastefulTypedArray: out.print(GrowableSharedWastefulTypedArray); return; case GrowableSharedAutoLengthWastefulTypedArray: out.print(GrowableSharedAutoLengthWastefulTypedArray); return; case DataViewMode: out.print(DataViewMode); return; case ResizableNonSharedDataViewMode: out.print(ResizableNonSharedDataViewMode); return; case ResizableNonSharedAutoLengthDataViewMode: out.print(ResizableNonSharedAutoLengthDataViewMode); return; case GrowableSharedDataViewMode: out.print(GrowableSharedDataViewMode); return; case GrowableSharedAutoLengthDataViewMode: out.print(GrowableSharedAutoLengthDataViewMode); return; } RELEASE_ASSERT_NOT_REACHED(); } } // namespace WTF

Read More