Skip to content

ckx() leaks the toString() jstring local reference #353

Description

@QiuYucheng2003

Summary

In ckx(), the jstring returned by Throwable.toString() is never released. Nearby local refs (cname, cls, x) are deleted, but s is not.

rJava/src/rJava.c

Lines 78 to 114 in 534ff33

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);

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions