/* * Copyright (C) 2009-2023 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 "ArrayBuffer.h" #include "JSArrayBufferView.h" #include "JSCellInlines.h" #include "JSWebAssemblyInstance.h" #include "WaiterListManager.h" #include "WeakInlines.h" #include #include #include #include #include #if ENABLE(WEBASSEMBLY) #include "WasmMemory.h" #endif WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN namespace JSC { namespace ArrayBufferInternal { static constexpr bool verbose = false; } static void zeroFill(void* base, size_t size) { constexpr size_t largeZeroFillThreshold = 1 * MB; if (size >= largeZeroFillThreshold) { size_t pageSizeValue = WTF::pageSize(); uintptr_t begin = reinterpret_cast(base); uintptr_t end = begin + size; uintptr_t pageAlignedBegin = roundUpToMultipleOf(pageSizeValue, begin); uintptr_t pageAlignedEnd = roundDownToMultipleOf(pageSizeValue, end); if (pageAlignedEnd > pageAlignedBegin) { if (begin != pageAlignedBegin) memset(base, 0, pageAlignedBegin - begin); OSAllocator::zeroFill(reinterpret_cast(pageAlignedBegin), pageAlignedEnd - pageAlignedBegin); if (end != pageAlignedEnd) memset(reinterpret_cast(pageAlignedEnd), 0, end - pageAlignedEnd); } else memset(base, 0, size); } else memset(base, 0, size); } Ref> ArrayBuffer::primitiveGigacageDestructor() { static LazyNeverDestroyed>> destructor; static std::once_flag onceKey; std::call_once(onceKey, [&] { destructor.construct(createSharedTask([] (void* p) { Gigacage::free(Gigacage::Primitive, p); })); }); return destructor.get().copyRef(); } template static bool tryAllocate(VM* vm, const Func& allocate) { unsigned numTries = 2; bool success = false; for (unsigned i = 0; i < numTries && !success; ++i) { switch (allocate()) { case BufferMemoryResult::Kind::Success: success = true; break; case BufferMemoryResult::Kind::SuccessAndNotifyMemoryPressure: if (vm) vm->heap.collectAsync(CollectionScope::Full); success = true; break; case BufferMemoryResult::Kind::SyncTryToReclaimMemory: if (i + 1 == numTries) break; if (vm) vm->heap.collectSync(CollectionScope::Full); break; } } return success; } static RefPtr tryAllocateResizableMemory(VM* vm, size_t sizeInBytes, size_t maxByteLength) { // Make sure malloc actually allocates something, but not too much. We use null to mean that the buffer is detached. size_t initialBytes = roundUpToMultipleOf(sizeInBytes); if (!initialBytes) initialBytes = PageCount::pageSize; size_t maximumBytes = roundUpToMultipleOf(maxByteLength); if (!maximumBytes) maximumBytes = PageCount::pageSize; bool done = tryAllocate(vm, [&] () -> BufferMemoryResult::Kind { return BufferMemoryManager::singleton().tryAllocatePhysicalBytes(initialBytes); }); if (!done) return nullptr; char* slowMemory = nullptr; tryAllocate(vm, [&] () -> BufferMemoryResult::Kind { auto result = BufferMemoryManager::singleton().tryAllocateGrowableBoundsCheckingMemory(maximumBytes); slowMemory = std::bit_cast(result.basePtr); return result.kind; }); if (!slowMemory) { BufferMemoryManager::singleton().freePhysicalBytes(initialBytes); return nullptr; } constexpr bool readable = false; constexpr bool writable = false; OSAllocator::protect(slowMemory + initialBytes, maximumBytes - initialBytes, readable, writable); return adoptRef(*new BufferMemoryHandle(slowMemory, initialBytes, maximumBytes, PageCount::fromBytes(initialBytes), PageCount::fromBytes(maximumBytes), MemorySharingMode::Shared, MemoryMode::BoundsChecking)); } void ArrayBufferContents::tryAllocate(size_t numElements, unsigned elementByteSize, InitializationPolicy policy) { CheckedSize sizeInBytes = numElements; sizeInBytes *= elementByteSize; if (sizeInBytes.hasOverflowed() || sizeInBytes.value() > MAX_ARRAY_BUFFER_SIZE) { reset(); return; } size_t allocationSize = sizeInBytes.value(); if (!allocationSize) allocationSize = 1; // Make sure malloc actually allocates something, but not too much. We use null to mean that the buffer is detached. void* data = nullptr; if (policy == InitializationPolicy::ZeroInitialize) data = Gigacage::tryZeroedMalloc(Gigacage::Primitive, allocationSize); else data = Gigacage::tryMalloc(Gigacage::Primitive, allocationSize); m_data = DataType(data); if (!data) { reset(); return; } m_sizeInBytes = sizeInBytes.value(); RELEASE_ASSERT(m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE); m_maxByteLength = m_sizeInBytes; m_hasMaxByteLength = false; m_destructor = ArrayBuffer::primitiveGigacageDestructor(); } void ArrayBufferContents::makeShared() { m_shared = SharedArrayBufferContents::create(mutableSpan(), maxByteLength(), m_memoryHandle, WTF::move(m_destructor), SharedArrayBufferContents::Mode::Default); m_destructor = nullptr; } SharedArrayBufferContents::~SharedArrayBufferContents() { WaiterListManager::singleton().unregister(std::bit_cast(data()), m_sizeInBytes); if (RefPtr destructor = m_destructor) { // FIXME: we shouldn't use getUnsafe here https://bugs.webkit.org/show_bug.cgi?id=197698 destructor->run(m_data.getUnsafe()); } } void ArrayBufferContents::copyTo(ArrayBufferContents& other) { ASSERT(!other.m_data); other.tryAllocate(m_sizeInBytes, sizeof(char), ArrayBufferContents::InitializationPolicy::DontInitialize); if (!other.m_data) return; memcpy(other.data(), data(), m_sizeInBytes); other.m_sizeInBytes = m_sizeInBytes; RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE); ASSERT(other.m_maxByteLength <= MAX_ARRAY_BUFFER_SIZE); } void ArrayBufferContents::shareWith(ArrayBufferContents& other) { ASSERT(!other.m_data); ASSERT(m_shared); other.m_data = m_data; other.m_destructor = nullptr; other.m_shared = m_shared; other.m_memoryHandle = m_memoryHandle; other.m_sizeInBytes = m_sizeInBytes; other.m_maxByteLength = m_maxByteLength; other.m_hasMaxByteLength = m_hasMaxByteLength; RELEASE_ASSERT(other.m_sizeInBytes <= MAX_ARRAY_BUFFER_SIZE); ASSERT(other.m_maxByteLength <= MAX_ARRAY_BUFFER_SIZE); } Ref ArrayBuffer::create(size_t numElements, unsigned elementByteSize) { auto buffer = tryCreate(numElements, elementByteSize); if (!buffer) CRASH(); return buffer.releaseNonNull(); } Ref ArrayBuffer::create(ArrayBuffer& other) { return ArrayBuffer::create(other.span()); } Ref ArrayBuffer::create(std::span span) { auto buffer = tryCreate(span); if (!buffer) CRASH(); return buffer.releaseNonNull(); } Ref ArrayBuffer::create(ArrayBufferContents&& contents) { return adoptRef(*new ArrayBuffer(WTF::move(contents))); } // FIXME: We cannot use this except if the memory comes from the cage. // Current this is only used from: // - JSGenericTypedArrayView<>::slowDownAndWasteMemory. But in that case, the memory should have already come // from the cage. Ref ArrayBuffer::createAdopted(std::span data) { ASSERT(!Gigacage::isEnabled() || (Gigacage::contains(data.data()) && Gigacage::contains(data.data() + data.size() - 1))); return createFromBytes(data, ArrayBuffer::primitiveGigacageDestructor()); } // FIXME: We cannot use this except if the memory comes from the cage. // Currently this is only used from: // - The C API. We could support that by either having the system switch to a mode where typed arrays are no // longer caged, or we could introduce a new set of typed array types that are uncaged and get accessed // differently. // - WebAssembly. Wasm should allocate from the cage. Ref ArrayBuffer::createFromBytes(std::span data, ArrayBufferDestructorFunction&& destructor) { if (data.data() && !Gigacage::isCaged(Gigacage::Primitive, data.data())) Gigacage::disablePrimitiveGigacage(); ArrayBufferContents contents(data, std::nullopt, WTF::move(destructor)); return create(WTF::move(contents)); } Ref ArrayBuffer::createShared(Ref&& shared, bool forceFixedLengthIfWasm) { ArrayBufferContents contents(WTF::move(shared), forceFixedLengthIfWasm); return create(WTF::move(contents)); } RefPtr ArrayBuffer::tryCreate(size_t numElements, unsigned elementByteSize, std::optional maxByteLength) { return tryCreate(numElements, elementByteSize, maxByteLength, ArrayBufferContents::InitializationPolicy::ZeroInitialize); } RefPtr ArrayBuffer::tryCreate(ArrayBuffer& other) { return tryCreate(other.span()); } RefPtr ArrayBuffer::tryCreate(std::span span) { ArrayBufferContents contents; contents.tryAllocate(span.size(), 1, ArrayBufferContents::InitializationPolicy::DontInitialize); if (!contents.m_data) return nullptr; return createInternal(WTF::move(contents), span.data(), span.size()); } Ref ArrayBuffer::createUninitialized(size_t numElements, unsigned elementByteSize) { return create(numElements, elementByteSize, ArrayBufferContents::InitializationPolicy::DontInitialize); } RefPtr ArrayBuffer::tryCreateUninitialized(size_t numElements, unsigned elementByteSize) { return tryCreate(numElements, elementByteSize, std::nullopt, ArrayBufferContents::InitializationPolicy::DontInitialize); } Ref ArrayBuffer::create(size_t numElements, unsigned elementByteSize, ArrayBufferContents::InitializationPolicy policy) { auto buffer = tryCreate(numElements, elementByteSize, std::nullopt, policy); if (!buffer) CRASH(); return buffer.releaseNonNull(); } Ref ArrayBuffer::createInternal(ArrayBufferContents&& contents, const void* source, size_t byteLength) { auto buffer = adoptRef(*new ArrayBuffer(WTF::move(contents))); if (byteLength) { ASSERT(source); memcpy(buffer->data(), source, byteLength); } return buffer; } RefPtr ArrayBuffer::tryCreate(size_t numElements, unsigned elementByteSize, std::optional maxByteLength, ArrayBufferContents::InitializationPolicy policy) { if (!maxByteLength) { ArrayBufferContents contents; contents.tryAllocate(numElements, elementByteSize, policy); if (!contents.m_data) return nullptr; return adoptRef(*new ArrayBuffer(WTF::move(contents))); } CheckedSize sizeInBytes = numElements; sizeInBytes *= elementByteSize; if (sizeInBytes.hasOverflowed() || sizeInBytes.value() > MAX_ARRAY_BUFFER_SIZE) return nullptr; if (sizeInBytes.value() > maxByteLength.value() || maxByteLength.value() > MAX_ARRAY_BUFFER_SIZE) return nullptr; auto handle = tryAllocateResizableMemory(nullptr, sizeInBytes.value(), maxByteLength.value()); if (!handle) return nullptr; void* memory = handle->memory(); ArrayBufferContents contents(memory, sizeInBytes.value(), maxByteLength.value(), handle.releaseNonNull()); return create(WTF::move(contents)); } ArrayBuffer::ArrayBuffer(ArrayBufferContents&& contents) : m_contents(WTF::move(contents)) { } size_t ArrayBuffer::clampValue(double x, size_t left, size_t right) { ASSERT(left <= right); if (x < left) x = left; if (right < x) x = right; return x; } size_t ArrayBuffer::clampIndex(double index) const { size_t currentLength = byteLength(); if (index < 0) index = currentLength + index; return clampValue(index, 0, currentLength); } RefPtr ArrayBuffer::slice(double begin, double end) const { return sliceWithClampedIndex(clampIndex(begin), clampIndex(end)); } RefPtr ArrayBuffer::slice(double begin) const { return sliceWithClampedIndex(clampIndex(begin), byteLength()); } RefPtr ArrayBuffer::sliceWithClampedIndex(size_t begin, size_t end) const { size_t size = begin <= end ? end - begin : 0; auto result = ArrayBuffer::tryCreate(span().subspan(begin, size)); if (result) result->setSharingMode(sharingMode()); return result; } void ArrayBuffer::makeShared() { m_contents.makeShared(); m_locked = true; ASSERT(!isDetached()); } void ArrayBuffer::makeWasmMemory() { m_locked = true; m_isWasmMemory = true; } void ArrayBuffer::setAssociatedWasmMemory(Wasm::Memory* memory) { // The pointer from a buffer to a memory is only required when the buffer is resizable non-shared, // to direct a grow request to the memory (see ArrayBuffer::resize). In other scenarios // the pointer is not necessary and we should not be setting it to anything but a nullptr. ASSERT(isWasmMemory() && (isResizableNonShared() || !memory)); #if ENABLE(WEBASSEMBLY) m_associatedWasmMemory = memory; #else UNUSED_PARAM(memory); #endif } void ArrayBuffer::refreshAfterWasmMemoryGrow(Wasm::Memory* memory) { ASSERT(isWasmMemory()); m_contents.refreshAfterWasmMemoryGrow(memory); } void ArrayBuffer::setSharingMode(ArrayBufferSharingMode newSharingMode) { if (newSharingMode == sharingMode()) return; RELEASE_ASSERT(!isShared()); // Cannot revert sharing. RELEASE_ASSERT(newSharingMode == ArrayBufferSharingMode::Shared); makeShared(); } bool ArrayBuffer::shareWith(ArrayBufferContents& result) { if (!m_contents.m_data || !isShared()) { result.m_data = nullptr; return false; } m_contents.shareWith(result); return true; } bool ArrayBuffer::transferTo(VM& vm, ArrayBufferContents& result) { Ref protect(*this); if (!m_contents.m_data) { result.m_data = nullptr; return false; } if (isShared()) { m_contents.shareWith(result); return true; } if (!isDetachable()) { m_contents.copyTo(result); if (!result.m_data) return false; return true; } result = m_contents.detach(); notifyDetaching(vm); return true; } // We allow detaching wasm memory ArrayBuffers even though they are locked. void ArrayBuffer::detach(VM& vm) { ASSERT(isWasmMemory()); auto unused = m_contents.detach(); notifyDetaching(vm); } void ArrayBuffer::notifyDetaching(VM& vm) { for (size_t i = numberOfIncomingReferences(); i--;) { JSCell* cell = incomingReferenceAt(i); if (JSArrayBufferView* view = jsDynamicCast(cell)) view->detachFromArrayBuffer(); } m_detachingWatchpointSet.fireAll(vm, "Array buffer was detached"); } // Wasm JS API redefines the abstract operation HostGrowSharedArrayBuffer as follows: // https://webassembly.github.io/threads/js-api/index.html#abstract-operation-hostgrowsharedarraybuffer Expected ArrayBuffer::grow(VM& vm, size_t newByteLength) { auto shared = m_contents.m_shared; if (!shared) [[unlikely]] return makeUnexpected(GrowFailReason::GrowSharedUnavailable); const bool requirePageMultiple = isWasmMemory(); auto result = shared->grow(vm, newByteLength, requirePageMultiple); if (result && result.value() > 0) vm.heap.reportExtraMemoryAllocated(static_cast(nullptr), result.value()); return result; } // Wasm JS API redefines the abstract operation HostResizeArrayBuffer as follows: // https://webassembly.github.io/threads/js-api/index.html#abstract-operation-hostresizearraybuffer Expected ArrayBuffer::resize(VM& vm, size_t newByteLength) { auto memoryHandle = m_contents.m_memoryHandle; if (!memoryHandle || m_contents.m_shared) [[unlikely]] return makeUnexpected(GrowFailReason::GrowSharedUnavailable); int64_t deltaByteLength = 0; { Locker { memoryHandle->lock() }; // Keep in mind that newByteLength may not be page-size-aligned. if (m_contents.m_maxByteLength < newByteLength) return makeUnexpected(GrowFailReason::InvalidGrowSize); deltaByteLength = static_cast(newByteLength) - static_cast(m_contents.m_sizeInBytes); #if ENABLE(WEBASSEMBLY) if (Options::useWasmMemoryToBufferAPIs()) { if (isWasmMemory() && (deltaByteLength < 0 || deltaByteLength % PageCount::pageSize)) return makeUnexpected(GrowFailReason::InvalidGrowSize); } #endif if (!deltaByteLength) return 0; auto newPageCount = PageCount::fromBytesWithRoundUp(newByteLength); auto oldPageCount = PageCount::fromBytes(memoryHandle->size()); // MemoryHandle's size is always page-size aligned. if (newPageCount.bytes() > MAX_ARRAY_BUFFER_SIZE) return makeUnexpected(GrowFailReason::WouldExceedMaximum); if (newPageCount != oldPageCount) { ASSERT(memoryHandle->maximum() >= newPageCount); #if ENABLE(WEBASSEMBLY) if (Options::useWasmMemoryToBufferAPIs()) { // If this is currently associated with a Wasm memory, let the memory do the growing. // The memory will call back to our refreshAfterWasmMemoryGrow(). RefPtr memory = m_associatedWasmMemory.get(); if (memory) { std::ignore = memory->grow(vm, PageCount(newPageCount.pageCount() - oldPageCount.pageCount())); return deltaByteLength; } } #endif size_t desiredSize = newPageCount.bytes(); RELEASE_ASSERT(desiredSize <= MAX_ARRAY_BUFFER_SIZE); if (desiredSize > memoryHandle->size()) { size_t bytesToAdd = desiredSize - memoryHandle->size(); ASSERT(bytesToAdd); ASSERT(roundUpToMultipleOf(bytesToAdd) == bytesToAdd); bool allocationSuccess = tryAllocate(&vm, [&] () -> BufferMemoryResult::Kind { return BufferMemoryManager::singleton().tryAllocatePhysicalBytes(bytesToAdd); }); if (!allocationSuccess) return makeUnexpected(GrowFailReason::OutOfMemory); void* memory = memoryHandle->memory(); RELEASE_ASSERT(memory); // Signaling memory must have been pre-allocated virtually. uint8_t* startAddress = static_cast(memory) + memoryHandle->size(); dataLogLnIf(ArrayBufferInternal::verbose, "Marking memory's ", RawPointer(memory), " as read+write in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + bytesToAdd), ")"); constexpr bool readable = true; constexpr bool writable = true; OSAllocator::protect(startAddress, bytesToAdd, readable, writable); } else { size_t bytesToSubtract = memoryHandle->size() - desiredSize; ASSERT(bytesToSubtract); ASSERT(roundUpToMultipleOf(bytesToSubtract) == bytesToSubtract); BufferMemoryManager::singleton().freePhysicalBytes(bytesToSubtract); void* memory = memoryHandle->memory(); RELEASE_ASSERT(memory); // Signaling memory must have been pre-allocated virtually. uint8_t* startAddress = static_cast(memory) + desiredSize; dataLogLnIf(ArrayBufferInternal::verbose, "Marking memory's ", RawPointer(memory), " as none in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + bytesToSubtract), ")"); constexpr bool readable = false; constexpr bool writable = false; OSAllocator::protect(startAddress, bytesToSubtract, readable, writable); } memoryHandle->updateSize(desiredSize); } if (m_contents.m_sizeInBytes < newByteLength) zeroFill(std::bit_cast(data()) + m_contents.m_sizeInBytes, newByteLength - m_contents.m_sizeInBytes); m_contents.m_sizeInBytes = newByteLength; } if (deltaByteLength > 0) vm.heap.reportExtraMemoryAllocated(static_cast(nullptr), deltaByteLength); return deltaByteLength; } RefPtr ArrayBuffer::tryCreateShared(VM& vm, size_t numElements, unsigned elementByteSize, size_t maxByteLength) { CheckedSize sizeInBytes = numElements; sizeInBytes *= elementByteSize; if (sizeInBytes.hasOverflowed() || sizeInBytes.value() > maxByteLength || maxByteLength > MAX_ARRAY_BUFFER_SIZE) return nullptr; auto handle = tryAllocateResizableMemory(&vm, sizeInBytes.value(), maxByteLength); if (!handle) return nullptr; auto* memory = static_cast(handle->memory()); return createShared(SharedArrayBufferContents::create({ memory, sizeInBytes.value() }, maxByteLength, WTF::move(handle), nullptr, SharedArrayBufferContents::Mode::Default)); } ArrayBuffer::~ArrayBuffer() { } Expected SharedArrayBufferContents::grow(VM& vm, size_t newByteLength, bool requirePageMultiple) { if (!m_hasMaxByteLength) return makeUnexpected(GrowFailReason::GrowSharedUnavailable); ASSERT(m_memoryHandle); return grow(Locker { m_memoryHandle->lock() }, vm, newByteLength, requirePageMultiple); } Expected SharedArrayBufferContents::grow(const AbstractLocker& locker, VM& vm, size_t newByteLength, bool requirePageMultiple) { // Keep in mind that newByteLength may not be page-size-aligned. If the buffer is a Wasm memory, that is an error. size_t sizeInBytes = m_sizeInBytes.load(std::memory_order_seq_cst); if (sizeInBytes > newByteLength || m_maxByteLength < newByteLength) return makeUnexpected(GrowFailReason::InvalidGrowSize); int64_t deltaByteLength = newByteLength - sizeInBytes; #if ENABLE(WEBASSEMBLY) if (Options::useWasmMemoryToBufferAPIs()) { if (requirePageMultiple && deltaByteLength % PageCount::pageSize) return makeUnexpected(GrowFailReason::InvalidGrowSize); } #else UNUSED_PARAM(requirePageMultiple); #endif if (!deltaByteLength) return 0; auto newPageCount = PageCount::fromBytesWithRoundUp(newByteLength); auto oldPageCount = PageCount::fromBytes(m_memoryHandle->size()); // MemoryHandle's size is always page-size aligned. if (newPageCount.bytes() > MAX_ARRAY_BUFFER_SIZE) return makeUnexpected(GrowFailReason::WouldExceedMaximum); RefPtr memoryHandle = m_memoryHandle; if (newPageCount != oldPageCount) { ASSERT(memoryHandle->maximum() >= newPageCount); size_t desiredSize = newPageCount.bytes(); RELEASE_ASSERT(desiredSize <= MAX_ARRAY_BUFFER_SIZE); RELEASE_ASSERT(desiredSize > memoryHandle->size()); size_t extraBytes = desiredSize - memoryHandle->size(); RELEASE_ASSERT(extraBytes); bool allocationSuccess = tryAllocate(&vm, [&] () -> BufferMemoryResult::Kind { return BufferMemoryManager::singleton().tryAllocatePhysicalBytes(extraBytes); }); if (!allocationSuccess) return makeUnexpected(GrowFailReason::OutOfMemory); void* memory = memoryHandle->memory(); RELEASE_ASSERT(memory); // Signaling memory must have been pre-allocated virtually. uint8_t* startAddress = static_cast(memory) + memoryHandle->size(); dataLogLnIf(ArrayBufferInternal::verbose, "Marking memory's ", RawPointer(memory), " as read+write in range [", RawPointer(startAddress), ", ", RawPointer(startAddress + extraBytes), ")"); constexpr bool readable = true; constexpr bool writable = true; OSAllocator::protect(startAddress, extraBytes, readable, writable); memoryHandle->updateSize(desiredSize); } zeroFill(std::bit_cast(data()) + sizeInBytes, newByteLength - sizeInBytes); updateSize(newByteLength); UNUSED_PARAM(locker); #if ENABLE(WEBASSEMBLY) // Update cache for instance for (Ref anchor : memoryHandle->anchors(locker)) { Locker locker { anchor->m_lock }; if (JSWebAssemblyInstance* instance = anchor->instance()) instance->updateCachedMemories(); } #endif return deltaByteLength; } ASCIILiteral errorMessageForTransfer(ArrayBuffer* buffer) { ASSERT(buffer->isLocked()); if (buffer->isShared()) return "Cannot transfer a SharedArrayBuffer"_s; if (buffer->isWasmMemory()) return "Cannot transfer a WebAssembly.Memory"_s; return "Cannot transfer an ArrayBuffer whose backing store has been accessed by the JavaScriptCore C API"_s; } std::optional ArrayBufferContents::fromSpan(std::span data) { void* buffer = Gigacage::tryMalloc(Gigacage::Primitive, data.size_bytes()); if (!buffer) return std::nullopt; memcpy(buffer, data.data(), data.size_bytes()); return ArrayBufferContents { buffer, data.size_bytes(), std::nullopt, ArrayBuffer::primitiveGigacageDestructor() }; } void ArrayBufferContents::refreshAfterWasmMemoryGrow(Wasm::Memory* memory) { #if ENABLE(WEBASSEMBLY) ASSERT(isResizableNonShared()); // If the memory is BoundChecking, the memory's handle is replaced with a different one when it grows. m_memoryHandle = memory->handle(); m_sizeInBytes = m_memoryHandle->size(); #else UNUSED_PARAM(memory); #endif } } // namespace JSC WTF_ALLOW_UNSAFE_BUFFER_USAGE_END