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.
I found a JNI array leak in
CreateFrameBufferFromBytes(): it acquires the image bytes withGetByteArrayElements()and, ifCreateFromRawBuffer()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.ccFunction:
CreateFrameBufferFromBytesRelevant code:
The acquisition uses a deferred-release scheme:
jimage_ptris stored injbyte_array_handleand is meant to be released later bydeleteFrameBuffer()(via
ReleaseByteArrayElements()), once ownership reaches Java as aFrameBufferData.But
CreateFromRawBuffer()returns aStatusOrthat can fail (e.g. non-positivedimensions 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 returnskInvalidPointer, so Javanever receives a
FrameBufferDataand cannot calldeleteFrameBuffer(). Thesuccessful
GetByteArrayElements()acquisition is lost — an image-sized buffer or apinned 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_ABORTavoids acopy-back:
Ideally
jimage_ptrshould also be written intojbyte_array_handleonly afterconstruction succeeds, so the handle never points at an already-released buffer.