Skip to content

Commit b644348

Browse files
committed
jni: partially address bblfsh#113 by removing memory leaks
Original impl used a reference as a key in native map, but in JNI object references are neither constant nor unique, and thus were violating the contract on hashCode. This allowed to get rid of global reference in 2 places, but there are 2 more (clearly identified) cases where we still rely on side-effects of having a glabal reference and thus leak some memory Signed-off-by: Alexander Bezzubov <[email protected]>
1 parent 2e7a900 commit b644348

File tree

2 files changed

+31
-10
lines changed

2 files changed

+31
-10
lines changed

src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc

+26-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <cassert>
2+
#include <unordered_map>
23

34
#include "jni_utils.h"
45
#include "org_bblfsh_client_v2_Context.h"
@@ -363,6 +364,7 @@ class Node : public uast::Node<Node *> {
363364
JNIEnv *env = getJNIEnv();
364365
jobject val =
365366
ObjectMethod(env, "valueAt", METHOD_JNODE_VALUE_AT, CLS_JNODE, obj, i);
367+
// TODO(#113) investigate, it looks like a potential memory leak
366368
return lookupOrCreate(env->NewGlobalRef(val)); // new ref
367369
}
368370

@@ -399,11 +401,25 @@ class Node : public uast::Node<Node *> {
399401
}
400402
};
401403

404+
struct EqByObj {
405+
bool operator()(jobject a, jobject b) const {
406+
return getJNIEnv()->IsSameObject(a, b);
407+
}
408+
};
409+
410+
struct HashByObj {
411+
std::size_t operator()(jobject obj) const noexcept {
412+
auto hash = IntMethod(getJNIEnv(), "hashCode", "()I", CLS_OBJ, obj);
413+
checkJvmException("failed to call hashCode()");
414+
return hash;
415+
}
416+
};
417+
402418
class Context;
403419

404420
class Interface : public uast::NodeCreator<Node *> {
405421
private:
406-
std::map<jobject, Node *> obj2node;
422+
std::unordered_map<jobject, Node *, HashByObj, EqByObj> obj2node;
407423

408424
// lookupOrCreate either creates a new object or returns existing one.
409425
// In the second case it creates a new reference.
@@ -414,15 +430,15 @@ class Interface : public uast::NodeCreator<Node *> {
414430
if (node) return node;
415431

416432
node = new Node(this, obj);
417-
obj2node[obj] = node;
433+
obj2node[node->obj] = node;
418434
return node;
419435
}
420436

421437
// create makes a new object with a specified kind.
422438
// Steals the reference.
423439
Node *create(NodeKind kind, jobject obj) {
424440
Node *node = new Node(this, kind, obj);
425-
obj2node[obj] = node;
441+
obj2node[node->obj] = node;
426442
return node;
427443
}
428444

@@ -440,9 +456,9 @@ class Interface : public uast::NodeCreator<Node *> {
440456
}
441457

442458
// toJ returns a JVM object associated with a node.
443-
// Returns a new reference.
444459
jobject toJ(Node *node) {
445460
if (!node) return nullptr;
461+
// TODO(#113) investigate, it looks like a potential memory leak
446462
jobject obj = getJNIEnv()->NewGlobalRef(node->obj);
447463
return obj;
448464
}
@@ -633,7 +649,7 @@ Java_org_bblfsh_client_v2_libuast_Libuast_00024UastIter_nativeInit(
633649
}
634650

635651
// global ref will be deleted by Interface destructor on ctx deletion
636-
auto it = ctx->Iterate(env->NewGlobalRef(jnode), (TreeOrder)order);
652+
auto it = ctx->Iterate(jnode, (TreeOrder)order);
637653

638654
// this.iter = it;
639655
setHandle<uast::Iterator<Node *>>(env, self, it, "iter");
@@ -754,8 +770,7 @@ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_Context_filter(
754770

755771
uast::Iterator<Node *> *it = nullptr;
756772
try {
757-
// global ref will be deleted by Interface destructor on ctx deletion
758-
it = ctx->Filter(env->NewGlobalRef(jnode), query);
773+
it = ctx->Filter(jnode, query);
759774
} catch (const std::exception &e) {
760775
ThrowByName(env, CLS_RE, e.what());
761776
return nullptr;
@@ -819,8 +834,10 @@ JNIEXPORT jobject JNICALL Java_org_bblfsh_client_v2_ContextExt_encode(
819834
JNIEXPORT void JNICALL
820835
Java_org_bblfsh_client_v2_ContextExt_dispose(JNIEnv *env, jobject self) {
821836
ContextExt *p = getHandle<ContextExt>(env, self, nativeContext);
822-
setHandle<ContextExt>(env, self, 0, nativeContext);
823-
delete p;
837+
if (!p) {
838+
delete p;
839+
setHandle<ContextExt>(env, self, 0, nativeContext);
840+
}
824841
}
825842

826843
// ==========================================

src/test/scala/org/bblfsh/client/v2/libuast/IteratorManagedTest.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class IteratorManagedTest extends FlatSpec
1919
))
2020

2121
after {
22-
iter.nativeDispose() // FIXME: rename to .dispose()
22+
iter.close()
2323
}
2424

2525
"Managed UAST iterator" should "return non-empty results on JVM objects" in {
@@ -77,6 +77,8 @@ class IteratorManagedTest extends FlatSpec
7777
val poActual = Seq("root", "son1", "son1_1", "son1_2", "son2", "son2_1", "son2_2")
7878
nodes should have size (poActual.size)
7979
nodes shouldEqual poActual
80+
81+
preIter.close()
8082
}
8183

8284
"Managed UAST iterator" should "return nodes in PostOrder" in {
@@ -86,6 +88,8 @@ class IteratorManagedTest extends FlatSpec
8688
val poActual = Seq("son1_1", "son1_2", "son1", "son2_1", "son2_2", "son2", "root")
8789
nodes should have size (poActual.size)
8890
nodes shouldEqual poActual
91+
92+
postIter.close()
8993
}
9094

9195
// TODO(#108) more tests coverage for other iteration orders, refactor to a table-driven test

0 commit comments

Comments
 (0)