Boost logo

Boost :

Subject: Re: [boost] [variant2] Formal review
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2019-04-18 07:40:51


śr., 17 kwi 2019 o 16:32 Peter Dimov via Boost <boost_at_[hidden]>
napisał(a):

> Andrzej Krzemienski wrote:
>
> > However, you seem to be missing the distinction which I find very
> > important: a "zombie" state that can only occur in *very special
> > situations* (which correspond to the incorrect programs) is something
> > substantially different than "zombie" states that can occur everywhere.
>
> In a correct program using two-phase init, zombie states also cannot
> occur.
> The same rule is in effect: a correct program should never access a
> partially constructed object. There's nothing that makes the situation of
> a
> partially constructed object being observed any less special or *very
> special* than in what you suggest.
>

The distinction that I see is quite clear: In the case of two-phase init,
you have to do nothing and the zombie object is observable. Usually these
types have a default constructor. You construct, forget to initialize in
some path (because you are catching exceptions too soon), and then you get
a bug: because you failed to do something.

In case of observing the state of the moved-from objects, or objects that
threw from operation that guaranteed only an unspecified albeit valid
state, you have to voluntarily do some action do get it. It cannot be done
by omission: you have to put an explicit std::move or cast, and you know
you are doing something potentially dangerous that requires special
attention. Or, you have to put an explicit try-catch block, and you know
you are doing something that requires special attention: because of stack
unwinding many functions that were supposed to be called were canceled and
now after the catch subsequent functions will be invoked and they may
depend on the side effects of the functions that were canceled. In both
this cases you have to voluntarily do something to get to this state.

So the difference is: can you get to the zombie state by omission or can
you get to the zombie state voluntarily.

Another important difference is that in many cases a two-phase init class
can be redesigned so that it is a RAII-like class, and then their users
cannot make an omission bug. In case of moved-from state or unspecified
(albeit valid) state, it is impossible to avoid the voluntarily put bugs:
they can only be concealed. I will try to explain it later in this reply.

> The problem is not one to be solved by putting asterisks around words. The
> problem, as I said, is that the rules governing correct programs are hard
> to
> enforce, and their violation is detected much too late, in parts of the
> program that have done nothing to violate the rules.
>

Yes. I acknowledge this as a serious problem.

>
> With your rules, when the following very special situation throws:
>
> x1 = std::move(x2);
>
> you now have x1 singular, because no basic exception safety, and x2
> singular, because moved-from. Now whether the program is correct or not
> depends on whether one of x1 or x2 escapes unscathed.

Yes, I think we agree up to this point. A correct program should make sure
that if the above operation fails, unless we know either of the objects
guarantees some concrete state after the throw, both objects should be
removed from the scope or reset to a known state.

> This is hard to
> diagnose statically - not that anyone has even tried -

Agreed: hard to diagnose, and it is to be expected that in real-life
programs that are often incorrect this will happen.

> and will not be diagnosed at runtime until two hours later an unrelated
> part of the program
> tries to access x1 or x2,

Yes: the symptoms may appear much later in unrelated parts of the program
that themselves are correct.

> in which case you'll have a crash ("fast fail"),
>

I disagree: in the worse case it will not be a crash, but a program will
continue its operations and give an impression that it is working fine, but
it will be doing something else than the programmer expected. A self
driving car will be reporting that all the systems are functioning
normally, but it will be crashing into people. This is fare worse than the
application crash.

> except it won't be fast, and it will tell you nothing about what caused it
> or who was at fault.
>

Yes.

>
> There's really not that much difference between the above and
>
> x1.init( f() );
>
> except this one is easier to diagnose statically.
>

I have explained the difference above.

So, let's now explore an alternative situation.

x1 = std::move(x2);

This throws, and leaves the objects in an unspecified (albeit "valid")
state, an exception handling is stopped prematurely, so that the objects
remain in scope and the programmer makes no attempt to put their values
into a known state. The only guarantee we have now is that no operation on
`x1` or `x2` will cause an UB. But we have no guarantee as to the
correctness or consequences of this program. A program cannot be expected
to work correctly if it uses unspecified values: you depend on the values
of the variables, but you do not care if they were selected at random.

In case of UB we at least had some probability that UB at some point would
be detected and the program stopped (self driving car would report
malfunction in one of its redundant systems), but now we have the guarantee
that no malfunction will be reported and the program will be perceived as
working correctly and the car will be crashing into people.

So, I agree with you that the case with UB is bad. But the alternative --
random values instead of UB -- is even worse.

If we map this situation onto the design of `variant`. Setting some default
constructed T upon throw is considered, at least by me, the worst possible
path. Leaving it in zombie state is better, although I acknowledge the FUD
over UB. Providing strong or semi-strong guarantee is also a better
solution: because in this case all my talk about objects surviving the
stack unwinding no longer apply.

>
> > Again, I am not convinced that you are seeing the distinction between
> > "zombie states in very special circumstances" and "zombie states
> > everywhere". I agree with you on "zombie states everywhere".
>
> You're constructing a strawman of your choosing and setting it on fire.
>

Peter, It is clear that you do not agree with me, but do some parts of what
I say make sense to you?

Regards,
&rzej;


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk