| /* |
| * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2009-2010. 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 COMPUTER, 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 COMPUTER, 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 "platform/SharedBuffer.h" |
| |
| #include "platform/PurgeableBuffer.h" |
| #include "wtf/PassOwnPtr.h" |
| #include "wtf/unicode/Unicode.h" |
| #include "wtf/unicode/UTF8.h" |
| |
| #undef SHARED_BUFFER_STATS |
| |
| #ifdef SHARED_BUFFER_STATS |
| #include "wtf/DataLog.h" |
| #include "wtf/MainThread.h" |
| #endif |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| static const unsigned segmentSize = 0x1000; |
| static const unsigned segmentPositionMask = 0x0FFF; |
| |
| static inline unsigned segmentIndex(unsigned position) |
| { |
| return position / segmentSize; |
| } |
| |
| static inline unsigned offsetInSegment(unsigned position) |
| { |
| return position & segmentPositionMask; |
| } |
| |
| static inline char* allocateSegment() |
| { |
| return static_cast<char*>(fastMalloc(segmentSize)); |
| } |
| |
| static inline void freeSegment(char* p) |
| { |
| fastFree(p); |
| } |
| |
| #ifdef SHARED_BUFFER_STATS |
| |
| static Mutex& statsMutex() |
| { |
| DEFINE_STATIC_LOCAL(Mutex, mutex, ()); |
| return mutex; |
| } |
| |
| static HashSet<SharedBuffer*>& liveBuffers() |
| { |
| DEFINE_STATIC_LOCAL(HashSet<SharedBuffer*>, buffers, ()); |
| return buffers; |
| } |
| |
| static bool sizeComparator(SharedBuffer* a, SharedBuffer* b) |
| { |
| return a->size() > b->size(); |
| } |
| |
| static CString snippetForBuffer(SharedBuffer* sharedBuffer) |
| { |
| const unsigned kMaxSnippetLength = 64; |
| char* snippet = 0; |
| unsigned snippetLength = std::min(sharedBuffer->size(), kMaxSnippetLength); |
| CString result = CString::newUninitialized(snippetLength, snippet); |
| |
| const char* segment; |
| unsigned offset = 0; |
| while (unsigned segmentLength = sharedBuffer->getSomeData(segment, offset)) { |
| unsigned length = std::min(segmentLength, snippetLength - offset); |
| memcpy(snippet + offset, segment, length); |
| offset += segmentLength; |
| if (offset >= snippetLength) |
| break; |
| } |
| |
| for (unsigned i = 0; i < snippetLength; ++i) { |
| if (!isASCIIPrintable(snippet[i])) |
| snippet[i] = '?'; |
| } |
| |
| return result; |
| } |
| |
| static void printStats(void*) |
| { |
| MutexLocker locker(statsMutex()); |
| Vector<SharedBuffer*> buffers; |
| for (HashSet<SharedBuffer*>::const_iterator iter = liveBuffers().begin(); iter != liveBuffers().end(); ++iter) |
| buffers.append(*iter); |
| std::sort(buffers.begin(), buffers.end(), sizeComparator); |
| |
| dataLogF("---- Shared Buffer Stats ----\n"); |
| for (size_t i = 0; i < buffers.size() && i < 64; ++i) { |
| CString snippet = snippetForBuffer(buffers[i]); |
| dataLogF("Buffer size=%8u %s\n", buffers[i]->size(), snippet.data()); |
| } |
| } |
| |
| static void didCreateSharedBuffer(SharedBuffer* buffer) |
| { |
| MutexLocker locker(statsMutex()); |
| liveBuffers().add(buffer); |
| |
| callOnMainThread(printStats, 0); |
| } |
| |
| static void willDestroySharedBuffer(SharedBuffer* buffer) |
| { |
| MutexLocker locker(statsMutex()); |
| liveBuffers().remove(buffer); |
| } |
| |
| #endif |
| |
| SharedBuffer::SharedBuffer() |
| : m_size(0) |
| { |
| #ifdef SHARED_BUFFER_STATS |
| didCreateSharedBuffer(this); |
| #endif |
| } |
| |
| SharedBuffer::SharedBuffer(size_t size) |
| : m_size(size) |
| , m_buffer(size) |
| { |
| #ifdef SHARED_BUFFER_STATS |
| didCreateSharedBuffer(this); |
| #endif |
| } |
| |
| SharedBuffer::SharedBuffer(const char* data, int size) |
| : m_size(0) |
| { |
| // FIXME: Use unsigned consistently, and check for invalid casts when calling into SharedBuffer from other code. |
| if (size < 0) |
| CRASH(); |
| |
| append(data, size); |
| |
| #ifdef SHARED_BUFFER_STATS |
| didCreateSharedBuffer(this); |
| #endif |
| } |
| |
| SharedBuffer::SharedBuffer(const unsigned char* data, int size) |
| : m_size(0) |
| { |
| // FIXME: Use unsigned consistently, and check for invalid casts when calling into SharedBuffer from other code. |
| if (size < 0) |
| CRASH(); |
| |
| append(reinterpret_cast<const char*>(data), size); |
| |
| #ifdef SHARED_BUFFER_STATS |
| didCreateSharedBuffer(this); |
| #endif |
| } |
| |
| SharedBuffer::~SharedBuffer() |
| { |
| clear(); |
| |
| #ifdef SHARED_BUFFER_STATS |
| willDestroySharedBuffer(this); |
| #endif |
| } |
| |
| PassRefPtr<SharedBuffer> SharedBuffer::adoptVector(Vector<char>& vector) |
| { |
| RefPtr<SharedBuffer> buffer = create(); |
| buffer->m_buffer.swap(vector); |
| buffer->m_size = buffer->m_buffer.size(); |
| return buffer.release(); |
| } |
| |
| PassRefPtr<SharedBuffer> SharedBuffer::adoptPurgeableBuffer(PassOwnPtr<PurgeableBuffer> purgeableBuffer) |
| { |
| ASSERT(!purgeableBuffer->isPurgeable()); |
| RefPtr<SharedBuffer> buffer = create(); |
| buffer->m_purgeableBuffer = purgeableBuffer; |
| return buffer.release(); |
| } |
| |
| unsigned SharedBuffer::size() const |
| { |
| if (m_purgeableBuffer) |
| return m_purgeableBuffer->size(); |
| |
| return m_size; |
| } |
| |
| void SharedBuffer::createPurgeableBuffer() const |
| { |
| if (m_purgeableBuffer) |
| return; |
| |
| m_purgeableBuffer = PurgeableBuffer::create(buffer().data(), m_size); |
| } |
| |
| const char* SharedBuffer::data() const |
| { |
| if (m_purgeableBuffer) |
| return m_purgeableBuffer->data(); |
| |
| return this->buffer().data(); |
| } |
| |
| void SharedBuffer::moveTo(Vector<char>& result) |
| { |
| ASSERT(result.isEmpty()); |
| if (m_purgeableBuffer) { |
| result.reserveCapacity(m_purgeableBuffer->size()); |
| result.append(m_purgeableBuffer->data(), m_purgeableBuffer->size()); |
| clear(); |
| return; |
| } |
| |
| unsigned bufferSize = m_buffer.size(); |
| if (m_size == bufferSize) { |
| m_buffer.swap(result); |
| clear(); |
| return; |
| } |
| |
| result.reserveCapacity(m_size); |
| |
| const char* segment = 0; |
| unsigned position = 0; |
| while (unsigned segmentSize = getSomeData(segment, position)) { |
| result.append(segment, segmentSize); |
| position += segmentSize; |
| } |
| ASSERT(result.size() == m_size); |
| clear(); |
| return; |
| } |
| |
| void SharedBuffer::append(SharedBuffer* data) |
| { |
| const char* segment; |
| size_t position = 0; |
| while (size_t length = data->getSomeData(segment, position)) { |
| append(segment, length); |
| position += length; |
| } |
| } |
| |
| void SharedBuffer::append(const char* data, unsigned length) |
| { |
| ASSERT(!m_purgeableBuffer); |
| if (!length) |
| return; |
| |
| unsigned positionInSegment = offsetInSegment(m_size - m_buffer.size()); |
| m_size += length; |
| |
| if (m_size <= segmentSize) { |
| // No need to use segments for small resource data |
| if (m_buffer.isEmpty()) |
| m_buffer.reserveInitialCapacity(length); |
| m_buffer.append(data, length); |
| return; |
| } |
| |
| char* segment; |
| if (!positionInSegment) { |
| segment = allocateSegment(); |
| m_segments.append(segment); |
| } else |
| segment = m_segments.last() + positionInSegment; |
| |
| unsigned segmentFreeSpace = segmentSize - positionInSegment; |
| unsigned bytesToCopy = min(length, segmentFreeSpace); |
| |
| for (;;) { |
| memcpy(segment, data, bytesToCopy); |
| if (static_cast<unsigned>(length) == bytesToCopy) |
| break; |
| |
| length -= bytesToCopy; |
| data += bytesToCopy; |
| segment = allocateSegment(); |
| m_segments.append(segment); |
| bytesToCopy = min(length, segmentSize); |
| } |
| } |
| |
| void SharedBuffer::append(const Vector<char>& data) |
| { |
| append(data.data(), data.size()); |
| } |
| |
| void SharedBuffer::clear() |
| { |
| for (unsigned i = 0; i < m_segments.size(); ++i) |
| freeSegment(m_segments[i]); |
| |
| m_segments.clear(); |
| m_size = 0; |
| |
| m_buffer.clear(); |
| m_purgeableBuffer.clear(); |
| } |
| |
| PassRefPtr<SharedBuffer> SharedBuffer::copy() const |
| { |
| RefPtr<SharedBuffer> clone(adoptRef(new SharedBuffer)); |
| if (m_purgeableBuffer) { |
| clone->append(data(), size()); |
| return clone.release(); |
| } |
| |
| clone->m_size = m_size; |
| clone->m_buffer.reserveCapacity(m_size); |
| clone->m_buffer.append(m_buffer.data(), m_buffer.size()); |
| if (!m_segments.isEmpty()) { |
| const char* segment = 0; |
| unsigned position = m_buffer.size(); |
| while (unsigned segmentSize = getSomeData(segment, position)) { |
| clone->m_buffer.append(segment, segmentSize); |
| position += segmentSize; |
| } |
| ASSERT(position == clone->size()); |
| } |
| return clone.release(); |
| } |
| |
| PassOwnPtr<PurgeableBuffer> SharedBuffer::releasePurgeableBuffer() |
| { |
| ASSERT(hasOneRef()); |
| return m_purgeableBuffer.release(); |
| } |
| |
| const Vector<char>& SharedBuffer::buffer() const |
| { |
| unsigned bufferSize = m_buffer.size(); |
| if (m_size > bufferSize) { |
| m_buffer.resize(m_size); |
| char* destination = m_buffer.data() + bufferSize; |
| unsigned bytesLeft = m_size - bufferSize; |
| for (unsigned i = 0; i < m_segments.size(); ++i) { |
| unsigned bytesToCopy = min(bytesLeft, segmentSize); |
| memcpy(destination, m_segments[i], bytesToCopy); |
| destination += bytesToCopy; |
| bytesLeft -= bytesToCopy; |
| freeSegment(m_segments[i]); |
| } |
| m_segments.clear(); |
| } |
| return m_buffer; |
| } |
| |
| unsigned SharedBuffer::getSomeData(const char*& someData, unsigned position) const |
| { |
| unsigned totalSize = size(); |
| if (position >= totalSize) { |
| someData = 0; |
| return 0; |
| } |
| |
| if (m_purgeableBuffer) { |
| ASSERT_WITH_SECURITY_IMPLICATION(position < size()); |
| someData = data() + position; |
| return totalSize - position; |
| } |
| |
| ASSERT_WITH_SECURITY_IMPLICATION(position < m_size); |
| unsigned consecutiveSize = m_buffer.size(); |
| if (position < consecutiveSize) { |
| someData = m_buffer.data() + position; |
| return consecutiveSize - position; |
| } |
| |
| position -= consecutiveSize; |
| unsigned segments = m_segments.size(); |
| unsigned maxSegmentedSize = segments * segmentSize; |
| unsigned segment = segmentIndex(position); |
| if (segment < segments) { |
| unsigned bytesLeft = totalSize - consecutiveSize; |
| unsigned segmentedSize = min(maxSegmentedSize, bytesLeft); |
| |
| unsigned positionInSegment = offsetInSegment(position); |
| someData = m_segments[segment] + positionInSegment; |
| return segment == segments - 1 ? segmentedSize - position : segmentSize - positionInSegment; |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| PassRefPtr<ArrayBuffer> SharedBuffer::getAsArrayBuffer() const |
| { |
| RefPtr<ArrayBuffer> arrayBuffer = ArrayBuffer::createUninitialized(static_cast<unsigned>(size()), 1); |
| |
| const char* segment = 0; |
| unsigned position = 0; |
| while (unsigned segmentSize = getSomeData(segment, position)) { |
| memcpy(static_cast<char*>(arrayBuffer->data()) + position, segment, segmentSize); |
| position += segmentSize; |
| } |
| |
| if (position != arrayBuffer->byteLength()) { |
| ASSERT_NOT_REACHED(); |
| // Don't return the incomplete ArrayBuffer. |
| return 0; |
| } |
| |
| return arrayBuffer; |
| } |
| |
| } // namespace WebCore |