1.5 KiB
godot-kotlin-jvm binding leak PoC
Minimal reproduction for a JVM-side binding leak in godot-kotlin-jvm 0.16.2
(present since the MemoryManager rework, still present on master): every
non-RefCounted Object freed at runtime leaves its entry in
MemoryManager.ObjectDB forever. For Objects the binding holds a strong
reference to the wrapper, so the JVM heap grows without bound while native
memory stays flat.
Run
Requires a godot-kotlin-jvm 0.16.2-4.6.3 editor binary and a JDK 17+.
./gradlew build
godot --headless --import .
godot --headless --import . # run twice, first pass may bail early
godot --headless --path . res://main.tscn
Output on 0.16.2-4.6.3 (macOS, headless):
[POC] freed=20000 nativeObjects=1472 jvmObjectDB=20039
[POC] LEAK CONFIRMED: ~18567 zombie bindings in MemoryManager.ObjectDB
All 20000 freed nodes are still in the JVM ObjectDB. How the node is freed
does not matter: free() from Kotlin, queueFree(), or engine-side deletion
all leak.
Root cause
KotlinBindingManager::_instance_binding_free_callback queues the dead object
via MemoryManager::queue_dead_object(obj), which reads
obj->get_instance_id(). Godot calls instance-binding free callbacks at the
very end of Object::~Object, after ObjectDB::remove_instance(this); _instance_id = ObjectID(); (core/object/object.cpp, same order in every 4.x
release since 4.3). The id is always the null ObjectID by then, so the JVM is
told to remove ObjectID(0) instead of the real id and the real entry stays.