[Development] Disavowing the Lakos Rule for Q_ASSERT

Thiago Macieira thiago.macieira at intel.com
Thu Aug 29 17:31:54 CEST 2024


On Thursday 29 August 2024 06:43:58 GMT-7 Marc Mutz via Development wrote:
> I'm ok with 1-3, I'm not ok with 4.
> 
> The state of the art is what the std prescribes, not what a stdlib
> implementation does. stdlibs are magic; they can theoretically remove
> the noexcept for new code and keep it for old. Or they can let contract
> violations pass magically. We can't, at least not with a lot of manual
> labour (QT_REMOVED_SINCE/QT6_NEW_OVERLOAD).

They're not _that_ magic. There is a certain amount of compiler-helped magic 
in them, true, but the majority of the Standard Libraries are simply libraries 
written in standard C++. So whatever solution they come up with, even if not 
part of the standard, may be something we can use too. If they decide for a 
binary- or source-incompatible build mode, we can too. If they decide for some 
attribute marker, we can too.

Moreover, this itself should be a consideration for the contracts feature in 
the first place, both for standardisation and for implementations. That is, the 
proponents of the feature ought to provide a means for adoption in content 
that is currently noexcept (wide or mostly-wide contract).

> If we get (1), I think we'll very quickly also get a build mode in which
> these throw.

Unsupported mode. We don't use exceptions and we don't plan on beginning now. 
You can make them throw, but you can't expect your application to work 
properly afterwards because the *rest* of Qt isn't designed for it. For a 
recent example, see https://codereview.qt-project.org/c/qt/qtbase/+/586032. 
Specifically, see the message in PS1 where I noted that QProcess will be in an 
unexpected and possibly corrupt state if an exception/unwind happens. Ossi had 
me remove the text because it's un-actionable.

Which brings me to why I am asking this. We are deliberately tying our hands 
for something that:
a) won't be in general use in Qt for some 6-8 years from today (thus may be 
  past 7.0)
b) needs adaptation in the Standard Library implementations and thus will
  likely provide solutions to the same issue
c) requires exceptions, something we do not support

> And I'm also questioning the alternatives to throwing from assertions
> (or Q_PRE, if you want). You can fork, yes. Very complicated, and
> probably slow as hell (I still remember Qt 5 QSharedPointer negative
> tests were even slow on Linux). We can't call a QtTest-handler, because
> that handler cannot return without an exception, because the code
> following the assertion typically assumes that the assertion holds and
> runs into Language UB (non-recoverable) as a consequence if it didn't.

The negative tests weren't just forking. They were a fork-and-exec of the 
compiler, then running the output. A fork-and-run-until-abort is much simpler 
and should be very fast. But I agree it's not for every case, because there 
are constraints in global state, such as open file descriptors (logs, sockets, 
etc.) On the other hand, it may work in conditions where exceptions don't: 
namely, exception-unsafe code.

Of which there's a lot in Qt.

PS: running stuff safely in fork-no-exec mode has been my $DAYJOB for the past 
5 years. I have a lot of experience in this.

> Finally, one thing that Lakos wrote in his paper
> (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2861r0.pdf)
> was that it should be up to *the owner of main()* to determine how
> precondition violations are handled. Neither std nor Qt should decide
> for the user here.

I agree with that in principle. The same rule applies to signal handlers like 
SIGINT, SIGTERM and SIGSEGV: the owner of main() decides what action the 
application should take. It may be a decision that they delegated to some 
library for consistency with their ecosystem, but that itself is a decision.

However, Qt has explicitly disclaimed support for exceptions transiting 
through its code. Therefore, the owner of main() may choose this mode, but 
they need to be aware of where it may happen and how far the exception is 
allowed to propagate. For one thing, C++ does not exist in isolation and the 
stack trace can (and does) contain non-C++ frames that may be unable to 
support unwinding. See all the issues with exceptions propagating through the 
Qt event loop that used to work on Intel Macs but do not work on Apple ARM 
silicon.

So, reality impinges and I question the ability to widely use exceptions for 
contract violations.

> I think this is very pertinent to the current software security
> legislation being proposed on both sides and Qt should not shut the door
> in front of users that would like to run checked build modes in production.
> 
> Therefore: no noexcept on narrow-contract functions is still paramount,
> IMNSHO.

I agree with the premise but not the conclusion. There are other ways of 
verifying preconditions besides exceptions.

Tying this into my $DAYJOB: I work on finding the Silent Data Errors where the 
processor (or any other compute unit) didn't execute the code as specified 
producing data errors, and failed to detect it as such at that point. Ideally 
we'd either correct the problem or not have it in the first place, but the next 
best thing to that is identifying it as an uncorrectable error and halting 
execution immediately. Forget exceptions; your process will get a SIGKILL in 
the best case.

> Again, a hunch is not enough to sway me here. A meaningful increase in
> runtime speed would need to be demonstrated, and even then, we would
> need to weigh that feature against the desire for certain customers (one
> being ourselves, for negative testing) to use a checked build mode in
> production.

I'm willing to be pragmatic.

For the majority of inline code, the compiler won't care about the 
noexceptness of the content: it *knows* whether something threw or not. 
Examples are QStringView::sliced() or the QCborStreamReader members.

What I'd like to change is:
- for inline code, where the function's noexceptness is likely to be used in a  
  noexcept expression elsewhere and that causes slower code to be used
- for out-of-line code, where the precondition is unverifiable anyway (such as
  "you've passed a valid pointer")
-- 
Thiago Macieira - thiago.macieira (AT) intel.com
  Principal Engineer - Intel DCAI Platform & System Engineering
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 5152 bytes
Desc: not available
URL: <http://lists.qt-project.org/pipermail/development/attachments/20240829/e5faaea7/attachment-0001.bin>


More information about the Development mailing list