status code mixin state is not preserved during erasure
BurningEnlightenment opened this issue · comments
Hi Niall!
mentions that mixin instances may contain state. However, converting such a status code to a
status_code<erased<>>
doesn't preserve it AFAICT. This seems like a very error prone design considering the fact that it seems to be common practice to cast the status_code
to the non erased version in the domain implementation, e.g. here:status-code/include/status-code/generic_code.hpp
Lines 345 to 348 in b4caedb
Even without pushing state into the mixin the above cast invokes UB if code
references an erased status code, doesn't it? AFAICT that code is effectively performing the following cast sequence
status_code<erased<T>> code{/* initialize to some status_code<Domain> value */};
auto const &c = static_cast<status_code<Domain> const &>(static_cast<status_code<void> const &>(code));
while status_code<Domain>
doesn't inherit from status_code<erased<T>>
.
Wouldn't it be possible/has it been considered to provide a similarly convenient API factoring without basically requiring UB?
Nice bug catch!
How about we disable erasure if the mixin's sizeof is non-zero?
How about we disable erasure if the mixin's sizeof is non-zero?
That should prevent the worst. However, the code snippet above is still UB. I think the UB cast could be avoided if we placement newed the value into the storage and passed a pointer to the storage instead of a status_code<void>
reference to the domain API
It is UB to access objects via types which are unrelated yes as per the strict standard, however all compilers extend the standard's minimums i.e. a lot of UB according to the standard is not UB according to specific compilers.
To be specific here, if types are trivially copyable, then they can be treated as a bag of bits and one can cast between bag of bits types safely. If we cared a bit more, we could use bit_cast
and then we really would be without UB.
However, as you're no doubt about to point out, erased status codes are not trivially copyable because they are move only! Therefore treating them as if they are bags of bits is straight out UB in all compilers.
I don't think that this can be avoided nor worked around - move only types cannot be treated as bags of bits, yet we rely on the three major compilers "just happening" to do the right thing. I originally had a paper "move = bitcopies" which would have removed the UB here, but EWG did not want to progress that paper for the time being.
I wouldn't worry about the UB here. LEWG is progressing standardising status codes, and library implementators are allowed to do UB. Also, none of the compiler vendors have objected, the UB here is simply because no compiler has formally extended any behaviour guarantees in this area, and if this is standardised, then they would be doing so. In other words, I am relaxed about this.
Does this all make sense?
I'd suggest testing the size of the mixins type rather than the final type, then you don't get chicken and egg ordering problems in consteval
ugh. Only checking MSVC was too lenient it seems.
Heh https://github.com/ned14/status-code/runs/8192836911?check_suite_focus=true
I'd suggest testing the size of the mixins type rather than the final type, then you don't get chicken and egg ordering problems in consteval
@ned14 gcc is trying to instantiate the constructor template with status_code<void>
and status_code_storage<void>
is not so well formed...
What's the progress on this one?
The original issue has been resolved. I've since played a bit with a unified erased type with a byte array based code storage which could be passed around and reinterpreted in order to avoid the UB casts. The major downside being that reinterpreting stuff in constexpr contexts doesn't work...
However, I'm currently sick and not really in the mood to continue that experiment.