Extensible Exception Library

Extensible Exception Library (EEL) is a simple framework for C++11 exception handling.

The concepts behind are the following:

  • compatibility with the existing std::exception(s);
  • C++ exceptions as a extendible generic payload for transporting any kind of informations (eel::Attributes)
  • simple mechanism for extending the payload during the excpetion handler backtrace (eel::Exception::operator << ())
  • coherent semantic in the so resulting exceptions for the final user (Message IDs templates and eel::Exception::what() overrided method)
  • low-level diagnostic informations provided 'on demand' (eel::Exception::dump() method);

Features:

  • compatibility with std::exception;
  • extensible attribute setting mechanism;
  • message IDs template based;

Class hierarchy

Each not final class is virtually derived from an already existing standard one, preserving its semantic and sytnax compatibility.

The semantic of the exception is similar to the standard homologous ones:

  • eel::Exception - generic exception class;
    • eel::RuntimeError - errors that can only be detected during runtime;
      • eel::DlError - errors  occurred from dlopen(), dlsym() or dlclose();
      • eel::Errno - errors as returned by std::errno;
    • eel::LogicError - errors presumably detectable before the program executes;
      • eel::AssertViolation - violation of a logical assertion or invariant;
      • eel::PreconditionViolation - failure in a condition or predicate that must always be true just prior to the execution of some section of code or before an operation in a formal specification;
      • eel::PostconditionViolation - failure in a condition or predicate that must always be true just after the execution of some section of code or after an operation in a formal specification;
      • eel::UnsupportedFeature - attempt to use a not supported feature;

Exception's payload

Exceptions handling in C++ is a mechanism able to move - when special conditions modify the normal flow of a program - information from the place in which the exception is thrown to the block of code in which the program is resumed. In the standard exception implementation:

  1. the payload is statically defined by the exception class definition;
  2. there is no uniform interface able to enrich the payload independently from the type of the exception;

These produce the following situations:

  • An outer code catching the exception can complete any eventually missing information in the exception at the cost of the implementation of as many catch blocks as - presumably - are the exception types thrown by the inner code; 
  • The exception's payload cannot be extended anyway beyond what statically defined at compilation time:

The eel::Exception payload implemented is a collection of named attributes so basically:

  • extensible during runtime;
  • type-independent;
  • with a common interface able to add/retrieve the attributes regardless the exception type;

Exception IDs

In the standard library the textual representation of the exception is based on the what() method, usually reporting a free string passed through constructor, so statically defined inside the code throwing the exception. Different instances of the same exception class produce completely different messages, loosing efficacy when dumped on a log. On EEL every class defines a set of IDs actually indexing the string reported by the what() method to a fixed template string, that a 'lazy' what() implementation actually populates with the named attributes present in the  payload.


An instance of eel::RuntimeError, thrown with the eel::RuntimeError::Id::RTMERR001 will use the corresponding template string:


RTMERR001 RuntimeError error: ${RTMERR_NUM} - ${RTMERR_DESC}.

 

Assuming - at throw-time - a value of for errno equal to ERANGE, the constructor will add the corresponding attributes so that a later invocation of the what() method inside a catch block will replace every ${ATTRIBUTE NAME} in the template with the corresponding attribute value: 

 

RTMERR001 RuntimeError error: 34 - Numerical result out of range.

 

The IDs add coherence and standard notation to displayed message delaying the composition at what() invocation time (so after stack unwinding has occurred, and presumably, released some resources) and reducing the risk of unwanted nested exceptions. Note also that the corresponding replace phase inside eel::Exception::what() is protected with a catch block.

 

Diagnostic

As already mentioned, every catch block can add whatever attributes of whatever type. This can be used to add any diagnostic information in whatever point of the program (source line, file name and so on). The what() method will use only the attributes referred inside the corresponding template, not impacting on the 'canonical' representation of the exception itself. In order to have a dump of all the contained information of the Exception payload, a catch block can eventually use the eel::Exception::dump() method.

Example code

The following example shows briefly the forementioned features:

#include <dlfcn.h>
#include <iostream>

#include <eel/eel.h>

using namespace std;
using namespace eel;

void inner_code(const char *soname,int soflags) {
  void *p=dlopen(soname,soflags);
  if (!p) {
    // throw a DlError instance initializing its attributes with the macros supplied
    // EELA_API --> eel::attribute::api(__PRETTY_FUNCTION__)
    throw DlError(EELA_API);
  } else {
    dlclose(p);
    // throw a RuntimeError instance initializing its attributes with the macros supplied
    // EELA_API           --> eel::attribute::api(__PRETTY_FUNCTION__)
    throw RuntimeError(EELA_API);
  }
}

void outer_code() {
  const char *name="/path/to/impossible.so";
  int flags=RTLD_LAZY;
  try {
    inner_code(name,flags);
  } catch (eel::Exception &error) {
    // in case of error let's add other attributes
    // EELA_FILE --> eel::attribute::file(__FILE__)
    // "SONAME"  --> custom attribute of type std::string
    // "SOFLAGS" --> custom attribute of type int
    throw error << EELA_FILE << eel::make_attribute("SONAME",name) << eel::make_attribute("SOFLAGS",flags);
  }
}

int main(int argc,const char *argv[]) {
  try {
    outer_code();
  } catch (const std::exception &error) { // let's trap standard exception as usual
    cerr << "*** ERROR ***\n" << error.what() << endl;
    if (argc>1 && strcmp(argv[1],"--debug")==0) {
      if (const eel::Exception *eerror=dynamic_cast(&error)) {
        // use the dump() method for printing the whole exception payload
        cerr << "\n*** DIAGNOSTIC ***\n"
	     << eerror->dump() << endl;
      }
    }
  }
}

The produced output will be:


*** ERROR ***
DLLERR000 /path/to/impossible.so: cannot open shared object file: No such file or directory.

 

if the example is invoked with the --debug parameter we'll have also:

 

*** DIAGNOSTIC ***
[EXCEPT_API ] std::string 'void inner_code(const char*, int)'
[EXCEPT_FILE ] std::string '/home/giampa/projects/kdevelop/eel/examples/simple.cpp'
[RTMERR_DESC ] std::string '/path/to/impossible.so: cannot open shared object file: No such file or directory'
[SOFLAGS ] int '1'
[SONAME ] std::string '/path/to/impossible.so'

 

References

Sourceforge download page

Error and Exception Handling

Boost Exception

System error support in C++0x

Joomla templates by a4joomla