Fix Q_GLOBAL_STATIC support for exceptions

The problem was that the HolderBase destructor was getting called after
the contained type's constructor threw an exception, as is required by
RAII semantics (the base was fully initialized, so it had to be
destroyed). That was required because we want to return a non-null
pointer from operator() during destruction and return null after
destruction, to keep compatibility with Qt 4.

The solution is to only set the guard to Destroyed only if it is already
at value Initialized. This way, if the HolderBase destructor is run as
part of the stack unwinding, it knows that the construction did not
complete.

Change-Id: I9849b43ed7112bf9e70861b48a56a924c286617e
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
This commit is contained in:
Thiago Macieira 2013-02-27 15:48:17 -08:00 committed by The Qt Project
parent ebfd85a499
commit 50d3a2917e
3 changed files with 30 additions and 5 deletions

View File

@ -288,9 +288,11 @@
structure so holder's destructor can set the guard variable to the value -2
(destroyed) when the type has finished destruction. Since we need to set
the guard \b after the destruction has finished, this code needs to be in a
base struct's destructor. A holder structure is used to avoid creating two
statics, which the ABI might require duplicating the thread-safe control
structures for.
base struct's destructor. And it only sets to -2 (destroyed) if it finds
the guard at -1 (initialized): this is done to ensure that the guard isn't
set to -2 in the event the type's constructor threw an exception. A holder
structure is used to avoid creating two statics, which the ABI might
require duplicating the thread-safe control structures for.
The other implementation is similar to Qt 4's Q_GLOBAL_STATIC, but unlike
that one, it uses a \l QBasicMutex to provide locking. It is also more

View File

@ -68,7 +68,8 @@ enum GuardValues {
{ \
struct HolderBase { \
~HolderBase() Q_DECL_NOTHROW \
{ guard.store(QtGlobalStatic::Destroyed); } \
{ if (guard.load() == QtGlobalStatic::Initialized) \
guard.store(QtGlobalStatic::Destroyed); } \
}; \
static struct Holder : public HolderBase { \
Type value; \

View File

@ -50,6 +50,7 @@ private Q_SLOTS:
void api();
void constVolatile();
void exception();
void threadedException();
void threadStressTest();
void afterDestruction();
};
@ -107,6 +108,11 @@ struct ThrowingType
{
static QBasicAtomicInt constructedCount;
static QBasicAtomicInt destructedCount;
ThrowingType()
{
throw 0;
}
ThrowingType(QBasicAtomicInt &throwControl)
{
constructedCount.ref();
@ -115,12 +121,28 @@ struct ThrowingType
}
~ThrowingType() { destructedCount.ref(); }
};
QBasicAtomicInt ThrowingType::constructedCount = Q_BASIC_ATOMIC_INITIALIZER(0);
QBasicAtomicInt ThrowingType::destructedCount = Q_BASIC_ATOMIC_INITIALIZER(0);
Q_GLOBAL_STATIC(ThrowingType, throwingGS)
void tst_QGlobalStatic::exception()
{
bool exceptionCaught = false;
try {
throwingGS();
} catch (int) {
exceptionCaught = true;
}
QVERIFY(exceptionCaught);
QCOMPARE(Q_QGS_throwingGS::guard.load(), 0);
QVERIFY(!throwingGS.exists());
QVERIFY(!throwingGS.isDestroyed());
}
QBasicAtomicInt exceptionControlVar = Q_BASIC_ATOMIC_INITIALIZER(1);
Q_GLOBAL_STATIC_WITH_ARGS(ThrowingType, exceptionGS, (exceptionControlVar))
void tst_QGlobalStatic::exception()
void tst_QGlobalStatic::threadedException()
{
if (exceptionControlVar.load() != 1)
QSKIP("This test cannot be run more than once");