C++0x std::rethrow_exception, data races and the Itanium ABI
Anthony Williams
anthony at justsoftwaresolutions.co.uk
Wed Nov 17 08:32:59 UTC 2010
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
More information about the cxx-abi-dev
mailing list