Skip to content

[BUG] NewGlobalRef return not NULL-checked in PyJObject_New — deferred crash #644

Description

@Fuyugithub

Bug Description

PyJObject_New calls NewGlobalRef to create global references for both the Java object and its class, but never checks the return value for NULL. Per JNI specification, NewGlobalRef returns NULL when the global reference table is exhausted or native memory allocation fails. The NULL is silently stored in the struct, causing deferred crashes on any subsequent JNI operation that uses the wrapper.

Location

src/main/c/Objects/pyjobject.c, lines ~51-63

Code

PyObject* PyJObject_New(JNIEnv *env, PyTypeObject* type, jobject obj,
                        jclass class)
{
    PyJObject *pyjob = (PyJObject*) type->tp_alloc(type, 0);

    if (obj) {
        pyjob->object = (*env)->NewGlobalRef(env, obj);
        // No NULL check — if global ref table is full, pyjob->object = NULL
    } else {
        pyjob->object = NULL;
    }
    if (class) {
        pyjob->clazz = (*env)->NewGlobalRef(env, class);
        // No NULL check — same issue
    } else {
        class = (*env)->GetObjectClass(env, obj);
        pyjob->clazz = (*env)->NewGlobalRef(env, class);
        // No NULL check — same issue
        (*env)->DeleteLocalRef(env, class);
        class = NULL;
    }

    return (PyObject*) pyjob;
}

Context

PyJObject_New is the primary factory function for all Java objects exposed to Python. Every jep.java_import, attribute access that returns a Java object, and method call that returns a Java object goes through this function. When NewGlobalRef fails, the NULL stored in pyjob->object or pyjob->clazz will cause a crash on any subsequent operation — method calls, field access, type checks, toString, etc.

The crash happens far from the actual failure (global ref table exhaustion), making it extremely difficult to diagnose.

Impact

In long-running applications or those that create many Java wrappers (e.g., iterating over large Java collections from Python), the JNI global reference table can fill up. When it does, every new wrapper silently stores NULL and any use of that wrapper crashes the JVM.

Suggested Fix

if (obj) {
    pyjob->object = (*env)->NewGlobalRef(env, obj);
    if (pyjob->object == NULL) {
        Py_DECREF(pyjob);
        return NULL;  // JVM has already thrown OutOfMemoryError
    }
}

Same pattern for pyjob->clazz.

Environment

  • Jep: current master branch (commit 247887d)
  • Affected: all Java-to-Python object wrapping under global ref table pressure

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions