JDK-6493522 : JNI_RegisterNatives fails to bind a method of an uninitialized class
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2006-11-14
  • Updated: 2014-06-26
  • Resolved: 2013-09-17
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
hs25Fixed
Description
If an unitialized class instance is passed to the JNI_RegisterNatives function for binding, it fails to bind requested methods correctly, although reporting about success.

Testcase is attached.
Some users encountered this issue in JDK 6 release.
The link on a related discussion:
    http://forum.java.sun.com/thread.jspa?messageID=9412706
Issue re-discovered. Martin Buchholz reports:

Hi ClassLoader maintainers,

This is a bug report.

(I think this is a hotspot bug, but it might be a core library
ClassLoader bug or even a URLClassLoader bug)

If you use RegisterNatives with a class loaded using URLClassLoader,
you must call GetMethodID
before RegisterNatives, or you get an UnsatisfiedLinkError as in the
test case below

(you will need to edit the below to fill in the location of your jni
include directory and your libjvm.so)

$ cat Test.java; echo ---------------------; cat jvm.cc; echo
--------------------------; g++ -I$JDK/include -I$JDK/include/linux
-ldl jvm.cc && ./a.out
public class Test {
  public Test() {
    System.out.println("My class loader is " + getClass().getClassLoader());
    testNative();
    System.out.println("back to Java");
  }
  public static native void testNative();
}
---------------------
#include <cstdio>
#include <dlfcn.h>
#include <jni.h>

JavaVM* jvm;
JNIEnv* env;
const char* libjvm =
    "$JDKDIR/libjvm.so";
const char* loader_url = "file://./";
const char* class_name = "Test";

void InitializeJVM() {
  JavaVMOption options[1];
  options[0].optionString = (char*)"-Djava.class.path=.";
  //options[1].optionString = (char*)"-verbose:jni";

  JavaVMInitArgs vm_args;
  vm_args.version = JNI_VERSION_1_2;
  vm_args.options = options;
  vm_args.nOptions = 2;
  vm_args.ignoreUnrecognized = JNI_TRUE;

  void* handle = dlopen(libjvm, RTLD_LAZY);
  if (handle == NULL) perror("dlopen");
  jint JNICALL (*func_create_java_vm)(JavaVM**, void**, void*) =
      reinterpret_cast<jint JNICALL (*)(JavaVM**, void**, void*)>
      (dlsym(handle, "JNI_CreateJavaVM"));
  if (func_create_java_vm == NULL) perror("dlsym");
  jint result = (*func_create_java_vm)(&jvm, (void**)(&env), &vm_args);
}

void TestNative(JNIEnv* env, jclass cls) {
  printf("Successfully called registered native method\n");
}

void RegisterNativeMethod(jclass cls) {
  static const JNINativeMethod jni_method =
      { (char*)"testNative",
        (char*)"()V",
        (void*)&TestNative };
  env->RegisterNatives(cls, &jni_method, 1);
  if (env->ExceptionCheck()) env->ExceptionDescribe();
}

void Test() {
  // URL[] urls = {new URL(url)}
  jclass cls = env->FindClass("java/net/URL");
  jmethodID method = env->GetMethodID(cls, "<init>", "(Ljava/lang/String;)V");
  jstring jurl_str = env->NewStringUTF(loader_url);
  jobject jurl = env->NewObject(cls, method, jurl_str);
  jobjectArray jurls = env->NewObjectArray(1, cls, jurl);

  // URLClassLoader loader = new URLClassLoaer(urls)
  cls = env->FindClass("java/net/URLClassLoader");
  method = env->GetMethodID(cls, "<init>", "([Ljava/net/URL;)V");
  jobject jloader = env->NewObject(cls, method, jurls);

  // Class cls = loader.loadClass(name)
  method = env->GetMethodID(
      cls, "loadClass", "(Ljava/lang/String;Z)Ljava/lang/Class;");
  jstring jname = env->NewStringUTF(class_name);
  cls = (jclass)env->CallObjectMethod(jloader, method, jname, (jboolean) true);

  method = env->GetMethodID(cls, "<init>", "()V");
  if (env->ExceptionCheck()) env->ExceptionDescribe();

  // RegisterNatives must be called after GetMethodID.
  // If the order is reversed, we get UnsatisfiedLinkError,
  // which seems like a bug.
  RegisterNativeMethod(cls);

  env->NewObject(cls, method);
  if (env->ExceptionCheck()) env->ExceptionDescribe();
}

int main(int argc, char** argv) {
  InitializeJVM();
  Test();

  return 0;
}
--------------------------
My class loader is sun.misc.Launcher$AppClassLoader@1f7182c1
Successfully called registered native method
back to Java

Thanks,

Martin

[Edited to fix typo in name]

Comments
I was able to reproduce this bug in JDK 6 and JDK 7, but not in JDK 8 (running on Linux 32 and 64 bit). So, I'm marking this bug as fixed.
17-09-2013

WORK AROUND Ensure the class is initialized first. For example, GetMethodID will trigger initialization and so can be used from native code before calling JNI_RegisterNatives. Of course it might not be correct to trigger class initialization from this native context.
09-06-2010