1

I am wrapping the following C++ code:

// content of cpp.h
class A
{
public:
  virtual int fnA() = 0;

  virtual ~A() { }
};

class B
{
public:
  int fnB(A &a)
  {
    return a.fnA();
  }
};

by SWIG using the SWIG wrapper:

// content of swigmodule.i
%module(directors="1") swigmodule

%feature("director");

%feature("director:except") {
    if ($error != NULL) {
      fprintf(stderr, "throw\n");
        throw Swig::DirectorMethodException();
    }
}

%exception {
    try { $action }
    catch (Swig::DirectorException &e) { fprintf(stderr, "catch\n"); SWIG_fail; }
}

%{
#include "cpp.h"
%}

%include "cpp.h"

where the exception handling is copied from the SWIG manual. Using this I generated the SWIG wrapper with: "swig -c++ -python swigmodule.i; g++ -shared -fPIC -I/usr/include/python2.7 swigmodule_wrap.cxx -o _swigmodule.so"

The problem now comes when incorrectly overloading the "int fnA()" function with a "void fnA()" in Python.

# content of useit.py
from swigmodule import A, B

class myA(A):

    def fnA(self):
        print("myA::fnA")

b = B();
a = myA();

print("%d"% b.fnB(a) )

The generated SWIG wrapper correctly flags this at runtime as an error; fnA(self) returns None which is not an int. However, the output at the console is:

$ python useit.py
myA::fnA
catch
Traceback (most recent call last):
  File "useit.py", line 12, in <module>
    print("%d"% b.fnB(a) )
  File "/home/schuttek/tmp/swigmodule.py", line 109, in fnB
    def fnB(self, *args): return _swigmodule.B_fnB(self, *args)
TypeError: SWIG director type mismatch in output value of type 'int'

which is misleading as it suggests that the error is in B::fnB where the actual error is in overloading A::fnA.

How do I get SWIG to provide a meaningfull diagnostic to where the error occured? In my real code (this is a simplified version) I had to use GDB to trap on the constructor of the Swig::DirectorException class. This is unwanted as the actual error was in the Python domain (where an incorrect overlaod was performed) and I want to shield future Python users from GDB and its usage, as well as from a SWIG internal such as a DirectorException.

Klamer Schutte
  • 1,063
  • 9
  • 18
  • I'm not sure this really is an error in A, it's during the application of `fnB` that the type mismatch is detected and rightly so. You probably could do something clever with metaclasses to spot it earlier if you really wanted though, but I'm not sure I buy it as "a big thing". – Flexo Jul 04 '16 at 19:06
  • What I hoped for is something along the lines that the text in the TypeError has something like "during matching A::fnA" added to its current diagnostic. – Klamer Schutte Jul 04 '16 at 19:44
  • Do you want to change it globally? I can tell you which typemap controls that `%typemap(directorout)`, using the macro `%dirout_fail` to actually do it but changing globally is tricky because there are a lot of typemaps for use with directors already, so it's not just a case of tweaking one to apply everywhere. Apply for all `int` or any other known return type should be simple enough though. – Flexo Jul 04 '16 at 20:04
  • I think I want the change globally. An example typemap will be greatly appreciated! – Klamer Schutte Jul 04 '16 at 20:18

2 Answers2

1

You can get some distance towards improving the error like you want using %typemap(directorout), for example:

// content of swigmodule.i
%module(directors="1") swigmodule

%feature("director");

%feature("director:except") {
    if ($error != NULL) {
      fprintf(stderr, "throw\n");
        throw Swig::DirectorMethodException();
    }
}

%exception {
    // $parentclassname
    try { $action }
    catch (Swig::DirectorException &e) { fprintf(stderr, "catch\n"); SWIG_fail; }
}

%{
#include "cpp.h"
%}

%typemap(directorout,noblock=1,fragment=SWIG_AsVal_frag(int)) int (int swig_val) {
  int swig_res = SWIG_AsVal(int)($input, &swig_val);
  if (!SWIG_IsOK(swig_res)) {
    //%dirout_fail(swig_res, "$type"); // This line expands into the default call
    Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_res)), "in output of $symname value of type '""$type""'");
  }
  $result = %static_cast(swig_val,$type);
}


%include "cpp.h"

Which replaces the directorout typemap for int. It seems that directorout doesn't get some special variables from %exception applied though, so the best we get from SWIG itself is $symname, which isn't fully qualified, but does let you slip fnA into the error message.

You can use __PRETTY_FUNCTION__ to get something a little more explicit about what caused it, but that will now include "SwigDirector_" in the type - probably not a big deal:

%typemap(directorout,noblock=1,fragment=SWIG_AsVal_frag(int)) int (int swig_val) {
  int swig_res = SWIG_AsVal(int)($input, &swig_val);
  if (!SWIG_IsOK(swig_res)) {
    //%dirout_fail(swig_res, "$type");
    const std::string msg = std::string("in output of ") + __PRETTY_FUNCTION__ + " ($symname) value of type '""$type""'";
    Swig::DirectorTypeMismatchException::raise(SWIG_ErrorType(SWIG_ArgError(swig_res)), msg.c_str());
  }
  $result = %static_cast(swig_val,$type);
}

Where that gets tricky though is if you want to do the same for all possible return types - directorout is generated by a series of macros inside the SWIG library, so there's one of return primitives by value (including const/reference/etc. variants), one for strings, one for non-primitive types so it's quite a lot of work to do it everywhere. (You probably need to patch the core SWIG library to do that everywhere, properly).

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
0

A way to fix this for all return types is by hacking the SWIG generated wrapper file. I have done this by:

cp swigmodule_wrap.cxx x
cp myRaise.h swigmodule_wrap.cxx
sed 's/Swig::DirectorTypeMismatchException::raise(/myRaise(__PRETTY_FUNCTION__,swig_get_self(),/' < x >> swigmodule_wrap.cxx

i.e. replacing all places where the DirectorTypeMismatchException is raised by a call to a separate function, and supplying the __PRETTY_FUNCTION__ and swig_get_self() extra information to generate the needed extra information.

The separate function is supplied (as a macro) in the myRaise.h file:

#include <string>
#include <Python.h>

// ReplaceString taken from http://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
inline std::string ReplaceString(std::string subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while ((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
    return subject;
}

#define myRaise(fn, obj, err, msg) \
  { \
     std::string fns(fn); \
     std::string objs(PyString_AsString(PyObject_Repr(obj)));       \
     std::string msgs(std::string("when calling ")+ReplaceString(fns, std::string("SwigDirector_"), std::string(""))+std::string(" in ")+objs+std::string(" ")+std::string(msg)); \
     Swig::DirectorTypeMismatchException::raise(err, msgs.c_str()); \
  }

and the resulting diagnostic is:

$ python useit.py 
myA::fnA
catch
Traceback (most recent call last):
  File "useit.py", line 12, in <module>
    print("%d"% b.fnB(a) )
  File "/home/schuttek/tmp/swigmodule.py", line 109, in fnB
    def fnB(self, *args): return _swigmodule.B_fnB(self, *args)
TypeError: SWIG director type mismatch when calling virtual int A::fnA() in <__main__.myA; proxy of <Swig Object of type 'A *' at 0x7f3628d6b540> > in output value of type 'int'

where it can be seen that the __main__.myA class does not provide the int return value as needed for the virtual int A::fnA() function.

Klamer Schutte
  • 1,063
  • 9
  • 18