diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 8ab7da5257ab9..903ca2aa07836 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -155,11 +155,12 @@ cc_test { android: { srcs: [ "tests/BackupData_test.cpp", - "tests/BackupHelpers_test.cpp", + "tests/BackupHelpers_test.cpp", + "tests/CursorWindow_test.cpp", "tests/ObbFile_test.cpp", "tests/PosixUtils_test.cpp", ], - shared_libs: common_test_libs + ["libui"], + shared_libs: common_test_libs + ["libbinder", "liblog", "libui"], }, host: { static_libs: common_test_libs + ["liblog", "libz"], @@ -185,9 +186,28 @@ cc_benchmark { // Actual benchmarks. "tests/AssetManager2_bench.cpp", "tests/AttributeResolution_bench.cpp", + "tests/CursorWindow_bench.cpp", "tests/SparseEntry_bench.cpp", "tests/Theme_bench.cpp", ], shared_libs: common_test_libs, data: ["tests/data/**/*.apk"], } + +cc_library { + name: "libandroidfw_fuzzer_lib", + defaults: ["libandroidfw_defaults"], + host_supported: true, + srcs: [ + "CursorWindow.cpp", + ], + export_include_dirs: ["include"], + target: { + android: { + shared_libs: common_test_libs + ["libbinder", "liblog"], + }, + host: { + static_libs: common_test_libs + ["libbinder", "liblog"], + }, + }, +} diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp index 71c8e1f6121f7..915c0d75a2803 100644 --- a/libs/androidfw/CursorWindow.cpp +++ b/libs/androidfw/CursorWindow.cpp @@ -14,19 +14,14 @@ * limitations under the License. */ -#undef LOG_TAG #define LOG_TAG "CursorWindow" #include -#include -#include -#include #include -#include -#include -#include +#include "android-base/logging.h" +#include "cutils/ashmem.h" namespace android { @@ -36,11 +31,10 @@ namespace android { */ static constexpr const size_t kInlineSize = 16384; -CursorWindow::CursorWindow(const String8& name, int ashmemFd, void* data, size_t size, - size_t inflatedSize, bool readOnly) : - mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), - mInflatedSize(inflatedSize), mReadOnly(readOnly) { - mHeader = static_cast(mData); +static constexpr const size_t kSlotShift = 4; +static constexpr const size_t kSlotSizeBytes = 1 << kSlotShift; + +CursorWindow::CursorWindow() { } CursorWindow::~CursorWindow() { @@ -52,234 +46,243 @@ CursorWindow::~CursorWindow() { } } -status_t CursorWindow::create(const String8& name, size_t inflatedSize, - CursorWindow** outCursorWindow) { - *outCursorWindow = nullptr; +status_t CursorWindow::create(const String8 &name, size_t inflatedSize, CursorWindow **outWindow) { + *outWindow = nullptr; - size_t size = std::min(kInlineSize, inflatedSize); - void* data = calloc(size, 1); - if (!data) return NO_MEMORY; + CursorWindow* window = new CursorWindow(); + if (!window) goto fail; - CursorWindow* window = new CursorWindow(name, -1, data, size, - inflatedSize, false /*readOnly*/); - status_t result = window->clear(); - if (!result) { - LOG_WINDOW("Created new CursorWindow: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", - window->mHeader->freeOffset, - window->mHeader->numRows, - window->mHeader->numColumns, - window->mSize, window->mData); - *outCursorWindow = window; - return OK; - } + window->mName = name; + window->mSize = std::min(kInlineSize, inflatedSize); + window->mInflatedSize = inflatedSize; + window->mData = malloc(window->mSize); + if (!window->mData) goto fail; + window->mReadOnly = false; + + window->clear(); + window->updateSlotsData(); + + LOG(DEBUG) << "Created: " << window->toString(); + *outWindow = window; + return OK; + +fail: + LOG(ERROR) << "Failed create"; +fail_silent: delete window; - return result; + return UNKNOWN_ERROR; } -status_t CursorWindow::inflate() { - // Shortcut when we can't expand any further - if (mSize == mInflatedSize) return INVALID_OPERATION; +status_t CursorWindow::maybeInflate() { + int ashmemFd = 0; + void* newData = nullptr; + + // Bail early when we can't expand any further + if (mReadOnly || mSize == mInflatedSize) { + return INVALID_OPERATION; + } String8 ashmemName("CursorWindow: "); ashmemName.append(mName); - status_t result; - int ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize); + ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize); if (ashmemFd < 0) { - result = -errno; - ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno); - } else { - result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE); - if (result < 0) { - ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno); - } else { - void* data = ::mmap(NULL, mInflatedSize, PROT_READ | PROT_WRITE, - MAP_SHARED, ashmemFd, 0); - if (data == MAP_FAILED) { - result = -errno; - ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); - } else { - result = ashmem_set_prot_region(ashmemFd, PROT_READ); - if (result < 0) { - ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno); - } else { - // Move inline contents into new ashmem region - memcpy(data, mData, mSize); - free(mData); - mAshmemFd = ashmemFd; - mData = data; - mHeader = static_cast(mData); - mSize = mInflatedSize; - LOG_WINDOW("Inflated CursorWindow: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", - mHeader->freeOffset, - mHeader->numRows, - mHeader->numColumns, - mSize, mData); - return OK; - } - } - ::munmap(data, mInflatedSize); - } - ::close(ashmemFd); + PLOG(ERROR) << "Failed ashmem_create_region"; + goto fail_silent; } - return result; + + if (ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE) < 0) { + PLOG(ERROR) << "Failed ashmem_set_prot_region"; + goto fail_silent; + } + + newData = ::mmap(nullptr, mInflatedSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); + if (newData == MAP_FAILED) { + PLOG(ERROR) << "Failed mmap"; + goto fail_silent; + } + + if (ashmem_set_prot_region(ashmemFd, PROT_READ) < 0) { + PLOG(ERROR) << "Failed ashmem_set_prot_region"; + goto fail_silent; + } + + { + // Migrate existing contents into new ashmem region + uint32_t slotsSize = mSize - mSlotsOffset; + uint32_t newSlotsOffset = mInflatedSize - slotsSize; + memcpy(static_cast(newData), + static_cast(mData), mAllocOffset); + memcpy(static_cast(newData) + newSlotsOffset, + static_cast(mData) + mSlotsOffset, slotsSize); + + free(mData); + mAshmemFd = ashmemFd; + mData = newData; + mSize = mInflatedSize; + mSlotsOffset = newSlotsOffset; + + updateSlotsData(); + } + + LOG(DEBUG) << "Inflated: " << this->toString(); + return OK; + +fail: + LOG(ERROR) << "Failed maybeInflate"; +fail_silent: + ::munmap(newData, mInflatedSize); + ::close(ashmemFd); + return UNKNOWN_ERROR; } -status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) { - *outCursorWindow = nullptr; +status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) { + *outWindow = nullptr; - String8 name; - status_t result = parcel->readString8(&name); - if (result) return result; + CursorWindow* window = new CursorWindow(); + if (!window) goto fail; + + if (parcel->readString8(&window->mName)) goto fail; + if (parcel->readUint32(&window->mNumRows)) goto fail; + if (parcel->readUint32(&window->mNumColumns)) goto fail; + if (parcel->readUint32(&window->mSize)) goto fail; + + if ((window->mNumRows * window->mNumColumns * kSlotSizeBytes) > window->mSize) { + LOG(ERROR) << "Unexpected size " << window->mSize << " for " << window->mNumRows + << " rows and " << window->mNumColumns << " columns"; + goto fail_silent; + } bool isAshmem; - result = parcel->readBool(&isAshmem); - if (result) return result; - + if (parcel->readBool(&isAshmem)) goto fail; if (isAshmem) { - return createFromParcelAshmem(parcel, name, outCursorWindow); - } else { - return createFromParcelInline(parcel, name, outCursorWindow); - } -} - -status_t CursorWindow::createFromParcelAshmem(Parcel* parcel, String8& name, - CursorWindow** outCursorWindow) { - status_t result; - int actualSize; - int ashmemFd = parcel->readFileDescriptor(); - if (ashmemFd == int(BAD_TYPE)) { - result = BAD_TYPE; - ALOGE("CursorWindow: readFileDescriptor() failed"); - } else { - ssize_t size = ashmem_get_size_region(ashmemFd); - if (size < 0) { - result = UNKNOWN_ERROR; - ALOGE("CursorWindow: ashmem_get_size_region() failed: errno=%d.", errno); - } else { - int dupAshmemFd = ::fcntl(ashmemFd, F_DUPFD_CLOEXEC, 0); - if (dupAshmemFd < 0) { - result = -errno; - ALOGE("CursorWindow: fcntl() failed: errno=%d.", errno); - } else { - // the size of the ashmem descriptor can be modified between ashmem_get_size_region - // call and mmap, so we'll check again immediately after memory is mapped - void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0); - if (data == MAP_FAILED) { - result = -errno; - ALOGE("CursorWindow: mmap() failed: errno=%d.", errno); - } else if ((actualSize = ashmem_get_size_region(dupAshmemFd)) != size) { - ::munmap(data, size); - result = BAD_VALUE; - ALOGE("CursorWindow: ashmem_get_size_region() returned %d, expected %d" - " errno=%d", - actualSize, (int) size, errno); - } else { - CursorWindow* window = new CursorWindow(name, dupAshmemFd, - data, size, size, true /*readOnly*/); - LOG_WINDOW("Created CursorWindow from ashmem parcel: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", - window->mHeader->freeOffset, - window->mHeader->numRows, - window->mHeader->numColumns, - window->mSize, window->mData); - *outCursorWindow = window; - return OK; - } - ::close(dupAshmemFd); - } + window->mAshmemFd = parcel->readFileDescriptor(); + if (window->mAshmemFd < 0) { + LOG(ERROR) << "Failed readFileDescriptor"; + goto fail_silent; } + + window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0); + if (window->mAshmemFd < 0) { + PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC"; + goto fail_silent; + } + + window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0); + if (window->mData == MAP_FAILED) { + PLOG(ERROR) << "Failed mmap"; + goto fail_silent; + } + } else { + window->mAshmemFd = -1; + + if (window->mSize > kInlineSize) { + LOG(ERROR) << "Unexpected size " << window->mSize << " for inline window"; + goto fail_silent; + } + + window->mData = malloc(window->mSize); + if (!window->mData) goto fail; + + if (parcel->read(window->mData, window->mSize)) goto fail; } - *outCursorWindow = NULL; - return result; -} -status_t CursorWindow::createFromParcelInline(Parcel* parcel, String8& name, - CursorWindow** outCursorWindow) { - uint32_t sentSize; - status_t result = parcel->readUint32(&sentSize); - if (result) return result; - if (sentSize > kInlineSize) return NO_MEMORY; + // We just came from a remote source, so we're read-only + // and we can't inflate ourselves + window->mInflatedSize = window->mSize; + window->mReadOnly = true; - void* data = calloc(sentSize, 1); - if (!data) return NO_MEMORY; + window->updateSlotsData(); - result = parcel->read(data, sentSize); - if (result) return result; - - CursorWindow* window = new CursorWindow(name, -1, data, sentSize, - sentSize, true /*readOnly*/); - LOG_WINDOW("Created CursorWindow from inline parcel: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", - window->mHeader->freeOffset, - window->mHeader->numRows, - window->mHeader->numColumns, - window->mSize, window->mData); - *outCursorWindow = window; + LOG(DEBUG) << "Created from parcel: " << window->toString(); + *outWindow = window; return OK; + +fail: + LOG(ERROR) << "Failed createFromParcel"; +fail_silent: + delete window; + return UNKNOWN_ERROR; } status_t CursorWindow::writeToParcel(Parcel* parcel) { - LOG_WINDOW("Writing CursorWindow: freeOffset=%d, " - "numRows=%d, numColumns=%d, mSize=%zu, mData=%p", - mHeader->freeOffset, - mHeader->numRows, - mHeader->numColumns, - mSize, mData); - - status_t result = parcel->writeString8(mName); - if (result) return result; + LOG(DEBUG) << "Writing to parcel: " << this->toString(); + if (parcel->writeString8(mName)) goto fail; + if (parcel->writeUint32(mNumRows)) goto fail; + if (parcel->writeUint32(mNumColumns)) goto fail; if (mAshmemFd != -1) { - result = parcel->writeBool(true); - if (result) return result; - return writeToParcelAshmem(parcel); + if (parcel->writeUint32(mSize)) goto fail; + if (parcel->writeBool(true)) goto fail; + if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail; } else { - result = parcel->writeBool(false); - if (result) return result; - return writeToParcelInline(parcel); + // Since we know we're going to be read-only on the remote side, + // we can compact ourselves on the wire, with just enough padding + // to ensure our slots stay aligned + size_t slotsSize = mSize - mSlotsOffset; + size_t compactedSize = mAllocOffset + slotsSize; + compactedSize = (compactedSize + 3) & ~3; + if (parcel->writeUint32(compactedSize)) goto fail; + if (parcel->writeBool(false)) goto fail; + void* dest = parcel->writeInplace(compactedSize); + if (!dest) goto fail; + memcpy(static_cast(dest), + static_cast(mData), mAllocOffset); + memcpy(static_cast(dest) + compactedSize - slotsSize, + static_cast(mData) + mSlotsOffset, slotsSize); } -} + return OK; -status_t CursorWindow::writeToParcelAshmem(Parcel* parcel) { - return parcel->writeDupFileDescriptor(mAshmemFd); -} - -status_t CursorWindow::writeToParcelInline(Parcel* parcel) { - status_t result = parcel->writeUint32(mHeader->freeOffset); - if (result) return result; - - return parcel->write(mData, mHeader->freeOffset); +fail: + LOG(ERROR) << "Failed writeToParcel"; +fail_silent: + return UNKNOWN_ERROR; } status_t CursorWindow::clear() { if (mReadOnly) { return INVALID_OPERATION; } - - mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk); - mHeader->firstChunkOffset = sizeof(Header); - mHeader->numRows = 0; - mHeader->numColumns = 0; - - RowSlotChunk* firstChunk = static_cast(offsetToPtr(mHeader->firstChunkOffset)); - firstChunk->nextChunkOffset = 0; + mAllocOffset = 0; + mSlotsOffset = mSize; + mNumRows = 0; + mNumColumns = 0; return OK; } +void CursorWindow::updateSlotsData() { + mSlotsStart = static_cast(mData) + mSize - kSlotSizeBytes; + mSlotsEnd = static_cast(mData) + mSlotsOffset; +} + +void* CursorWindow::offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) { + if (offset > mSize) { + LOG(ERROR) << "Offset " << offset + << " out of bounds, max value " << mSize; + return nullptr; + } + if (offset + bufferSize > mSize) { + LOG(ERROR) << "End offset " << (offset + bufferSize) + << " out of bounds, max value " << mSize; + return nullptr; + } + return static_cast(mData) + offset; +} + +uint32_t CursorWindow::offsetFromPtr(void* ptr) { + return static_cast(ptr) - static_cast(mData); +} + status_t CursorWindow::setNumColumns(uint32_t numColumns) { if (mReadOnly) { return INVALID_OPERATION; } - - uint32_t cur = mHeader->numColumns; - if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) { - ALOGE("Trying to go from %d columns to %d", cur, numColumns); + uint32_t cur = mNumColumns; + if ((cur > 0 || mNumRows > 0) && cur != numColumns) { + LOG(ERROR) << "Trying to go from " << cur << " columns to " << numColumns; return INVALID_OPERATION; } - mHeader->numColumns = numColumns; + mNumColumns = numColumns; return OK; } @@ -287,30 +290,19 @@ status_t CursorWindow::allocRow() { if (mReadOnly) { return INVALID_OPERATION; } - - // Fill in the row slot - RowSlot* rowSlot = allocRowSlot(); - if (rowSlot == NULL) { - return NO_MEMORY; + size_t size = mNumColumns * kSlotSizeBytes; + off_t newOffset = mSlotsOffset - size; + if (newOffset < mAllocOffset) { + maybeInflate(); + newOffset = mSlotsOffset - size; + if (newOffset < mAllocOffset) { + return NO_MEMORY; + } } - uint32_t rowSlotOffset = offsetFromPtr(rowSlot); - - // Allocate the slots for the field directory - size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot); - uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/); - if (!fieldDirOffset) { - mHeader->numRows--; - LOG_WINDOW("The row failed, so back out the new row accounting " - "from allocRowSlot %d", mHeader->numRows); - return NO_MEMORY; - } - FieldSlot* fieldDir = static_cast(offsetToPtr(fieldDirOffset)); - memset(fieldDir, 0, fieldDirSize); - - LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %zu bytes at offset %u\n", - mHeader->numRows - 1, rowSlotOffset, fieldDirSize, fieldDirOffset); - rowSlot = static_cast(offsetToPtr(rowSlotOffset)); - rowSlot->offset = fieldDirOffset; + memset(offsetToPtr(newOffset), 0, size); + mSlotsOffset = newOffset; + updateSlotsData(); + mNumRows++; return OK; } @@ -318,90 +310,48 @@ status_t CursorWindow::freeLastRow() { if (mReadOnly) { return INVALID_OPERATION; } - - if (mHeader->numRows > 0) { - mHeader->numRows--; + size_t size = mNumColumns * kSlotSizeBytes; + off_t newOffset = mSlotsOffset + size; + if (newOffset > mSize) { + return NO_MEMORY; } + mSlotsOffset = newOffset; + updateSlotsData(); + mNumRows--; return OK; } -uint32_t CursorWindow::alloc(size_t size, bool aligned) { - uint32_t padding; - if (aligned) { - // 4 byte alignment - padding = (~mHeader->freeOffset + 1) & 3; - } else { - padding = 0; +status_t CursorWindow::alloc(size_t size, uint32_t* outOffset) { + if (mReadOnly) { + return INVALID_OPERATION; } - - uint32_t offset = mHeader->freeOffset + padding; - uint32_t nextFreeOffset = offset + size; - if (nextFreeOffset > mSize) { - // Try inflating to ashmem before finally giving up - inflate(); - if (nextFreeOffset > mSize) { - ALOGW("Window is full: requested allocation %zu bytes, " - "free space %zu bytes, window size %zu bytes", - size, freeSpace(), mSize); - return 0; + size_t alignedSize = (size + 3) & ~3; + off_t newOffset = mAllocOffset + alignedSize; + if (newOffset > mSlotsOffset) { + maybeInflate(); + newOffset = mAllocOffset + alignedSize; + if (newOffset > mSlotsOffset) { + return NO_MEMORY; } } - - mHeader->freeOffset = nextFreeOffset; - return offset; -} - -CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) { - uint32_t chunkPos = row; - RowSlotChunk* chunk = static_cast( - offsetToPtr(mHeader->firstChunkOffset)); - while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) { - chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); - chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS; - } - return &chunk->slots[chunkPos]; -} - -CursorWindow::RowSlot* CursorWindow::allocRowSlot() { - uint32_t chunkPos = mHeader->numRows; - RowSlotChunk* chunk = static_cast( - offsetToPtr(mHeader->firstChunkOffset)); - while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) { - chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); - chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS; - } - if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) { - if (!chunk->nextChunkOffset) { - uint32_t chunkOffset = offsetFromPtr(chunk); - uint32_t newChunk = alloc(sizeof(RowSlotChunk), true /*aligned*/); - chunk = static_cast(offsetToPtr(chunkOffset)); - chunk->nextChunkOffset = newChunk; - if (!chunk->nextChunkOffset) { - return NULL; - } - } - chunk = static_cast(offsetToPtr(chunk->nextChunkOffset)); - chunk->nextChunkOffset = 0; - chunkPos = 0; - } - mHeader->numRows += 1; - return &chunk->slots[chunkPos]; + *outOffset = mAllocOffset; + mAllocOffset = newOffset; + return OK; } CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) { - if (row >= mHeader->numRows || column >= mHeader->numColumns) { - ALOGE("Failed to read row %d, column %d from a CursorWindow which " - "has %d rows, %d columns.", - row, column, mHeader->numRows, mHeader->numColumns); - return NULL; + // This is carefully tuned to use as few cycles as + // possible, since this is an extremely hot code path; + // see CursorWindow_bench.cpp for more details + void *result = static_cast(mSlotsStart) + - (((row * mNumColumns) + column) << kSlotShift); + if (result < mSlotsEnd || column >= mNumColumns) { + LOG(ERROR) << "Failed to read row " << row << ", column " << column + << " from a window with " << mNumRows << " rows, " << mNumColumns << " columns"; + return nullptr; + } else { + return static_cast(result); } - RowSlot* rowSlot = getRowSlot(row); - if (!rowSlot) { - ALOGE("Failed to find rowSlot for row %d.", row); - return NULL; - } - FieldSlot* fieldDir = static_cast(offsetToPtr(rowSlot->offset)); - return &fieldDir[column]; } status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) { @@ -423,16 +373,15 @@ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column, if (!fieldSlot) { return BAD_VALUE; } - uint32_t fieldSlotOffset = offsetFromPtr(fieldSlot); - uint32_t offset = alloc(size); - if (!offset) { + uint32_t offset; + if (alloc(size, &offset)) { return NO_MEMORY; } memcpy(offsetToPtr(offset), value, size); - fieldSlot = static_cast(offsetToPtr(fieldSlotOffset)); + fieldSlot = getFieldSlot(row, column); fieldSlot->type = type; fieldSlot->data.buffer.offset = offset; fieldSlot->data.buffer.size = size; diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp new file mode 100644 index 0000000000000..2dac47b0dac6e --- /dev/null +++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp @@ -0,0 +1,31 @@ +cc_fuzz { + name: "cursorwindow_fuzzer", + srcs: [ + "cursorwindow_fuzzer.cpp", + ], + host_supported: true, + corpus: ["corpus/*"], + static_libs: ["libgmock"], + target: { + android: { + shared_libs: [ + "libandroidfw_fuzzer_lib", + "libbase", + "libbinder", + "libcutils", + "liblog", + "libutils", + ], + }, + host: { + static_libs: [ + "libandroidfw_fuzzer_lib", + "libbase", + "libbinder", + "libcutils", + "liblog", + "libutils", + ], + }, + }, +} diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/corpus/typical.bin b/libs/androidfw/fuzz/cursorwindow_fuzzer/corpus/typical.bin new file mode 100644 index 0000000000000..c7e22dd26ea77 Binary files /dev/null and b/libs/androidfw/fuzz/cursorwindow_fuzzer/corpus/typical.bin differ diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/cursorwindow_fuzzer.cpp b/libs/androidfw/fuzz/cursorwindow_fuzzer/cursorwindow_fuzzer.cpp new file mode 100644 index 0000000000000..8dce212201999 --- /dev/null +++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/cursorwindow_fuzzer.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include "android-base/logging.h" +#include "androidfw/CursorWindow.h" +#include "binder/Parcel.h" + +#include + +using android::CursorWindow; +using android::Parcel; + +extern "C" int LLVMFuzzerInitialize(int *, char ***) { + setenv("ANDROID_LOG_TAGS", "*:s", 1); + android::base::InitLogging(nullptr, &android::base::StderrLogger); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + Parcel p; + p.setData(data, size); + + CursorWindow* w = nullptr; + if (!CursorWindow::createFromParcel(&p, &w)) { + LOG(WARNING) << "Valid cursor with " << w->getNumRows() << " rows, " + << w->getNumColumns() << " cols"; + + // Try obtaining heap allocations for most items; we trim the + // search space to speed things up + auto rows = std::min(w->getNumRows(), static_cast(128)); + auto cols = std::min(w->getNumColumns(), static_cast(128)); + for (auto row = 0; row < rows; row++) { + for (auto col = 0; col < cols; col++) { + auto field = w->getFieldSlot(row, col); + if (!field) continue; + switch (w->getFieldSlotType(field)) { + case CursorWindow::FIELD_TYPE_STRING: { + size_t size; + w->getFieldSlotValueString(field, &size); + break; + } + case CursorWindow::FIELD_TYPE_BLOB: { + size_t size; + w->getFieldSlotValueBlob(field, &size); + break; + } + } + } + } + + // Finally, try obtaining the furthest valid field + if (rows > 0 && cols > 0) { + w->getFieldSlot(w->getNumRows() - 1, w->getNumColumns() - 1); + } + } + delete w; + + return 0; +} diff --git a/libs/androidfw/include/androidfw/CursorWindow.h b/libs/androidfw/include/androidfw/CursorWindow.h index 0bee60929cc9d..6e55a9a0eb8b9 100644 --- a/libs/androidfw/include/androidfw/CursorWindow.h +++ b/libs/androidfw/include/androidfw/CursorWindow.h @@ -20,38 +20,36 @@ #include #include #include +#include -#include -#include -#include +#include "android-base/stringprintf.h" +#include "binder/Parcel.h" +#include "utils/String8.h" -#if LOG_NDEBUG - -#define IF_LOG_WINDOW() if (false) #define LOG_WINDOW(...) -#else - -#define IF_LOG_WINDOW() IF_ALOG(LOG_DEBUG, "CursorWindow") -#define LOG_WINDOW(...) ALOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__) - -#endif - namespace android { /** - * This class stores a set of rows from a database in a buffer. The begining of the - * window has first chunk of RowSlots, which are offsets to the row directory, followed by - * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case - * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a - * FieldSlot per column, which has the size, offset, and type of the data for that field. - * Note that the data types come from sqlite3.h. + * This class stores a set of rows from a database in a buffer. Internally + * data is structured as a "heap" of string/blob allocations at the bottom + * of the memory region, and a "stack" of FieldSlot allocations at the top + * of the memory region. Here's an example visual representation: + * + * +----------------------------------------------------------------+ + * |heap\0of\0strings\0 222211110000| ... + * +-------------------+--------------------------------+-------+---+ + * ^ ^ ^ ^ ^ ^ + * | | | | | | + * | +- mAllocOffset mSlotsOffset -+ | | | + * +- mData mSlotsStart -+ | | + * mSize -+ | + * mInflatedSize -+ * * Strings are stored in UTF-8. */ class CursorWindow { - CursorWindow(const String8& name, int ashmemFd, void* data, size_t size, - size_t inflatedSize, bool readOnly); + CursorWindow(); public: /* Field types. */ @@ -88,9 +86,9 @@ public: inline String8 name() { return mName; } inline size_t size() { return mSize; } - inline size_t freeSpace() { return mSize - mHeader->freeOffset; } - inline uint32_t getNumRows() { return mHeader->numRows; } - inline uint32_t getNumColumns() { return mHeader->numColumns; } + inline size_t freeSpace() { return mSlotsOffset - mAllocOffset; } + inline uint32_t getNumRows() { return mNumRows; } + inline uint32_t getNumColumns() { return mNumColumns; } status_t clear(); status_t setNumColumns(uint32_t numColumns); @@ -138,75 +136,57 @@ public: return offsetToPtr(fieldSlot->data.buffer.offset, fieldSlot->data.buffer.size); } + inline std::string toString() const { + return android::base::StringPrintf("CursorWindow{name=%s, fd=%d, size=%d, inflatedSize=%d, " + "allocOffset=%d, slotsOffset=%d, numRows=%d, numColumns=%d}", mName.c_str(), + mAshmemFd, mSize, mInflatedSize, mAllocOffset, mSlotsOffset, mNumRows, mNumColumns); + } + private: - static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100; - - struct Header { - // Offset of the lowest unused byte in the window. - uint32_t freeOffset; - - // Offset of the first row slot chunk. - uint32_t firstChunkOffset; - - uint32_t numRows; - uint32_t numColumns; - }; - - struct RowSlot { - uint32_t offset; - }; - - struct RowSlotChunk { - RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS]; - uint32_t nextChunkOffset; - }; - String8 mName; - int mAshmemFd; - void* mData; - size_t mSize; - size_t mInflatedSize; - bool mReadOnly; - Header* mHeader; + int mAshmemFd = -1; + void* mData = nullptr; + /** + * Pointer to the first FieldSlot, used to optimize the extremely + * hot code path of getFieldSlot(). + */ + void* mSlotsStart = nullptr; + void* mSlotsEnd = nullptr; + uint32_t mSize = 0; + /** + * When a window starts as lightweight inline allocation, this value + * holds the "full" size to be created after ashmem inflation. + */ + uint32_t mInflatedSize = 0; + /** + * Offset to the top of the "heap" of string/blob allocations. By + * storing these allocations at the bottom of our memory region we + * avoid having to rewrite offsets when inflating. + */ + uint32_t mAllocOffset = 0; + /** + * Offset to the bottom of the "stack" of FieldSlot allocations. + */ + uint32_t mSlotsOffset = 0; + uint32_t mNumRows = 0; + uint32_t mNumColumns = 0; + bool mReadOnly = false; - inline void* offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) { - if (offset > mSize) { - ALOGE("Offset %" PRIu32 " out of bounds, max value %zu", offset, mSize); - return NULL; - } - if (offset + bufferSize > mSize) { - ALOGE("End offset %" PRIu32 " out of bounds, max value %zu", - offset + bufferSize, mSize); - return NULL; - } - return static_cast(mData) + offset; - } + void updateSlotsData(); - inline uint32_t offsetFromPtr(void* ptr) { - return static_cast(ptr) - static_cast(mData); - } - - static status_t createFromParcelAshmem(Parcel*, String8&, CursorWindow**); - static status_t createFromParcelInline(Parcel*, String8&, CursorWindow**); - - status_t writeToParcelAshmem(Parcel*); - status_t writeToParcelInline(Parcel*); + void* offsetToPtr(uint32_t offset, uint32_t bufferSize); + uint32_t offsetFromPtr(void* ptr); /** * By default windows are lightweight inline allocations; this method * inflates the window into a larger ashmem region. */ - status_t inflate(); + status_t maybeInflate(); /** - * Allocate a portion of the window. Returns the offset - * of the allocation, or 0 if there isn't enough space. - * If aligned is true, the allocation gets 4 byte alignment. + * Allocate a portion of the window. */ - uint32_t alloc(size_t size, bool aligned = false); - - RowSlot* getRowSlot(uint32_t row); - RowSlot* allocRowSlot(); + status_t alloc(size_t size, uint32_t* outOffset); status_t putBlobOrString(uint32_t row, uint32_t column, const void* value, size_t size, int32_t type); diff --git a/libs/androidfw/tests/CursorWindow_bench.cpp b/libs/androidfw/tests/CursorWindow_bench.cpp new file mode 100644 index 0000000000000..f1191c3d72136 --- /dev/null +++ b/libs/androidfw/tests/CursorWindow_bench.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "benchmark/benchmark.h" + +#include "androidfw/CursorWindow.h" + +namespace android { + +static void BM_CursorWindowWrite(benchmark::State& state, size_t rows, size_t cols) { + CursorWindow* w; + CursorWindow::create(String8("test"), 1 << 21, &w); + + while (state.KeepRunning()) { + w->clear(); + w->setNumColumns(cols); + for (int row = 0; row < rows; row++) { + w->allocRow(); + for (int col = 0; col < cols; col++) { + w->putLong(row, col, 0xcafe); + } + } + } +} + +static void BM_CursorWindowWrite4x4(benchmark::State& state) { + BM_CursorWindowWrite(state, 4, 4); +} +BENCHMARK(BM_CursorWindowWrite4x4); + +static void BM_CursorWindowWrite1Kx4(benchmark::State& state) { + BM_CursorWindowWrite(state, 1024, 4); +} +BENCHMARK(BM_CursorWindowWrite1Kx4); + +static void BM_CursorWindowWrite16Kx4(benchmark::State& state) { + BM_CursorWindowWrite(state, 16384, 4); +} +BENCHMARK(BM_CursorWindowWrite16Kx4); + +static void BM_CursorWindowRead(benchmark::State& state, size_t rows, size_t cols) { + CursorWindow* w; + CursorWindow::create(String8("test"), 1 << 21, &w); + w->setNumColumns(cols); + for (int row = 0; row < rows; row++) { + w->allocRow(); + } + + while (state.KeepRunning()) { + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + w->getFieldSlot(row, col); + } + } + } +} + +static void BM_CursorWindowRead4x4(benchmark::State& state) { + BM_CursorWindowRead(state, 4, 4); +} +BENCHMARK(BM_CursorWindowRead4x4); + +static void BM_CursorWindowRead1Kx4(benchmark::State& state) { + BM_CursorWindowRead(state, 1024, 4); +} +BENCHMARK(BM_CursorWindowRead1Kx4); + +static void BM_CursorWindowRead16Kx4(benchmark::State& state) { + BM_CursorWindowRead(state, 16384, 4); +} +BENCHMARK(BM_CursorWindowRead16Kx4); + +} // namespace android diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp new file mode 100644 index 0000000000000..dfcf76e6edf65 --- /dev/null +++ b/libs/androidfw/tests/CursorWindow_test.cpp @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "androidfw/CursorWindow.h" + +#include "TestHelpers.h" + +#define CREATE_WINDOW_1K \ + CursorWindow* w; \ + CursorWindow::create(String8("test"), 1 << 10, &w); + +#define CREATE_WINDOW_1K_3X3 \ + CursorWindow* w; \ + CursorWindow::create(String8("test"), 1 << 10, &w); \ + ASSERT_EQ(w->setNumColumns(3), OK); \ + ASSERT_EQ(w->allocRow(), OK); \ + ASSERT_EQ(w->allocRow(), OK); \ + ASSERT_EQ(w->allocRow(), OK); + +#define CREATE_WINDOW_2M \ + CursorWindow* w; \ + CursorWindow::create(String8("test"), 1 << 21, &w); + +static constexpr const size_t kHalfInlineSize = 8192; +static constexpr const size_t kGiantSize = 1048576; + +namespace android { + +TEST(CursorWindowTest, Empty) { + CREATE_WINDOW_1K; + + ASSERT_EQ(w->getNumRows(), 0); + ASSERT_EQ(w->getNumColumns(), 0); + ASSERT_EQ(w->size(), 1 << 10); + ASSERT_EQ(w->freeSpace(), 1 << 10); +} + +TEST(CursorWindowTest, SetNumColumns) { + CREATE_WINDOW_1K; + + // Once we've locked in columns, we can't adjust + ASSERT_EQ(w->getNumColumns(), 0); + ASSERT_EQ(w->setNumColumns(4), OK); + ASSERT_NE(w->setNumColumns(5), OK); + ASSERT_NE(w->setNumColumns(3), OK); + ASSERT_EQ(w->getNumColumns(), 4); +} + +TEST(CursorWindowTest, SetNumColumnsAfterRow) { + CREATE_WINDOW_1K; + + // Once we've locked in a row, we can't adjust columns + ASSERT_EQ(w->getNumColumns(), 0); + ASSERT_EQ(w->allocRow(), OK); + ASSERT_NE(w->setNumColumns(4), OK); + ASSERT_EQ(w->getNumColumns(), 0); +} + +TEST(CursorWindowTest, AllocRow) { + CREATE_WINDOW_1K; + + ASSERT_EQ(w->setNumColumns(4), OK); + + // Rolling forward means we have less free space + ASSERT_EQ(w->getNumRows(), 0); + auto before = w->freeSpace(); + ASSERT_EQ(w->allocRow(), OK); + ASSERT_LT(w->freeSpace(), before); + ASSERT_EQ(w->getNumRows(), 1); + + // Verify we can unwind + ASSERT_EQ(w->freeLastRow(), OK); + ASSERT_EQ(w->freeSpace(), before); + ASSERT_EQ(w->getNumRows(), 0); + + // Can't unwind when no rows left + ASSERT_NE(w->freeLastRow(), OK); +} + +TEST(CursorWindowTest, AllocRowBounds) { + CREATE_WINDOW_1K; + + // 60 columns is 960 bytes, which means only a single row can fit + ASSERT_EQ(w->setNumColumns(60), OK); + ASSERT_EQ(w->allocRow(), OK); + ASSERT_NE(w->allocRow(), OK); +} + +TEST(CursorWindowTest, StoreNull) { + CREATE_WINDOW_1K_3X3; + + ASSERT_EQ(w->putNull(1, 1), OK); + ASSERT_EQ(w->putNull(0, 0), OK); + + { + auto field = w->getFieldSlot(1, 1); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL); + } + { + auto field = w->getFieldSlot(0, 0); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_NULL); + } +} + +TEST(CursorWindowTest, StoreLong) { + CREATE_WINDOW_1K_3X3; + + ASSERT_EQ(w->putLong(1, 1, 0xf00d), OK); + ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); + + { + auto field = w->getFieldSlot(1, 1); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); + ASSERT_EQ(w->getFieldSlotValueLong(field), 0xf00d); + } + { + auto field = w->getFieldSlot(0, 0); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); + ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); + } +} + +TEST(CursorWindowTest, StoreString) { + CREATE_WINDOW_1K_3X3; + + ASSERT_EQ(w->putString(1, 1, "food", 5), OK); + ASSERT_EQ(w->putString(0, 0, "cafe", 5), OK); + + size_t size; + { + auto field = w->getFieldSlot(1, 1); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING); + auto actual = w->getFieldSlotValueString(field, &size); + ASSERT_EQ(std::string(actual), "food"); + } + { + auto field = w->getFieldSlot(0, 0); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_STRING); + auto actual = w->getFieldSlotValueString(field, &size); + ASSERT_EQ(std::string(actual), "cafe"); + } +} + +TEST(CursorWindowTest, StoreBounds) { + CREATE_WINDOW_1K_3X3; + + // Can't work with values beyond bounds + ASSERT_NE(w->putLong(0, 3, 0xcafe), OK); + ASSERT_NE(w->putLong(3, 0, 0xcafe), OK); + ASSERT_NE(w->putLong(3, 3, 0xcafe), OK); + ASSERT_EQ(w->getFieldSlot(0, 3), nullptr); + ASSERT_EQ(w->getFieldSlot(3, 0), nullptr); + ASSERT_EQ(w->getFieldSlot(3, 3), nullptr); +} + +TEST(CursorWindowTest, Inflate) { + CREATE_WINDOW_2M; + + auto before = w->size(); + ASSERT_EQ(w->setNumColumns(4), OK); + ASSERT_EQ(w->allocRow(), OK); + + // Scratch buffer that will fit before inflation + void* buf = malloc(kHalfInlineSize); + + // Store simple value + ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); + + // Store first object that fits inside + memset(buf, 42, kHalfInlineSize); + ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK); + ASSERT_EQ(w->size(), before); + + // Store second simple value + ASSERT_EQ(w->putLong(0, 2, 0xface), OK); + + // Store second object that requires inflation + memset(buf, 84, kHalfInlineSize); + ASSERT_EQ(w->putBlob(0, 3, buf, kHalfInlineSize), OK); + ASSERT_GT(w->size(), before); + + // Verify data is intact + { + auto field = w->getFieldSlot(0, 0); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); + ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); + } + { + auto field = w->getFieldSlot(0, 1); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); + size_t actualSize; + auto actual = w->getFieldSlotValueBlob(field, &actualSize); + ASSERT_EQ(actualSize, kHalfInlineSize); + memset(buf, 42, kHalfInlineSize); + ASSERT_NE(actual, buf); + ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); + } + { + auto field = w->getFieldSlot(0, 2); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); + ASSERT_EQ(w->getFieldSlotValueLong(field), 0xface); + } + { + auto field = w->getFieldSlot(0, 3); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); + size_t actualSize; + auto actual = w->getFieldSlotValueBlob(field, &actualSize); + ASSERT_EQ(actualSize, kHalfInlineSize); + memset(buf, 84, kHalfInlineSize); + ASSERT_NE(actual, buf); + ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); + } +} + +TEST(CursorWindowTest, ParcelEmpty) { + CREATE_WINDOW_2M; + + Parcel p; + w->writeToParcel(&p); + p.setDataPosition(0); + w = nullptr; + + ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK); + ASSERT_EQ(w->getNumRows(), 0); + ASSERT_EQ(w->getNumColumns(), 0); + ASSERT_EQ(w->size(), 0); + ASSERT_EQ(w->freeSpace(), 0); + + // We can't mutate the window after parceling + ASSERT_NE(w->setNumColumns(4), OK); + ASSERT_NE(w->allocRow(), OK); +} + +TEST(CursorWindowTest, ParcelSmall) { + CREATE_WINDOW_2M; + + auto before = w->size(); + ASSERT_EQ(w->setNumColumns(4), OK); + ASSERT_EQ(w->allocRow(), OK); + + // Scratch buffer that will fit before inflation + void* buf = malloc(kHalfInlineSize); + + // Store simple value + ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); + + // Store first object that fits inside + memset(buf, 42, kHalfInlineSize); + ASSERT_EQ(w->putBlob(0, 1, buf, kHalfInlineSize), OK); + ASSERT_EQ(w->size(), before); + + // Store second object with zero length + ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK); + ASSERT_EQ(w->size(), before); + + // Force through a parcel + Parcel p; + w->writeToParcel(&p); + p.setDataPosition(0); + w = nullptr; + + ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK); + ASSERT_EQ(w->getNumRows(), 1); + ASSERT_EQ(w->getNumColumns(), 4); + + // Verify data is intact + { + auto field = w->getFieldSlot(0, 0); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); + ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); + } + { + auto field = w->getFieldSlot(0, 1); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); + size_t actualSize; + auto actual = w->getFieldSlotValueBlob(field, &actualSize); + ASSERT_EQ(actualSize, kHalfInlineSize); + memset(buf, 42, kHalfInlineSize); + ASSERT_NE(actual, buf); + ASSERT_EQ(memcmp(buf, actual, kHalfInlineSize), 0); + } + { + auto field = w->getFieldSlot(0, 2); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); + size_t actualSize; + auto actual = w->getFieldSlotValueBlob(field, &actualSize); + ASSERT_EQ(actualSize, 0); + ASSERT_NE(actual, nullptr); + } +} + +TEST(CursorWindowTest, ParcelLarge) { + CREATE_WINDOW_2M; + + ASSERT_EQ(w->setNumColumns(4), OK); + ASSERT_EQ(w->allocRow(), OK); + + // Store simple value + ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK); + + // Store object that forces inflation + void* buf = malloc(kGiantSize); + memset(buf, 42, kGiantSize); + ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK); + + // Store second object with zero length + ASSERT_EQ(w->putBlob(0, 2, buf, 0), OK); + + // Force through a parcel + Parcel p; + w->writeToParcel(&p); + p.setDataPosition(0); + w = nullptr; + + ASSERT_EQ(CursorWindow::createFromParcel(&p, &w), OK); + ASSERT_EQ(w->getNumRows(), 1); + ASSERT_EQ(w->getNumColumns(), 4); + + // Verify data is intact + { + auto field = w->getFieldSlot(0, 0); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_INTEGER); + ASSERT_EQ(w->getFieldSlotValueLong(field), 0xcafe); + } + { + auto field = w->getFieldSlot(0, 1); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); + size_t actualSize; + auto actual = w->getFieldSlotValueBlob(field, &actualSize); + ASSERT_EQ(actualSize, kGiantSize); + memset(buf, 42, kGiantSize); + ASSERT_EQ(memcmp(buf, actual, kGiantSize), 0); + } + { + auto field = w->getFieldSlot(0, 2); + ASSERT_EQ(w->getFieldSlotType(field), CursorWindow::FIELD_TYPE_BLOB); + size_t actualSize; + auto actual = w->getFieldSlotValueBlob(field, &actualSize); + ASSERT_EQ(actualSize, 0); + ASSERT_NE(actual, nullptr); + } +} + +} // android