C++ Logo

sg12

Advanced search

Re: [ub] memcpy blessing a new object of statically unknown type

From: Patrice Roy <patricer_at_[hidden]>
Date: Sat, 9 Jan 2016 20:36:08 -0500
I know it is. I just hope it stays hidden deep where those who know about
it are... essentially those who read this mailing list :)

2016-01-09 16:42 GMT-05:00 Gabriel Dos Reis <gdr_at_[hidden]>:

> My assumption has always been that std::launder is a zero-overhead
> abstraction (really a no-op at the machine level) whole sole purpose is to
> inform the static semantics elaborator about the lifetime and type of a
> given storage. I hope we aren’t considering anything more complicated than
> that.
>
>
>
> -- Gaby
>
>
>
> *From:* ub-bounces_at_[hidden] [mailto:ub-bounces_at_[hidden]] *On
> Behalf Of *David Krauss
> *Sent:* Friday, January 8, 2016 10:36 PM
> *To:* WG21 UB study group <ub_at_[hidden]>
> *Cc:* joel.lamotte_at_[hidden]
> *Subject:* Re: [ub] memcpy blessing a new object of statically unknown
> type
>
>
>
>
>
> On 2016–01–09, at 11:35 AM, Hubert Tong <hubert.reinterpretcast_at_[hidden]>
> wrote:
>
>
>
> Yes, std::launder requires a static type; however, it does not limit the
> ability of memcpy to operate without knowing the type of the object being
> copied. The std::launder call is involved *after* the completion of memcpy
> to access the object that the memcpyinitialized.
>
>
>
> Right, but in type erasure, the static type must be determined by
> inspecting the blob somehow. (Stashing a discriminating value elsewhere is
> one solution, but it’s more common and often more efficient to use an
> abstract base class or a discriminator inside a union.)
>
>
>
> My library would launder the erasure_base subobject to retrieve its
> dispatch table, but then it’d be stuck. Dispatching to a derived class
> would lead back to UB.
>
>
>
> One workaround could be to launder the same address repeatedly as the type
> becomes better resolved. For example, the call wrapper could launder a base
> class address, then perform an indirect call, then the callee could launder
> again to the derived class. For the common case of virtual dynamic
> dispatch, this sounds like it would incur UB before first line of the
> callee. My library doesn’t use virtual, but similar ones do. If only
> complete objects can be laundered, devirtualization could kick in… or
> launder could refuse to handle an abstract class at all. The workaround
> would also imply an excessive number of derived-type launder calls, which
> could compromise optimization by suggesting that bitwise manipulations are
> occurring when none are. Reloading the dispatch table pointer costs cycles.
>
>
>
> Perhaps a second style of laundering could implement a compromise. First, auto
> &header = *launder(header_ptr) gets a fully-formed header object from a
> blob, and then auto &whole = launder_extend<whole_type>(header) revises
> the object identity to make header a subobject sharing its address with
> another already-fully-formed object of type whole. (For example, header could
> be a base, a union member, or an initial struct member.) The
> launder_extend function differs in that it acts only if its argument was
> believed to be a complete object (i.e. fresh from launder), and it only
> launders the remainder of the new complete object. To solve the virtual issue,
> do not let launder imply that its result is most-derived. Perhaps, let
> virtual dispatch implicitly do launder_extend.
>
>
>
> This scheme leaves launder open-ended so a polymorphic object or union
> can be used, yet still laundered further. A simple implementation can opt
> to treat launder_extend the same as launder.
>
>
>
> Example:
>
> struct discriminator { int value; };
>
> struct foo { discriminator d; int i; };
>
> struct bar { discriminator d; float f; };
>
> union foobar { foo a; bar b; };
>
> struct baz : discriminator { double x; };
>
> struct bad : discriminator { virtual ~ bad(); };
>
>
>
> void unpack( discriminator * p ) {
>
> std::launder( p ); // OK: now we can access p.
>
> int disc = p->value;
>
> if ( disc == 0 ) {
>
> auto & f = std::launder_extend< foo >( * p ); // OK: now we can
> access a foo.
>
> int q = p->value; // Load may be elided. Value is already in disc,
> equal to zero.
>
> auto & fb = std::launder_extend< foobar >( * f ); // OK: a further
> extension to a super-object.
>
> auto & f2 = std::launder_extend< foo >( * p ); // OK, no-op: p was
> already extended, becoming a subobject.
>
> auto & b = std::launder_extend< bar >( * p ); // UB: there’s
> already a different object there.
>
> auto & i = std::launder_extend< int >( * p ); // Library
> precondition violation: invalid object extension.
>
> } else if ( disc == 1 ) {
>
> baz & z = std::launder_extend< baz >( * p ); // OK, but
> implementation-dependent in theory.
>
> } else if ( disc == 2 ) {
>
> // Library precondition violation: no discriminator subobject
> shares an address with class bad.
>
> bad & x = std::launder_extend< bad >( * p );
>
> }
>
> }
>
>
>
> _______________________________________________
> ub mailing list
> ub_at_[hidden]
> http://www.open-std.org/mailman/listinfo/ub
>
>

Received on 2016-01-10 02:36:10