/**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include QT_USE_NAMESPACE // this test only works with // * GLIBC // * MSVC - only debug builds (we need the crtdbg.h helpers) // * SYMBIAN #if (defined(QT_NO_EXCEPTIONS) || (!defined(__GLIBC__) && !defined(Q_CC_MSVC) && !defined(Q_OS_SYMBIAN))) && !defined(Q_MOC_RUN) QTEST_NOOP_MAIN #else #include "oomsimulator.h" #if !defined(Q_OS_SYMBIAN) #include "3rdparty/memcheck.h" #endif class tst_ExceptionSafety_Objects: public QObject { Q_OBJECT public slots: void initTestCase(); void cleanupTestCase(); private slots: void objects_data(); void objects(); void widgets_data(); void widgets(); void vector_data(); void vector(); void list_data(); void list(); void linkedList_data(); void linkedList(); private: static QtMsgHandler testMessageHandler; static void safeMessageHandler(QtMsgType, const char *); }; // helper structs to create an arbitrary widget struct AbstractTester { virtual void operator()(QObject *parent) = 0; }; Q_DECLARE_METATYPE(AbstractTester *) typedef void (*TestFunction)(QObject*); Q_DECLARE_METATYPE(TestFunction) template struct ObjectCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer ptr(new T); } }; struct BitArrayCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer bitArray(new QBitArray(100, true)); } }; struct ByteArrayMatcherCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer ptr(new QByteArrayMatcher("ralf test",8)); } }; struct CryptographicHashCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer ptr(new QCryptographicHash(QCryptographicHash::Sha1)); ptr->addData("ralf test",8); } }; struct DataStreamCreator : public AbstractTester { void operator()(QObject *) { QScopedPointer arr(new QByteArray("hallo, test")); QScopedPointer ptr(new QDataStream(arr.data(), QIODevice::ReadWrite)); ptr->writeBytes("ralf test",8); } }; struct DirCreator : public AbstractTester { void operator()(QObject *) { QDir::cleanPath("../////././"); QScopedPointer ptr(new QDir(".")); while( ptr->cdUp() ) ; // just going up ptr->count(); ptr->exists(ptr->path()); QStringList filters; filters << "*.cpp" << "*.cxx" << "*.cc"; ptr->setNameFilters(filters); } }; void tst_ExceptionSafety_Objects::objects_data() { QTest::addColumn("objectCreator"); #define NEWROW(T) QTest::newRow(#T) << static_cast(new ObjectCreator) NEWROW(QObject); NEWROW(QBuffer); NEWROW(QFile); NEWROW(QFSFileEngine); NEWROW(QProcess); NEWROW(QSettings); NEWROW(QThread); NEWROW(QThreadPool); NEWROW(QTranslator); #define NEWROW2(T, CREATOR) QTest::newRow(#T) << static_cast(new CREATOR) NEWROW2(QBitArray, BitArrayCreator); NEWROW2(QByteArrayMatcher, ByteArrayMatcherCreator); NEWROW2(QCryptographicHash, CryptographicHashCreator); NEWROW2(QDataStream, DataStreamCreator); NEWROW2(QDir, DirCreator); } // create and destructs an object, and lets each and every allocation // during construction and destruction fail. template static void doOOMTest(T &testFunc, QObject *parent, int start=0) { int currentOOMIndex = start; bool caught = false; bool done = false; AllocFailer allocFailer(0); int allocCountBefore = allocFailer.currentAllocIndex(); do { allocFailer.reactivateAt(++currentOOMIndex); caught = false; try { testFunc(parent); } catch (const std::bad_alloc &) { caught = true; } catch (const std::exception &ex) { if (strcmp(ex.what(), "autotest swallow") != 0) throw; caught = true; } if (!caught) { void *buf = malloc(42); if (buf) { // we got memory here - oom test is over. free(buf); done = true; } } // if we get a FAIL, stop executing now if (QTest::currentTestFailed()) done = true; //#define REALLY_VERBOSE #ifdef REALLY_VERBOSE fprintf(stderr, " OOM Index: %d\n", currentOOMIndex); #endif } while (caught || !done); allocFailer.deactivate(); //#define VERBOSE #ifdef VERBOSE fprintf(stderr, "OOM Test done, checked allocs: %d (range %d - %d)\n", currentOOMIndex, allocCountBefore, allocFailer.currentAllocIndex()); #else Q_UNUSED(allocCountBefore); #endif } static int alloc1Failed = 0; static int alloc2Failed = 0; static int alloc3Failed = 0; static int alloc4Failed = 0; static int malloc1Failed = 0; static int malloc2Failed = 0; // Tests that new, new[] and malloc() fail at least once during OOM testing. class SelfTestObject : public QObject { public: SelfTestObject(QObject *parent = 0) : QObject(parent) { try { delete new int; } catch (const std::bad_alloc &) { ++alloc1Failed; throw; } try { delete [] new double[5]; } catch (const std::bad_alloc &) { ++alloc2Failed; throw ;} void *buf = malloc(42); if (buf) free(buf); else ++malloc1Failed; } ~SelfTestObject() { try { delete new int; } catch (const std::bad_alloc &) { ++alloc3Failed; } try { delete [] new double[5]; } catch (const std::bad_alloc &) { ++alloc4Failed; } void *buf = malloc(42); if (buf) free(buf); else ++malloc2Failed = true; } }; QtMsgHandler tst_ExceptionSafety_Objects::testMessageHandler; void tst_ExceptionSafety_Objects::safeMessageHandler(QtMsgType type, const char *msg) { // this temporarily suspends OOM testing while handling a message int currentIndex = mallocFailIndex; AllocFailer allocFailer(0); allocFailer.deactivate(); (*testMessageHandler)(type, msg); allocFailer.reactivateAt(currentIndex); } typedef void (*PVF)(); PVF defaultTerminate; void debugTerminate() { // you can detect uncaught exceptions with a breakpoint in here (*defaultTerminate)(); } PVF defaultUnexpected; void debugUnexpected() { // you can detect unexpected exceptions with a breakpoint in here (*defaultUnexpected)(); } void tst_ExceptionSafety_Objects::initTestCase() { // set handlers for bad exception cases, you might want to step in and breakpoint the default handlers too defaultTerminate = std::set_terminate(&debugTerminate); defaultUnexpected = std::set_unexpected(&debugUnexpected); testMessageHandler = qInstallMsgHandler(safeMessageHandler); QVERIFY(AllocFailer::initialize()); // sanity check whether OOM simulation works AllocFailer allocFailer(0); // malloc fail index is 0 -> this malloc should fail. void *buf = malloc(42); allocFailer.deactivate(); QVERIFY(!buf); // malloc fail index is 1 - second malloc should fail. allocFailer.reactivateAt(1); buf = malloc(42); void *buf2 = malloc(42); allocFailer.deactivate(); QVERIFY(buf); free(buf); QVERIFY(!buf2); #ifdef Q_OS_SYMBIAN // temporary workaround for INC138398 std::new_handler nh_func = std::set_new_handler(0); (void) std::set_new_handler(nh_func); #endif ObjectCreator *selfTest = new ObjectCreator; doOOMTest(*selfTest, 0); delete selfTest; QCOMPARE(alloc1Failed, 1); QCOMPARE(alloc2Failed, 1); QCOMPARE(alloc3Failed, 2); QCOMPARE(alloc4Failed, 3); QCOMPARE(malloc1Failed, 1); QCOMPARE(malloc2Failed, 1); } void tst_ExceptionSafety_Objects::cleanupTestCase() { qInstallMsgHandler(testMessageHandler); } void tst_ExceptionSafety_Objects::objects() { QLatin1String tag = QLatin1String(QTest::currentDataTag()); if (tag == QLatin1String("QFile") || tag == QLatin1String("QProcess") || tag == QLatin1String("QSettings") || tag == QLatin1String("QThread") || tag == QLatin1String("QThreadPool")) QSKIP("This type of object is not currently strongly exception safe", SkipSingle); QFETCH(AbstractTester *, objectCreator); doOOMTest(*objectCreator, 0); delete objectCreator; } template struct WidgetCreator : public AbstractTester { void operator()(QObject *parent) { if (parent && !parent->isWidgetType()) qFatal("%s: parent must be either null or a widget type", Q_FUNC_INFO); QScopedPointer ptr(parent ? new T(static_cast(parent)) : new T); } }; // QSizeGrip doesn't have a default constructor - always pass parent (even though it might be 0) template <> struct WidgetCreator : public AbstractTester { void operator()(QObject *parent) { if (parent && !parent->isWidgetType()) qFatal("%s: parent must be either null or a widget type", Q_FUNC_INFO); QScopedPointer ptr(new QSizeGrip(static_cast(parent))); } }; // QDesktopWidget doesn't need a parent. template <> struct WidgetCreator : public AbstractTester { void operator()(QObject *parent) { if (parent && !parent->isWidgetType()) qFatal("%s: parent must be either null or a widget type", Q_FUNC_INFO); QScopedPointer ptr(new QDesktopWidget()); } }; void tst_ExceptionSafety_Objects::widgets_data() { #ifdef Q_OS_SYMBIAN // Initialise the S60 rasteriser, which crashes if started while out of memory QImage image(20, 20, QImage::Format_RGB32); QPainter p(&image); p.drawText(0, 15, "foo"); #endif QTest::addColumn("widgetCreator"); #undef NEWROW #define NEWROW(T) QTest::newRow(#T) << static_cast(new WidgetCreator) NEWROW(QWidget); NEWROW(QButtonGroup); NEWROW(QCheckBox); NEWROW(QColumnView); NEWROW(QComboBox); NEWROW(QCommandLinkButton); NEWROW(QDateEdit); NEWROW(QDateTimeEdit); NEWROW(QDesktopWidget); NEWROW(QDial); NEWROW(QDoubleSpinBox); NEWROW(QFocusFrame); NEWROW(QFontComboBox); NEWROW(QFrame); NEWROW(QGroupBox); NEWROW(QLabel); NEWROW(QLCDNumber); NEWROW(QLineEdit); NEWROW(QListView); NEWROW(QListWidget); NEWROW(QMainWindow); NEWROW(QMenu); NEWROW(QMenuBar); NEWROW(QPlainTextEdit); NEWROW(QProgressBar); NEWROW(QPushButton); NEWROW(QRadioButton); NEWROW(QScrollArea); NEWROW(QScrollBar); NEWROW(QSizeGrip); NEWROW(QSlider); NEWROW(QSpinBox); NEWROW(QSplitter); NEWROW(QStackedWidget); NEWROW(QStatusBar); NEWROW(QTabBar); NEWROW(QTableView); NEWROW(QTableWidget); NEWROW(QTabWidget); NEWROW(QTextBrowser); NEWROW(QTextEdit); NEWROW(QTimeEdit); NEWROW(QToolBar); NEWROW(QToolBox); NEWROW(QToolButton); NEWROW(QTreeView); NEWROW(QTreeWidget); NEWROW(QWorkspace); } void tst_ExceptionSafety_Objects::widgets() { QLatin1String tag = QLatin1String(QTest::currentDataTag()); if (tag == QLatin1String("QColumnView") || tag == QLatin1String("QComboBox") || tag == QLatin1String("QCommandLinkButton") || tag == QLatin1String("QDateEdit") || tag == QLatin1String("QDateTimeEdit") || tag == QLatin1String("QDesktopWidget") || tag == QLatin1String("QDoubleSpinBox") || tag == QLatin1String("QFontComboBox") || tag == QLatin1String("QGroupBox") || tag == QLatin1String("QLineEdit") || tag == QLatin1String("QListView") || tag == QLatin1String("QListWidget") || tag == QLatin1String("QMainWindow") || tag == QLatin1String("QMenu") || tag == QLatin1String("QMenuBar") || tag == QLatin1String("QPlainTextEdit") || tag == QLatin1String("QProgressBar") || tag == QLatin1String("QPushButton") || tag == QLatin1String("QScrollArea") || tag == QLatin1String("QSpinBox") || tag == QLatin1String("QStackedWidget") || tag == QLatin1String("QStatusBar") || tag == QLatin1String("QTableView") || tag == QLatin1String("QTableWidget") || tag == QLatin1String("QTabWidget") || tag == QLatin1String("QTextBrowser") || tag == QLatin1String("QTextEdit") || tag == QLatin1String("QTimeEdit") || tag == QLatin1String("QToolBar") || tag == QLatin1String("QToolBox") || tag == QLatin1String("QTreeView") || tag == QLatin1String("QTreeWidget") || tag == QLatin1String("QWorkspace")) QSKIP("This type of widget is not currently strongly exception safe", SkipSingle); QFETCH(AbstractTester *, widgetCreator); doOOMTest(*widgetCreator, 0, 00000); QWidget parent; doOOMTest(*widgetCreator, &parent, 00000); delete widgetCreator; // if the test reaches here without crashing, we passed :) QVERIFY(true); } struct Integer { Integer(int value = 42) : ptr(new int(value)) { ++instanceCount; } Integer(const Integer &other) : ptr(new int(*other.ptr)) { ++instanceCount; } Integer &operator=(const Integer &other) { int *newPtr = new int(*other.ptr); delete ptr; ptr = newPtr; return *this; } ~Integer() { --instanceCount; delete ptr; } int value() const { return *ptr; } int *ptr; static int instanceCount; }; int Integer::instanceCount = 0; struct IntegerMoveable { IntegerMoveable(int value = 42) : val(value) { delete new int; ++instanceCount; } IntegerMoveable(const IntegerMoveable &other) : val(other.val) { delete new int; ++instanceCount; } IntegerMoveable &operator=(const IntegerMoveable &other) { delete new int; val = other.val; return *this; } ~IntegerMoveable() { --instanceCount; } int value() const { return val; } int val; static int instanceCount; }; int IntegerMoveable::instanceCount = 0; QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(IntegerMoveable, Q_MOVABLE_TYPE); QT_END_NAMESPACE template class Container> void containerInsertTest(QObject*) { Container container; // insert an item in an empty container try { container.insert(container.begin(), 41); } catch (...) { QVERIFY(container.isEmpty()); QCOMPARE(T::instanceCount, 0); return; } QCOMPARE(container.size(), 1); QCOMPARE(T::instanceCount, 1); // insert an item before another item try { container.insert(container.begin(), 42); } catch (...) { QCOMPARE(container.size(), 1); QCOMPARE(container.first().value(), 41); QCOMPARE(T::instanceCount, 1); return; } QCOMPARE(T::instanceCount, 2); // insert an item in between try { container.insert(container.begin() + 1, 43); } catch (...) { QCOMPARE(container.size(), 2); QCOMPARE(container.first().value(), 41); QCOMPARE((container.begin() + 1)->value(), 42); QCOMPARE(T::instanceCount, 2); return; } QCOMPARE(T::instanceCount, 3); } template class Container> void containerAppendTest(QObject*) { Container container; // append to an empty container try { container.append(42); } catch (...) { QCOMPARE(container.size(), 0); QCOMPARE(T::instanceCount, 0); return; } // append to a container with one item try { container.append(43); } catch (...) { QCOMPARE(container.size(), 1); QCOMPARE(container.first().value(), 42); QCOMPARE(T::instanceCount, 1); return; } Container container2; try { container2.append(44); } catch (...) { // don't care return; } QCOMPARE(T::instanceCount, 3); // append another container with one item try { container += container2; } catch (...) { QCOMPARE(container.size(), 2); QCOMPARE(container.first().value(), 42); QCOMPARE((container.begin() + 1)->value(), 43); QCOMPARE(T::instanceCount, 3); return; } QCOMPARE(T::instanceCount, 4); } template class Container> void containerEraseTest(QObject*) { Container container; try { container.append(42); container.append(43); container.append(44); container.append(45); container.append(46); } catch (...) { // don't care return; } // sanity checks QCOMPARE(container.size(), 5); QCOMPARE(T::instanceCount, 5); // delete the first one try { container.erase(container.begin()); } catch (...) { QCOMPARE(container.size(), 5); QCOMPARE(container.first().value(), 42); QCOMPARE(T::instanceCount, 5); return; } QCOMPARE(container.size(), 4); QCOMPARE(container.first().value(), 43); QCOMPARE(T::instanceCount, 4); // delete the last one try { container.erase(container.end() - 1); } catch (...) { QCOMPARE(container.size(), 4); QCOMPARE(T::instanceCount, 4); return; } QCOMPARE(container.size(), 3); QCOMPARE(container.first().value(), 43); QCOMPARE((container.begin() + 1)->value(), 44); QCOMPARE((container.begin() + 2)->value(), 45); QCOMPARE(T::instanceCount, 3); // delete the middle one try { container.erase(container.begin() + 1); } catch (...) { QCOMPARE(container.size(), 3); QCOMPARE(container.first().value(), 43); QCOMPARE((container.begin() + 1)->value(), 44); QCOMPARE((container.begin() + 2)->value(), 45); QCOMPARE(T::instanceCount, 3); return; } QCOMPARE(container.size(), 2); QCOMPARE(container.first().value(), 43); QCOMPARE((container.begin() + 1)->value(), 45); QCOMPARE(T::instanceCount, 2); } template class Container> static void containerData() { QTest::addColumn("testFunction"); QTest::newRow("insert static") << static_cast(containerInsertTest); QTest::newRow("append static") << static_cast(containerAppendTest); QTest::newRow("erase static") << static_cast(containerEraseTest); QTest::newRow("insert moveable") << static_cast(containerInsertTest); QTest::newRow("append moveable") << static_cast(containerAppendTest); QTest::newRow("erase moveable") << static_cast(containerEraseTest); } void tst_ExceptionSafety_Objects::vector_data() { containerData(); } void tst_ExceptionSafety_Objects::vector() { QFETCH(TestFunction, testFunction); if (QLatin1String(QTest::currentDataTag()) == QLatin1String("insert static") || QLatin1String(QTest::currentDataTag()) == QLatin1String("insert moveable")) QSKIP("QVector::insert is currently not strongly exception safe", SkipSingle); doOOMTest(testFunction, 0); } void tst_ExceptionSafety_Objects::list_data() { containerData(); } void tst_ExceptionSafety_Objects::list() { QFETCH(TestFunction, testFunction); doOOMTest(testFunction, 0); } void tst_ExceptionSafety_Objects::linkedList_data() { containerData(); } void tst_ExceptionSafety_Objects::linkedList() { QFETCH(TestFunction, testFunction); doOOMTest(testFunction, 0); } QTEST_MAIN(tst_ExceptionSafety_Objects) #include "tst_exceptionsafety_objects.moc" #endif // QT_NO_EXCEPTIONS