[Development] HEADS UP: do not pass const objects to QObject::disconnect(const QMetaObject::Connection&)!
Giuseppe D'Angelo
giuseppe.dangelo at kdab.com
Fri Mar 27 02:16:26 CET 2026
Hi,
Il 26/03/26 19:41, Marc Mutz via Development ha scritto:
> Hi,
>
> We Qt developers cannot figure out how to proceed from here, so let me
> give you a heads-up here, so you can take matters into your own hands.
>
> TL;DR: never pass Connection objects originally declared const to
> QObject::disconnect(Connection).
>
> The function const_cast<>s away the const of the argument to reset the
> d_ptr to nullptr. It doesn't matter why, suffice to say that it's not
> trivial to fix either way on the Qt side.
To be honest, I don't understand the alarmism of this email. There's a
bug in Qt; and we fix bugs all the time. The impact of this particular
bug is between miniscule and non-existent.
And I disagree, the bug *is* completely trivial to fix: we can make the
affected member as `mutable`. The only reason the patch wasn't +2
already on Gerrit was for the commit message that talked about impending
deprecations (when there's strong disagreement around it).
If we follow what I consider an extremely speculative line of reasoning,
and conclude that the bug might be manifesting itself in inline code,
then there's no solution: people have to update Qt _and_ recompile,
which is always the case when there's a bug in inline code.
If we reject that line of reasoning (and I do, see below), then it's
sufficient that people update Qt (UB happens in out-of-line code) and
they'll get the fix.
In any case the "avoid passing const objects to disconnect()" is there
as a workaround for those who can recompile their code, but can't
upgrade Qt. Maybe it can become a gated Clazy check.
But I doubt anyone will listen; their code works completely fine today
as their compiler doesn't do anything wrong, and they'll never upgrade
their toolchain without also upgrading Qt.
I don't get why we disagree on such a simple fix that works, can be 100%
safely cherrypicked in all the open branches, and completely preserves
behavior (incl. no soft-leaks); and instead we are now researching
extremely more invasive solutions, including possible deprecations of a
15-20 years old API for a purely theoretical problem, deprecation which
requires fixes in all our repositories and will just annoy the heck out
of our users.
--
Now, the gritty details:
The bug is: there's UB happening in an out of line function. We
const_cast away an object and mutate it; if the object was originally
marked as const, that's formally UB.
As many people associate "UB" with "segfault", I need to clarify: NO,
the program does not crash; it does not write into arbitrary memory /
readonly memory; it does not trigger sanitizers or anything of the sorts.
In fact, programs work just fine, as demonstrated by the fact that this
issue is some ~14 years old and we noticed only now (and not because we
got bug reports: we detected the issue by reading the sources).
I'll state it again:
* the UB happens in out of line code (inside QObject::disconnect(const
QMetaObject::Connection &)).
* the declaration of a QMetaObject::Connection object as `const` happens
in client code;
* therefore, compilers do NOT normally see both the const_cast mutating
the object and original object declared as const, detect UB, and do evil
things based on the presence of UB. LTO or similar technology would be
necessary for the compiler to even be able to do this kind of reasoning.
This firewalling alone reduces the possibility of things going horribly
wrong to infinitesimally small quantities.
* but even without firewalling, no optimizing compiler we support today
detects or exploits UBs in const_cast; even when they're "blatant". If
the Big Three don't optimize on this I reject the notion that "some
other compiler out there might do it". "Some other compiler we don't
know about (and therefore DO NOT support)" isn't a starting point.
* I also reject the notion of "what about compilers of tomorrow". People
who upgrade compilers also upgrade Qt.
* Finally, what *might* be happening (which makes this an issue in
"inline code", see the discussion above) is that a compiler doesn't
reload QMetaObject::Connection's data members because the object was
originally declared `const`.
A sequence like:
const QMetaObject::Connection c = QObject::connect(~~~);
assert(c); // #1
disconnect(c); // const_cast in here
assert(c); // #2
in #1 and #2 touches an inline code path (operator bool()) which loads
c.d_ptr. An optimizing compiler might read c.d_ptr on #1, and not
re-read it on #2 (that is, re-use the value read on #1), despite the
fact that disconnect() call in principle has changed it.
And again major compiler actually does this today:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86318
https://github.com/llvm/llvm-project/issues/160441
So, all in all: this a problem that has zero impact, and we're creating
a meteor-size blast radius (deprecation, users annoyed, code to be
ported, documentation, best practices) ... for what gain exactly?
> In anticipation of a deprecation of the const overload.
I still reject this idea. It *works* but it's just not worth the hassle.
My 2 c,
--
Giuseppe D'Angelo | giuseppe.dangelo at kdab.com | Senior Software Engineer
KDAB (France) S.A.S., a KDAB Group company
Tel. France +33 (0)4 90 84 08 53, http://www.kdab.com
KDAB - Trusted Software Excellence
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 4850 bytes
Desc: Firma crittografica S/MIME
URL: <http://lists.qt-project.org/pipermail/development/attachments/20260327/022c3a01/attachment.bin>
More information about the Development
mailing list