macOS: Add root level NSAutoreleasePool for objects autoreleased in main()

Any objects directly or indirectly autoreleased in main(), before we start
the event loop, will never be released, as there are no pools present yet.

This includes all resources allocated during application and window setup,
unless those function have local pools. Ideally that setup code would be
called from within the runloop callstack, where there is a pool present,
but that requires a new main/startup-API for Qt.

To aid in debugging object ownership and hierarchies within Qt, we set up
our own root level pool tied to QApplication, which ensures that most objects
autoreleased in main() will eventually be released and have their dealloc
methods called.

The feature can be disabled by setting an environment variable:

  QT_DISABLE_ROOT_LEVEL_AUTORELEASE_POOL=1

Combined with OBJC_DEBUG_MISSING_POOLS=YES, this allows breaking on the
function objc_autoreleaseNoPool to weed out codepaths in Qt that should
have local pools.

Change-Id: Id02e1edaaaeaa04c53862d7228e519214c99ab51
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Tor Arne Vestbø 2017-04-20 18:02:16 +02:00
parent a6f3bb21e3
commit 4a5a4245b7
3 changed files with 60 additions and 0 deletions

View File

@ -93,6 +93,45 @@ QMacAutoReleasePool::~QMacAutoReleasePool()
[static_cast<NSAutoreleasePool*>(pool) drain];
}
#ifdef Q_OS_MACOS
/*!
Ensure that Objective-C objects auto-released in main(), directly or indirectly,
after QCoreApplication construction, are released when the app goes out of scope.
The memory will be reclaimed by the system either way when the process exits,
but by having a root level pool we ensure that the objects get their dealloc
methods called, which is useful for debugging object ownership graphs, etc.
*/
QT_END_NAMESPACE
#define ROOT_LEVEL_POOL_MARKER QT_ROOT_LEVEL_POOL__THESE_OBJECTS_WILL_BE_RELEASED_WHEN_QAPP_GOES_OUT_OF_SCOPE
@interface QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) : NSObject @end
@implementation QT_MANGLE_NAMESPACE(ROOT_LEVEL_POOL_MARKER) @end
QT_NAMESPACE_ALIAS_OBJC_CLASS(ROOT_LEVEL_POOL_MARKER);
QT_BEGIN_NAMESPACE
const char ROOT_LEVEL_POOL_DISABLE_SWITCH[] = "QT_DISABLE_ROOT_LEVEL_AUTORELEASE_POOL";
QMacRootLevelAutoReleasePool::QMacRootLevelAutoReleasePool()
{
if (qEnvironmentVariableIsSet(ROOT_LEVEL_POOL_DISABLE_SWITCH))
return;
pool.reset(new QMacAutoReleasePool);
[[[ROOT_LEVEL_POOL_MARKER alloc] init] autorelease];
if (qstrcmp(qgetenv("OBJC_DEBUG_MISSING_POOLS"), "YES") == 0) {
qDebug("QCoreApplication root level NSAutoreleasePool in place. Break on ~%s and use\n" \
"'p [NSAutoreleasePool showPools]' to show leaked objects, or set %s",
__FUNCTION__, ROOT_LEVEL_POOL_DISABLE_SWITCH);
}
}
QMacRootLevelAutoReleasePool::~QMacRootLevelAutoReleasePool()
{
}
#endif
// -------------------------------------------------------------------------
#ifdef Q_OS_OSX

View File

@ -68,6 +68,7 @@
#endif
#include "qstring.h"
#include "qscopedpointer.h"
#if defined( __OBJC__) && defined(QT_NAMESPACE)
#define QT_NAMESPACE_ALIAS_OBJC_CLASS(__KLASS__) @compatibility_alias __KLASS__ QT_MANGLE_NAMESPACE(__KLASS__)
@ -96,6 +97,18 @@ protected:
T value;
};
#ifdef Q_OS_MACOS
class QMacRootLevelAutoReleasePool
{
public:
QMacRootLevelAutoReleasePool();
~QMacRootLevelAutoReleasePool();
private:
QScopedPointer<QMacAutoReleasePool> pool;
};
#endif
/*
Helper class that automates refernce counting for CFtypes.
After constructing the QCFType object, it can be copied like a

View File

@ -58,6 +58,10 @@
#include "private/qobject_p.h"
#endif
#ifdef Q_OS_MACOS
#include "private/qcore_mac_p.h"
#endif
QT_BEGIN_NAMESPACE
typedef QList<QTranslator*> QTranslatorList;
@ -85,6 +89,10 @@ public:
QString appName() const;
QString appVersion() const;
#ifdef Q_OS_MACOS
QMacRootLevelAutoReleasePool autoReleasePool;
#endif
#ifdef Q_OS_DARWIN
static QString infoDictionaryStringProperty(const QString &propertyName);
#endif