Fix using exception in modules importing other modules

Ensure that we have only a single SWIG_CException_Raise() function
across all modules instead of having per-module functions and, worse,
per-module PendingException variables, which resulted in compile-time
errors and couldn't work anyhow because function checking for the
current exception didn't necessarily use the same "global" variable
where it was stored. More formally, old version resulted in ODR
violations and undefined behaviour.

The way we avoid it now is rather ugly and consists in excluding
SWIG_CException from wrapping using the hack in the source code which
postpones wrapping this class until the very end and checks if we had
encountered any %import directives and simply doesn't wrap it if we did.

The same code is used to define the special SWIG_CException_DEFINED
preprocessor symbol which is then used in the generated code to prevent
the SWIG_CException class declaration from being compiled as part of the
wrapper too (because this still happens due to %inline being used for
its definition, and there doesn't seem to be any better way to avoid
this).

This is definitely not pretty, but at least adding "throw(char*)" to a
couple of functions in mod_[ab].i test suite files works now instead of
failing (even without linking and running) as before. This commit
doesn't modify the test suite to avoid possible problems with the other
languages, however.
This commit is contained in:
Vadim Zeitlin 2021-11-25 21:14:56 +01:00
commit 727a65c0e8
2 changed files with 50 additions and 2 deletions

View file

@ -11,7 +11,14 @@
extern "C" void SWIG_CException_Raise(int code, const char* msg);
%}
// This class is special too because its name is used in c.cxx source. It is
// only defined if the code there didn't predefine SWIG_CException_DEFINED
// because the class is already defined in another module.
//
// It has to be seen by SWIG because we want to generate wrappers for its
// public functions to be able to use it from the application code.
%inline %{
#ifndef SWIG_CException_DEFINED
class SWIG_CException {
public:
SWIG_CException(const SWIG_CException& ex) throw() : code(ex.code), msg(strdup(ex.msg)) { }
@ -40,16 +47,19 @@ private:
SWIG_CException& operator=(const SWIG_CException& ex);
};
#endif // SWIG_CException_DEFINED
%}
// This part is implementation only and doesn't need to be seen by SWIG.
%{
#ifndef SWIG_CException_DEFINED
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);
}
#endif // SWIG_CException_DEFINED
%}
%insert("runtime") "swigerrors.swg"

View file

@ -240,10 +240,12 @@ struct cxx_wrappers
f_fwd_decls = NewStringEmpty();
f_decls = NewStringEmpty();
f_impls = NewStringEmpty();
}
void output_exception_support(File* f_out) {
// 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,
Printv(f_out,
"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",
@ -1005,6 +1007,10 @@ class C:public Language {
// Non-owning pointer to the current C++ class wrapper if we're currently generating one or NULL.
cxx_class_wrapper* cxx_class_wrapper_;
// Non-owning, possibly null pointer to SWIG_CException class node. We only wrap this class if we don't import any other modules because there must be only
// one pending exception pointer for the entire module, not one per each of its submodules.
Node* exception_class_node_;
public:
/* -----------------------------------------------------------------------------
@ -1017,7 +1023,8 @@ public:
ns_prefix(NULL),
module_name(NULL),
outfile_h(NULL),
cxx_class_wrapper_(NULL)
cxx_class_wrapper_(NULL),
exception_class_node_(NULL)
{
}
@ -1380,6 +1387,11 @@ public:
// emit code for children
Language::top(n);
if (exception_class_node_) {
// Really emit the wrappers for the exception class now that we know that we need it.
Language::classDeclaration(exception_class_node_);
}
} // close extern "C" guards
Dump(f_wrappers_types, f_wrappers_h);
@ -1424,6 +1436,11 @@ public:
Printv(f_wrappers_h, "\n", NIL);
Dump(cxx_wrappers_.f_decls, f_wrappers_h);
// Also emit C++-specific exceptions support if not done in another module yet.
if (exception_class_node_) {
cxx_wrappers_.output_exception_support(f_wrappers_h);
}
Printv(f_wrappers_h, "\n", NIL);
Dump(cxx_wrappers_.f_impls, f_wrappers_h);
@ -1464,6 +1481,11 @@ public:
// Finally inject inclusion of this header.
Printv(Swig_filebyname("cheader"), "#include \"", header_name.get(), "\"\n", NIL);
// One more thing: if we have imported another module, it must have already defined SWIG_CException, so set the flag indicating that we shouldn't do it
// again in this one and define the symbol to skip compiling its implementation.
exception_class_node_ = NULL;
Printv(Swig_filebyname("runtime"), "#define SWIG_CException_DEFINED 1\n", NIL);
}
return Language::importDirective(n);
@ -2168,6 +2190,22 @@ public:
}
}
/* ---------------------------------------------------------------------
* classDeclaration()
* --------------------------------------------------------------------- */
virtual int classDeclaration(Node *n) {
if (Cmp(Getattr(n, "name"), "SWIG_CException") == 0) {
// We don't know if we're going to need to wrap this class or not yet, it depends on whether any other modules are included by this one, and while we
// could walk the entire tree looking for "import" nodes, it seems simpler to just wait until our importDirective() is called and handle this class at the
// end in top() if it won't have been.
exception_class_node_ = n;
return SWIG_NOWRAP;
}
return Language::classDeclaration(n);
}
/* ---------------------------------------------------------------------
* classHandler()
* --------------------------------------------------------------------- */