Make avifAlloc(0) deterministic and fix RWData shrink-to-zero handling (#3212)
diff --git a/include/avif/avif.h b/include/avif/avif.h index 204827b..e7e14f9 100644 --- a/include/avif/avif.h +++ b/include/avif/avif.h
@@ -154,7 +154,7 @@ // --------------------------------------------------------------------------- // Memory management -// Returns NULL on memory allocation failure. +// Returns NULL on memory allocation failure or if size is 0. AVIF_API void * avifAlloc(size_t size); AVIF_API void avifFree(void * p);
diff --git a/src/mem.c b/src/mem.c index 5ccf5f3..54d9495 100644 --- a/src/mem.c +++ b/src/mem.c
@@ -3,12 +3,17 @@ #include "avif/avif.h" -#include <assert.h> #include <stdlib.h> void * avifAlloc(size_t size) { - assert(size != 0); // Implementation-defined. See https://en.cppreference.com/w/cpp/memory/c/malloc + // malloc(0) is implementation-defined (see + // https://en.cppreference.com/w/cpp/memory/c/malloc), so collapse the + // zero-size case to a deterministic NULL return. Callers must either treat + // 0 as an allocation failure or guard against it before calling. + if (size == 0) { + return NULL; + } return malloc(size); }
diff --git a/src/rawdata.c b/src/rawdata.c index 6f8ff67..7ffdaf6 100644 --- a/src/rawdata.c +++ b/src/rawdata.c
@@ -8,9 +8,14 @@ avifResult avifRWDataRealloc(avifRWData * raw, size_t newSize) { if (raw->size != newSize) { + if (newSize == 0) { + // avifAlloc(0) returns NULL, so handle the shrink-to-zero case by freeing the buffer. + avifRWDataFree(raw); + return AVIF_RESULT_OK; + } uint8_t * newData = (uint8_t *)avifAlloc(newSize); AVIF_CHECKERR(newData, AVIF_RESULT_OUT_OF_MEMORY); - if (raw->size && newSize) { + if (raw->size) { memcpy(newData, raw->data, AVIF_MIN(raw->size, newSize)); } avifFree(raw->data);
diff --git a/tests/gtest/avifallocationtest.cc b/tests/gtest/avifallocationtest.cc index 8854ba3..9668e18 100644 --- a/tests/gtest/avifallocationtest.cc +++ b/tests/gtest/avifallocationtest.cc
@@ -208,5 +208,27 @@ } #endif +TEST(AvifAllocTest, ZeroReturnsNull) { + // avifAlloc(0) must return NULL deterministically (rather than aborting via + // assert or relying on implementation-defined malloc(0) behavior). Callers + // rely on this contract to treat 0 as an allocation failure. + EXPECT_EQ(avifAlloc(0), nullptr); +} + +TEST(AvifAllocTest, RWDataReallocShrinkToZeroSucceeds) { + // Shrinking an avifRWData to size 0 must release the buffer and return OK, + // not surface a phantom OOM from avifAlloc(0) returning NULL. + avifRWData raw = {}; + ASSERT_EQ(avifRWDataRealloc(&raw, 16), AVIF_RESULT_OK); + ASSERT_NE(raw.data, nullptr); + ASSERT_EQ(raw.size, 16u); + + EXPECT_EQ(avifRWDataRealloc(&raw, 0), AVIF_RESULT_OK); + EXPECT_EQ(raw.data, nullptr); + EXPECT_EQ(raw.size, 0u); + + avifRWDataFree(&raw); +} + } // namespace } // namespace avif