Java director exception handling improvements

When a director method throws an exception and it is caught by DirectorException
and passed back to Java using DirectorException::raiseJavaException, the Java
stack trace now contains the original source line that threw the exception.

Director exception handling code improved slightly to add some missing
ExceptionClear calls before calling JNI code.
This commit is contained in:
William S Fulton 2017-11-10 19:50:22 +00:00
commit 7aa28e37ec
7 changed files with 74 additions and 44 deletions

View file

@ -3843,7 +3843,6 @@ handle the mapping of Java exceptions into C++ exceptions.
%feature("director:except") MyClass::method(int x) {
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear();
if (Swig::ExceptionMatches(jenv, $error, "java/lang/IndexOutOfBoundsException"))
throw std::out_of_range(Swig::JavaExceptionMessage(jenv, $error).message());
if (Swig::ExceptionMatches(jenv, $error, "$packagepath/MyJavaException"))
@ -3921,7 +3920,6 @@ is used in the feature code. Consider the following, which also happens to be th
%feature("director:except") %{
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear();
$directorthrowshandlers
Swig::DirectorException::raise(jenv, $error);
}
@ -3970,7 +3968,6 @@ the resulting code generated in the director method after calling up to Java wil
<pre>
jthrowable swigerror = jenv-&gt;ExceptionOccurred();
if (swigerror) {
jenv-&gt;ExceptionClear();
if (Swig::ExceptionMatches(jenv, swigerror, "java/lang/IndexOutOfBoundsException")) {
throw std::out_of_range(Swig::JavaExceptionMessage(jenv, swigerror).message());
}
@ -3992,7 +3989,7 @@ this potential fate.
<p>Because the default code generation maps any unhandled Java exceptions to
<code>Swig::DirectorException</code>, any director methods that have exception
specifications may cause program termination. To simply ignore
unexpected exceptions, the default handling can be changed with:
unexpected exceptions, the default handling can be changed by adding:
</p>
<div class="code">
@ -4068,14 +4065,10 @@ DEFINE_EXCEPTION(ExceptionA)
DEFINE_EXCEPTION(ExceptionB)
DEFINE_EXCEPTION(Unexpected)
// Mark three methods to map director thrown exceptions.
%feature("director:except") MyClass::meth1(int);
%feature("director:except") MyClass::meth2;
%feature("director:except") meth3;
%typemap(directorthrows) MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected %{
if (Swig::ExceptionMatches(jenv, $error, "$packagepath/$javaclassname"))
if (Swig::ExceptionMatches(jenv, $error, "$packagepath/$javaclassname")) {
throw $1_type(Swig::JavaExceptionMessage(jenv, $error).message());
}
%}
DECLARE_EXCEPTION(ExceptionA)
@ -4084,6 +4077,8 @@ DECLARE_EXCEPTION(Unexpected)
%catches(MyNS::ExceptionA, MyNS::ExceptionB, MyNS::Unexpected) MyClass::meth2();
%feature("director") MyClass;
%inline {
class MyClass {
public:
@ -4100,13 +4095,13 @@ DECLARE_EXCEPTION(Unexpected)
In this case the three different "directorthrows" typemaps will be used
to generate the three different exception handlers for
<code>meth1</code>, <code>meth2</code> and <code>meth3</code>. The generated
handlers will have "if" blocks for each exception type specified, in
handlers will have "if" blocks for each exception type, where the exception types are listed in either
the exception specification or <code>%catches</code> feature.
</p>
<p>Note that the "directorthrows" typemaps are important
only if it is important for the exceptions passed through the C++
layer to be mapped to distinct C++ exceptions. If director methods
layer need to be mapped to distinct C++ exceptions. If director methods
are being called by C++ code that is itself wrapped in a
SWIG generated Java wrapper and access is always through this wrapper,
the default <code>Swig::DirectorException</code> class provides enough information

View file

@ -126,19 +126,28 @@ public class java_director_exception_feature_nspace_runme {
try { b.genericpong(1); fail("No exception thrown in genericpong(1)"); }
catch (MyJavaException1 e) {
failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP1.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
StackTraceElement[] st = e.getStackTrace();
failif( st.length != 5, "Stack length is only " + st.length);
failif( ! st[0].toString().startsWith("java_director_exception_feature_nspace_MyFooDirectorImpl.genericpong(java_director_exception_feature_nspace_runme.java:"), "Incorrect top of stack: " + st[0]);
}
try { b.genericpong(2); fail("No exception thrown in genericpong(2)");}
catch (java_director_exception_feature_nspace_NewCheckedException e) {
failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
StackTraceElement[] st = e.getStackTrace();
failif( st.length != 5, "Stack length is only " + st.length);
failif( ! st[0].toString().startsWith("java_director_exception_feature_nspace_MyFooDirectorImpl.genericpong(java_director_exception_feature_nspace_runme.java:"), "Incorrect top of stack: " + st[0]);
}
try { b.genericpong(3); fail("No exception thrown in genericpong(3)");}
catch (java_director_exception_feature_nspace_NewUncheckedException e) {
failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP3.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
StackTraceElement[] st = e.getStackTrace();
failif( st.length != 5, "Stack length is only " + st.length);
failif( ! st[0].toString().startsWith("java_director_exception_feature_nspace_MyFooDirectorImpl.genericpong(java_director_exception_feature_nspace_runme.java:"), "Incorrect top of stack: " + st[0]);
}
try { b.genericpong(4); fail("No exception thrown in genericpong(4)");}
catch (RuntimeException e) {
failif ( e.getClass() != RuntimeException.class, "Exception " + e + " is not exactly RumtimeException");
failif( ! java_director_exception_feature_nspace_Consts.GENERICPONGEXCP4.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
failif ( e.getClass() != RuntimeException.class, "Exception " + e + " is not exactly RuntimeException");
failif( ! "Unspecified DirectorException message".equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
}
}
catch (Exception e) {

View file

@ -127,19 +127,28 @@ public class java_director_exception_feature_runme {
try { b.genericpong(1); fail("No exception thrown in genericpong(1)"); }
catch (MyJavaException1 e) {
failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP1.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
StackTraceElement[] st = e.getStackTrace();
failif( st.length != 5, "Stack length is only " + st.length);
failif( ! st[0].toString().startsWith("java_director_exception_feature_MyFooDirectorImpl.genericpong(java_director_exception_feature_runme.java:"), "Incorrect top of stack: " + st[0]);
}
try { b.genericpong(2); fail("No exception thrown in genericpong(2)");}
catch (NewCheckedException e) {
failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP2.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
StackTraceElement[] st = e.getStackTrace();
failif( st.length != 5, "Stack length is only " + st.length);
failif( ! st[0].toString().startsWith("java_director_exception_feature_MyFooDirectorImpl.genericpong(java_director_exception_feature_runme.java:"), "Incorrect top of stack: " + st[0]);
}
try { b.genericpong(3); fail("No exception thrown in genericpong(3)");}
catch (NewUncheckedException e) {
failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP3.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
StackTraceElement[] st = e.getStackTrace();
failif( st.length != 5, "Stack length is only " + st.length);
failif( ! st[0].toString().startsWith("java_director_exception_feature_MyFooDirectorImpl.genericpong(java_director_exception_feature_runme.java:"), "Incorrect top of stack: " + st[0]);
}
try { b.genericpong(4); fail("No exception thrown in genericpong(4)");}
catch (RuntimeException e) {
failif ( e.getClass() != RuntimeException.class, "Exception " + e + " is not exactly RumtimeException");
failif( ! java_director_exception_feature_Consts.GENERICPONGEXCP4.equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
failif ( e.getClass() != RuntimeException.class, "Exception " + e + " is not exactly RuntimeException");
failif( ! "Unspecified DirectorException message".equals(e.getMessage()), "Expected exception has unexpected message: '" + e.getMessage() + "'");
}
}

View file

@ -42,7 +42,6 @@
%feature("director:except") MyNS::Foo::ping {
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear(); // clear java exception since mapping to c++ exception
if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyJavaException1")) {
throw 1;
} else if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyJavaException2")) {
@ -71,7 +70,6 @@
%feature("director:except") MyNS::Foo::pong %{
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear();
$directorthrowshandlers
throw ::MyNS::Unexpected(Swig::JavaExceptionMessage(jenv,$error).message());
}
@ -121,7 +119,10 @@
%feature("director:except") MyNS::Foo::genericpong {
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear();
if (Swig::ExceptionMatches(jenv,$error,"UnconstructableException")) {
// Purposefully test NULL
throw Swig::DirectorException(jenv, NULL);
}
throw Swig::DirectorException(jenv,$error);
}
}

View file

@ -49,7 +49,6 @@
%feature("director:except") MyNS::Foo::ping {
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear(); // clear java exception since mapping to c++ exception
if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyNS/MyJavaException1")) {
throw 1;
} else if (Swig::ExceptionMatches(jenv,$error,"$packagepath/MyNS/MyJavaException2")) {
@ -78,7 +77,6 @@
%feature("director:except") MyNS::Foo::pong %{
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear();
$directorthrowshandlers
throw ::MyNS::Unexpected(Swig::JavaExceptionMessage(jenv,$error).message());
}
@ -128,7 +126,10 @@
%feature("director:except") MyNS::Foo::genericpong {
jthrowable $error = jenv->ExceptionOccurred();
if ($error) {
jenv->ExceptionClear();
if (Swig::ExceptionMatches(jenv,$error,"java_director_exception_feature_nspace_UnconstructibleException")) {
// Purposefully test NULL
throw Swig::DirectorException(jenv, NULL);
}
throw Swig::DirectorException(jenv,$error);
}
}

View file

@ -259,8 +259,8 @@ namespace Swig {
JavaExceptionMessage(JNIEnv *jenv, jthrowable throwable) : message_(jenv, exceptionMessageFromThrowable(jenv, throwable)) {
}
const char *message() const {
return message_.c_str("Could not get exception message in JavaExceptionMessage");
const char *message(const char *null_string = "Could not get exception message in JavaExceptionMessage") const {
return message_.c_str(null_string);
}
private:
@ -294,10 +294,11 @@ namespace Swig {
public:
// Construct exception from a Java throwable
DirectorException(JNIEnv *jenv, jthrowable throwable) : classname_(0), msg_(0) {
DirectorException(JNIEnv *jenv, jthrowable throwable) : jenv_(jenv), throwable_(throwable), classname_(0), msg_(0) {
// Call Java method Object.getClass().getName() to obtain the throwable's class name (delimited by '/')
if (throwable) {
if (jenv && throwable) {
jenv->ExceptionClear(); // Cannot invoke methods with any pending exceptions
jclass throwclz = jenv->GetObjectClass(throwable);
if (throwclz) {
jclass clzclz = jenv->GetObjectClass(throwclz);
@ -318,11 +319,11 @@ namespace Swig {
}
JavaExceptionMessage exceptionmsg(jenv, throwable);
msg_ = copystr(exceptionmsg.message());
msg_ = copystr(exceptionmsg.message(0));
}
// More general constructor for handling as a java.lang.RuntimeException
DirectorException(const char *msg) : classname_(0), msg_(copystr(msg ? msg : "Unspecified DirectorException message")) {
DirectorException(const char *msg) : jenv_(0), throwable_(0), classname_(0), msg_(msg ? copystr(msg) : 0) {
}
~DirectorException() throw() {
@ -331,28 +332,40 @@ namespace Swig {
}
const char *what() const throw() {
return msg_;
return msg_ ? msg_ : "Unspecified DirectorException message";
}
// Reconstruct and raise/throw the Java Exception that caused the DirectorException
// Note that any error in the JNI exception handling results in a Java RuntimeException
void raiseJavaException(JNIEnv *jenv) const {
if (jenv) {
jenv->ExceptionClear();
if (jenv == jenv_ && throwable_) {
// Throw original exception if not already pending
jthrowable throwable = jenv->ExceptionOccurred();
if (throwable && jenv->IsSameObject(throwable, throwable_) == JNI_FALSE) {
jenv->ExceptionClear();
throwable = 0;
}
if (!throwable)
jenv->Throw(throwable_);
} else {
// Try and reconstruct original exception, but original stacktrace is not reconstructed
jenv->ExceptionClear();
jmethodID ctorMethodID = 0;
jclass throwableclass = 0;
if (classname_) {
throwableclass = jenv->FindClass(classname_);
if (throwableclass)
ctorMethodID = jenv->GetMethodID(throwableclass, "<init>", "(Ljava/lang/String;)V");
}
jmethodID ctorMethodID = 0;
jclass throwableclass = 0;
if (classname_) {
throwableclass = jenv->FindClass(classname_);
if (throwableclass)
ctorMethodID = jenv->GetMethodID(throwableclass, "<init>", "(Ljava/lang/String;)V");
}
if (ctorMethodID) {
jenv->ThrowNew(throwableclass, what());
} else {
SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what());
}
if (ctorMethodID) {
jenv->ThrowNew(throwableclass, what());
} else {
SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, what());
}
}
}
}
@ -380,6 +393,8 @@ namespace Swig {
return target;
}
JNIEnv *jenv_;
jthrowable throwable_;
const char *classname_;
const char *msg_;
};

View file

@ -4463,8 +4463,8 @@ public:
if (!directorexcept) {
directorexcept = NewString("");
Printf(directorexcept, "jthrowable $error = jenv->ExceptionOccurred();\n");
Printf(directorexcept, "if ($error) {\n");
Printf(directorexcept, " jenv->ExceptionClear();$directorthrowshandlers\n");
Printf(directorexcept, "if ($error) {");
Printf(directorexcept, "$directorthrowshandlers\n");
Printf(directorexcept, " Swig::DirectorException::raise(jenv, $error);\n");
Printf(directorexcept, "}\n");
} else {