#7831·libgdx

Local reference accumulation in `btMultiSphereShape` `Vector3[]` conversion

Author: K-ANOYCreated Jul 16, 2026Updated Jul 16, 2026

Describe the bug

I found a possible JNI local reference leak in the btMultiSphereShape Vector3[] conversion helper. The helper calls GetObjectArrayElement() for each Java Vector3, but the returned local references are not deleted.

File: extensions/gdx-bullet/jni/swig/collision/btMultiSphereShape.i

Generated file: extensions/gdx-bullet/jni/swig-src/collision/collision_wrap.cpp

Function: Vector3ArrayToBtVector3Array

Relevant code:

cpp
btVector3* Vector3ArrayToBtVector3Array(JNIEnv * jenv, jobjectArray source) {
    static jfieldID xField = NULL, yField = NULL, zField = NULL;
    jint len = jenv->GetArrayLength(source);
    if (len <= 0)
        return NULL;

    btVector3* result = new btVector3[len];

    if (xField == NULL) {
        jobject vec = jenv->GetObjectArrayElement(source, 0);
        jclass sc = jenv->GetObjectClass(vec);
        xField = jenv->GetFieldID(sc, "x", "F");
        yField = jenv->GetFieldID(sc, "y", "F");
        zField = jenv->GetFieldID(sc, "z", "F");
        jenv->DeleteLocalRef(sc);
    }

    for (int i = 0; i < len; i++) {
        jobject vec = jenv->GetObjectArrayElement(source, i);
        result[i].setValue(jenv->GetFloatField(vec, xField), jenv->GetFloatField(vec, yField), jenv->GetFloatField(vec, zField));
    }
    return result;
}

GetObjectArrayElement() returns a JNI local reference. The loop creates one local reference per input element and keeps all of them live until the native method returns. With a large enough Vector3[], this can overflow the JNI local reference table during one call.

There is also a smaller one-time leak in the field-cache initialization path: the source[0] object local reference is used to get the class, but only the class reference sc is deleted.

This helper is used by the SWIG typemap for btVector3*:

cpp
%typemap(in, fragment="gdxBulletHelpersVector3Array", noblock=1) btVector3* {
    static jfieldID xField = NULL, yField = NULL, zField = NULL;
    $1 = Vector3ArrayToBtVector3Array(jenv, $input);
    gdxAutoDeleteBtVector3Array auto_delete($1);
}

The Java-facing constructor reaches this path:

java
public btMultiSphereShape (Vector3[] positions, float[] radi, int numSpheres) {
    this(CollisionJNI.new_btMultiSphereShape(positions, radi, numSpheres), true);
}

Suggested fix: delete each vec local reference after its fields are copied, and also delete the vec local reference used while initializing xField, yField, and zField. The fix should be made in btMultiSphereShape.i, then the generated SWIG wrapper should be regenerated.

For example:

cpp
jobject vec = jenv->GetObjectArrayElement(source, i);
result[i].setValue(jenv->GetFloatField(vec, xField), jenv->GetFloatField(vec, yField), jenv->GetFloatField(vec, zField));
jenv->DeleteLocalRef(vec);

Reproduction

No response

version

No response

Stack trace

bash

Affected platforms

  • Android
  • iOS
  • HTML/GWT
  • Windows
  • Linux
  • macOS