Support __cxa_free_exception and fix exception handling.
gcc/cp/ * except.c (do_free_exception): Use transactional wrapper. libitm/ * testsuite/libitm.c++/eh-5.C: New. * libitm.h (_ITM_cxa_free_exception): New. * libitm.map (_ITM_cxa_free_exception): Add it. * libitm.texi: Update ABI docs. * libitm_i.h (gtm_transaction_cp::cxa_unthrown): Remove. (gtm_transaction_cp::cxa_uncaught_count): Add. (gtm_thread::cxa_unthrown): Remove. (gtm_thread::cxa_uncaught_count_ptr): Add. (gtm_thread::cxa_uncaught_count): Add. (gtm_thread::drop_references_allocations): Rename to... (gtm_thread::discard_allocation): ... this and adapt. (gtm_thread::init_cpp_exceptions): New. * beginend.cc (gtm_thread::gtm_thread): Adapt EH handling. (gtm_thread::begin_transaction): Likewise. (gtm_transaction_cp::save): Likewise. (gtm_thread::trycommit): Likewise. * eh_cpp.cc: Add overview comments. (__cxa_eh_globals, __cxa_get_globals, __cxa_free_exception): Declare. (free_any_exception, _ITM_cxa_free_exception): New. (gtm_thread::init_cpp_exceptions): Define. (_ITM_cxa_allocate_exception, _ITM_cxa_throw): Adapt. (_ITM_cxa_begin_catch, _ITM_cxa_end_catch): Likewise. (gtm_thread::revert_cpp_exceptions): Likewise. From-SVN: r230634
This commit is contained in:
parent
9afebea2d5
commit
258c1d0722
10 changed files with 243 additions and 30 deletions
|
@ -1,3 +1,7 @@
|
|||
2015-11-19 Torvald Riegel <triegel@redhat.com>
|
||||
|
||||
* except.c (do_free_exception): Use transactional wrapper.
|
||||
|
||||
2015-11-19 Jason Merrill <jason@redhat.com>
|
||||
|
||||
PR c++/68422
|
||||
|
|
|
@ -662,6 +662,16 @@ do_free_exception (tree ptr)
|
|||
/* Declare void __cxa_free_exception (void *) throw(). */
|
||||
fn = declare_library_fn (fn, void_type_node, ptr_type_node,
|
||||
ECF_NOTHROW | ECF_LEAF);
|
||||
|
||||
if (flag_tm)
|
||||
{
|
||||
tree fn2 = get_identifier ("_ITM_cxa_free_exception");
|
||||
if (!get_global_value_if_present (fn2, &fn2))
|
||||
fn2 = declare_library_fn (fn2, void_type_node,
|
||||
ptr_type_node,
|
||||
ECF_NOTHROW | ECF_LEAF | ECF_TM_PURE);
|
||||
record_tm_replacement (fn, fn2);
|
||||
}
|
||||
}
|
||||
|
||||
return cp_build_function_call_nary (fn, tf_warning_or_error, ptr, NULL_TREE);
|
||||
|
|
|
@ -1,3 +1,29 @@
|
|||
2015-11-19 Torvald Riegel <triegel@redhat.com>
|
||||
|
||||
* testsuite/libitm.c++/eh-5.C: New.
|
||||
* libitm.h (_ITM_cxa_free_exception): New.
|
||||
* libitm.map (_ITM_cxa_free_exception): Add it.
|
||||
* libitm.texi: Update ABI docs.
|
||||
* libitm_i.h (gtm_transaction_cp::cxa_unthrown): Remove.
|
||||
(gtm_transaction_cp::cxa_uncaught_count): Add.
|
||||
(gtm_thread::cxa_unthrown): Remove.
|
||||
(gtm_thread::cxa_uncaught_count_ptr): Add.
|
||||
(gtm_thread::cxa_uncaught_count): Add.
|
||||
(gtm_thread::drop_references_allocations): Rename to...
|
||||
(gtm_thread::discard_allocation): ... this and adapt.
|
||||
(gtm_thread::init_cpp_exceptions): New.
|
||||
* beginend.cc (gtm_thread::gtm_thread): Adapt EH handling.
|
||||
(gtm_thread::begin_transaction): Likewise.
|
||||
(gtm_transaction_cp::save): Likewise.
|
||||
(gtm_thread::trycommit): Likewise.
|
||||
* eh_cpp.cc: Add overview comments.
|
||||
(__cxa_eh_globals, __cxa_get_globals, __cxa_free_exception): Declare.
|
||||
(free_any_exception, _ITM_cxa_free_exception): New.
|
||||
(gtm_thread::init_cpp_exceptions): Define.
|
||||
(_ITM_cxa_allocate_exception, _ITM_cxa_throw): Adapt.
|
||||
(_ITM_cxa_begin_catch, _ITM_cxa_end_catch): Likewise.
|
||||
(gtm_thread::revert_cpp_exceptions): Likewise.
|
||||
|
||||
2015-11-09 Torvald Riegel <triegel@redhat.com>
|
||||
|
||||
* alloc_cpp.cc (_ZdlPvX, _ZdlPvXRKSt9nothrow_t, _ZGTtdlPvX,
|
||||
|
|
|
@ -132,6 +132,8 @@ GTM::gtm_thread::gtm_thread ()
|
|||
number_of_threads_changed(number_of_threads - 1, number_of_threads);
|
||||
serial_lock.write_unlock ();
|
||||
|
||||
init_cpp_exceptions ();
|
||||
|
||||
if (pthread_once(&thr_release_once, thread_exit_init))
|
||||
GTM_fatal("Initializing thread release TLS key failed.");
|
||||
// Any non-null value is sufficient to trigger destruction of this
|
||||
|
@ -383,6 +385,11 @@ GTM::gtm_thread::begin_transaction (uint32_t prop, const gtm_jmpbuf *jb)
|
|||
#endif
|
||||
}
|
||||
|
||||
// Log the number of uncaught exceptions if we might have to roll back this
|
||||
// state.
|
||||
if (tx->cxa_uncaught_count_ptr != 0)
|
||||
tx->cxa_uncaught_count = *tx->cxa_uncaught_count_ptr;
|
||||
|
||||
// Run dispatch-specific restart code. Retry until we succeed.
|
||||
GTM::gtm_restart_reason rr;
|
||||
while ((rr = disp->begin_or_restart()) != NO_RESTART)
|
||||
|
@ -411,7 +418,7 @@ GTM::gtm_transaction_cp::save(gtm_thread* tx)
|
|||
id = tx->id;
|
||||
prop = tx->prop;
|
||||
cxa_catch_count = tx->cxa_catch_count;
|
||||
cxa_unthrown = tx->cxa_unthrown;
|
||||
cxa_uncaught_count = tx->cxa_uncaught_count;
|
||||
disp = abi_disp();
|
||||
nesting = tx->nesting;
|
||||
}
|
||||
|
@ -583,7 +590,6 @@ GTM::gtm_thread::trycommit ()
|
|||
undolog.commit ();
|
||||
// Reset further transaction state.
|
||||
cxa_catch_count = 0;
|
||||
cxa_unthrown = NULL;
|
||||
restart_total = 0;
|
||||
|
||||
// Ensure privatization safety, if necessary.
|
||||
|
|
140
libitm/eh_cpp.cc
140
libitm/eh_cpp.cc
|
@ -26,6 +26,54 @@
|
|||
|
||||
using namespace GTM;
|
||||
|
||||
/* Exceptions can exist in three phases: (1) after having been allocated by
|
||||
__cxa_allocate_exception but before being handed off to __cxa_throw,
|
||||
(2) when they are in flight, so between __cxa_throw and __cxa_begin_catch,
|
||||
and (3) when they are being handled (between __cxa_begin_catch and
|
||||
__cxa_end_catch). Note that when an exception is re-thrown in (3), it is
|
||||
not moving back to (2) but handled as a special case of (3) by the EH
|
||||
runtime.
|
||||
|
||||
We can get aborts in all three phases, for example in (1) during
|
||||
construction of the exception object, or in (2) in destructors called
|
||||
while unwinding the stack. The transaction that created an exception
|
||||
object can only commit in phase (3) by re-throwing the exception; it cannot
|
||||
commit in other phases because throw expressions and catch clauses are
|
||||
properly nested wrt transactions and because the compiler wraps
|
||||
transaction bodies in a try/catch-all construct.
|
||||
|
||||
We handle phase (1) by dealing with exception objects similar to how we
|
||||
deal with other (de)allocations, which also ensures that we can have more
|
||||
than one exception object allocated at the same time (e.g., if the
|
||||
throw expression itself throws an exception and thus calls
|
||||
__cxa_allocate_exception). However, on the call to __cxa_begin_catch
|
||||
we hand off the exception to the special handling of phase (3) and
|
||||
remove the undo log entry of the allocation. Note that if the allocation
|
||||
happened outside of this transaction, we do not need to do anything.
|
||||
|
||||
When an exception reaches phase (2) due to a call to __cxa_throw, the count
|
||||
of uncaught exceptions is incremented. We roll back this effect by saving
|
||||
and restoring this number in the structure returned from __cxa_get_globals.
|
||||
This also takes care of increments of this count when re-throwing an
|
||||
exception.
|
||||
|
||||
For phase (3), we keep track of the number of times __cxa_begin_catch
|
||||
has been called without a matching call to __cxa_end_catch. This count
|
||||
is then used by __cxa_tm_cleanup to roll back the exception handling state
|
||||
by calling __cxa_end_catch for the exceptions that have not been finished
|
||||
yet (without running destructors though because we roll back the memory
|
||||
anyway).
|
||||
Once an exception that was allocated in this transaction enters phase (3),
|
||||
it does not need to be deallocated on abort anymore because the calls to
|
||||
__cxa_end_catch will take care of that.
|
||||
|
||||
We require all code executed by the transaction to be transaction_safe (or
|
||||
transaction_pure, or to have wrappers) if the transaction is to be rolled
|
||||
back. However, we take care to not require this for transactions that
|
||||
just commit; this way, transactions that enter serial mode and then call
|
||||
uninstrumented code continue to work.
|
||||
*/
|
||||
|
||||
/* Everything from libstdc++ is weak, to avoid requiring that library
|
||||
to be linked into plain C applications using libitm.so. */
|
||||
|
||||
|
@ -33,85 +81,139 @@ using namespace GTM;
|
|||
|
||||
extern "C" {
|
||||
|
||||
struct __cxa_eh_globals
|
||||
{
|
||||
void * caughtExceptions;
|
||||
unsigned int uncaughtExceptions;
|
||||
};
|
||||
|
||||
extern void *__cxa_allocate_exception (size_t) WEAK;
|
||||
extern void __cxa_free_exception (void *) WEAK;
|
||||
extern void __cxa_throw (void *, void *, void *) WEAK;
|
||||
extern void *__cxa_begin_catch (void *) WEAK;
|
||||
extern void __cxa_end_catch (void) WEAK;
|
||||
extern void __cxa_tm_cleanup (void *, void *, unsigned int) WEAK;
|
||||
extern __cxa_eh_globals *__cxa_get_globals (void) WEAK;
|
||||
|
||||
#if !defined (HAVE_ELF_STYLE_WEAKREF)
|
||||
void *__cxa_allocate_exception (size_t) { return NULL; }
|
||||
void __cxa_free_exception (void *) { return; }
|
||||
void __cxa_throw (void *, void *, void *) { return; }
|
||||
void *__cxa_begin_catch (void *) { return NULL; }
|
||||
void __cxa_end_catch (void) { return; }
|
||||
void __cxa_tm_cleanup (void *, void *, unsigned int) { return; }
|
||||
void _Unwind_DeleteException (_Unwind_Exception *) { return; }
|
||||
__cxa_eh_globals *__cxa_get_globals (void) { return NULL; }
|
||||
#endif /* HAVE_ELF_STYLE_WEAKREF */
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
free_any_exception (void *exc_ptr)
|
||||
{
|
||||
// The exception could be in phase (2) and thus calling just
|
||||
// _cxa_free_exception might not be sufficient.
|
||||
__cxa_tm_cleanup (NULL, exc_ptr, 0);
|
||||
}
|
||||
|
||||
void *
|
||||
_ITM_cxa_allocate_exception (size_t size)
|
||||
{
|
||||
void *r = __cxa_allocate_exception (size);
|
||||
gtm_thr()->cxa_unthrown = r;
|
||||
gtm_thr()->record_allocation (r, free_any_exception);
|
||||
return r;
|
||||
}
|
||||
|
||||
void
|
||||
_ITM_cxa_free_exception (void *exc_ptr)
|
||||
{
|
||||
// __cxa_free_exception can be called from user code directly if
|
||||
// construction of an exception object throws another exception, in which
|
||||
// case we need to roll back the initial exception. We handle this similar
|
||||
// to dead allocations in that we deallocate the exception on both commit
|
||||
// and abort of an outermost transaction.
|
||||
gtm_thr()->forget_allocation (exc_ptr, free_any_exception);
|
||||
}
|
||||
|
||||
void
|
||||
_ITM_cxa_throw (void *obj, void *tinfo, void *dest)
|
||||
{
|
||||
gtm_thr()->cxa_unthrown = NULL;
|
||||
// This used to be instrumented, but does not need to be anymore.
|
||||
__cxa_throw (obj, tinfo, dest);
|
||||
}
|
||||
|
||||
void *
|
||||
_ITM_cxa_begin_catch (void *exc_ptr)
|
||||
{
|
||||
gtm_thr()->cxa_catch_count++;
|
||||
// If this exception object has been allocated by this transaction, we
|
||||
// discard the undo log entry for the allocation; we are entering phase (3)
|
||||
// now and will handle this exception specially.
|
||||
// Note that this exception cannot have been allocated in a parent
|
||||
// transaction or enclosing nontransactional block because an atomic block
|
||||
// cannot contain just a catch clause but not the associated try clause.
|
||||
// The exception can have been allocated in a nested transaction, in which
|
||||
// case the commit of the nested transaction will have inserted the undo
|
||||
// log entry of the allocation in our undo log.
|
||||
// The exception can also have been allocated in a nested nontransactional
|
||||
// block, but then this transaction cannot abort anymore; functions that
|
||||
// are marked transaction_pure, for example, must not side-step the
|
||||
// transactional exception handling we implement here.
|
||||
gtm_thread *t = gtm_thr ();
|
||||
t->discard_allocation (exc_ptr);
|
||||
// Keep track of the number of unfinished catch handlers.
|
||||
t->cxa_catch_count++;
|
||||
return __cxa_begin_catch (exc_ptr);
|
||||
}
|
||||
|
||||
void
|
||||
_ITM_cxa_end_catch (void)
|
||||
{
|
||||
// Keep track of the number of unfinished catch handlers.
|
||||
gtm_thr()->cxa_catch_count--;
|
||||
__cxa_end_catch ();
|
||||
}
|
||||
|
||||
void
|
||||
GTM::gtm_thread::init_cpp_exceptions ()
|
||||
{
|
||||
// Only save and restore the number of uncaught exceptions if this is
|
||||
// actually used in the program.
|
||||
if (__cxa_get_globals != NULL && __cxa_get_globals () != 0)
|
||||
cxa_uncaught_count_ptr = &__cxa_get_globals ()->uncaughtExceptions;
|
||||
else
|
||||
cxa_uncaught_count_ptr = 0;
|
||||
}
|
||||
|
||||
void
|
||||
GTM::gtm_thread::revert_cpp_exceptions (gtm_transaction_cp *cp)
|
||||
{
|
||||
if (cp)
|
||||
{
|
||||
// If rolling back a nested transaction, only clean up unthrown
|
||||
// exceptions since the last checkpoint. Always reset eh_in_flight
|
||||
// because it just contains the argument provided to
|
||||
// _ITM_commitTransactionEH
|
||||
void *unthrown =
|
||||
(cxa_unthrown != cp->cxa_unthrown ? cxa_unthrown : NULL);
|
||||
// If rolling back a nested transaction, only clean up incompletely
|
||||
// caught exceptions since the last checkpoint.
|
||||
assert (cxa_catch_count >= cp->cxa_catch_count);
|
||||
uint32_t catch_count = cxa_catch_count - cp->cxa_catch_count;
|
||||
if (unthrown || catch_count)
|
||||
if (catch_count)
|
||||
{
|
||||
__cxa_tm_cleanup (unthrown, this->eh_in_flight, catch_count);
|
||||
__cxa_tm_cleanup (NULL, NULL, catch_count);
|
||||
cxa_catch_count = cp->cxa_catch_count;
|
||||
cxa_unthrown = cp->cxa_unthrown;
|
||||
this->eh_in_flight = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Both cxa_catch_count and cxa_unthrown are maximal because EH regions
|
||||
// and transactions are properly nested.
|
||||
if (this->cxa_unthrown || this->cxa_catch_count)
|
||||
if (cxa_catch_count)
|
||||
{
|
||||
__cxa_tm_cleanup (this->cxa_unthrown, this->eh_in_flight,
|
||||
this->cxa_catch_count);
|
||||
this->cxa_catch_count = 0;
|
||||
this->cxa_unthrown = NULL;
|
||||
this->eh_in_flight = NULL;
|
||||
__cxa_tm_cleanup (NULL, NULL, cxa_catch_count);
|
||||
cxa_catch_count = 0;
|
||||
}
|
||||
}
|
||||
// Reset the number of uncaught exceptions. Any allocations for these
|
||||
// exceptions have been rolled back already, if necessary.
|
||||
if (cxa_uncaught_count_ptr != 0)
|
||||
*cxa_uncaught_count_ptr = cxa_uncaught_count;
|
||||
// Always reset eh_in_flight because it just contains the argument provided
|
||||
// to _ITM_commitTransactionEH.
|
||||
eh_in_flight = NULL;
|
||||
}
|
||||
|
|
|
@ -283,6 +283,7 @@ extern void _ITM_registerTMCloneTable (void *, size_t);
|
|||
extern void _ITM_deregisterTMCloneTable (void *);
|
||||
|
||||
extern void *_ITM_cxa_allocate_exception (size_t);
|
||||
extern void _ITM_cxa_free_exception (void *exc_ptr);
|
||||
extern void _ITM_cxa_throw (void *obj, void *tinfo, void *dest);
|
||||
extern void *_ITM_cxa_begin_catch (void *exc_ptr);
|
||||
extern void _ITM_cxa_end_catch (void);
|
||||
|
|
|
@ -186,4 +186,5 @@ LIBITM_1.1 {
|
|||
global:
|
||||
_ZGTtdlPv?;
|
||||
_ZGTtdlPv?RKSt9nothrow_t;
|
||||
_ITM_cxa_free_exception;
|
||||
} LIBITM_1.0;
|
||||
|
|
|
@ -268,17 +268,26 @@ transactions.
|
|||
@example
|
||||
void _ITM_commitTransactionEH(void *exc_ptr) ITM_REGPARM;
|
||||
void *_ITM_cxa_allocate_exception (size_t);
|
||||
void _ITM_cxa_free_exception (void *exc_ptr);
|
||||
void _ITM_cxa_throw (void *obj, void *tinfo, void *dest);
|
||||
void *_ITM_cxa_begin_catch (void *exc_ptr);
|
||||
void _ITM_cxa_end_catch (void);
|
||||
@end example
|
||||
|
||||
@code{_ITM_commitTransactionEH} must be called to commit a transaction if an
|
||||
exception could be in flight at this position in the code. @code{exc_ptr} is
|
||||
the current exception or zero if there is no current exception.
|
||||
The EH scheme changed in version 6 of GCC. Previously, the compiler
|
||||
added a call to @code{_ITM_commitTransactionEH} to commit a transaction if
|
||||
an exception could be in flight at this position in the code; @code{exc_ptr} is
|
||||
the address of the current exception and must be non-zero. Now, the
|
||||
compiler must catch all exceptions that are about to be thrown out of a
|
||||
transaction and call @code{_ITM_commitTransactionEH} from the catch clause,
|
||||
with @code{exc_ptr} being zero.
|
||||
|
||||
Note that the old EH scheme never worked completely in GCC's implementation;
|
||||
libitm currently does not try to be compatible with the old scheme.
|
||||
|
||||
The @code{_ITM_cxa...} functions are transactional wrappers for the respective
|
||||
@code{__cxa...} functions and must be called instead of these in transactional
|
||||
code.
|
||||
code. @code{_ITM_cxa_free_exception} is new in GCC 6.
|
||||
|
||||
To support this EH scheme, libstdc++ needs to provide one additional function
|
||||
(@code{_cxa_tm_cleanup}), which is used by the TM to clean up the exception
|
||||
|
@ -289,7 +298,8 @@ void __cxa_tm_cleanup (void *unthrown_obj, void *cleanup_exc,
|
|||
unsigned int caught_count);
|
||||
@end example
|
||||
|
||||
@code{unthrown_obj} is non-null if the program called
|
||||
Since GCC 6, @code{unthrown_obj} is not used anymore and always null;
|
||||
prior to that, @code{unthrown_obj} is non-null if the program called
|
||||
@code{__cxa_allocate_exception} for this exception but did not yet called
|
||||
@code{__cxa_throw} for it. @code{cleanup_exc} is non-null if the program is
|
||||
currently processing a cleanup along an exception path but has not caught this
|
||||
|
@ -406,6 +416,10 @@ These functions are essentially transactional wrappers for @code{malloc},
|
|||
@code{calloc}, and @code{free}. Within transactions, the compiler should
|
||||
replace calls to the original functions with calls to the wrapper functions.
|
||||
|
||||
libitm also provides transactional clones of C++ memory management functions
|
||||
such as global operator new and delete. They are part of libitm for historic
|
||||
reasons but do not need to be part of this ABI.
|
||||
|
||||
|
||||
@section [No changes] Future Enhancements to the ABI
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ struct gtm_transaction_cp
|
|||
_ITM_transactionId_t id;
|
||||
uint32_t prop;
|
||||
uint32_t cxa_catch_count;
|
||||
void *cxa_unthrown;
|
||||
unsigned int cxa_uncaught_count;
|
||||
// We might want to use a different but compatible dispatch method for
|
||||
// a nested transaction.
|
||||
abi_dispatch *disp;
|
||||
|
@ -242,7 +242,9 @@ struct gtm_thread
|
|||
|
||||
// Data used by eh_cpp.c for managing exceptions within the transaction.
|
||||
uint32_t cxa_catch_count;
|
||||
void *cxa_unthrown;
|
||||
// If cxa_uncaught_count_ptr is 0, we don't need to roll back exceptions.
|
||||
unsigned int *cxa_uncaught_count_ptr;
|
||||
unsigned int cxa_uncaught_count;
|
||||
void *eh_in_flight;
|
||||
|
||||
// Checkpoints for closed nesting.
|
||||
|
@ -284,9 +286,9 @@ struct gtm_thread
|
|||
void record_allocation (void *, void (*)(void *));
|
||||
void forget_allocation (void *, void (*)(void *));
|
||||
void forget_allocation (void *, size_t, void (*)(void *, size_t));
|
||||
void drop_references_allocations (const void *ptr)
|
||||
void discard_allocation (const void *ptr)
|
||||
{
|
||||
this->alloc_actions.erase((uintptr_t) ptr);
|
||||
alloc_actions.erase((uintptr_t) ptr);
|
||||
}
|
||||
|
||||
// In beginend.cc
|
||||
|
@ -306,6 +308,7 @@ struct gtm_thread
|
|||
static uint32_t begin_transaction(uint32_t, const gtm_jmpbuf *)
|
||||
__asm__(UPFX "GTM_begin_transaction") ITM_REGPARM;
|
||||
// In eh_cpp.cc
|
||||
void init_cpp_exceptions ();
|
||||
void revert_cpp_exceptions (gtm_transaction_cp *cp = 0);
|
||||
|
||||
// In retry.cc
|
||||
|
|
46
libitm/testsuite/libitm.c++/eh-5.C
Normal file
46
libitm/testsuite/libitm.c++/eh-5.C
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Test throwing an exception whose constructor might throw. This tests that
|
||||
// _cxa_free_exception is instrumented.
|
||||
|
||||
// { dg-do run }
|
||||
// { dg-options "-fgnu-tm" }
|
||||
|
||||
void __attribute__ ((transaction_pure,noinline)) dontoptimize (int *i)
|
||||
{ }
|
||||
|
||||
struct test
|
||||
{
|
||||
int* data;
|
||||
test (int i)
|
||||
{
|
||||
// new may throw
|
||||
data = new int[1];
|
||||
data[0] = i;
|
||||
dontoptimize (data);
|
||||
}
|
||||
test (const test& t) : test (t.data[0])
|
||||
{ }
|
||||
~test ()
|
||||
{
|
||||
delete data;
|
||||
}
|
||||
bool operator !=(const test& other)
|
||||
{
|
||||
return data[0] != other.data[0];
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
try
|
||||
{
|
||||
atomic_commit
|
||||
{
|
||||
throw test(23);
|
||||
}
|
||||
}
|
||||
catch (test ex)
|
||||
{
|
||||
if (ex.data[0] != 23) __builtin_abort ();
|
||||
}
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue