Exceptions

Options

Exceptions are raised if an error is detected. Seldon is able to check for several mistakes:

  • SELDON_CHECK_IO: checks input/output operations on disk.
  • SELDON_CHECK_MEMORY: checks memory allocations and deallocations.
  • SELDON_CHECK_DIMENSIONS: checks that the dimensions of involved structures are compatible. Notice that there are methods in which the compatibility is not checked however.
  • SELDON_CHECK_BOUNDS: checks that indices are not out of range.

To enable SELDON_CHECK_MEMORY, for example, put (before #include <Seldon.hxx>):

#define SELDON_CHECK_MEMORY

Alternatively, there are debug levels:

  • SELDON_DEBUG_LEVEL_0: nothing is checked.
  • SELDON_DEBUG_LEVEL_1: equivalent to SELDON_CHECK_IO plus SELDON_CHECK_MEMORY.
  • SELDON_DEBUG_LEVEL_2: equivalent to SELDON_DEBUG_LEVEL_1 plus SELDON_CHECK_DIMENSIONS.
  • SELDON_DEBUG_LEVEL_3: equivalent to SELDON_DEBUG_LEVEL_2 plus SELDON_CHECK_BOUNDS.
  • SELDON_DEBUG_LEVEL_4: equivalent to SELDON_DEBUG_LEVEL_3.

In practice, it is advocated to choose SELDON_DEBUG_LEVEL_4 in the development stage and SELDON_DEBUG_LEVEL_2 for the stable version. Indeed SELDON_DEBUG_LEVEL_4 slows down the program but checks many things and SELDON_DEBUG_LEVEL_2 should not slow down the program and ensures that it is reasonably safe.

Development stage:

#define SELDON_DEBUG_LEVEL_4

Stable version:

#define SELDON_DEBUG_LEVEL_2

Exceptions raised

The objects that may be launched by Seldon are of type: WrongArgument, NoMemory, WrongDim, WrongIndex, WrongRow, WrongCol, IOError and LapackError. They all derive from Error. They provide the method What that returns a string explaining the error, and the method CoutWhat that displays on screen this explanation. Therefore, to catch an exception raised by Seldon, put:

catch(Seldon::Error& Err)
{
Err.CoutWhat();
}

Two macros are defined to help:

TRY:

#define TRY try {

END:

#define END                                                     \
  }                                                             \
    catch(Seldon::Error& Err)                                   \
      {                                                         \
        Err.CoutWhat();                                         \
        return 1;                                               \
      }                                                         \
    catch (std::exception& Err)                                 \
      {                                                         \
        cout << "C++ exception: " << Err.what() << endl;        \
        return 1;                                               \
      }                                                         \
    catch (std::string& str)                                    \
      {                                                         \
        cout << str << endl;                                    \
        return 1;                                               \
      }                                                         \
    catch (const char* str)                                     \
      {                                                         \
        cout << str << endl;                                    \
        return 1;                                               \
      }                                                         \
    catch(...)                                                  \
      {                                                         \
        cout << "Unknown exception..." << endl;                 \
        return 1;                                               \
      }

It is advocated that you enclose your code (in the main function) with TRY; and END;:

int main(int argc, char** argv)
{
  TRY;

  // Here goes your code.

  END;

  return 0;
}

Exceptions and debugging

Suppose your code contains an error and raises an exception. You probably want to identify the function that raised the exception. The error message should contain the name of the function. But you probably want to know the exact line where the error occurred and the sequence of calls. Then, you have two options, using a debugger.

One option is to place a breakpoint in Error::Error(string function = "", string comment = "") (see file share/Errors.cxx) because this constructor should be called before the exception is actually raised.

Another option, more convenient because no breakpoint is to be placed, is to define SELDON_WITH_ABORT. With that flag activated, if a Seldon exception is raised, the program will simply abort. The call stack is then at hand. See the example below, using gdb under Linux. The program error.cpp demonstrates the technique:

#define SELDON_WITH_ABORT
#define SELDON_DEBUG_LEVEL_4 // which checks bounds.
#include "Seldon.hxx"
using namespace Seldon;

int main(int argc, char** argv)
{
  TRY;

  IVect V(3);
  cout << "Bad access: " << V(3) << endl;

  END;

  return 0;
}

The error and the calls sequence are easy to obtain:

$ g++ -o error -g -I ~/src/seldon error.cpp
$ ./error                                        
ERROR!                                                     
Index out of range in Vector<VectFull>::operator().        
   Index should be in [0, 2], but is equal to 3.           
Aborted
$ gdb ./error
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu"...
(gdb) run
Starting program: /tmp/error 
ERROR!
Index out of range in Vector<VectFull>::operator().
   Index should be in [0, 2], but is equal to 3.

Program received signal SIGABRT, Aborted.
0x00007f4b7e136fb5 in raise () from /lib/libc.so.6
(gdb) up
#1  0x00007f4b7e138bc3 in abort () from /lib/libc.so.6
(gdb) up
#2  0x000000000040308e in WrongIndex (this=0x1a83370, function=
        {static npos = 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7fff86e3e0f0 "H?\001"}}, comment=
        {static npos = 18446744073709551615, _M_dataplus = {<std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>}, _M_p = 0x7fff86e3e100 "??\001"}}) at /home/mallet/src/seldon/share/Errors.cxx:206
206	    abort();
(gdb) up
#3  0x0000000000404888 in Seldon::Vector<int, Seldon::VectFull, Seldon::MallocAlloc<int> >::operator() (this=0x7fff86e3e1c0, i=3)
    at /home/mallet/src/seldon/vector/Vector.cxx:440
440	      throw WrongIndex("Vector<VectFull>::operator()",
(gdb) up
#4  0x000000000040364b in main (argc=1, argv=0x7fff86e3e2d8) at error.cpp:11
11	  cout << "Bad access: " << V(3) << endl;

Notes

Apart from exceptions, two useful macros are defined to ease the debugging activities:

ERR:

#define ERR(x) cout << "Hermes - " #x << endl

DISP:

DISP(x) cout << #x ": " << x << endl

In a code:

ERR(5);
ERR(5a);
int i = 7;
DISP(i + 1);
ERR(Last line);

returns on screen:

Hermes - 5
Hermes - 5a
i + 1: 8
Hermes - Last line