Recipe – Extract exception details in Java (JVM) from a JNI C++ solution

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

Leave a Reply

Your email address will not be published. Required fields are marked *