Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JVM does not work as expected with JNI C++ code containing a class named "Node"

Myself and some teammates have been unable to understand why the following snippet of code will not give the correct output when using JVMs versions 1.6u23 through 1.6u31 (the latest as of this posting). This code snippet represents a simplification of a larger problem:

UPDATE: Modified the example slightly to put focus on the issue that "virtual_function()" does not seem to get called.

UPDATE: Simplified the example even more based on comments to-date.

NodeTester.cpp:

#include <iostream>
#include <jni.h>

class Node {
  public:
    Node () :m_counter(0) {}
    virtual ~Node () {}

    virtual void virtual_function () {
      m_counter += 10;
    }

    void non_virtual_function () {
      m_counter += 1;
    }

    int get_counter () {
      return m_counter;
    }

  private:
    int m_counter;

};

extern "C" {
  JNIEXPORT void JNICALL Java_NodeTester_testNode (JNIEnv *jni_env_rptr, 
                                                   jclass java_class) {
    Node *node_rptr = new Node();
    node_rptr->non_virtual_function();
    node_rptr->virtual_function();

    std::cout << node_rptr->get_counter() << std::endl;

    delete node_rptr;
  }
}

NodeTester.java:

public class NodeTester {
  public static native void testNode ();

  static {
    System.loadLibrary("nodetester");
  }

  public static final void main (String[] args) {
    NodeTester.testNode();
  }
}

expected output:

11

actual output with JVM 1.6u23 through 1.6u31:

1

It seems like the JVM is incorrectly constructing the "Node" object within JNI; although it's possible that this code has something incorrect about its use of JNI. When the class "Node" gets more functionality added to it (e.g. more attributes, additional virtual and non-virtual operations), we can cause a segmentation fault, rather than just incorrect output. We're compiling the cpp code into a RedHat linux 64-bit shared object library using g++, and running the java code with the 64-bit Server VM. Note that on JVMs 1.6u20 through 1.6u22, this produces expected output. I haven't tried any earlier versions.

We've decided to put a bounty on this question! Here's more information on what we already know:

  • JVMs 1.6u22 (and prior) produce expected results
  • Renaming "Node" or putting it in a namespace produces expected results
  • Allocating a "Node" object on the stack instead of the heap in the JNI function produces expected results
  • There are no issues with non-virtual components of the class "Node"

Unfortunately for us, none of these items lead to viable solutions - the "larger problem" I alluded to was that we're dealing with a large, existing code base with a C++ class named "Node", which we need to access via JNI. We also tried several g++ and javac compiler options, and several JVM options, to no avail (although if someone stumbles on one that actually yields expected results, this would be an acceptable solution).

like image 350
Tom Avatar asked Mar 09 '12 00:03

Tom


4 Answers

Ok, this is not a perfect answer, but if we have nothing better, the following may help. As explained a bit in the other comments, the crux of the problem lies in two distinct C++ classes both named Node in the global namespace, one from OpenJDK or SunJDK 1.6u23 and up on RedHat Linux (at least) and another from another library, both of which need to have their symbols shared with other libraries. To get our symbols loaded before the JDK, we may set the LD_PRELOAD environment variable, by calling e.g.:

LD_PRELOAD=libTheNodeTester.so java ...

But this may crash the JDK, if it actually starts using our symbols as if it were the ones from its libraries...

like image 59
Samuel Audet Avatar answered Nov 13 '22 14:11

Samuel Audet


By looking at the HotSpot code there is a node.hpp/node.cpp that declares a node class without a namespace.
Perhaps there is a collision with the pure virtual functions.
I don't have enough VM knowledge to dig any further...

like image 33
Chen Harel Avatar answered Nov 13 '22 14:11

Chen Harel


It seems like the JVM is incorrectly constructing the "Node" object within JNI

Be clear. The JVM doesn't construct the "Node" object at all. The C++ runtime system does that.

I have used tons of C++ within JNI without any problems other than those I caused.

The first thing that comes to mind is that you aren't checking the result of the 'new' operator for null. That won't affect the non-virtual function, it will just see 'this', which you aren't using, as null, but it will prevent despatching of the virtual function, as the indirection via the vtable will seg-fault.

Why it would be null is another question ...

like image 2
user207421 Avatar answered Nov 13 '22 16:11

user207421


For kicks, try flushing stdout and stderr before you exit the native code. I'm thinking maybe the JVM is exiting with data in some output buffer.

like image 1
Java42 Avatar answered Nov 13 '22 15:11

Java42