tensorflow / tensorflow/tflite-support

`CreateFrameBufferFromBytes()` leaks acquired byte-array elements when `FrameBuffer` creation fails

Open Beginner friendly
#1,012 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
441
Forks
146
PR merge metrics
No merged PRs in 30d

Description

I found a JNI array leak in CreateFrameBufferFromBytes(): it acquires the image bytes with GetByteArrayElements() and, if CreateFromRawBuffer() fails, returns the error without ever releasing them — and on that path Java never gets a handle to release them either.

File: tensorflow_lite_support/java/src/native/task/vision/jni_utils.cc

Function: CreateFrameBufferFromBytes

Relevant code:

jbyte* jimage_ptr = env->GetByteArrayElements(jimage_bytes, NULL);
// Free jimage_ptr together with frame_buffer after inference is finished.
jlong jimage_ptr_handle = reinterpret_cast<jlong>(jimage_ptr);
env->SetLongArrayRegion(jbyte_array_handle, 0, 1, &jimage_ptr_handle);

if (jimage_ptr == NULL) {
  ThrowException(env, kIllegalStateException, "...");
  return nullptr;
}

return CreateFromRawBuffer(
    reinterpret_cast<const uint8*>(jimage_ptr),
    FrameBuffer::Dimension{width, height},
    ConvertToFrameBufferFormat(env, jcolor_space_type),
    ConvertToFrameBufferOrientation(env, jorientation));

The acquisition uses a deferred-release scheme: jimage_ptr is stored in
jbyte_array_handle and is meant to be released later by deleteFrameBuffer()
(via ReleaseByteArrayElements()), once ownership reaches Java as a
FrameBufferData.

But CreateFromRawBuffer() returns a StatusOr that can fail (e.g. non-positive
dimensions on the planar paths, unsupported target format). The function returns
that status directly, so no branch releases jimage_ptr. The JNI entry point
(base_vision_task_api_jni.cc) then throws and returns kInvalidPointer, so Java
never receives a FrameBufferData and cannot call deleteFrameBuffer(). The
successful GetByteArrayElements() acquisition is lost — an image-sized buffer or a
pinned Java array, leaked on every failed construction.

Suggested fix: check the result before transferring ownership and release the
elements on failure. Since the code only reads the data, JNI_ABORT avoids a
copy-back:

auto frame_buffer_or = CreateFromRawBuffer(
    reinterpret_cast<const uint8*>(jimage_ptr),
    FrameBuffer::Dimension{width, height},
    ConvertToFrameBufferFormat(env, jcolor_space_type),
    ConvertToFrameBufferOrientation(env, jorientation));

if (!frame_buffer_or.ok()) {
  env->ReleaseByteArrayElements(jimage_bytes, jimage_ptr, JNI_ABORT);
  return frame_buffer_or.status();
}
return frame_buffer_or;

Ideally jimage_ptr should also be written into jbyte_array_handle only after
construction succeeds, so the handle never points at an already-released buffer.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in tensorflow_lite_support/java/src/native/task/vision/jni_utils.cc at CreateFrameBufferFromBytes, then follow the failure handling through base_vision_task_api_jni.cc. Verify the acquired array elements are released when CreateFromRawBuffer fails and that ownership is transferred only for a successful FrameBufferData; confirm the relevant JNI vision tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, java
Domain
mobile-dev
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.