[cxx-abi-dev] C++0x std::rethrow_exception, data races and the Itanium ABI
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cxx-abi-dev] C++0x std::rethrow_exception, data races and the Itanium ABI



Hi,

With the current draft of the upcoming C++0x standard, it is unclear whether the exception thrown by std::rethrow_exception is the same exception object that was originally thrown, or a copy thereof. Indeed, different implementations do different things: gcc rethrows the same exception object, and MSVC2010 throws a copy.

I believe that this is a mistake; std::rethrow_exception should always throw a copy. If it rethrows the same exception then the same exception object may now become active in multiple threads. This then exposes the **handlers** to data races should they catch by reference and call any member functions that modify the exception object.

One use case I have seen for this is to add call stack information to an exception for logging purposes. e.g.

void g();

struct my_exception
{
    void add_caller(std::string const& function_name,int arg1,int arg2);
};

void f(int arg1,int arg2){
   try{
      g();
   catch(my_exception& e)
   {
      e.add_caller("f(int,int)",arg1,arg2);
      throw;
   }
}

Under C++03, any exception thrown by g() must originate in this thread, so there is no possibility of a data race in f(). Under C++0x we must contend with the possibility that the exception originated in another thread. e.g.

std::mutex m;
std::exception_ptr ep;

void g()
{
    std::lock_guard<std::mutex> lk(m);
    if(ep)
        std::rethrow_exception(ep);
}

If multiple threads call g() they may thus each rethrow the same exception. If this truly is the **same** exception object (and not a copy) then callers such as f() have now been exposed to a data race, **without changing f()**.

Also, under the latitude provided by the C++0x draft, this behaviour may vary from compiler to compiler. I can write some code that is race-free under MSVC2010, but if I then recompile it with gcc then I have a data race, **without any indication from the compiler**.

I am aware that the reason for gcc's behaviour in this case is that the Itanium ABI does not provide the necessary information to copy an exception object, which is why I am posting here. I would like to propose changing the Itanium ABI to provide the necessary information to copy the exception object, **whilst remaining backwards compatible**.

Based on the documentation at http://www.codesourcery.com/public/cxx-abi/abi.html I have a couple of ideas on how this could be done.

One option I see would be to add a new class derived from abi::__class_type_info that had virtual member functions for the size and copy constructor:

class __copyable_class_type_info: public __class_type_info
{
public:
    size_t __object_size;
    virtual void __copy_construct(void* __source, void* __dest)=0;
};

You would need similar derived classes for __si_class_type_info and __vmi_class_type_info.

Because these are derived classes, they shouldn't affect the existing ABI structures. The size of non-class types can be determined from the type directly, since all pointers have the same size and fundamental types have a fixed size under the ABI. Such types can also be copied with memcpy(). The type_info for classes for which there is no public copy constructor would not derive from these new type-info classes.

rethrow_exception can then check the type_info pointed to by the exceptionType member of the __cxa_exception header, and either

(i) copy the exception with memcpy (because it's a fundamental type or pointer)

(ii) throw bad_alloc because this is an exception that cannot therefore be copied (i.e. it has no public copy constructor, or because it is from the old ABI)

(iii) dynamic_cast the type info to __copyable_class_type_info use the new __object_size and __copy_construct virtual functions to clone the exception

A second option is to add the size and copy construction functions to the __cxa_exception header. The ABI says "By convention, a __cxa_exception pointer points at the C++ object representing the exception being thrown, immediately following the header. The header structure is accessed at a negative offset from the __cxa_exception pointer. This layout allows consistent treatment of exception objects from different languages (or different implementations of the same language), and allows future extensions of the header structure while maintaining binary compatibility. "

We could therefore take advantage of this leeway to "extend the header structure while maintaining binary compatibility" to add the new size and copy construction members. In this case, the stored copy constructor could be NULL if there is no public copy constructor for the class.

In either case, the intention is that old code would work without recompilation even if the library code that it called was changed to use the new ABI, and new code could take advantage of the ability to copy exceptions where the exception was thrown from code that used the new ABI.

What do you think? Are these options implementable in a backwards-binary-compatible way? Is there an alternative implementation option?

Thanks,

Anthony
--
Author of C++ Concurrency in Action     http://www.stdthread.co.uk/book/
just::thread C++0x thread library             http://www.stdthread.co.uk
Just Software Solutions Ltd       http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976