common/QskObjectCounter.cpp

Functions

  Name
bool qskIsItem(const QObject * object)
void qskInstallCleanupHookHandler()
QDebug operator«(QDebug debug, const QskObjectCounter & counter)

Attributes

  Name
bool qskAutoDeleteHook
CounterHook * qskCounterHook

Defines

  Name
  QSK_OBJECT_INFO

Functions Documentation

function qskIsItem

static inline bool qskIsItem(
    const QObject * object
)

function qskInstallCleanupHookHandler

static void qskInstallCleanupHookHandler()

function operator«

QDebug operator<<(
    QDebug debug,
    const QskObjectCounter & counter
)

Attributes Documentation

variable qskAutoDeleteHook

static bool qskAutoDeleteHook = false;

variable qskCounterHook

static CounterHook * qskCounterHook = nullptr;

Macro Documentation

define QSK_OBJECT_INFO

#define QSK_OBJECT_INFO 0

Source code

/******************************************************************************
 * QSkinny - Copyright (C) 2016 Uwe Rathmann
 * This file may be used under the terms of the QSkinny License, Version 1.0
 *****************************************************************************/

#include "QskObjectCounter.h"

#include <qdebug.h>
#include <qset.h>


#include <private/qhooks_p.h>
#include <private/qobject_p.h>
#include <private/qquickitem_p.h>


#define QSK_OBJECT_INFO 0

#if QSK_OBJECT_INFO
#include <qset.h>
#endif

static inline bool qskIsItem( const QObject* object )
{
    QObjectPrivate* o_p = QObjectPrivate::get( const_cast< QObject* >( object ) );

    /*
        The addObject hook is called from the constructor of QObject,
        where we don't have the derived class constructed yet.
        So we can't cast the object itself and also have to rely on
        RTTI being enabled. TODO ...
     */

    return dynamic_cast< QQuickItemPrivate* >( o_p ) != nullptr;
}

namespace
{
    class Counter
    {
      public:
        Counter()
        {
            reset();
        }

        void reset()
        {
            created = destroyed = current = maximum = 0;
        }

        void increment()
        {
            created++;
            current++;

            if ( current > maximum )
                maximum = current;
        }

        void decrement()
        {
            destroyed++;
            current--;
        }

        int created;
        int destroyed;
        int current;
        int maximum;
    };

    class CounterData final : public QObject
    {
      public:
        Counter counter[ 2 ];

#if QSK_OBJECT_INFO
        QSet< const QObject* > objectTable;
#endif
    };

    class CounterHook
    {
      public:
        CounterHook();
        ~CounterHook();

        void registerCounters( CounterData*, bool on );
        bool isCountersRegistered( const CounterData*  ) const;

        bool isActive() const;

        void startup();
        void addObject( QObject* );
        void removeObject( QObject* );

        static void cleanupHook();

      private:
        static void startupHook();
        static void addObjectHook( QObject* );
        static void removeObjectHook( QObject* );

        QSet< CounterData* > m_counterDataSet;

        quintptr m_otherStartup;
        quintptr m_otherAddObject;
        quintptr m_otherRemoveObject;
    };
}

static bool qskAutoDeleteHook = false;
static CounterHook* qskCounterHook = nullptr;

CounterHook::CounterHook()
{
    m_otherStartup = qtHookData[ QHooks::Startup ];
    m_otherAddObject = qtHookData[ QHooks::AddQObject ];
    m_otherRemoveObject = qtHookData[ QHooks::RemoveQObject ];

    qtHookData[ QHooks::Startup ] = reinterpret_cast< quintptr >( &startupHook );
    qtHookData[ QHooks::AddQObject ] = reinterpret_cast< quintptr >( &addObjectHook );
    qtHookData[ QHooks::RemoveQObject ] = reinterpret_cast< quintptr >( &removeObjectHook );
}

CounterHook::~CounterHook()
{
    qtHookData[ QHooks::Startup ] = m_otherStartup;
    qtHookData[ QHooks::AddQObject ] = m_otherAddObject;
    qtHookData[ QHooks::RemoveQObject ] = m_otherRemoveObject;
}

void CounterHook::registerCounters( CounterData* data, bool on )
{
    if ( on )
        m_counterDataSet.insert( data );
    else
        m_counterDataSet.remove( data );
}

bool CounterHook::isCountersRegistered( const CounterData* data ) const
{
    return m_counterDataSet.contains( const_cast< CounterData* >( data ) );
}

bool CounterHook::isActive() const
{
    return !m_counterDataSet.isEmpty();
}

void CounterHook::startup()
{
#if 0
    qDebug() << "** QskObjectCounterHook enabled";
#endif
    if ( m_otherStartup )
        reinterpret_cast< QHooks::StartupCallback >( m_otherStartup )();
}

void CounterHook::addObject( QObject* object )
{
    const bool isItem = qskIsItem( object );

    for ( auto counterData : qskAsConst( m_counterDataSet ) )
    {
        counterData->counter[ QskObjectCounter::Objects ].increment();

        if ( isItem )
            counterData->counter[ QskObjectCounter::Items ].increment();

#if QSK_OBJECT_INFO
        counterData->objectTable.insert( object );
#endif
    }

    if ( m_otherAddObject )
        reinterpret_cast< QHooks::AddQObjectCallback >( m_otherAddObject )( object );
}

void CounterHook::removeObject( QObject* object )
{
    const bool isItem = qskIsItem( object );

    for ( auto counterData : qskAsConst( m_counterDataSet ) )
    {
        counterData->counter[ QskObjectCounter::Objects ].decrement();

        if ( isItem )
            counterData->counter[ QskObjectCounter::Items ].decrement();

#if QSK_OBJECT_INFO
        counterData->objectTable.remove( object );
#endif
    }

    if ( m_otherRemoveObject )
        reinterpret_cast< QHooks::RemoveQObjectCallback >( m_otherRemoveObject )( object );
}

void CounterHook::startupHook()
{
    if ( qskCounterHook )
        qskCounterHook->startup();
}

void CounterHook::addObjectHook( QObject* object )
{
    if ( qskCounterHook )
        qskCounterHook->addObject( object );
}

void CounterHook::removeObjectHook( QObject* object )
{
    if ( qskCounterHook )
        qskCounterHook->removeObject( object );
}

void CounterHook::cleanupHook()
{
    if ( qskCounterHook && !qskCounterHook->isActive() )
    {
        delete qskCounterHook;
        qskCounterHook = nullptr;
    }

    // From now on we remove the hooks as soon as there are no counters
    qskAutoDeleteHook = true;
}

static void qskInstallCleanupHookHandler()
{
    qAddPostRoutine( CounterHook::cleanupHook );
}

Q_COREAPP_STARTUP_FUNCTION( qskInstallCleanupHookHandler )

class QskObjectCounter::PrivateData
{
  public:
    PrivateData( bool debugAtDestruction )
        : debugAtDestruction( debugAtDestruction )
    {
    }

    CounterData counterData;
    const bool debugAtDestruction;
};

QskObjectCounter::QskObjectCounter( bool debugAtDestruction )
    : m_data( new PrivateData( debugAtDestruction ) )
{
    setActive( true );
}

QskObjectCounter::~QskObjectCounter()
{
    setActive( false );

    if ( m_data->debugAtDestruction )
        dump();
}

void QskObjectCounter::setActive( bool on )
{
    if ( on )
    {
        if ( qskCounterHook == nullptr )
            qskCounterHook = new CounterHook();

        qskCounterHook->registerCounters( &m_data->counterData, on );
    }
    else
    {
        qskCounterHook->registerCounters( &m_data->counterData, on );
        if ( !qskCounterHook->isActive() )
        {
            if ( qskAutoDeleteHook )
            {
                delete qskCounterHook;
                qskCounterHook = nullptr;
            }
        }
    }
}

bool QskObjectCounter::isActive() const
{
    return qskCounterHook && qskCounterHook->isCountersRegistered( &m_data->counterData );
}

void QskObjectCounter::reset()
{
    auto& counters = m_data->counterData.counter;

    counters[ Objects ].reset();
    counters[ Items ].reset();
}

int QskObjectCounter::created( ObjectType objectType ) const
{
    return m_data->counterData.counter[ objectType ].created;
}

int QskObjectCounter::destroyed( ObjectType objectType ) const
{
    return m_data->counterData.counter[ objectType ].destroyed;
}

int QskObjectCounter::current( ObjectType objectType ) const
{
    return m_data->counterData.counter[ objectType ].current;
}

int QskObjectCounter::maximum( ObjectType objectType ) const
{
    return m_data->counterData.counter[ objectType ].maximum;
}

void QskObjectCounter::debugStatistics( QDebug debug, ObjectType objectType ) const
{
    const auto& c = m_data->counterData.counter[ objectType ];

    QDebugStateSaver saver( debug );
    debug.nospace();
    debug << '(';
    debug << "created: " << c.created
          << ", destroyed: " << c.destroyed
          << ", current: " << c.current
          << ", maximum: " << c.maximum;
    debug << ')';

#if QSK_OBJECT_INFO
    if ( objectType == Objects )
    {
        const auto& objectTable = m_data->counterData.objectTable;

        if ( !objectTable.isEmpty() )
        {
            debug << "\n\t=== Leaks ===\n";
            for ( const auto object : objectTable )
            {
                debug << "\tClass: " << object->metaObject()->className();
                if ( !object->objectName().isEmpty() )
                    debug << " Name: " << object->objectName(); 
                debug << '\n';
            }
        }
    }
#endif
}

void QskObjectCounter::dump() const
{
    QDebug debug = qDebug();

    QDebugStateSaver saver( debug );

    debug.nospace();

    debug << "* Statistics\n";
    debug << "  Objects: ";
    debugStatistics( debug, Objects );

    debug << "\n  Items: ";
    debugStatistics( debug, Items );
}

#ifndef QT_NO_DEBUG_STREAM

QDebug operator<<( QDebug debug, const QskObjectCounter& counter )
{
    counter.debugStatistics( debug, QskObjectCounter::Objects );
    return debug;
}

#endif

Updated on 28 July 2023 at 14:02:29 CEST