Fix memory leak in avifImageSplitGrid() on partial initialization failure#3092
Conversation
| gridCells[i] = NULL; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
NiDU-NINJA: Thank you for the patch. I looked into this issue. I found that the callers of avifImageSplitGrid() are responsible for destroying the images in the gridCells array if avifImageSplitGrid() fails. So this patch is not needed.
The comment for avifImageSplitGrid() says:
// Splits an image into a grid of cells, including its gain map, if any.
// The returned cells must be destroyed with avifImageDestroy().
We can clarify the second sentence to note that it also applies when the function fails.
There was a problem hiding this comment.
NiDU-NINJA: I just realized that having the function destroy the images in the gridCells array before returning AVIF_FALSE is overall a win because we can delete the corresponding code in two callers. The new behavior is also more intuitive because the function doesn't return any cells on failure.
Please edit the commit message because there is no memory leak in the current code.
Please apply the following patch:
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index 9a7e4e88..d133e231 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -105,11 +105,6 @@ avifResult WriteAvifGrid(const avifImage* image, int grid_cols, int grid_rows,
std::vector<avifImage*> grid_cells_ptrs(grid_cell_count);
if (!avifImageSplitGrid(image, grid_cols, grid_rows,
grid_cells_ptrs.data())) {
- for (avifImage* img : grid_cells_ptrs) {
- if (img) {
- avifImageDestroy(img);
- }
- }
return AVIF_RESULT_UNKNOWN_ERROR;
}
// Take ownership of the pointers returned by avifImageSplitGrid.
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 702c95d4..d2828a46 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -598,11 +598,6 @@ avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols,
goto cleanup;
}
if (!avifImageSplitGrid(gridSplitImage->gainMap->image, gridCols, gridRows, gainMapGridCells)) {
- for (uint32_t i = 0; i < gridCols * gridRows; ++i) {
- if (gainMapGridCells[i]) {
- avifImageDestroy(gainMapGridCells[i]);
- }
- }
free(gainMapGridCells);
goto cleanup;
}
@@ -655,10 +650,8 @@ avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols,
cleanup:
if (!success) {
for (uint32_t i = 0; i < createdCells; ++i) {
- if (gridCells && gridCells[i]) {
- avifImageDestroy(gridCells[i]);
- gridCells[i] = NULL;
- }
+ avifImageDestroy(gridCells[i]);
+ gridCells[i] = NULL;
}
}
return success;
@@ -821,4 +814,4 @@ avifResult avifApplyTransforms(avifRGBImage * dstView, avifRGBImage * srcImage,
}
}
return AVIF_RESULT_OK;
-}
\ No newline at end of file
+}
| gridCells[i] = NULL; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
NiDU-NINJA: I just realized that having the function destroy the images in the gridCells array before returning AVIF_FALSE is overall a win because we can delete the corresponding code in two callers. The new behavior is also more intuitive because the function doesn't return any cells on failure.
Please edit the commit message because there is no memory leak in the current code.
Please apply the following patch:
diff --git a/apps/avifgainmaputil/imageio.cc b/apps/avifgainmaputil/imageio.cc
index 9a7e4e88..d133e231 100644
--- a/apps/avifgainmaputil/imageio.cc
+++ b/apps/avifgainmaputil/imageio.cc
@@ -105,11 +105,6 @@ avifResult WriteAvifGrid(const avifImage* image, int grid_cols, int grid_rows,
std::vector<avifImage*> grid_cells_ptrs(grid_cell_count);
if (!avifImageSplitGrid(image, grid_cols, grid_rows,
grid_cells_ptrs.data())) {
- for (avifImage* img : grid_cells_ptrs) {
- if (img) {
- avifImageDestroy(img);
- }
- }
return AVIF_RESULT_UNKNOWN_ERROR;
}
// Take ownership of the pointers returned by avifImageSplitGrid.
diff --git a/apps/shared/avifutil.c b/apps/shared/avifutil.c
index 702c95d4..d2828a46 100644
--- a/apps/shared/avifutil.c
+++ b/apps/shared/avifutil.c
@@ -598,11 +598,6 @@ avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols,
goto cleanup;
}
if (!avifImageSplitGrid(gridSplitImage->gainMap->image, gridCols, gridRows, gainMapGridCells)) {
- for (uint32_t i = 0; i < gridCols * gridRows; ++i) {
- if (gainMapGridCells[i]) {
- avifImageDestroy(gainMapGridCells[i]);
- }
- }
free(gainMapGridCells);
goto cleanup;
}
@@ -655,10 +650,8 @@ avifBool avifImageSplitGrid(const avifImage * gridSplitImage, uint32_t gridCols,
cleanup:
if (!success) {
for (uint32_t i = 0; i < createdCells; ++i) {
- if (gridCells && gridCells[i]) {
- avifImageDestroy(gridCells[i]);
- gridCells[i] = NULL;
- }
+ avifImageDestroy(gridCells[i]);
+ gridCells[i] = NULL;
}
}
return success;
@@ -821,4 +814,4 @@ avifResult avifApplyTransforms(avifRGBImage * dstView, avifRGBImage * srcImage,
}
}
return AVIF_RESULT_OK;
-}
\ No newline at end of file
+}
apps/shared/avifutil.c
Outdated
| cleanup: | ||
| if (!success) { | ||
| for (uint32_t i = 0; i < createdCells; ++i) { | ||
| if (gridCells && gridCells[i]) { |
There was a problem hiding this comment.
This check is not needed. (See patch below.)
- This function assumes
gridCellsis not null. This is the API contract. gridCells[i]cannot possibly be null.
|
@wantehchang i have done the changes as you said |
| } | ||
| const avifBool hasGainMap = gridSplitImage->gainMap && gridSplitImage->gainMap->image; | ||
|
|
||
| uint32_t createdCells = 0; |
There was a problem hiding this comment.
I moved the declaration of createdCells right before the nested for loops that increment it.
| goto cleanup; | ||
| } | ||
| gridCells[gridIndex] = cellImage; | ||
| assert(gridIndex == createdCells); |
There was a problem hiding this comment.
I added an assertion here to verify that gridIndex is consecutive. The cleanup code depends on this property.
After pull request AOMediaCodec#3092, avifImageSplitGrid() doesn't return any grid cells on failure. So only need to destroy returned cells on success.
After pull request #3092, avifImageSplitGrid() doesn't return any grid cells on failure. So only need to destroy returned cells on success.
This patch fixes a memory leak in avifImageSplitGrid() that occurs when the function exits early due to an error after allocating one or more grid cells.
The function allocates grid cell images incrementally inside nested loops and performs several operations that may fail (such as avifImageSetViewRect(), avifGainMapCreate(), or metadata copy operations). If any of these steps fail, the function previously returned immediately without destroying already allocated cells.
This patch introduces centralized cleanup logic that ensures all previously created grid cells are destroyed before returning on failure.
The change preserves the existing API and behavior while ensuring correct resource cleanup.