ned14 / status-code

Proposed SG14 status_code for the C++ standard

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

status code mixin state is not preserved during erasure

BurningEnlightenment opened this issue · comments

Hi Niall!

size_t total_size{0}; //!< The total status code size in bytes (includes domain pointer and mixins state)

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:
virtual _base::string_ref _do_message(const status_code<void> &code) const noexcept override // NOLINT
{
assert(code.domain() == *this); // NOLINT
const auto &c = static_cast<const generic_code &>(code); // NOLINT

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.