Summary
In ckx(), the jstring returned by Throwable.toString() is never released. Nearby local refs (cname, cls, x) are deleted, but s is not.
|
jclass cls = (*env)->GetObjectClass(env, x); |
|
if (cls) { |
|
jstring cname; |
|
jmethodID mid = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); |
|
if (mid) { |
|
jstring s = (jstring)(*env)->CallObjectMethod(env, x, mid); |
|
if (s) { |
|
const char *c = (*env)->GetStringUTFChars(env, s, 0); |
|
if (c) { |
|
msg = PROTECT(mkString(c)); |
|
(*env)->ReleaseStringUTFChars(env, s, c); |
|
} |
|
} |
|
} |
|
/* beside toString() we also need to call getName() on cls to get the subclass */ |
|
cname = (jstring) (*env)->CallObjectMethod(env, cls, mid_getName); |
|
if (cname) { |
|
const char *c = (*env)->GetStringUTFChars(env, cname, 0); |
|
if (c) { |
|
/* convert full class name to JNI notation */ |
|
char *cn = strdup(c), *d = cn; |
|
while (*d) { if (*d == '.') *d = '/'; d++; } |
|
xclass = PROTECT(mkString(cn)); |
|
free(cn); |
|
(*env)->ReleaseStringUTFChars(env, cname, c); |
|
} |
|
(*env)->DeleteLocalRef(env, cname); |
|
} |
|
if ((*env)->ExceptionOccurred(env)) |
|
(*env)->ExceptionClear(env); |
|
(*env)->DeleteLocalRef(env, cls); |
|
} else (*env)->ExceptionClear(env); |
|
if (!msg) |
|
msg = PROTECT(mkString("Java Exception <no description because toString() failed>")); |
|
} |
|
/* delete the local reference to the exception (jobjRef has a global copy) */ |
|
(*env)->DeleteLocalRef(env, x); |
jstring s = (jstring)(*env)->CallObjectMethod(env, x, mid);
if (s) {
const char *c = (*env)->GetStringUTFChars(env, s, 0);
if (c) {
msg = PROTECT(mkString(c));
(*env)->ReleaseStringUTFChars(env, s, c);
}
}
/* cname / cls / x are DeleteLocalRef'd; s is not */
ReleaseStringUTFChars only releases the UTF-8 buffer. It does not delete the JNI local reference.
Why it sticks around
ckx() runs on the R thread after AttachCurrentThread (the JNIEnv is cached in eenv and is not detached for the session). This is not a Java-to-native JNI entry, so locals are not freed on return to R. throwR() then calls stop(), which typically does not return.
Each Java exception handled by ckx() therefore leaves one extra local ref (s) on that attached thread.
Impact
One leaked local reference per exception. This is a small, persistent leak on a long-lived attached thread. It is unlikely to overflow the local reference table unless many Java exceptions are handled in the same R session.
Suggested fix
After ReleaseStringUTFChars (and on the s != NULL path even if GetStringUTFChars fails):
(*env)->DeleteLocalRef(env, s);
Summary
In
ckx(), thejstringreturned byThrowable.toString()is never released. Nearby local refs (cname,cls,x) are deleted, butsis not.rJava/src/rJava.c
Lines 78 to 114 in 534ff33
ReleaseStringUTFCharsonly releases the UTF-8 buffer. It does not delete the JNI local reference.Why it sticks around
ckx()runs on the R thread afterAttachCurrentThread(theJNIEnvis cached ineenvand is not detached for the session). This is not a Java-to-native JNI entry, so locals are not freed on return to R.throwR()then callsstop(), which typically does not return.Each Java exception handled by
ckx()therefore leaves one extra local ref (s) on that attached thread.Impact
One leaked local reference per exception. This is a small, persistent leak on a long-lived attached thread. It is unlikely to overflow the local reference table unless many Java exceptions are handled in the same R session.
Suggested fix
After
ReleaseStringUTFChars(and on thes != NULLpath even ifGetStringUTFCharsfails):