Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A mechanism to ensure exception propagation when mixing C and C++ code

Tags:

c++

c

exception

I'm not asking if it is safe for a C++ exception to propagate through C code, nor what happens when such thing occurs. I have read the following questions in SO(1, 2, 3) and this FAQ. I'm asking how to proceed to :

  • Avoid leaking any C++ exception toward C code (this implies catching all exceptions in the C++ land before calling C code)
  • Also be able to catch the exceptions outside the C code (in a higher C++ code).

Let me illustrate my idea :

Say libfoo is a C library, that I want to use in my bar C++ program. libfoo needs a callback function foo_callback that I must provide. The functions and methods used in my callback may throw an exception, so I wrote :

void my_callback(void)
{
    try
    {
        // Do processing here.
    }
    catch(...)
    {
        // Catch anything to prevent an exception reaching C code.
        // Fortunately, libfoo provides a foo_error function to
        // signal errors and stop processing.
        foo_error() ;
    }
}

And then I use my callback as shown below :

// The bar program.
int main()
{
    // Use libfoo function to set the desired callback
    foo_set_callback(&my_callback) ;

    // Start processing. This libfoo function uses internally my_callback.
    foo_process() ;

    // Check for errors
    if( foo_ok() )
    {
        // Hurray ! 
    }
    else
    {
        // Something gone wrong.
        // Unfortunately, we lost the exception that caused the error :(
    }
}

What I want is to be able to catch the exceptions thrown from my_callback in the main function, without having the exception propagating through libfoo (Yes, it's a sort of quantum exception experimenting quantum tunnelling through C code).

So the code I would love to use :

void my_callback(void)
{
    try
    {
        // Do processing here.
    }
    catch(...)
    {
        // Catch anything to prevent an exception reaching C code.
        // We save the exception using (the magic) ExceptionHolder.
        ExceptionHolder::Hold() ;

        // Call foo_error function to signal errors and stop processing.
        foo_error() ;
    }
}

// The bar program.
int main()
{
    // Use libfoo function to set the desired callback
    foo_set_callback(&my_callback) ;

    try
    {
        // Start processing. This libfoo function uses internally my_callback.
        foo_process() ;

        // Once gone out of the C land, release any hold exception.
        ExceptionHolder::Release() ;
    }
    catch(exception & e)
    {
        // Something gone wrong.
        // Fortunately, we can handle it in some manner.
    }
    catch( /*something else */ )
    {
    }
    // ...
}

Given the following constraints :

  • libfoo is source-closed, written in C and provided in compiled format from a vendor. Tests conducted on the library showed that exceptions cannot propagate through it. I have no access to the source files nor I can obtain a compiled version that supports exceptions.
  • The callback function makes extensive use of C++ code that uses exceptions. All the error handling is built around the exception mechanism. In no way I can simply use the code that swallows all exceptions.
  • No multithreading involved.
  • No c++0x support.

My questions :

  • Is it already solved by some library or some C++ magic (like boost), or even c++0x ?
  • If not, how can I write an ExceptionHolder that works with any exception type ? I'm confortable with C++ but I haven't found a way to write a reliable and easy to use ExceptionHolder that works with any exception type.

Many thanks for any advice !


EDIT : I added a response with a little implementation of the exception holding/releasing mechanism. All critics or propositions are welcome.

like image 982
overcoder Avatar asked Oct 15 '11 23:10

overcoder


4 Answers

I believe that boost.exception has a mechanism that could be adapted to be useful for your purpose. See here for inspiration:

http://www.boost.org/doc/libs/1_47_0/libs/exception/doc/tutorial_exception_ptr.html

It seems to be intended for communicating their special exception type between threads, but I think it is pretty much the same thing - stop an exception from propagating across the thread boundary or C code boundary, stash a copy of it away behind a pointer, then retrieve it later via the pointer on the other side of the boundary and optionally rethrow it.

I'm not sure how feasible it is to make your own exceptions derive from boost's magical exception type, but if I remember correctly from tooling around with it a year or so ago, it's fairly reasonable.

like image 144
ben Avatar answered Oct 03 '22 05:10

ben


In c++11 you can use current_exception() in your callback to set a variable of type exception_ptr, and then when your calling code is informed of the error by the library use rethow_exception().

This is the same mechanism used to propagate exceptions across threads in c++0x.

Example:


void foo();                      // C++
extern "C" void bar(void *exp);  // C
void baz(void *exp);             // C++

void foo() {
    std::exception_ptr exp;
    bar(&exp);
    if (exp) {
      std::rethrow_exception(exp);
    }
}

extern "C" void bar(void *exp) {
    baz(exp);
}

void baz(void *exp) {
    try {
        // some code that might throw an exception
        // ...
    } catch (...) { // catch all exceptions, so as to avoid leaking any into the calling C code.
        // capture the exception and make it available to the C++ code above the C code.
        static_cast<std::exception_ptr*>(exp) = std::current_exception();
    }
}
like image 25
bames53 Avatar answered Oct 03 '22 03:10

bames53


Edit : You can use fungo, a better implementation of the idea I described below. From its author :

fungo is a C++ library that is designed for those of us stuck using older C++ implementations that do not yet support std::exception_ptr.

In other words, fungo allows you to make a fair attempt at storing and later re-throwing exceptions caught by a catch(...) block. This is useful for propagating exceptions across thread-joins or over C/C++ boundary interfaces.

I'll mark this as an answer.


As I mentioned in my question, I cannot use C++0x/11 features for now (using new features isn't planned for now), and I will present here what I have done so far :

Exceptions have a lifetime that spans across the try-catcher block. In order to save an exception, one must create a copy on the heap. We get rid of the copy when re-throwing the exception. I wrote an exception holder interface :

class ExceptionHolderInterface
{
    public :
      ExceptionHolderInterface(void) ;
      virtual ~ExceptionHolderInterface(void) ;
      
      /* For holding an exception. To be called inside a catch block.*/
      virtual void  Hold(void)    = 0 ;

      /* For releasing an exception. To be called inside a try block.*/
      virtual void  Release(void) = 0 ;
    private :
} ;

This is a type independent class. The exception type is introduced using templates :

template<typename ExceptionType>
class ExceptionHolder : public ExceptionHolderInterface
{
    public :
      ExceptionHolder(void) ;
      virtual ~ExceptionHolder(void) ;

      virtual void Hold(void)
      {
           try
           {
               throw ;
           }
           catch(const ExceptionType & e)
           {
               exception.reset(new ExceptionType(e)) ;
           }
      }

      virtual void Release(void)
      {
          if(exception.get())
          {
              throw ExceptionType(*exception.get()) ;
          }
      }
    private :
      std::auto_ptr<ExceptionType> exception ;
      
      // declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;

I removed a bunch of tests/optimizations/verifications, I kept the main idea. So far we have an exception holder for one type, so we can build an exception store that can hold many types at a time.

class ExceptionStore
{
    public :
      ExceptionStore(void) ;
      ~ExceptionStore(void)
      {
          for(Iterator holder = exception_holders.begin() ; holder != exception_holders.end() ; ++holder)
          {
              delete (*holder) ;
          }
      }

      // Add an exception type to handle
      template<typename ExceptionType>
      void AddExceptionHolder(void)
      {
          exception_holders.push_back(new ExceptionHolder<ExceptionType>()) ; 
      }

      // Try to hold an exception using available holders. Use this inside a catch block.
      void Hold(void)
      {
          Iterator holder = exception_holders.begin() :
          while(holder != exception_holders.end())
          {
              try
              {
                  (*holder)->Hold() ;
                  break ;
              }
              catch(...)
              {
                  ++holder ;
              }
          }
      }

      // Try to release any hold exception. Call this inside a try-block.
      void Release(void)
      {
          Iterator holder = exception_holders.begin() :
          while(holder != exception_holders.end())
          {
              (*holder++)->Release() ;
          }
      }

    private :
      std::list<ExceptionHolderInterface *>  exception_holders ;
      typedef std::list<ExceptionHolderInterface *>::iterator Iterator ;
      
      // Declare the copy-constructor and the assignment operator here to make the class non-copyable
} ;

I can use the exception store as shown below :

// I made a global ExceptionStore just to keep the example simple.
ExceptionStore exception_store ;

void callable_from_c_code(void)
{
    // Normally, we should retrieve the exception store in some manner.
    
    try
    {
        // Do processing here. Exceptions may be thrown.
    }
    catch(...)
    {
        // Something wrong happened. Let's store the error for later.
        exception_store.Hold() ;
    }

    // Exceptions do not propagate to C code. 
} 

int main(int, char * [])
{
    // First, set the exception types we want to handle. The handling is done in
    // the same order as below.
    exception_store.AddExceptionHolder<std::runtime_error>() ;
    exception_store.AddExceptionHolder<std::logic_error>() ;
    exception_store.AddExceptionHolder<MyFancyException>() ;

    // Somehow invoke some C code that uses `callable_from_c_code`
    use_some_c_library_with_callback(&callable_from_c_code) ;

    // Handle any caught exception
    try
    {
        exception_holder.Release() ;
    }
    catch(std::exception &)
    {
        // Something gone wrong ...
    }
    catch(MyFancyException &)
    {
        // Nothing fancy despite the name. We have problems here ...
    }
}

This is very basic, and there might be some unexpected scenarii that are not handled by this example. If an exception with a type not declared using AddExceptionHolder is throw, you have two possibilities :

  • A holder for a base type is added to the store, so the exception will be caught and sliced, keeping only a copy of the base type.
  • No holder fits the exception, and it is simply lost. Nothing leaks to the C land.

For now, I prefer using this solution to the more tested/used/verified boost::enable_current_exception because I can't afford refactoring the whole C++ code to surround all throw sites with boost::enable_current_exception(...).

Anyway, the std::exception_ptr seems to be the perfect solution, and I will replace the above code once I can move to the new C++ standard.

like image 35
overcoder Avatar answered Oct 03 '22 05:10

overcoder


If you are not up for a major refactor, are you able to identify a specific subset of exception types that might be thrown?

If so, you can probably write a container to store copies of each of these specifically, and then retrieve them on return from the C-call. This requires being able to copy the exception type.

Alternatively, consider what the ultimate resolution of the various exceptions is (if you can), and pack that up into some object for processing on return. This only works if you own enough of the codebase to know what potential handling the exceptions may generate.

A hydrid model would trap the exceptions, create a new exception based on what was found, and then throw that on return from the C lib.

like image 33
Keith Avatar answered Oct 03 '22 04:10

Keith