Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic exception handling: How to catch subclass exception?

Tags:

I have the following simple hierarchy of two C++ exceptions:

class LIB_EXP ClusterException : public std::exception { public:     ClusterException() { }     ClusterException(const std::string& what) { init(what); }     virtual const char* what() const throw() { return what_.c_str(); }     virtual ~ClusterException() throw() {}     virtual ClusterException* clone() { return new ClusterException(*this);  }  protected:     void init(const std::string& what) { what_ = what; } private:     std::string what_; };  class LIB_EXP ClusterExecutionException : public ClusterException { public:     ClusterExecutionException(const std::string& jsonResponse);     std::string getErrorType() const throw() { return errorType_; }     std::string getClusterResponse() const throw() { return clusterResponse_; }     virtual ~ClusterExecutionException() throw() {}     virtual ClusterExecutionException* clone() { return new ClusterExecutionException(*this);  }  private:     std::string errorType_;     std::string clusterResponse_; }; 

I then export them to Python with Boost-Python as follows. Note my use of the bases to make sure that the inheritance relationship is preserved in the translation:

class_<ClusterException> clusterException("ClusterException", no_init); clusterException.add_property("message", &ClusterException::what); clusterExceptionType = clusterException.ptr(); register_exception_translator<ClusterException>(&translateClusterException);  class_<ClusterExecutionException, bases<ClusterException> > clusterExecutionException("ClusterExecutionException", no_init); clusterExecutionException.add_property("message", &ClusterExecutionException::what)                          .add_property("errorType", &ClusterExecutionException::getErrorType)                          .add_property("clusterResponse", &ClusterExecutionException::getClusterResponse); clusterExecutionExceptionType = clusterExecutionException.ptr(); register_exception_translator<ClusterExecutionException>(&translateClusterExecutionException); 

Then the exception translation method:

static PyObject *clusterExceptionType = NULL; static void translateClusterException(ClusterException const &exception) {   assert(clusterExceptionType != NULL);    boost::python::object pythonExceptionInstance(exception);   PyErr_SetObject(clusterExceptionType, pythonExceptionInstance.ptr()); }  static PyObject *clusterExecutionExceptionType = NULL; static void translateClusterExecutionException(ClusterExecutionException const &exception) {   assert(clusterExecutionExceptionType != NULL);   boost::python::object pythonExceptionInstance(exception);   PyErr_SetObject(clusterExecutionExceptionType, pythonExceptionInstance.ptr()); } 

I created the following test C++ function that throws the exceptions:

static void boomTest(int exCase) {   switch (exCase) {     case 0:  throw ClusterException("Connection to server failed");              break;     case 1:  throw ClusterExecutionException("Error X while executing in the cluster");              break;     default: throw std::runtime_error("Unknown exception type");   } } 

Finally the Python test code that calls my C++ boomTest:

import cluster reload(cluster) from cluster import ClusterException, ClusterExecutionException  def test_exception(exCase):     try:         cluster.boomTest(exCase)      except ClusterException as ex:         print 'Success! ClusterException gracefully handled:' \             '\n message="%s"' % ex.message     except ClusterExecutionException as ex:         print 'Success! ClusterExecutionException gracefully handled:' \             '\n message="%s"' \             '\n errorType="%s"' \             '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)     except:         print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1])  def main():     print '\n************************ throwing ClusterException ***********************************************************************'     test_exception(0)     print '\n************************ throwing ClusterExecutionException **************************************************************'     test_exception(1)     print '\n************************ throwing std::runtime_error *********************************************************************'     test_exception(2)  if __name__ == "__main__":     main() 

Up to here it all works. However if I remove the ClusterExecutionException catch handler from Python then this exception will be caught and fallback to an unknown exception instead of being caught as its base ClusterException.

I have tried in Boost-Python while registering the exception translation of ClusterExecutionException to register it as its base ClusterException and then it gets caught "polymorphically" but then it won't be caught as a ClusterExecutionException. How can make it so that ClusterExecutionException gets caught as both ClusterException and ClusterExecutionException? I have tried of course registering this ClusterExecutionException exception as both ClusterException and ClusterExecutionException but it follows a last wins strategy, and only one works not both.

Is there any other way to solve this?

UPDATE 1: The wholy grail of this problem is to find out on the C++ side the type of the except Python statement e.g. except ClusterException as ex: which is unknown once inside the C++ side. The exception translate by Boost.Python will call the translate function that corresponds to the dynamic type of the exception and the Python catch static type is not known.

UPDATE 2: As suggested changing the Python code to the following i.e. adding print(type(ex).__bases__) gives:

def test_exception(exCase):     try:         cluster.boomTest(exCase)      except ClusterException as ex:         print(type(ex).__bases__)         print 'Success! ClusterException gracefully handled:' \             '\n message="%s"' % ex.message     except ClusterExecutionException as ex:         print(type(ex).__bases__)         print 'Success! ClusterExecutionException gracefully handled:' \             '\n message="%s"' \             '\n errorType="%s"' \             '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)     except:         print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1]) 

and the output:

************************ throwing ClusterException *********************************************************************** (<type 'Boost.Python.instance'>,) Success! ClusterException gracefully handled:  message="Connection to server failed"  ************************ throwing ClusterExecutionException ************************************************************** (<class 'cluster.ClusterException'>,) Success! ClusterExecutionException gracefully handled:  message="Error X while executing in the cluster"  errorType="LifeCycleException"  clusterResponse="{ "resultStatus": "Error", "errorType": "LifeCycleException", "errorMessage": "Error X while executing in the cluster" }" 

Meaning that the inheritance relationship is "seen" from Python. But the polymorphic handling still doesn't work.

UPDATE 3 This is the output of running VS dumpbin.exe:

The command I used is:

dumpbin.exe /EXPORTS /SYMBOLS C:\ClusterDK\x64\Debug\ClusterDK.dll > c:\temp\dumpbin.out 

and the relevant parts of the output:

Microsoft (R) COFF/PE Dumper Version 11.00.50727.1 Copyright (C) Microsoft Corporation.  All rights reserved.  Dump of file C:\ClusterDK\x64\Debug\ClusterDK.dll  File Type: DLL  Section contains the following exports for ClusterDK.dll  00000000 characteristics 5A1689DA time date stamp Thu Nov 23 09:42:02 2017     0.00 version        1 ordinal base       78 number of functions       78 number of names  ordinal hint RVA      name        8    7 00004485 ??0ClusterException@cluster@@QEAA@AEBV01@@Z = @ILT+13440(??0ClusterException@cluster@@QEAA@AEBV01@@Z)       9    8 00001659 ??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+1620(??0ClusterException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)      10    9 00001F1E ??0ClusterException@cluster@@QEAA@XZ = @ILT+3865(??0ClusterException@cluster@@QEAA@XZ)      11    A 00004D4F ??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z = @ILT+15690(??0ClusterExecutionException@cluster@@QEAA@AEBV01@@Z)      12    B 000010AA ??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+165(??0ClusterExecutionException@cluster@@QEAA@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)      27   1A 000035D0 ??1ClusterException@cluster@@UEAA@XZ = @ILT+9675(??1ClusterException@cluster@@UEAA@XZ)      28   1B 00003C7E ??1ClusterExecutionException@cluster@@UEAA@XZ = @ILT+11385(??1ClusterExecutionException@cluster@@UEAA@XZ)      37   24 00002BD5 ??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+7120(??4ClusterException@cluster@@QEAAAEAV01@AEBV01@@Z)      38   25 000034D1 ??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z = @ILT+9420(??4ClusterExecutionException@cluster@@QEAAAEAV01@AEBV01@@Z)      46   2D 000D2220 ??_7ClusterException@cluster@@6B@ = ??_7ClusterException@cluster@@6B@ (const cluster::ClusterException::`vftable')      47   2E 000D2248 ??_7ClusterExecutionException@cluster@@6B@ = ??_7ClusterExecutionException@cluster@@6B@ (const cluster::ClusterExecutionException::`vftable')      52   33 00004BB5 ?clone@ClusterException@cluster@@UEAAPEAV12@XZ = @ILT+15280(?clone@ClusterException@cluster@@UEAAPEAV12@XZ)      53   34 00004D31 ?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ = @ILT+15660(?clone@ClusterExecutionException@cluster@@UEAAPEAV12@XZ)      61   3C 00001D43 ?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ = @ILT+3390(?getErrorType@ClusterExecutionException@cluster@@QEBA?AV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ)      69   44 0000480E ?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z = @ILT+14345(?init@ClusterException@cluster@@IEAAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z)      78   4D 000032FB ?what@ClusterException@cluster@@UEBAPEBDXZ = @ILT+8950(?what@ClusterException@cluster@@UEBAPEBDXZ)  Summary      4000 .data     5000 .idata    12000 .pdata    54000 .rdata     2000 .reloc     1000 .rsrc    C9000 .text     1000 .tls 
like image 978
SkyWalker Avatar asked Sep 26 '17 07:09

SkyWalker


2 Answers

My python skills are rusty and I haven't tested this, so this may need further improvements, but try adding the exception translation method to handle the base class exception type:

static PyObject *clusterExecutionAsClusterExceptionType = NULL; static void translateClusterExecutionAsClusterException(ClusterException const &exception) {    ClusterExecutionException* upcasted = dynamic_cast<ClusterExecutionException*>(&exception);   if (upcasted)   {     assert(clusterExecutionAsClusterExceptionType != NULL);     boost::python::object pythonExceptionInstance(*upcasted);   PyErr_SetObject(clusterExecutionAsClusterExceptionType, pythonExceptionInstance.ptr());   } }  register_exception_translator<ClusterException>(&translateClusterExecutionAsClusterException); 
like image 66
StarShine Avatar answered Oct 13 '22 06:10

StarShine


I don't know about your C++ code. But there is small problem with your python code. Catch ClusterExecutionException before ClusterException. You should always put child exception handler before base exception.

In test_exception if ClusterExecutionException raised it will be catch by ClusterException before it could reach ClusterExecutionException.

code should be as below code

def test_exception(exCase):     try:         cluster.boomTest(exCase)      except ClusterExecutionException as ex:         print 'Success! ClusterExecutionException gracefully handled:' \             '\n message="%s"' \             '\n errorType="%s"' \             '\n clusterResponse="%s"' % (ex.message, ex.errorType, ex.clusterResponse)     except ClusterException as ex:         print 'Success! ClusterException gracefully handled:' \             '\n message="%s"' % ex.message     except:         print 'Caught unknown exception: %s "%s"' % (sys.exc_info()[0], sys.exc_info()[1]) 

now do I have tried in Boost-Python while registering the exception translation of ClusterExecutionException to register it as its base ClusterException , what you have mentioned in question.

like image 20
surya singh Avatar answered Oct 13 '22 06:10

surya singh