summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2017-07-22 16:41:44 +0200
committerSimon Hausmann <simon.hausmann@qt.io>2017-09-08 12:05:21 +0000
commitd2a988512efcad7a72c6624f7015fc08271ae0a6 (patch)
tree8280777663077bd7fe3624dc71f81354d7b0a7d3
parentff9080e74062f5409eebd7dcb823a1a7d9bf2bf7 (diff)
macOS: Detect use of heap-allocated QMacAutoReleasePool
QMacAutoReleasePool is backed by an NSAutoreleasePool, which documents that "you should always drain an autorelease pool in the same context (invocation of a method or function, or body of a loop) that it was created". This means allocating QMacAutoReleasePool on the heap is not a supported use-case, but unfortunately we can't detect it on construction time. Instead we detect whether or not the associated NSAutoreleasePool has been drained, and prevent a double-drain of the pool. Change-Id: Ifd7380a06152e9e742d2e199476ed3adab326d9c Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
-rw-r--r--src/corelib/kernel/qcore_mac_objc.mm66
-rw-r--r--src/corelib/kernel/qcore_mac_p.h4
-rw-r--r--tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro4
-rw-r--r--tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm111
-rw-r--r--tests/auto/corelib/tools/tools.pro1
5 files changed, 185 insertions, 1 deletions
diff --git a/src/corelib/kernel/qcore_mac_objc.mm b/src/corelib/kernel/qcore_mac_objc.mm
index db7e55f4b1..24d73fa8be 100644
--- a/src/corelib/kernel/qcore_mac_objc.mm
+++ b/src/corelib/kernel/qcore_mac_objc.mm
@@ -84,18 +84,82 @@ QT_FOR_EACH_MUTABLE_CORE_GRAPHICS_TYPE(QT_DECLARE_WEAK_QDEBUG_OPERATOR_FOR_CF_TY
// -------------------------------------------------------------------------
+QT_END_NAMESPACE
+QT_USE_NAMESPACE
+@interface QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker) : NSObject
+{
+ NSAutoreleasePool **m_pool;
+}
+-(id)initWithPool:(NSAutoreleasePool**)pool;
+@end
+@implementation QT_MANGLE_NAMESPACE(QMacAutoReleasePoolTracker)
+-(id)initWithPool:(NSAutoreleasePool**)pool
+{
+ if (self = [super init])
+ m_pool = pool;
+ return self;
+}
+-(void)dealloc
+{
+ if (*m_pool) {
+ // The pool is still valid, which means we're not being drained from
+ // the corresponding QMacAutoReleasePool (see below).
+
+ // QMacAutoReleasePool has only a single member, the NSAutoreleasePool*
+ // so the address of that member is also the QMacAutoReleasePool itself.
+ QMacAutoReleasePool *pool = reinterpret_cast<QMacAutoReleasePool *>(m_pool);
+ qWarning() << "Premature drain of" << pool << "This can happen if you've allocated"
+ << "the pool on the heap, or as a member of a heap-allocated object. This is not a"
+ << "supported use of QMacAutoReleasePool, and might result in crashes when objects"
+ << "in the pool are deallocated and then used later on under the assumption they"
+ << "will be valid until" << pool << "has been drained.";
+
+ // Reset the pool so that it's not drained again later on
+ *m_pool = nullptr;
+ }
+
+ [super dealloc];
+}
+@end
+QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAutoReleasePoolTracker);
+QT_BEGIN_NAMESPACE
+
QMacAutoReleasePool::QMacAutoReleasePool()
: pool([[NSAutoreleasePool alloc] init])
{
+ [[[QMacAutoReleasePoolTracker alloc] initWithPool:
+ reinterpret_cast<NSAutoreleasePool **>(&pool)] autorelease];
}
QMacAutoReleasePool::~QMacAutoReleasePool()
{
+ if (!pool) {
+ qWarning() << "Prematurely drained pool" << this << "finally drained. Any objects belonging"
+ << "to this pool have already been released, and have potentially been invalid since the"
+ << "premature drain earlier on.";
+ return;
+ }
+
+ // Save and reset pool before draining, so that the pool tracker can know
+ // that it's being drained by its owning pool.
+ NSAutoreleasePool *savedPool = static_cast<NSAutoreleasePool*>(pool);
+ pool = nullptr;
+
// Drain behaves the same as release, with the advantage that
// if we're ever used in a garbage-collected environment, the
// drain acts as a hint to the garbage collector to collect.
- [static_cast<NSAutoreleasePool*>(pool) drain];
+ [savedPool drain];
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool)
+{
+ QDebugStateSaver saver(debug);
+ debug.nospace();
+ debug << "QMacAutoReleasePool(" << (const void *)pool << ')';
+ return debug;
}
+#endif // !QT_NO_DEBUG_STREAM
#ifdef Q_OS_MACOS
/*
diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h
index 5522a617b3..13143a08bb 100644
--- a/src/corelib/kernel/qcore_mac_p.h
+++ b/src/corelib/kernel/qcore_mac_p.h
@@ -153,6 +153,10 @@ Q_CORE_EXPORT QChar qt_mac_qtKey2CocoaKey(Qt::Key key);
Q_CORE_EXPORT Qt::Key qt_mac_cocoaKey2QtKey(QChar keyCode);
#endif
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug debug, const QMacAutoReleasePool *pool);
+#endif
+
Q_CORE_EXPORT void qt_apple_check_os_version();
QT_END_NAMESPACE
diff --git a/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro b/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro
new file mode 100644
index 0000000000..26b3a47472
--- /dev/null
+++ b/tests/auto/corelib/tools/qmacautoreleasepool/qmacautoreleasepool.pro
@@ -0,0 +1,4 @@
+CONFIG += testcase
+TARGET = tst_qmacautoreleasepool
+QT = core testlib
+SOURCES = tst_qmacautoreleasepool.mm
diff --git a/tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm b/tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm
new file mode 100644
index 0000000000..8f1069f419
--- /dev/null
+++ b/tests/auto/corelib/tools/qmacautoreleasepool/tst_qmacautoreleasepool.mm
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+
+#include <Foundation/Foundation.h>
+
+class tst_QMacAutoreleasePool : public QObject
+{
+ Q_OBJECT
+private slots:
+ void noPool();
+ void rootLevelPool();
+ void stackAllocatedPool();
+ void heapAllocatedPool();
+};
+
+static id lastDeallocedObject = nil;
+
+@interface DeallocTracker : NSObject @end
+@implementation DeallocTracker
+-(void)dealloc
+{
+ lastDeallocedObject = self;
+ [super dealloc];
+}
+@end
+
+void tst_QMacAutoreleasePool::noPool()
+{
+ // No pool, will not be released, but should not crash
+
+ [[[DeallocTracker alloc] init] autorelease];
+}
+
+void tst_QMacAutoreleasePool::rootLevelPool()
+{
+ // The root level case, no NSAutoreleasePool since we're not in the main
+ // runloop, and objects autoreleased as part of main.
+
+ NSObject *allocedObject = nil;
+ {
+ QMacAutoReleasePool qtPool;
+ allocedObject = [[[DeallocTracker alloc] init] autorelease];
+ }
+ QCOMPARE(lastDeallocedObject, allocedObject);
+}
+
+void tst_QMacAutoreleasePool::stackAllocatedPool()
+{
+ // The normal case, other pools surrounding our pool, draining
+ // our pool before any other pool.
+
+ NSObject *allocedObject = nil;
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ {
+ QMacAutoReleasePool qtPool;
+ allocedObject = [[[DeallocTracker alloc] init] autorelease];
+ }
+ QCOMPARE(lastDeallocedObject, allocedObject);
+ [pool drain];
+}
+
+void tst_QMacAutoreleasePool::heapAllocatedPool()
+{
+ // The special case, a pool allocated on the heap, or as a member of a
+ // heap allocated object. This is not a supported use of QMacAutoReleasePool,
+ // and will result in warnings if the pool is prematurely drained.
+
+ NSObject *allocedObject = nil;
+ {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ QMacAutoReleasePool *qtPool = nullptr;
+ {
+ qtPool = new QMacAutoReleasePool;
+ allocedObject = [[[DeallocTracker alloc] init] autorelease];
+ }
+ [pool drain];
+ delete qtPool;
+ }
+ QCOMPARE(lastDeallocedObject, allocedObject);
+}
+
+QTEST_APPLESS_MAIN(tst_QMacAutoreleasePool)
+
+#include "tst_qmacautoreleasepool.moc"
diff --git a/tests/auto/corelib/tools/tools.pro b/tests/auto/corelib/tools/tools.pro
index 6720307d59..f35ed026ac 100644
--- a/tests/auto/corelib/tools/tools.pro
+++ b/tests/auto/corelib/tools/tools.pro
@@ -67,3 +67,4 @@ SUBDIRS=\
qvector_strictiterators \
qversionnumber
+darwin: SUBDIRS += qmacautoreleasepool