Repository navigation
Description
Describe the issue
Both provider options bindings acquire the key and value UTF-8 buffers from the correct arrays, but the release loop fetches value from jKeyArr instead of jValueArr, so values[i] is released against the wrong Java string object.
Files:
java/src/main/native/ai_onnxruntime_providers_OrtCUDAProviderOptions.cjava/src/main/native/ai_onnxruntime_providers_OrtTensorRTProviderOptions.c
Function: applyToNative (both files)
The acquire loop is correct β key comes from jKeyArr, value comes from jValueArr (CUDA, lines 49-54):
// Copy out strings into UTF-8.
for (jsize i = 0; i < keyLength; i++) {
jobject key = (*jniEnv)->GetObjectArrayElement(jniEnv, jKeyArr, i);
keys[i] = (*jniEnv)->GetStringUTFChars(jniEnv, key, NULL);
jobject value = (*jniEnv)->GetObjectArrayElement(jniEnv, jValueArr, i);
values[i] = (*jniEnv)->GetStringUTFChars(jniEnv, value, NULL);
}The release loop, nine lines below, reads both operands from jKeyArr (CUDA, lines 58-63):
// Release allocated strings.
for (jsize i = 0; i < keyLength; i++) {
jobject key = (*jniEnv)->GetObjectArrayElement(jniEnv, jKeyArr, i);
(*jniEnv)->ReleaseStringUTFChars(jniEnv,key,keys[i]);
jobject value = (*jniEnv)->GetObjectArrayElement(jniEnv, jKeyArr, i); // <-- should be jValueArr
(*jniEnv)->ReleaseStringUTFChars(jniEnv,value,values[i]);
}So values[i], whose chars were obtained from the value string, is released while passing the key string as the owning object. The two loops sit in the same function and disagree with each other, which is why this looks like a copy-paste slip in the release loop rather than an intentional choice.
The TensorRT binding is character-for-character the same: acquire at lines 48-53 uses jValueArr, release at lines 57-62 uses jKeyArr twice.
The JNI specification requires ReleaseStringUTFChars() to be called with the same jstring that GetStringUTFChars() was called on; the pair identifies which string's buffer is being released. On HotSpot, GetStringUTFChars() always returns a freshly allocated copy and the matching release just frees that pointer, ignoring the jstring, so on that JVM the mismatch is latent rather than immediately harmful. It is still worth correcting: -Xcheck:jni reports the mismatched pair, and on any JVM where GetStringUTFChars() can return a pinned pointer into the object (isCopy == JNI_FALSE), releasing against the wrong object unpins the key string a second time and leaves the value string pinned for the lifetime of the object.
The path is ordinary API usage, not an edge case: any caller that configures OrtCUDAProviderOptions or OrtTensorRTProviderOptions with at least one option reaches it, since StringConfigProviderOptions.applyToNative() passes the accumulated key and value arrays straight down.
Expected behavior: each UTF-8 buffer is released against the jstring it was acquired from β keys[i] against the element of jKeyArr, values[i] against the element of jValueArr.
Actual behavior: values[i] is released against the element of jKeyArr. Correctness currently depends on HotSpot's copying implementation of GetStringUTFChars().
Suggested fix β one identifier in each of the two release loops:
jobject value = (*jniEnv)->GetObjectArrayElement(jniEnv, jValueArr, i);
(*jniEnv)->ReleaseStringUTFChars(jniEnv, value, values[i]);A more robust version keeps the jstring references from the acquire loop and releases against those, which removes the possibility of the two loops drifting apart again. That restructuring also addresses the local references these loops leave behind, which I have filed separately.
To reproduce
This is a source-level JNI contract violation, confirmed by reading the code at the commit below. On HotSpot it does not produce incorrect behavior today, which is why functional tests do not catch it.
Static confirmation:
- Open
java/src/main/native/ai_onnxruntime_providers_OrtCUDAProviderOptions.c, functionapplyToNative. - In the acquire loop (lines 49-54), confirm
valueis read fromjValueArr. - In the release loop (lines 58-63), confirm
valueis read fromjKeyArrwhilevalues[i]is what gets released. - Repeat for
ai_onnxruntime_providers_OrtTensorRTProviderOptions.c, lines 48-53 and 57-62.
Runtime observation (optional):
- Build the Java API with CUDA or TensorRT enabled.
- Construct
OrtCUDAProviderOptions(orOrtTensorRTProviderOptions), calladd(key, value)with at least one pair whose key and value are different strings, and apply it to aSessionOptions. - Run under
java -Xcheck:jniand observe the warning aboutReleaseStringUTFCharsbeing called with a string that does not match the acquisition.
Urgency
No response
Platform
Linux
OS Version
Ubuntu 22.04.5 LTS
ONNX Runtime Installation
Built from Source
ONNX Runtime Version or Commit ID
ONNX Runtime API
Python
Architecture
X64
Execution Provider
Default CPU
Execution Provider Library Version
No response
Activity
Saad-Mallebhari commentedon Aug 7, 2026
Hi sx-KAN (@K-ANOY)
Looking at
java/src/main/native/ai_onnxruntime_providers_OrtCUDAProviderOptions.c(lines 58β63) andjava/src/main/native/ai_onnxruntime_providers_OrtTensorRTProviderOptions.c(lines 57β62), the release loop inapplyToNative()fetchesvaluefromjKeyArrinstead ofjValueArrprior to callingReleaseStringUTFChars().Because the JNI specification requires
ReleaseStringUTFChars()to receive the exact samejstringpassed toGetStringUTFChars(), releasingvalues[i]against the key element violates the JNI ownership contract. This triggers-Xcheck:jnimismatch warnings and can leak pinned handles on JVM implementations whereGetStringUTFChars()returns pinned buffers without copying (isCopy == JNI_FALSE).Fixing the array lookup in both files ensures each UTF-8 buffer is released against its acquiring array element:
Would you like me to open a Pull Request with this fix?
Java: release provider option values with the correct strings (#31828)