I have attempted to hack into the Java workload cpp code. Changing addToClassPath from
to
void addToClassPath(const std::string& path) {
log->trace(info, "TryAddToClassPath", { { "Path", path } });
if (!env) {
throw JNIError{};
}
if (classPath.count(path) > 0) {
// already added
return;
}
auto p = env->NewStringUTF(path.c_str());
checkException();
auto fileClass = getClass("java/io/File");
auto file = env->NewObject(fileClass, getMethod(fileClass, "<init>", "(Ljava/lang/String;)V"), p);
checkException();
auto uri = env->CallObjectMethod(file, env->GetMethodID(fileClass, "toURI", "()Ljava/net/URI;"));
checkException();
auto uriClass = getClass("java/net/URI");
auto url = env->CallObjectMethod(uri, getMethod(uriClass, "toURL", "()Ljava/net/URL;"));
checkException();
auto classLoaderClass = getClass("java/lang/ClassLoader");
auto sysLoaderMethod =
env->GetStaticMethodID(classLoaderClass, "getSystemClassLoader", "()Ljava/lang/ClassLoader;");
checkException();
auto classLoader = env->CallStaticObjectMethod(classLoaderClass, sysLoaderMethod);
checkException();
auto urlLoaderClass = getClass("java/net/URLClassLoader");
// previous official impl.
// env->CallVoidMethod(classLoader, getMethod(urlLoaderClass, "addURL", "(Ljava/net/URL;)V"), url);
//START
// URLClassLoader is created with the new URL.
// - Grab the current thread via Thread.currentThread().
// - Set the context class loader of the current thread to the new URLClassLoader using Thread.setContextClassLoader(ClassLoader).
// - This way, the URLClassLoader with the updated classpath is set as the context class loader for the current thread, which should be used for class loading in subsequent JNI calls.
auto urlArray = env->NewObjectArray(1, env->FindClass("java/net/URL"), url);
checkException();
auto urlClassLoaderConstructor = env->GetMethodID(urlLoaderClass, "<init>", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V");
checkException();
auto urlClassLoader = env->NewObject(urlLoaderClass, urlClassLoaderConstructor, urlArray, classLoader);
checkException();
auto threadClass = getClass("java/lang/Thread");
auto currentThreadMethod = env->GetStaticMethodID(threadClass, "currentThread", "()Ljava/lang/Thread;");
checkException();
auto currentThread = env->CallStaticObjectMethod(threadClass, currentThreadMethod);
checkException();
auto setContextClassLoaderMethod = env->GetMethodID(threadClass, "setContextClassLoader", "(Ljava/lang/ClassLoader;)V");
checkException();
env->CallVoidMethod(currentThread, setContextClassLoaderMethod, urlClassLoader);
// END
checkException();
env->DeleteLocalRef(classLoader);
checkException();
}
I think this indicates that the addToClassPath function succeeds, but then the classloader is not actually reused (?) resulting in NoClassDefFoundError down the line.
This seems to support the hypothesis that the Java client testing does not work on Java 9+, but that my attempt is incorrect / incomplete: the classloader needs to be reused in all the subsequent calls.