Add improved exception handling:

- Disable calling ExceptionDescribe, as this pollutes stderr, making it harder
to implement clean exception handling. JNI documentation describes it as being for
debugging purposes.

- Extract separate fields for the exception class name, exception message, and
exception stack trace, and add these as attributes to JavaException. Calling
code can now decide for itself whether and how to present this information.
This commit is contained in:
Lenbok 2014-05-19 10:22:36 +12:00
parent 6328de8142
commit d93c911d9e
4 changed files with 105 additions and 7 deletions

View File

@ -2,7 +2,15 @@
class JavaException(Exception):
'''Can be a real java exception, or just an exception from the wrapper.
'''
pass
classname = None # The classname of the exception
innermessage = None # The message of the inner exception
stacktrace = None # The stack trace of the inner exception
def __init__(self, message, classname=None, innermessage=None, stacktrace=None):
self.classname = classname
self.innermessage = innermessage
self.stacktrace = stacktrace
Exception.__init__(self, message)
cdef class JavaObject(object):

View File

@ -37,19 +37,79 @@ cdef parse_definition(definition):
cdef void check_exception(JNIEnv *j_env) except *:
cdef jmethodID toString = NULL
cdef jstring e_string
cdef jmethodID getCause = NULL
cdef jmethodID getStackTrace = NULL
cdef jmethodID getMessage = NULL
cdef jstring e_msg
cdef jboolean isCopy
cdef jthrowable exc = j_env[0].ExceptionOccurred(j_env)
if exc:
j_env[0].ExceptionDescribe(j_env)
# ExceptionDescribe always writes to stderr, preventing tidy exception
# handling, so should only be for debugging
# j_env[0].ExceptionDescribe(j_env)
j_env[0].ExceptionClear(j_env)
toString = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Object"), "toString", "()Ljava/lang/String;");
e_string = j_env[0].CallObjectMethod(j_env, exc, toString);
getMessage = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Throwable"), "getMessage", "()Ljava/lang/String;");
getCause = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Throwable"), "getCause", "()Ljava/lang/Throwable;");
getStackTrace = j_env[0].GetMethodID(j_env, j_env[0].FindClass(j_env, "java/lang/Throwable"), "getStackTrace", "()[Ljava/lang/StackTraceElement;");
pystr = convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', e_string)
e_msg = j_env[0].CallObjectMethod(j_env, exc, getMessage);
pymsg = None if e_msg == NULL else convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', e_msg)
raise JavaException('JVM exception occurred: ' + pystr)
pystack = []
_append_exception_trace_messages(j_env, pystack, exc, getCause, getStackTrace, toString)
pyexcclass = lookup_java_object_name(j_env, exc).replace('/', '.')
raise JavaException('JVM exception occurred: %s' % (pymsg if pymsg is not None else pyexcclass), pyexcclass, pymsg, pystack)
cdef void _append_exception_trace_messages(
JNIEnv* j_env,
list pystack,
jthrowable exc,
jmethodID mid_getCause,
jmethodID mid_getStackTrace,
jmethodID mid_toString):
# Get the array of StackTraceElements.
cdef jobjectArray frames = j_env[0].CallObjectMethod(j_env, exc, mid_getStackTrace)
cdef jsize frames_length = j_env[0].GetArrayLength(j_env, frames)
cdef jstring msg_obj
cdef jobject frame
cdef jthrowable cause
# Add Throwable.toString() before descending stack trace messages.
if frames != NULL:
msg_obj = j_env[0].CallObjectMethod(j_env, exc, mid_toString)
pystr = None if msg_obj == NULL else convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', msg_obj)
# If this is not the top-of-the-trace then this is a cause.
if len(pystack) > 0:
pystack.append("Caused by:")
pystack.append(pystr)
j_env[0].DeleteLocalRef(j_env, msg_obj)
# Append stack trace messages if there are any.
if frames_length > 0:
for i in range(frames_length):
# Get the string returned from the 'toString()' method of the next frame and append it to the error message.
frame = j_env[0].GetObjectArrayElement(j_env, frames, i)
msg_obj = j_env[0].CallObjectMethod(j_env, frame, mid_toString)
pystr = None if msg_obj == NULL else convert_jobject_to_python(j_env, <bytes> 'Ljava/lang/String;', msg_obj)
pystack.append(pystr)
j_env[0].DeleteLocalRef(j_env, msg_obj)
j_env[0].DeleteLocalRef(j_env, frame)
# If 'exc' has a cause then append the stack trace messages from the cause.
if frames != NULL:
cause = j_env[0].CallObjectMethod(j_env, exc, mid_getCause)
if cause != NULL:
_append_exception_trace_messages(j_env, pystack, cause,
mid_getCause, mid_getStackTrace, mid_toString)
j_env[0].DeleteLocalRef(j_env, cause)
j_env[0].DeleteLocalRef(j_env, frames)
cdef dict assignable_from = {}

View File

@ -22,6 +22,17 @@ public class BasicsTest {
public float methodF() { return 1.23456789f; };
public double methodD() { return 1.23456789; };
public String methodString() { return new String("helloworld"); }
public void methodException(int depth) throws IllegalArgumentException {
if (depth == 0) throw new IllegalArgumentException("helloworld");
else methodException(depth -1);
}
public void methodExceptionChained() throws IllegalArgumentException {
try {
methodException(5);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("helloworld2", e);
}
}
static public boolean fieldStaticZ = true;
static public byte fieldStaticB = 127;

View File

@ -29,4 +29,23 @@ class BadDeclarationTest(unittest.TestCase):
stack.pop()
self.fail("Expected exception to be thrown")
except JavaException as je:
self.assertIn("EmptyStackException", str(je))
# print "Got JavaException: " + str(je)
# print "Got Exception Class: " + je.classname
# print "Got stacktrace: \n" + '\n'.join(je.stacktrace)
self.assertEquals("java.util.EmptyStackException", je.classname)
def test_java_exception_chaining(self):
BasicsTest = autoclass('org.jnius.BasicsTest')
basics = BasicsTest()
try:
basics.methodExceptionChained()
self.fail("Expected exception to be thrown")
except JavaException as je:
# print "Got JavaException: " + str(je)
# print "Got Exception Class: " + je.classname
# print "Got Exception Message: " + je.innermessage
# print "Got stacktrace: \n" + '\n'.join(je.stacktrace)
self.assertEquals("java.lang.IllegalArgumentException", je.classname)
self.assertEquals("helloworld2", je.innermessage)
self.assertIn("Caused by:", je.stacktrace)
self.assertEquals(11, len(je.stacktrace))