mirror of https://github.com/kivy/pyjnius.git
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:
parent
6328de8142
commit
d93c911d9e
|
@ -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):
|
||||
|
|
|
@ -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 = {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
Loading…
Reference in New Issue