Hi Community,
Today’s recipe is about a nifty way to extract useful and detailed information from exceptions thrown in JVM . As part of this personal project I’m currently working on, I needed to interact with some Jar files that are handled by JVM through JNI and C++. One of the features required was to access as much information on errors and exceptions that occur outside my native implementation, hence I came up with two approaches to gather this information. The first approach (listed below) only returns the value of getMessage() in Java (Throwable class), in many scenarios this might not be enough.
// JNIEnv** env
auto ex = env->ExceptionOccurred();
env->ExceptionClear(); // clears the exception; exception seems to remain valid
auto clazz = env->GetObjectClass(ex);
auto getMessage = env->GetMethodID(clazz, "getMessage","()Ljava/lang/String;");
auto message = (jstring)env->CallObjectMethod(ex, getMessage);
const char *str = env->GetStringUTFChars(message, NULL);
// Release JNI resources
env->ReleaseStringUTFChars(message, str);
env->DeleteLocalRef(message);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(ex);
So I came up with a second approach with combines a Java method that’s called from my C++ code which passes the exception to an instance of JVM (both code snippets are listed below).
std::string ProjectX::Core::Interop::GetJniExpectionDetails(const jthrowable& e, JNIEnv** env) {
jobject res;
jclass clazz;
jmethodID method;
std::string retval;
if (env != nullptr) {
clazz = (*env)->FindClass("com/projectx/Inspector");
method = (*env)->GetStaticMethodID(clazz, "extractExceptionDetails", "(Ljava/lang/Object;)Ljava/lang/String;");
res = (*env)->CallStaticObjectMethod(clazz, method, e);
const char* val = (*env)->GetStringUTFChars((jstring)res, nullptr);
retval = std::string(val);
// Release JNI resources
(*env)->ReleaseStringUTFChars((jstring) res, val);
(*env)->DeleteLocalRef(res);
(*env)->DeleteLocalRef(clazz);
}
return retval;
}
In Java similar to C# everything is a reference, therefore the jthrowable pointer (native) can be translated into an Object in Java (managed) and then downcasted to Exception
public static String extractExceptionDetails(Object ex) {
String retval = "";
try {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
Exception jniEx = new Exception((Throwable) ex);
jniEx.printStackTrace(pw);
String stackTrace = sw.toString();
retval = String.format("Error: %s\nStackTrace: %s", jniEx.getMessage(), stackTrace);
pw.close();
sw.close();
} catch(Exception e) {
retval = "Unable to cast from JNI pointer - " + e.getCause().toString();
}
return retval;
}
Then string returned to C++ from Java contains all the information required, as it’s expected to occur with managed code.
Error: java.lang.NoClassDefFoundError: com/sun/deploy/cache/CacheUpgradeHelper
StackTrace: java.lang.Exception: java.lang.NoClassDefFoundError: com/sun/deploy/cache/CacheUpgradeHelper
at com.simplicity.Inspector.extractExceptionDetails(Inspector.java:66)
Caused by: java.lang.NoClassDefFoundError: com/sun/deploy/cache/CacheUpgradeHelper
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$100(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.net.FactoryURLClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at com.simplicity.Inspector.getMethodsInClasses(Inspector.java:48)
Caused by: java.lang.ClassNotFoundException: com.sun.deploy.cache.CacheUpgradeHelper
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 15 more