[Development] Disavowing the Lakos Rule for Q_ASSERT
Marc Mutz
marc.mutz at qt.io
Tue Aug 27 07:16:46 CEST 2024
Your two examples are quite different, though:
On 26.08.24 22:28, Thiago Macieira wrote:
> On Monday 26 August 2024 12:59:54 GMT-7 Marc Mutz via Development wrote:
>> Part of why I'm sympathetic to the assertions throw mantra is that I
>> used it very successfully in Kleopatra way back when. We could easily
>> take the exception's what() and report back over the UI server channel
>> that an assertion happened and the client would show a message box with
>> the assertion details, copyable from a QMessageBox into a bugreport.
>> T'was a boon for testing.
>
> Only developers would be interested in seeing exception names. Regular users
> won't understand what that exception means and will just be confused.
>
> I am also vehemently against catching exceptions you don't have a recovery
> code path for. That destroys the stack trace and state that would have helped
> the developer understand and actually fix the problem.
>
> I suppose it's possible someone will use preconditions for recoverable
> conditions, but I've never seen that even proposed, for any API. More
> importantly, for the cases we have at hand, like:
>
> static Qt::strong_ordering compareThreeWay_helper(const Iterator &lhs,
> const Iterator &rhs)
> noexcept
> {
> Q_ASSERT(lhs.item.d == rhs.item.d);
> return Qt::compareThreeWay(lhs.item.i, rhs.item.i);
> }
>
> The most likely reason for violating this precondition is that the caller is
> already corrupt and totally lost. I don't see how it could recover.
This is an iterator API, and that lhs.item.d == rhs.item.d is a
precondition of the equality operator, so outside of our control. As I
mentioned on Gerrit, we can control the width of the contract we choose
to provide, though: we could make the function have a narrow contract if
we declared iterators with mimatching item.d as partial_ordering::unordered.
The fundamental problem that iterators into different containers are
incomparable doesn't go away if you slap noexcept on op==. You have a
decision to make: UB or unordered? And _that_ informs whether it's
noexcept or not.
> Another example is:
> QUuid QCborValue::toUuid(const QUuid &defaultValue) const
> {
> if (!container || !isUuid() || container->elements.size() != 2)
> return defaultValue;
>
> Q_ASSERT(n == -1);
> const ByteData *byteData = container->byteData(1);
> if (!byteData)
> return defaultValue; // UUIDs must always be 16 bytes, so this must be
> invalid
>
> return QUuid::fromRfc4122(byteData->asByteArrayView());
> }
>
> This is verifying an internal invariant state, not a precondition. The only
> two reasons this assertion would trigger are a buggy implementation or
> corrupted memory. Neither of which you can recover from with C++ code.
I agree. This is an internal check about the invariants of the class and
we are in full control over whether they are maintained or broken. This
is not a precondition and the function could be noexcept (if the other
checks pass: no memory allocated and no POSIX cancellation points called).
The distinction between the two is why I a weary about the removal of
Q_ASSUME. I guess we could resurrect the distinction with other names
(Q_PRE/Q_POST/Q_INVARIANT)?
Thanks,
Marc
--
Marc Mutz <marc.mutz at qt.io> (he/his)
Principal Software Engineer
The Qt Company
Erich-Thilo-Str. 10 12489
Berlin, Germany
www.qt.io
Geschäftsführer: Mika Pälsi, Juha Varelius, Jouni Lintunen
Sitz der Gesellschaft: Berlin,
Registergericht: Amtsgericht Charlottenburg,
HRB 144331 B
More information about the Development
mailing list