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>
This commit is contained in:
parent
ff9080e740
commit
d2a988512e
@ -84,19 +84,83 @@ 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
|
||||
/*
|
||||
Ensure that Objective-C objects auto-released in main(), directly or indirectly,
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,4 @@
|
||||
CONFIG += testcase
|
||||
TARGET = tst_qmacautoreleasepool
|
||||
QT = core testlib
|
||||
SOURCES = 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"
|
@ -67,3 +67,4 @@ SUBDIRS=\
|
||||
qvector_strictiterators \
|
||||
qversionnumber
|
||||
|
||||
darwin: SUBDIRS += qmacautoreleasepool
|
||||
|
Loading…
Reference in New Issue
Block a user