Skip to content

Commit 9415d17

Browse files
committed
jni: partially address bblfsh#113 by avoiding refs as map keys
Original impl used a reference as a key in a native map, but in JNI object references are neither constant nor unique, and thus were violating the contract on hashCode for a map key. This should allow to get rid of global reference in interface lookup, 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 e0fca9f commit 9415d17

File tree

1 file changed

+24
-4
lines changed

1 file changed

+24
-4
lines changed

src/main/native/org_bblfsh_client_v2_libuast_Libuast.cc

+24-4
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,29 @@ class Node : public uast::Node<Node *> {
399401
}
400402
};
401403

404+
// Custom comparator for keys in std::map<object>.
405+
// Compares actual objects instead of JNI references.
406+
struct EqualByObj {
407+
bool operator()(jobject a, jobject b) const {
408+
return getJNIEnv()->IsSameObject(a, b);
409+
}
410+
};
411+
412+
// Custom hasing function for keys in std::map<object>.
413+
// Deligates actual hasing to the managed .hashCode() impl.
414+
struct HashByObj {
415+
std::size_t operator()(jobject obj) const noexcept {
416+
auto hash = IntMethod(getJNIEnv(), "hashCode", "()I", CLS_OBJ, &obj);
417+
checkJvmException("failed to call hashCode()");
418+
return hash;
419+
}
420+
};
421+
402422
class Context;
403423

404424
class Interface : public uast::NodeCreator<Node *> {
405425
private:
406-
std::map<jobject, Node *> obj2node;
426+
std::unordered_map<jobject, Node *, HashByObj, EqualByObj> obj2node;
407427

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

416436
node = new Node(this, obj);
417-
obj2node[obj] = node;
437+
obj2node[node->obj] = node;
418438
return node;
419439
}
420440

421441
// create makes a new object with a specified kind.
422442
// Steals the reference.
423443
Node *create(NodeKind kind, jobject obj) {
424444
Node *node = new Node(this, kind, obj);
425-
obj2node[obj] = node;
445+
obj2node[node->obj] = node;
426446
return node;
427447
}
428448

@@ -440,9 +460,9 @@ class Interface : public uast::NodeCreator<Node *> {
440460
}
441461

442462
// toJ returns a JVM object associated with a node.
443-
// Returns a new reference.
444463
jobject toJ(Node *node) {
445464
if (!node) return nullptr;
465+
// TODO(#113) investigate, it looks like a potential memory leak
446466
jobject obj = getJNIEnv()->NewGlobalRef(node->obj);
447467
return obj;
448468
}

0 commit comments

Comments
 (0)