1
votes

Okay, I'm all out of ideas on this one. Does anyone have any idea how I can hook into Java's exception pipeline in order to catch (and log to a text file) all exceptions that are occurring?

The situation is this: I have a library in a JAR file (A) which in turn depends on a second JAR file (B). A has no main class, as it's simply a class library, which I'm accessing and invoking through the JNI. The problem I'm having is this. When I attempt to initialise the JNI with A loaded, the JNI returns an unspecified error.

I strongly suspect that this error originates from an instantiation of Log4J's logger unit, which is occurring in static code (outside of a method) in B, which I believe is throwing an IOException as a result of permissions problems on the log file. I'm having issues finding out what's going on, however, as the exception (which I suspect is the cause of the problem) is being thrown during the linking stage (when A imports B) and so cannot be caught by a try-catch block. Also, since there is no main method there is no obvious place to put a try-catch block in order to catch this exception.

I would like some way of catching all exceptions that arise in either JAR and dumping them into a text file. I cannot (easily) modify B (I do not have the decompiled JAR). Any ideas?

Here is the C code which invokes the JNI with the specified libraries and options:

_DLL_EXPORT PyObject *initVM(PyObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwnames[] = {
        "classpath", "initialheap", "maxheap", "maxstack",
        "vmargs", NULL
    };
    char *classpath = NULL;
    char *initialheap = NULL, *maxheap = NULL, *maxstack = NULL;
    char *vmargs = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|zzzzz", kwnames,
                                     &classpath,
                                     &initialheap, &maxheap, &maxstack,
                                     &vmargs))
        return NULL;

    if (env->vm)
    {
        PyObject *module_cp = NULL;

        if (initialheap || maxheap || maxstack || vmargs)
        {
            PyErr_SetString(PyExc_ValueError,
                            "JVM is already running, options are ineffective");
            return NULL;
        }

        if (classpath == NULL && self != NULL)
        {
            module_cp = PyObject_GetAttrString(self, "CLASSPATH");
            if (module_cp != NULL)
                classpath = PyString_AsString(module_cp);
        }

        if (classpath && classpath[0])
            env->setClassPath(classpath);

        Py_XDECREF(module_cp);

        return getVMEnv(self);
    }
    else
    {
        JavaVMInitArgs vm_args;
        JavaVMOption vm_options[32];
        JNIEnv *vm_env;
        JavaVM *vm;
        unsigned int nOptions = 0;
        PyObject *module_cp = NULL;

        vm_args.version = JNI_VERSION_1_4;
        JNI_GetDefaultJavaVMInitArgs(&vm_args);

        if (classpath == NULL && self != NULL)
        {
            module_cp = PyObject_GetAttrString(self, "CLASSPATH");
            if (module_cp != NULL)
                classpath = PyString_AsString(module_cp);
        }

#ifdef _jcc_lib
        PyObject *jcc = PyImport_ImportModule("jcc");
        PyObject *cp = PyObject_GetAttrString(jcc, "CLASSPATH");

        if (classpath)
            add_paths("-Djava.class.path=", PyString_AsString(cp), classpath,
                      &vm_options[nOptions++]);
        else
            add_option("-Djava.class.path=", PyString_AsString(cp),
                       &vm_options[nOptions++]);

        Py_DECREF(cp);
        Py_DECREF(jcc);
#else
        if (classpath)
            add_option("-Djava.class.path=", classpath,
                       &vm_options[nOptions++]);
#endif

        Py_XDECREF(module_cp);

        if (initialheap)
            add_option("-Xms", initialheap, &vm_options[nOptions++]);
        if (maxheap)
            add_option("-Xmx", maxheap, &vm_options[nOptions++]);
        if (maxstack)
            add_option("-Xss", maxstack, &vm_options[nOptions++]);

        if (vmargs)
        {
#ifdef _MSC_VER
            char *buf = _strdup(vmargs);
#else
            char *buf = strdup(vmargs);
#endif
            char *sep = ",";
            char *option;

            for (option = strtok(buf, sep); option; option = strtok(NULL, sep))
            {
                if (nOptions < sizeof(vm_options) / sizeof(JavaVMOption))
                    add_option("", option, &vm_options[nOptions++]);
                else
                {
                    free(buf);
                    for (unsigned int i = 0; i < nOptions; i++)
                        delete vm_options[i].optionString;
                    PyErr_Format(PyExc_ValueError, "Too many options (> %d)",
                                 nOptions);
                    return NULL;
                }
            }
            free(buf);
        }

        //vm_options[nOptions++].optionString = "-verbose:gc";
        //vm_options[nOptions++].optionString = "-Xcheck:jni";

        vm_args.nOptions = nOptions;
        vm_args.ignoreUnrecognized = JNI_FALSE;
        vm_args.options = vm_options;
 if (JNI_CreateJavaVM(&vm, (void **) &vm_env, &vm_args) < 0)
        {
            for (unsigned int i = 0; i < nOptions; i++)
                delete vm_options[i].optionString;

            PyErr_Format(PyExc_ValueError,
                         "An error occurred while creating Java VM");
            return NULL;
        }

        env->set_vm(vm, vm_env);

        for (unsigned int i = 0; i < nOptions; i++)
            delete vm_options[i].optionString;

        t_jccenv *jccenv = (t_jccenv *) PY_TYPE(JCCEnv).tp_alloc(&PY_TYPE(JCCEnv), 0);
        jccenv->env = env;

#ifdef _jcc_lib
        registerNatives(vm_env);
#endif

        return (PyObject *) jccenv;
    }
}
1
Put a breakpoint in all of Exception's constructors, and run the code with the debugger. Also, I don't really understand why you are trying to run a jar with jni? JNI is for accessing native libraries not java libraries. - Muel
Show your JNI-side code. If an exception is thrown from Java, you should be able to retrieve it in JNI with ExceptionOccurred. Also, have you tried creating a simple Java main() that invokes the operation? - parsifal
Muel: I'm using JCC to support Python-Java integration as part of a web development project. There's a library I want to use for intelligent reasoning which only exists for Java, but the web app must be written in Python. JCC uses the JNI to instantiate the JVM and then interact with Java objects. - James Naish
Parsifal: There's a lot of JNI-side code, as JCC creates wrapper objects for every Java class which is passed to it. The problem relates specifically to exceptions which occur when initialising the JVM with library A. Exceptions thrown after this work just fine. I'll post the JCC function, however, which initialises the JVM and which is throwing the Python exception I'm getting. - James Naish
So where does the error occur? Is it from the JNI_CreateJavaVM call? That wouldn't normally indicate a problem with any actual Java code, because you don't actually call any Java code (Java is load-on-first-use). However, if that's the case, you need to report the value returned by JNI_CreateJavaVM, not simply ignore it; that will give you more information about what's happening. If the error is happening somewhere else, show that code. - parsifal

1 Answers

1
votes

Okay, so I've got the solution I was after. The solution is an update to the following segment of the code listed in the question:

    if (JNI_CreateJavaVM(&vm, (void **) &vm_env, &vm_args) < 0)
    {
        for (unsigned int i = 0; i < nOptions; i++)
            delete vm_options[i].optionString;

        PyErr_Format(PyExc_ValueError,
                     "An error occurred while creating Java VM");
        return NULL;
    }

The adaptation supports the construction of a more detailed error message which adds two specific pieces of information:

  1. The error code (if any) which is returned by the JNI_CreateJavaVM method;
  2. The detailed Java exception which occurs in the event that such an error code arises.

The above snippet from the original code was replaced with the following:

    vmInitSuccess = JNI_CreateJavaVM(&vm, (void **) &vm_env, &vm_args);
    if (vmInitSuccess < 0)
    {
        for (unsigned int i = 0; i < nOptions; i++)
            delete vm_options[i].optionString;

        //Set up basic error message
        sprintf(strVMInitSuccess, "%d", vmInitSuccess);
        strcpy(strVMError, "An error occurred while creating Java VM (No Exception): ");
        strcat(strVMError, strVMInitSuccess);

        //Get exception if there is one
        if((exc = vm_env->ExceptionOccurred()))
        {
            //Clear the exception since we have it now
            vm_env->ExceptionClear();
            //Get the getMessage() method
            if ((java_class = vm_env->FindClass ("java/lang/Throwable")))
            {
                if ((method = vm_env->GetMethodID(java_class, "getMessage", "()Ljava/lang/String;")))
                {
                    int size;
                    strExc = static_cast<jstring>(vm_env->CallObjectMethod(exc, method)); 
                    charExc = vm_env->GetStringUTFChars(strExc, NULL);
                    size = sizeof(strVMError) + sizeof(charExc);
                    char strVMException[size];
                    strcpy(strVMException, "An error occurred while creating Java VM (Exception): ");
                    strcat(strVMException, charExc);
                    PyErr_Format(PyExc_ValueError, strVMException);
                    return NULL;
                }
            }
        }
        PyErr_Format(PyExc_ValueError, strVMError);
        return NULL;
    }

Thanks to @Parsifal for help with this solution.