[variant2] never-empty guarantee considered harmful
Hi Everyone,
I waned to re-iterate my concerns regarding the never-empty guarantee
provided by variant2 (as well as by boost::variant). Sorry for starting a
yet another thread, but I think it necessary to cleanse, group and
structure the information scattered among a number of threads.
I think that the never-empty variant deceives people into thinking that it
offers something useful and important, but in fact it does not, and more,
it can encourage people to trust that they have some guarantee which they
haven't, and owing to this to write buggy code.
The solution that never-empty variant offers is similar to another common
idea of replacing undefined behavior with *any *defined behavior
whatsoever, and that is supposed to be "safe" because programmers no longer
see symptoms of their bugs.
Let me digress on the subject of UB for a second. UB is always caused by a
bug in the program. Thus if the program had no bugs, it would have no UB.
But many people are very afraid of UB per se, and less afraid of bugs. They
will divert much attention to prevent UB at surprisingly high cost, but
focus less on preventing their bugs. UB is a useful symptom of a bug and
therefore can help detect and remove bugs, but people are sometimes so
afraid of UB that they loose this connection to the extent that they are
willing to remove the link between the two and get rid of potential UB even
at the cost of concealing the bugs. And they consider it "safe".
For instance, someone writes a sequence container class and faces the
problem. I will need to provide access to elements by index, but people
will give me bad indexes (because they can), this would be a UB, and I do
not want a UB, therefore I apply a "safety" feature. Whenever someone gives
me bad index, I will return a reference to the first element. Sure, it is
surprising, and likely not what the caller wanted, but it is better than
UB. And in case I have no elements in my container I will default-construct
one in the container. Sure it is not nice to to increase the size of the
container only because someone gave me the wrong index, but... *anything is
better than UB.*
I wonder if everyone reading this mail will agree with me that reasoning
"anything is better than UB" is wrong. The author of this "safe" container
calls his container "safe" because whatever bugs his callers have, they
will never trigger UB *in his library*. Maybe in another library, at
another level, but not in his component. In the end we will get programs
that will cause self-driving cars to crash and kill people, space rockets
to explode, but the "safe" program itself will never crash.
Instead the author of the "safe" container should be focusing on something
else. Why is my caller giving me the wrong index given that he can check my
size and easily determine that this index is wrong? The answer is, the
programmer probably intended to do something else and this needs to be
brought to his attention. Tools like compilers and static analyzers cannot
detect bugs in general because they cannot guess our intentions, but they
can detect things like UB, so if we keep a correspondence between bugs and
UB, we can detect bugs by detecting UB. This has been described in more
detail in this paper:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1043r0.html
(section 6).
Now, the situation is similar with the never-empty guarantee of the variant
that offers only a basic exception safety guarantee on assignment. Because
the guarantee is basic (that is, it is not the strong guarantee), the only
thing we can do with it in order not to risk program bugs is, following the
Dave's article (https://www.boost.org/community/exception_safety.html), to
either destroy or reset the variant that threw from assignment. And this is
what correct programs do, and the technique is quite simple: unless objects
provide strong exception safety guarantee (or some other guarantee that
allows you to reason about the values of objects that threw) they should
die in the stack unwinding process. The damage that the never-empty
guarantee (implemented by setting unexpected values) does is to encourage
people to think that with variant they can do something more than just
destroy or reset. And while technically, variant itself is "clean" in this
case (it really doesn't invoke UB itself) it causes the code that is using
it to be incorrect. Two examples.
I may have the following instance:
```
variant2
On Fri, Apr 12, 2019 at 7:26 PM Andrzej Krzemienski via Boost
UB is a useful symptom of a bug and therefore can help detect and remove bugs
It seems like you are using a different definition of undefined behavior than what I understand it to be. UB is not a useful symptom because it can be anything, and can vary by implementation. It doesn't necessarily mean the program crashes, it could for example result in exactly the harmless unnoticed response to a bug you object to.
On 13.04.19 01:47, Frank Mori Hess via Boost wrote:
On Fri, Apr 12, 2019 at 7:26 PM Andrzej Krzemienski via Boost
wrote: UB is a useful symptom of a bug and therefore can help detect and remove bugs
It seems like you are using a different definition of undefined behavior than what I understand it to be. UB is not a useful symptom because it can be anything, and can vary by implementation. It doesn't necessarily mean the program crashes, it could for example result in exactly the harmless unnoticed response to a bug you object to.
UB is not a runtime check that detects bugs (although compiling with undefined behavior sanitizer turns it into one). It is a conceptual tool for verifying the correctness of your program. If your program invokes undefined behavior, it is incorrect. Conversely, if your program is correct, then it does not invoke undefined behavior. Undefined behavior is not a defect of the C++ language. It's a deliberate feature. The standards committee could have easily defined the result of reading an uninitialized variable as "whatever arbitrary value happens to occupy that memory location". Instead, they chose to mark it as undefined behavior, because a program that uses uninitialized variables is /wrong/. This bears repeating. Reading from an uninitialized variable is not wrong because it is undefined behavior, but the other way around. Reading from an uninitialized variable is undefined behavior because it is wrong. -- Rainer Deyke (rainerd@eldwood.com)
While UB sanitizers and other tools like valgrind are incredibly powerful and useful, a test suite is and always will be more portable, reliable and self-documenting. Emphasizing UB for the sake of debugging or ensuring correctness is not as effective as emphasizing test suites instead. A strong guarantee gives sufficient reasoning and logical guarantees for programmers. - Chris On Fri, Apr 12, 2019 at 11:06 PM Rainer Deyke via Boost < boost@lists.boost.org> wrote:
On 13.04.19 01:47, Frank Mori Hess via Boost wrote:
On Fri, Apr 12, 2019 at 7:26 PM Andrzej Krzemienski via Boost
wrote: UB is a useful symptom of a bug and therefore can help detect and remove bugs
It seems like you are using a different definition of undefined behavior than what I understand it to be. UB is not a useful symptom because it can be anything, and can vary by implementation. It doesn't necessarily mean the program crashes, it could for example result in exactly the harmless unnoticed response to a bug you object to.
UB is not a runtime check that detects bugs (although compiling with undefined behavior sanitizer turns it into one). It is a conceptual tool for verifying the correctness of your program. If your program invokes undefined behavior, it is incorrect. Conversely, if your program is correct, then it does not invoke undefined behavior.
Undefined behavior is not a defect of the C++ language. It's a deliberate feature. The standards committee could have easily defined the result of reading an uninitialized variable as "whatever arbitrary value happens to occupy that memory location". Instead, they chose to mark it as undefined behavior, because a program that uses uninitialized variables is /wrong/.
This bears repeating. Reading from an uninitialized variable is not wrong because it is undefined behavior, but the other way around. Reading from an uninitialized variable is undefined behavior because it is wrong.
-- Rainer Deyke (rainerd@eldwood.com)
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
sob., 13 kwi 2019 o 21:33 Christian Mazakas via Boost
While UB sanitizers and other tools like valgrind are incredibly powerful and useful, a test suite is and always will be more portable, reliable and self-documenting. Emphasizing UB for the sake of debugging or ensuring correctness is not as effective as emphasizing test suites instead.
A strong guarantee gives sufficient reasoning and logical guarantees for programmers.
Let me offer my perspective on this. When I say "UB helping tools detect bugs" I do not primarily mean UB sanitizers or Valgrind, or running the program inside the debugger , which detect bugs while the program is running. I mean static analysis tools which do not require the program to be run, such as clang's static analyzer: they just examine the program's abstract syntax tree and it s through this analysis that bugs are detected. There is no need to compare the effectiveness of static analysis against the test suite: the two are complementary and should be both exercised on a program to ensure correctness. Also, in test suites, UB is helpful. When pieces of the program need to be run, you can compile with UB-sanitizer enabled and your test suite can detect all UB-s in a uniform way. And each UB is a symptom of a bug. UB sanitizers are available practically on any compiler ever since constexpr has been added to C++. During the compile-time evaluation of constexpr function every UB is required to be detected and reported as compile time error. Since compiler vendors are required to implement it for constant evaluation, providing it later also for run-time evaluation costs them practically nothing. Regards, &rzej;
- Chris
On Fri, Apr 12, 2019 at 11:06 PM Rainer Deyke via Boost < boost@lists.boost.org> wrote:
On 13.04.19 01:47, Frank Mori Hess via Boost wrote:
On Fri, Apr 12, 2019 at 7:26 PM Andrzej Krzemienski via Boost
wrote: UB is a useful symptom of a bug and therefore can help detect and remove bugs
It seems like you are using a different definition of undefined behavior than what I understand it to be. UB is not a useful symptom because it can be anything, and can vary by implementation. It doesn't necessarily mean the program crashes, it could for example result in exactly the harmless unnoticed response to a bug you object to.
UB is not a runtime check that detects bugs (although compiling with undefined behavior sanitizer turns it into one). It is a conceptual tool for verifying the correctness of your program. If your program invokes undefined behavior, it is incorrect. Conversely, if your program is correct, then it does not invoke undefined behavior.
Undefined behavior is not a defect of the C++ language. It's a deliberate feature. The standards committee could have easily defined the result of reading an uninitialized variable as "whatever arbitrary value happens to occupy that memory location". Instead, they chose to mark it as undefined behavior, because a program that uses uninitialized variables is /wrong/.
This bears repeating. Reading from an uninitialized variable is not wrong because it is undefined behavior, but the other way around. Reading from an uninitialized variable is undefined behavior because it is wrong.
-- Rainer Deyke (rainerd@eldwood.com)
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
sob., 13 kwi 2019 o 08:06 Rainer Deyke via Boost
On 13.04.19 01:47, Frank Mori Hess via Boost wrote:
On Fri, Apr 12, 2019 at 7:26 PM Andrzej Krzemienski via Boost
wrote: UB is a useful symptom of a bug and therefore can help detect and remove bugs
It seems like you are using a different definition of undefined behavior than what I understand it to be. UB is not a useful symptom because it can be anything, and can vary by implementation. It doesn't necessarily mean the program crashes, it could for example result in exactly the harmless unnoticed response to a bug you object to.
UB is not a runtime check that detects bugs (although compiling with undefined behavior sanitizer turns it into one). It is a conceptual tool for verifying the correctness of your program. If your program invokes undefined behavior, it is incorrect. Conversely, if your program is correct, then it does not invoke undefined behavior.
Exactly. UB is not a tool for detecting bugs *at run-time* (even though it is possible today). It is too late to detect bugs at run-time. It is for detecting the bugs before the program is run for the first time: at static analysis time, or code-review time. Regards, &rzej;
Undefined behavior is not a defect of the C++ language. It's a deliberate feature. The standards committee could have easily defined the result of reading an uninitialized variable as "whatever arbitrary value happens to occupy that memory location". Instead, they chose to mark it as undefined behavior, because a program that uses uninitialized variables is /wrong/.
This bears repeating. Reading from an uninitialized variable is not wrong because it is undefined behavior, but the other way around. Reading from an uninitialized variable is undefined behavior because it is wrong.
-- Rainer Deyke (rainerd@eldwood.com)
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (4)
-
Andrzej Krzemienski
-
Christian Mazakas
-
Frank Mori Hess
-
Rainer Deyke