Throw SWIG_CException from C++ wrappers automatically

Check for the pending exception after every call to a wrapper function
not marked "noexcept" and throw a C++ exception if necessary.

Add C++ version of the exception example to show how this works.

Also change SWIG_CException to use member functions for checking for and
resetting pending exceptions, this seems better than having separate
functions for it and will make it easier to customize exception handling
later by just replacing SWIG_CException class with something else.

Note that we still use a global (and not a member) function for raising
the exception, but this one is not exported at all, and needs to be a
function in order to be easily callable from other modules (see the
upcoming commit).
This commit is contained in:
Vadim Zeitlin 2021-11-25 01:43:25 +01:00
commit 9a8ebbb998
6 changed files with 207 additions and 37 deletions

View file

@ -5,10 +5,16 @@ CXXSRCS = example.cxx
TARGET = example
INTERFACE = example.i
check: build
check_c: build
$(MAKE) -f $(TOP)/Makefile SRCDIR='$(SRCDIR)' \
TARGET='$(TARGET)' INTERFACE='$(INTERFACE)' RUNME_EXT=c c_run
check_cxx: build
$(MAKE) -f $(TOP)/Makefile SRCDIR='$(SRCDIR)' \
TARGET='$(TARGET)' INTERFACE='$(INTERFACE)' RUNME_EXT=cxx c_run
check: check_c check_cxx
build:
$(MAKE) -f $(TOP)/Makefile SRCDIR='$(SRCDIR)' CXXSRCS='$(CXXSRCS)' \
SWIG_LIB_DIR='$(SWIG_LIB_DIR)' SWIGEXE='$(SWIGEXE)' \

View file

@ -8,10 +8,10 @@
#include "example_wrap.h"
static void show_exception(const char* prefix) {
SWIG_CException* ex = example_SWIG_PendingException_get();
SWIG_CException* ex = SWIG_CException_get_pending();
assert(ex);
printf("%s exception: %s (%d)\n", prefix, SWIG_CException_msg_get(ex), SWIG_CException_code_get(ex));
example_SWIG_PendingException_reset();
SWIG_CException_reset_pending();
}
int main() {
@ -32,7 +32,7 @@ int main() {
int i;
for (i = 0; i < 4; ++i) {
Test_multi(t, i);
if (!example_SWIG_PendingException_get()) {
if (!SWIG_CException_get_pending()) {
printf("Success for i=%d\n", i);
} else {
printf("For i=%d", i);

View file

@ -0,0 +1,69 @@
/*
* NOTE: this won't run with -noexcept flag
*/
#include <stdio.h>
#include <assert.h>
#include "example_wrap.h"
using Exception = example::SWIG_CException;
static int exit_code = 0;
static void show_exception(const char* prefix, Exception const& ex) {
printf("%s exception: %s (%d)\n", prefix, ex.msg(), ex.code());
}
static void missing_exception(const char* prefix) {
printf("*** ERROR: %s: expected exception not thrown.\n", prefix);
exit_code++;
}
int main() {
example::Test t;
try {
t.unknown();
missing_exception("Unknown");
} catch (Exception const& e) {
show_exception("Unknown", e);
}
try {
t.simple();
missing_exception("Int");
} catch (Exception const& e) {
show_exception("Int", e);
}
try {
t.message();
missing_exception("String");
} catch (Exception const& e) {
show_exception("String", e);
}
try {
t.hosed();
missing_exception("Custom");
} catch (Exception const& e) {
show_exception("Custom", e);
}
for (int i = 0; i < 4; ++i) {
try {
t.multi(i);
if (i == 0) {
printf("Success for i=%d\n", i);
} else {
missing_exception("Multi");
}
} catch (Exception const& e) {
printf("For i=%d", i);
show_exception("", e);
}
}
return exit_code;
}

View file

@ -7,38 +7,38 @@ int main() {
exception_order_A* a = exception_order_A_new();
exception_order_A_foo(a);
if (!exception_order_SWIG_PendingException_get()) {
if (!exception_order_SWIG_CException_get_pending()) {
fprintf(stderr, "foo: bad exception order\n");
} else {
exception_order_SWIG_PendingException_reset();
exception_order_SWIG_CException_reset_pending();
}
exception_order_A_bar(a);
if (!exception_order_SWIG_PendingException_get()) {
if (!exception_order_SWIG_CException_get_pending()) {
fprintf(stderr, "bar: bad exception order\n");
} else {
exception_order_SWIG_PendingException_reset();
exception_order_SWIG_CException_reset_pending();
}
exception_order_A_foobar(a);
if (!exception_order_SWIG_PendingException_get()) {
if (!exception_order_SWIG_CException_get_pending()) {
fprintf(stderr, "foobar: bad exception order\n");
} else {
exception_order_SWIG_PendingException_reset();
exception_order_SWIG_CException_reset_pending();
}
exception_order_A_barfoo(a, 1);
if (!exception_order_SWIG_PendingException_get()) {
if (!exception_order_SWIG_CException_get_pending()) {
fprintf(stderr, "barfoo(1): bad exception order\n");
} else {
exception_order_SWIG_PendingException_reset();
exception_order_SWIG_CException_reset_pending();
}
exception_order_A_barfoo(a, 2);
if (!exception_order_SWIG_PendingException_get()) {
if (!exception_order_SWIG_CException_get_pending()) {
fprintf(stderr, "barfoo(2): bad exception order\n");
} else {
exception_order_SWIG_PendingException_reset();
exception_order_SWIG_CException_reset_pending();
}
exit(0);

View file

@ -4,39 +4,58 @@
* Exception handling code and typemaps for C module.
* ----------------------------------------------------------------------------- */
%ignore SWIG_CException::SWIG_CException;
// This function is special: it's used by various typemaps (via SWIG_exception
// macro below) and needs to be defined, but we don't want to export it.
%ignore SWIG_CException_Raise;
%{
extern "C" void SWIG_CException_Raise(int code, const char* msg);
%}
%inline %{
class SWIG_CException {
public:
SWIG_CException(int code, const char* msg) : code(code), msg(strdup(msg)) { }
SWIG_CException(const SWIG_CException& ex) throw() : code(ex.code), msg(strdup(ex.msg)) { }
const int code;
const char* const msg;
private:
friend void SWIG_PendingException_reset();
static SWIG_CException* get_pending() throw() {
return PendingException;
}
static void reset_pending() throw() {
if (PendingException) {
delete PendingException;
PendingException = 0;
}
}
private:
friend void SWIG_CException_Raise(int code, const char* msg);
static SWIG_CException* PendingException;
SWIG_CException(int code, const char* msg) : code(code), msg(strdup(msg)) { }
~SWIG_CException() { free(const_cast<char*>(msg)); }
SWIG_CException(const SWIG_CException& ex);
SWIG_CException& operator=(const SWIG_CException& ex);
};
%}
static SWIG_CException *SWIG_PendingException = 0;
inline SWIG_CException* SWIG_PendingException_get() { return SWIG_PendingException; }
inline void SWIG_PendingException_reset() {
if (SWIG_PendingException) {
delete SWIG_PendingException;
SWIG_PendingException = 0;
}
// This part is implementation only and doesn't need to be seen by SWIG.
%{
SWIG_CException *SWIG_CException::PendingException = 0;
SWIGEXPORTC void SWIG_CException_Raise(int code, const char* msg) {
delete SWIG_CException::PendingException;
SWIG_CException::PendingException = new SWIG_CException(code, msg);
}
%}
%insert("runtime") "swigerrors.swg"
#define SWIG_exception(code, msg)\
SWIG_PendingException = new SWIG_CException(code, msg)
SWIG_CException_Raise(code, msg)
%typemap(throws, noblock="1") char *, const char * {
SWIG_exception(SWIG_RuntimeError, $1);

View file

@ -223,17 +223,51 @@ String* get_c_proxy_name(Node* n) {
struct cxx_wrappers
{
// Default ctor doesn't do anything, use initialize() if C++ wrappers really need to be generated.
cxx_wrappers() : f_fwd_decls(NULL), f_decls(NULL), f_impls(NULL) {}
cxx_wrappers() :
except_check_start("swig_check("), except_check_end(")"),
f_fwd_decls(NULL), f_decls(NULL), f_impls(NULL) {
}
// This can be called before initialize() to disable generating exception support code.
void disable_exceptions() {
assert(!f_fwd_decls); // Check we are not called too late.
except_check_start =
except_check_end = "";
}
void initialize() {
f_fwd_decls = NewStringEmpty();
f_decls = NewStringEmpty();
f_impls = NewStringEmpty();
// Generate the functions which will be used in all wrappers to check for the exceptions if necessary.
if (*except_check_start != '\0') {
Printv(f_impls,
"void swig_check() {\n",
cindent, "if (SWIG_CException* swig_ex = SWIG_CException::get_pending()) {\n",
cindent, cindent, "SWIG_CException swig_ex_copy{*swig_ex};\n",
cindent, cindent, "SWIG_CException::reset_pending();\n",
cindent, cindent, "throw swig_ex_copy;\n",
cindent, "}\n",
"}\n\n",
"template <typename T> T swig_check(T x) {\n",
cindent, "swig_check();\n",
cindent, "return x;\n",
"}\n\n",
NIL
);
}
}
bool is_initialized() const { return f_fwd_decls != NULL; }
// Used for generating exception checks around the calls unless disable_exceptions() is called.
const char* except_check_start;
const char* except_check_end;
// The order of the members here is the same as the order in which they appear in the output file.
// Forward declarations of the classes.
@ -324,7 +358,6 @@ public:
// Deal with the return type: it may be different from the type of the C wrapper function if it involves objects, and so we may need to add a cast.
const char* maybe_return;
type_desc rtype_desc;
if (SwigType_type(Getattr(n, "type")) != T_VOID) {
rtype_desc = lookup_cxx_ret_type(n);
@ -336,12 +369,9 @@ public:
);
return;
}
maybe_return = "return ";
} else {
// There is no need to do anything else with "void" and we don't even need "return" for it.
rtype_desc.set_void_type();
maybe_return = "";
}
// We also need the list of parameters to take in the C++ function being generated and the list of them to pass to the C wrapper.
@ -393,6 +423,17 @@ public:
Printv(parms_call, ptype_desc.wrap_start(), name, ptype_desc.wrap_end(), NIL);
}
// Avoid checking for exceptions unnecessarily. Note that this is more than an optimization: we'd get into infinite recursion if we checked for exceptions
// thrown by members of SWIG_CException itself if we didn't do it.
const char* except_check_start = cxx_wrappers_.except_check_start;
const char* except_check_end = cxx_wrappers_.except_check_end;
if (*except_check_start) {
if (Checkattr(n, "noexcept", "true") || (Checkattr(n, "throw", "1") && !Getattr(n, "throws"))) {
except_check_start =
except_check_end = "";
}
}
// For some reason overloaded functions use fully-qualified name, so we can't just use the name directly.
scoped_dohptr name_ptr(Swig_scopename_last(Getattr(n, "name")));
String* const name = name_ptr.get();
@ -448,7 +489,11 @@ public:
Printv(cxx_wrappers_.f_impls,
"inline ", classname, "::", classname, "(", parms_cxx.get(), ") : ",
classname, "{", wname, "(", parms_call.get(), ")} {}\n",
classname, "{",
except_check_start,
wname, "(", parms_call.get(), ")",
except_check_end,
"} {}\n",
NIL
);
@ -501,10 +546,36 @@ public:
classname, "::", name, "(", parms_cxx.get(), ")",
get_const_suffix(n),
" { ",
maybe_return,
rtype_desc.wrap_start(),
wname, "(", wparms.get(), ")",
rtype_desc.wrap_end(),
NIL
);
if (rtype_desc.is_void()) {
Printv(cxx_wrappers_.f_impls,
wname, "(", wparms.get(), ")",
NIL
);
if (*except_check_start) {
Printv(cxx_wrappers_.f_impls,
"; ",
except_check_start,
except_check_end,
NIL
);
}
} else {
Printv(cxx_wrappers_.f_impls,
"return ",
rtype_desc.wrap_start(),
except_check_start,
wname, "(", wparms.get(), ")",
except_check_end,
rtype_desc.wrap_end(),
NIL
);
}
Printv(cxx_wrappers_.f_impls,
"; }\n",
NIL
);
@ -662,6 +733,8 @@ private:
void set_type(String* type) { type_ = Copy(type); }
void set_void_type() { type_ = NewString("void"); }
bool is_void() const { return type_ && Cmp(type_, "void") == 0; }
// If this one returns NULL, it means that we don't have any type information at all.
String* type() const { return type_; }
@ -1224,6 +1297,9 @@ public:
Delete(ns_prefix_);
if (use_cxx_wrappers) {
if (!except_flag)
cxx_wrappers_.disable_exceptions();
cxx_wrappers_.initialize();
}