Skip to content

fix: free Content object in error paths of loadModelFile/createFromBuffer#4384

Open
srpatcha wants to merge 7 commits into
alibaba:masterfrom
srpatcha:fix/interpreter-memory-leak
Open

fix: free Content object in error paths of loadModelFile/createFromBuffer#4384
srpatcha wants to merge 7 commits into
alibaba:masterfrom
srpatcha:fix/interpreter-memory-leak

Conversation

@srpatcha

Copy link
Copy Markdown

Fix memory leaks in model loading error paths and typo

Problem

Two memory leaks exist in source/core/Interpreter.cpp:

  1. In loadModelFile(): new Content is allocated but never freed if loader->merge() fails.
  2. In createFromBuffer(): new Content is allocated but never freed if buffer.reset() returns nullptr.

Additionally, the error message "Memory not enought!" contains a typo.

Root Cause

Both functions allocate a Content object with new on the heap, then check a subsequent operation for failure. On failure, both return nullptr without calling delete on the allocated object, leaking memory.

Fix

  • Added delete net; before return nullptr; in both error paths.
  • Fixed typo: "Memory not enought!""Memory not enough!"

Testing

  • Load an invalid/corrupt model file that triggers the loader->merge() failure path.
  • Create an interpreter from a buffer with insufficient memory.
  • Both paths should now properly clean up the allocated Content object.
  • Run under Valgrind or AddressSanitizer to verify no leaks.

Impact

Affects MNN users loading models, especially in long-running services or on memory-constrained devices where repeated failed model loads would accumulate leaked memory.

@CLAassistant

CLAassistant commented Apr 17, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@wangzhaode wangzhaode self-assigned this Apr 18, 2026
@srpatcha srpatcha force-pushed the fix/interpreter-memory-leak branch from 75d3f21 to 8c3a651 Compare April 25, 2026 02:16
srpatcha and others added 5 commits May 16, 2026 09:31
…ffer

Both functions allocate Content with new but don't delete it when
subsequent operations fail, leaking memory. Also fixed typo
'Memory not enought' -> 'Memory not enough'.
deepCopyQnnTensorInfo() passed unchecked malloc results to memcpy,
causing null pointer dereference on allocation failure. strdup
allocations were never freed on error paths. Added NULL checks after
all allocations and proper cleanup on error paths.

Signed-off-by: Srikanth Patchava <spatchava@meta.com>
Signed-off-by: Srikanth Patchava <srikanth.patchava@outlook.com>
Add ImageOpTest.cpp with tests for:
- Bilinear resize upscale/downscale with reference comparison
- Nearest neighbor resize with exact pixel matching
- Crop operations including boundary clamp edge cases
- Rotation at 90, 180, 270 degrees
- Colorspace: RGB↔BGR, RGB→GRAY, RGBA→GRAY
- Edge cases: single pixel, large image (256x256), stride mismatch, identity transform
- All tests registered with MNNTestSuiteRegister under cv/image_op/

Signed-off-by: Srikanth Patchava <spatchava@meta.com>
When the source coordinate is exactly at the boundary (e.g. x == xMax),
ceilf(x) could return a value equal to the image dimension, causing
out-of-bounds access in the source buffer. Fix by clamping y1 and x1
to ih-1 and iw-1 respectively, matching the clamp already applied to
the float coordinates.

Signed-off-by: Srikanth Patchava <spatchava@meta.com>
createFromBufferInternal takes ownership of the Content* parameter, but the early-return on line 124-126 (when checkNet fails) was missing 'delete net'. The two later error paths (lines 130 and 138) already free correctly. Same class of leak as the createFromBuffer/loadModelFile fix in the previous commit.

Signed-off-by: Srikanth Patchava <spatchava@meta.com>
@srpatcha srpatcha force-pushed the fix/interpreter-memory-leak branch from 923a77d to 0dd70b2 Compare May 16, 2026 16:31
@wangzhaode

Copy link
Copy Markdown
Collaborator

Hi @srpatcha, friendly reminder — the core fixes (memory leak in Interpreter.cpp, bilinear boundary check, and QNN error handling) all look solid and correct. The only remaining item is consolidating the test file: please move the valuable new test cases (SinglePixelResizeTest, StrideMismatchTest) into the existing test/cv/ImageProcessTest.cpp and remove the separate ImageOpTest.cpp file to keep the test suite consolidated. Once that's done, this is ready to merge. Thanks for the contribution!

@wangzhaode wangzhaode added the awaiting contributor Waiting for contributor to address review comments or rebase label Jun 11, 2026
Comment thread PR_DESCRIPTION.md Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please delete this file.

Comment thread test/cv/ImageOpTest.cpp Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have comprehensive image processing tests in test/cv/ImageProcessTest.cpp covering bilinear/nearest resize, colorspace conversions (RGB↔BGR, RGBA→Gray, BGR→Gray, etc.), YUV blitting, and transform-based resize.

Most of the tests in this new file overlap with existing ones. However, SinglePixelResizeTest and StrideMismatchTest are genuinely new edge-case scenarios that would be valuable additions.

Could you move just the useful new test cases (e.g., single-pixel resize, stride mismatch) into the existing test/cv/ImageProcessTest.cpp instead of creating a separate file? This keeps the test suite consolidated and avoids confusion between ImageProcessTest.cpp and ImageOpTest.cpp.

Move SinglePixelResizeTest and StrideMismatchTest from the separate
ImageOpTest.cpp into the existing test/cv/ImageProcessTest.cpp test
suite, and delete ImageOpTest.cpp.

Test suite names updated from cv/image_op/* to cv/image_process/* to
match the existing convention.

Requested by @wangzhaode in review.
@srpatcha

Copy link
Copy Markdown
Author

Hi @wangzhaode, done! Consolidated the test cases per your request:

  • Moved SinglePixelResizeTest and StrideMismatchTest into test/cv/ImageProcessTest.cpp
  • Deleted test/cv/ImageOpTest.cpp
  • Updated test suite names from cv/image_op/* to cv/image_process/* to match existing convention

Commit: daf87a3. Ready for final review!

@wangzhaode wangzhaode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @srpatcha, thanks for the contribution! The core Interpreter.cpp memory leak fixes look solid and correct.

However, the PR currently fails to compile with -DMNN_BUILD_TEST=ON. The two new test cases introduced in test/cv/ImageProcessTest.cpp use unqualified enum names that are ambiguous in this translation unit:

test/cv/ImageProcessTest.cpp:1299:29: error: reference to 'NEAREST' is ambiguous
test/cv/ImageProcessTest.cpp:1337:29: error: reference to 'BILINEAR' is ambiguous

Both MNN::CV::Filter::BILINEAR and MNN::Express::BILINEAR are in scope, causing the ambiguity. The existing test cases in the same file all use fully qualified names.

Fix: please update the two lines to use the fully qualified enum:

// line 1299
config.filterType = MNN::CV::Filter::NEAREST;

// line 1337
config.filterType = MNN::CV::Filter::BILINEAR;

Build command used:

cmake .. -DMNN_BUILD_LLM=ON -DMNN_LOW_MEMORY=ON -DMNN_BUILD_TEST=ON -DMNN_SUPPORT_TRANSFORMER_FUSE=ON -GNinja && ninja

Please push the fix and we'll re-verify. Thanks!

Use MNN::CV::Filter::NEAREST and MNN::CV::Filter::BILINEAR in the
consolidated test cases to avoid ambiguity between MNN::CV::Filter
and MNN::Express enums when building with -DMNN_BUILD_TEST=ON.

Fixes build error reported by @wangzhaode.
@srpatcha

Copy link
Copy Markdown
Author

Hi @wangzhaode, fixed! Updated both lines to use fully qualified enum names:

  • \config.filterType = MNN::CV::Filter::NEAREST;\ (line 1299)
  • \config.filterType = MNN::CV::Filter::BILINEAR;\ (line 1337)

Commit: a1b0b20. Should now compile cleanly with -DMNN_BUILD_TEST=ON. Thanks for catching this!

@wangzhaode

Copy link
Copy Markdown
Collaborator

Hi @srpatcha, thanks for this PR! The memory leak fixes in Interpreter.cpp, the bilinear clamp in ImageProcessFunction.cpp, and the QNN null-check hardening all look correct and well-motivated.

However, the two newly added tests (stride_mismatch and single_pixel_resize) are currently failing on our local build (macOS arm64, CPU backend). Here is the test output:

running cv/image_process/stride_mismatch.
Error for run, 1318

running cv/image_process/single_pixel_resize.
Error for run, 1358

TEST_CASE_AMOUNT_UNIT: {"blocked":0,"failed":2,"passed":18,"skipped":0}

The issue appears to be an incorrect call to the second overload of ImageProcess::convert:

// Current (incorrect):
process->convert(src.data(), W, H, srcStride, dst.data(), W, H, channels * W, RGB);

// Signature:
// convert(source, iw, ih, stride, dest, ow, oh, outputBpp=0, outputStride=0, type=halide_type_of<float>())
  • outputBpp is receiving channels * W (should be channels or 0 to auto-detect from destFormat)
  • outputStride is receiving the RGB/RGBA enum value (which is unrelated)
  • type defaults to halide_type_of<float>(), but the test reads the output buffer as uint8_t

Could you please:

  1. Fix the convert() call parameters (set outputBpp = 0 or the correct channel count, outputStride = 0 or ow * channels, and pass halide_type_of<uint8_t>() as the type)
  2. Run the tests locally to verify they pass before updating the PR

The rest of the fixes (Interpreter leaks, bilinear clamp, QNN null checks) are solid — just need the test code corrected. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

awaiting contributor Waiting for contributor to address review comments or rebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants