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