Jafar
|
Jafar adopts an error management system based on exceptions. The exception mechanism is a tool to report an error. When an error is detected, an exception is thrown and the execution of the program enters catch()
control sequence. All the errors are defined as exceptions and all derive from the jafar::kernel::Exception class. An operator<<
is defined to print standard debug information when the exception is catched:
try { // ... } catch(jafar::kernel::Exception& e) { std::cerr << e << std::endl << std::flush; }
In Jafar, distinction between several kinds of errors is made:
A given module can use common errors or errors defined in this given module. A function defined in a module must not throw an exception defined in an other module (except a jafar::kernel::JafarException). This is not enforced by any compiler, but is a rule to enable traceability of errors. Of course, if module A
uses functions defined in module B
, relevant functions of module A
can propagate exceptions thrown in module B
if they cannot be handled in module A
.
Moreover, each exception must have an ID attribute. This attribute is used to easily define new errors without having to write a whole new exception class when it is not needed. This ID is also useful to map errors between Jafar and other robotic softwares (GenoM), or to report errors to a supervisor process (OpenPRS).
These sections describe the glue with Tcl (Exceptions, Swig and Tcl) and with GenoM (secGenomError).
The developper will carefully choose the kind of exception he needs. Examples can be found in the helloworld module.
The C++ keyword throw()
declares which exceptions a method may throw (if unsure have a look at a C++ manual, Developper useful links). Use it with care, we recommend using it only with exceptions constructors and destructors to declare they do not throw any exception.
The corresponding exception IDs are defined in jafar::kernel::JafarException :
enum ExceptionId {
ASSERT,
PRECONDITION,
POSTCONDITION
INVARIANT,
RUN_TIME,
IO_STREAM,
NUMERIC
};
These errors are defined in the jafar::kernel::JafarException class. To use them #include
"kernel/jafarException.hpp"
.
These common errors have been defined according to the programmation by contract paragdim. These ideas are well-known and implemented in java by JML for example. Jafar standard exceptions are build on three main concepts:
These concepts are well-adapted to object oriented programming, but can also be used in functionnal programmation, substituting method with function, and thinking of invariant on global variables.
And for ease of use three macros are also defined (in jafarMacro.hpp, already included by jafarException.hpp):
which will throw the corresponding exception (along with the given message) if the predicate does not hold to TRUE
.
Further more, documentation on the contract of an entity can be added using doxygen special commands (Documenting errors).
This error should be used when a condition must be verified and this condition does not fit in the programmation by contract paragdim.
For ease of use a macro is defined:
An other standard error is called RUN_TIME
. It can be used to throw an error when the execution end up in a forbidden place, for example in the default
of a switch/case.
For ease of use a macro is defined:
Input/Output operations must always be handled with care since they can fail. STL offers the stream mechanism, but this is up to the user to check if the operation succeded. In jafar developpers must do these verifications, and in many cases, a detected error must be propagated using an exception. For this purpose, a jafar::kernel::JafarException with ID IO_STREAM is defined, it can be easily thrown with the macro:
Example:
void T3D::read(const std::string& fileName_) { std::ifstream file(fileName_.c_str()); JFR_IO_STREAM(file, "error while opening file " << fileName_); try { file >> (*this); } catch(jafar::kernel::Exception& e) { JFR_TRACE(e," (reading file:" << fileName_ << ")"); throw; } file.close(); }
When a module is created using the jafar-module command (New module), it comes with a standard exception class ModuleException
defined in moduleException.hpp
. The class ModuleException
derives from jafar::kernel::Exception as required, and adds an ID attribute. ID is an enum called ExceptionId
, and this enum is initially empty. To add a simple error, the developper only has to add a new enum value in ExceptionId
, that's all !
To throw such an exception, two macros are defined in (jafarMacro.hpp):
Further more, it is recommended to modify the exceptionIdToString()
method to take into account the new enum value. This method is called when the message of an exception is built (if exceptionIdToString()
is not modified, only an integer corresponding to the enum is printed).
If the developper needs a more complex exception, for example extra attributes or extra methods, a new exception class can be defined. This must be done on a per module basis, and the new class must derive form ModuleException
class. Also a corresponding enum value must be added to ModuleException::ExceptionId
, and ModuleException::exceptionIdToString()
method should be modified accordingly.
If the constructor signature of the exception match the standard one, JFR_ERROR(ExceptionName, id, message) can still be used.
All the macros described above are defined in jafarMacro.hpp (module Module kernel). For ease of use, the argument called message
is in fact given to a std::ostringstream
to build a std::string
. In C++, this is what I think to be the good way of building a complex string. The developper can then use the powerfull stream mechanism to build error messages, including value of relevant variables.
An exception is an object which goes up the calling stack until an appropriate try
{} catch
block is found. What this calling stack looks like is a usefull information. For this purpose, jafar::kernel::Exception::callingStack attribute has been added. It can be filled in by the user using JFR_TRACE macro.
The general method is to catch the exception, add information to the calling stack attribute, and throw again the exception. Convenient macros are defined (in jafarMacro.hpp) which do this for you :
Be carefull when you enclose a local variable declaration in such a block, since the block defines a restricted scope.
The macro JFR_TRACE is intended to be used in a catch()
{...} block which catches a jafar::kernel::Exception (or an instance of one of its child class). It is used to add information to the trace, along with a message.
Example:
void T3D::read(const std::string& fileName_) { std::ifstream file(fileName_.c_str()); JFR_IO_STREAM(file, "error while opening file " << fileName_); try { file >> (*this); } catch(jafar::kernel::Exception& e) { JFR_TRACE(e," (reading file:" << fileName_ << ")"); throw; } file.close(); }
will output, when file >> (*this); throws an exception:
tclsh8.4 [~/tmp]$t read "toto.t3d" ** Exception from jafar module cine Id: JafarException::IO_STREAM src/t3dEuler.cpp:409: T3DEuler::get: bad format trace: src/t3dEuler.cpp:433 src/t3d.cpp:110 (reading file: toto.t3d)
The compilation flag JFR_NDEBUG
disables all these macros (Customize the compilation process: Jafar specific flags).
The simple class jafar::helloworld::HelloWorld defines two new errors (a simple one, and a more complexe), and also makes use of a precondition check. Have a look at:
When a Jafar module is used within a tcl shell, what happens to exceptions ?
There can be two different cases whether or not the exception is catched inside the module. If the exception goes up the calling stack until the tcl function, then the catch clause defined in moduleException.i
inside %exception
starts a default exception treatment (a complete debug message is printed). The default moduleException.i
is already enough to catch all the exceptions, even the user-defined errors. It looks like:
/* $Id:$ */ %{ #include <iostream> #include <exception> #include "kernel/jafarException.hpp" #include "_jfr_module_/_jfr_module_Exception.hpp" %} #if defined(SWIGTCL) /* Tcl exceptions handler. * * You can customize this handler and add catch blocks to handle your * own exceptions. Be aware that order in catch clauses is meaningfull * because of JafarException heritage tree. */ %exception { try { $action // Gets substituted by actual function call } catch (jafar::kernel::Exception& e) { std::cout << e; SWIG_fail; } catch (std::exception& e) { std::cout << "** std::exception: **" << std::endl; std::cout << e.what() << std::endl; SWIG_fail; } catch(...) { std::cout << "** unknown exception **" << std::endl; SWIG_fail; } } #elif defined(SWIGRUBY) %import "kernel/jafarException.hpp" %include "_jfr_module_/_jfr_module_Exception.hpp" JAFAR_EXCEPTION_SUPPORT JAFAR_RUBY_EXCEPTION(_jfr_Module_, _jfr_Module_Exception); %exception { try { $action } JAFAR_CATCH_MODULE_EXCEPTION(_jfr_module_, _jfr_Module_Exception) JAFAR_CATCH_GENERIC } #endif
It deals with all the exceptions defined in jafar (jafar::kernel:Exception), the exceptions defined in the Standard Template Library (std::exception), and does quite nothing with all other exceptions (...). If one would like to add a special treatment for its own exceptions, or for exceptions defined in third-party libraries, a catch()
clause can be easily added. Be aware that the order of the catch()
clauses is meaningfull (read a C++ manual if you are not sure see C++ STL).
For those who wants to know exceptions a little bit more deeper:
Generated on Wed Oct 15 2014 00:37:30 for Jafar by doxygen 1.7.6.1 |