From: Clarence Dang <dang@kde.org>
To: qt-bugs@trolltech.com
Subject: QObject receives signal from object disconnected from it
Date: Fri, 21 Jul 2006 15:36:18 +1000


Qt 4.2 TP on X11

A. Test Case
~~~~~~~~~~~~

The attached test case (signalbug.{h,cpp}) results in the slot 
"Disconnector::received()" receiving a signal from an object it is no longer 
connected to ("Sender").
This results in QObject::sender() returning 0 in a slot.  You'll see the 
message "WE SHOULD NOT BE RECEIVING THIS SIGNAL".

The test case works like this (please turn on fixed text to view):

T = class "RandomObject"

1    2    3    4
S -> r -> S -> r
 \        \
  \        \    5
   \        \_> d disconnects S-d, adds T-d
    \
     \    6
      \_> d (connection supposed to be gone)

S = class "Sender", r = class "Receiver", d = class "Disconnector"
Initially, the connections are S-r and S-d.  The arrows represent the call 
graph.  The numbers correspond to the variable "::Step" and represent the 
chronological order of the problem.


B. Source Code Analysis of Qt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Essentially, QMetaObject::activate() is executing in Step 1 but is re-entered 
in Step 3.

By Step 5, this re-entry causes the "inUse" flag to be set to 0 for the 
connection between S-d.  However the S-d connection is actually still in use 
(for the invocation of the second slot in Step 1).

We then disconnect S-d.  We connect RandomObject-d but because "inUse" has 
been set to 0, Qt reuses the QConnection instance (see 
QConnectionList::addConnection()).

At Step 6, Qt considers whether it should invoke the 2nd slot:

       if (!c.receiver || ((c.signal < from_signal_index || c.signal > 
to_signal_index) &&
                            c.signal != -1))
            continue;

It sees a valid QConnection instance even though it is now connected from 
RandomObject-d, not from S-d.  So it invokes the slot when it shouldn't.

Does not happen in Qt 3.3.


C. Quick Fix
~~~~~~~~~~~~

Attached diff.  Sufficient to fix this case but probably not general.


D. Impact
~~~~~~~~~

If you are interested in seeing a "real-world" impact:

This bug was discovered when debugging KolourPaint and KSelectAction in KDE 4.  
I will commit a patch to KSelectAction that triggers this Qt bug very shortly 
(the patch is needed to fix a KSelectAction bug).

The exact control flow is quite complicated and take a few tries to rerpoduce.  
Essentially, selecting a new zoom level from the "View / Zoom" menu in 
KolourPaint trips an assertion as sender() == 0:

~~~~
ASSERT failure in QWidgetGroup::_q_actionTriggered: "internal error", file 
kernel/qactiongroup.cpp, line 71

(gdb) bt
#0  0x00300402 in __kernel_vsyscall ()
#1  0x0706d1f8 in raise () from /lib/libc.so.6
#2  0x0706e948 in abort () from /lib/libc.so.6
#3  0x003dcc49 in qt_message_output () 
from /home/kde4/celerysvn/qt-copy/lib/libQtCore_debug.so.4
#4  0x003dcc9a in qFatal () 
from /home/kde4/celerysvn/qt-copy/lib/libQtCore_debug.so.4
#5  0x003dccd0 in qt_assert_x () 
from /home/kde4/celerysvn/qt-copy/lib/libQtCore_debug.so.4
#6  0x0078358d in QActionGroupPrivate::_q_actionTriggered (this=0x82cf038) at 
kernel/qactiongroup.cpp:71
#7  0x00783b8f in QActionGroup::qt_metacall (this=0x82cf028, 
_c=QMetaObject::InvokeMetaMethod, _id=7, _a=0xbfee216c)
    at .moc/debug-shared/moc_qactiongroup.cpp:91
#8  0x00499531 in QMetaObject::activate (sender=0x82d4890, 
from_signal_index=5, to_signal_index=6, argv=0xbfee216c) at 
kernel/qobject.cpp:2863
#9  0x00499683 in QMetaObject::activate (sender=0x82d4890, m=0xc92d50, 
from_local_signal_index=1, to_local_signal_index=2, argv=0xbfee216c)
    at kernel/qobject.cpp:2917
#10 0x0077df78 in QAction::triggered (this=0x82d4890, _t1=true) 
at .moc/debug-shared/moc_qaction.cpp:200
#11 0x0077e951 in QAction::activate (this=0x82d4890, event=QAction::Trigger) 
at kernel/qaction.cpp:1051
#12 0x00ab870a in QMenuPrivate::activateAction (this=0x82cf0c8, 
action=0x82d4890, action_e=QAction::Trigger) at widgets/qmenu.cpp:731
#13 0x00abce7a in QMenu::mouseReleaseEvent (this=0x82cee60, e=0xbfee29ec) at 
widgets/qmenu.cpp:1812
#14 0x02216e48 in KMenu::mouseReleaseEvent () 
from /home/kde4/dist/lib/libkdeui.so.5
#15 0x007d53ba in QWidget::event () 
at ../../include/QtCore/../../src/corelib/global/qnamespace.h:1343
#16 0x00ab6080 in QMenu::event (this=0x82cee60, e=0xbfee29ec) at 
widgets/qmenu.cpp:1883
#17 0x00785ddf in QApplicationPrivate::notify_helper () 
at ../../include/QtCore/../../src/corelib/global/qglobal.h:1582
#18 0x007869ea in QApplication::notify () 
at ../../include/QtCore/../../src/corelib/global/qglobal.h:1582
#19 0x01099853 in KApplication::notify () 
from /home/kde4/dist/lib/libkdecore.so.5
#20 0x00df05f5 in QCoreApplication::sendSpontaneousEvent () 
from /home/kde4/celerysvn/qt-copy/lib/libQt3Support_debug.so.4
#21 0x007ed435 in QETWidget::translateMouseEvent () 
at ../../include/QtGui/../../src/gui/kernel/qdesktopwidget.h:59
#22 0x007eb5fe in QApplication::x11ProcessEvent () 
at ../../include/QtGui/../../src/gui/kernel/qdesktopwidget.h:59
#23 0x00812ebe in x11EventSourceDispatch () 
at ../../include/QtCore/../../src/corelib/tools/qmap.h:568
#24 0x07c043ee in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0
#25 0x07c073f6 in g_main_context_check () from /usr/lib/libglib-2.0.so.0
#26 0x07c078d8 in g_main_context_iteration () from /usr/lib/libglib-2.0.so.0
#27 0x004b1932 in QEventDispatcherGlib::processEvents () 
from /home/kde4/celerysvn/qt-copy/lib/libQtCore_debug.so.4
#28 0x0081295b in QGuiEventDispatcherGlib::processEvents () 
at ../../include/QtCore/../../src/corelib/tools/qmap.h:568
#29 0x004876c9 in QEventLoop::processEvents () at 
kernel/qcoreapplication.h:171
#30 0x004879a8 in QEventLoop::exec () at kernel/qcoreapplication.h:171
#31 0x0048c421 in QCoreApplication::exec () at kernel/qcoreapplication.h:171
#32 0x00785285 in QApplication::exec () 
at ../../include/QtCore/../../src/corelib/global/qglobal.h:1582
#33 0x080d4cf1 in main ()
~~~~
