C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Ensure ABI compatibility at runtime -- std::layout_of

From: Jeremy Rifkin <rifkin.jer_at_[hidden]>
Date: Thu, 7 Nov 2024 09:35:09 -0600
> My suggestion: Rrquire plugins to publish a C string with some identifier
indicating the standard library being used (e.g. "libstdc++" or "libcxx"
depending on preprocessor macros set by the libraries). Then check that on
load.

I should have mentioned before sending that this isn’t perfect, there can
be additional subtleties with compiler versions and whether
assertions/other hardening is enabled for a given standard library etc.
This approach could be expanded to take into account all these other
factors.

Jeremy

On Thu, Nov 7, 2024 at 09:29 Jeremy Rifkin <rifkin.jer_at_[hidden]> wrote:

> Hi,
>
> This is an interesting idea. It a tough problem but a problem that would
> be nice to solve.
>
> In general this is of course something that should be diagnosed at link
> time (and it usually is in the case of a libc++ vs libstdc++ mismatch
> albeit not super clearly). The plugin case is special. As others have
> mentioned, simply seeing the list of field sizes isn’t enough though.
>
> My suggestion: Rrquire plugins to publish a C string with some identifier
> indicating the standard library being used (e.g. "libstdc++" or "libcxx"
> depending on preprocessor macros set by the libraries). Then check that on
> load.
>
> Jeremy
>
> On Thu, Nov 7, 2024 at 04:13 Frederick Virchanza Gotham via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> First a quick note on motivation:
>> Yesterday I was debugging a main program that loads a plugin at
>> runtime. The main program passed a vector by reference to the plugin,
>> the plugin populated the vector, and then the main program worked with
>> the vector contents. It wasn't working properly and I eventually
>> figured out that the plugin was compiled with LLVM clang++ while the
>> main program was compiled with GNU g++. The container type
>> "std::vector" isn't ABI-compatible between the two (or at least not
>> between the two compiler versions in question).
>>
>> I thought it might be nice to be able to check this at runtime, something
>> like:
>>
>> namespace std {
>> template<typename T>
>> consteval size_t const *layout_of(void);
>> }
>>
>> This function returns a pointer to a null-terminated sequence of
>> size_t's as follows:
>>
>> { alignment, size, alignment, size, alignment, size, alignment,
>> size, . . . , null terminator }
>>
>> I'm thinking that the 'size' parameter would be '__datasizeof' rather
>> than 'sizeof'.
>>
>> Using the following struct as an example:
>>
>> struct Monkey {
>> int a;
>> char b;
>> char *p;
>> alignas(32) char c;
>> };
>>
>> So then std::layout_of<Monkey> would come back with:
>>
>> { 4, 4, 1, 1, 8, 8, 32, 1 }
>>
>> By the way if it were 'sizeof' instead of '__datasizeof', then it would
>> be:
>>
>> { 4, 4, 1, 1, 8, 8, 32, 32 }
>>
>> So then the main program, when it loads a plugin, could compare its
>> own vector to the vector in the plugin, something like:
>>
>> #include <cstddef> // size_t
>> #include <cstdlib> // EXIT_FAILURE
>> #include <vector> // vector
>> using std::size_t;
>> extern size_t const *GetVectorLayoutFromPlugin(void); // defined
>> in the plugin
>> int main(void)
>> {
>> constexpr size_t const *domestic = std::layout_of<
>> std::vector<void*> >();
>> size_t const *const exotic = GetVectorLayoutFromPlugin();
>>
>> if ( 0 != std::char_traits<size_t>::compare( domestic, exotic
>> ) ) return EXIT_FAILURE;
>> }
>>
>> Of course there are a few more things to consider such as:
>> (1) Should the sequence be prefixed with 1 byte to indicate the size
>> of "size_t" -- just in case it's 4 on one compiler and 8 on another?
>> (2) It's possible for two classes to have identical layout but to
>> still have differing and incompatible implementations.
>> (3) Should the pointer to the VTable have a special marking? I was
>> thinking that the 'alignment' parameter could be negative for the
>> VTable pointer, so if you had:
>>
>> struct Donkey {
>> virtual ~Donkey(void) = default;
>> int a;
>> };
>>
>> then it would be:
>>
>> { -8, 8, 4, 4 }
>>
>> This would be useful in particular when dealing with the Microsoft
>> compiler, as the VTable pointer might not be the first item if you
>> have a polymorphic class inheriting from a non-polymorphic class, such
>> as the following:
>>
>> struct Base {
>> int a;
>> };
>>
>> struct Derived : Base {
>> virtual ~Derived(void) = default;
>> };
>>
>> So then on the Microsoft compiler, std::layout_of<Derived>() would
>> come back with:
>>
>> { 4, 4, -8, 8 }
>>
>> where as on every other C++ compiler in existence, it would be:
>>
>> { -8, 8, 4, 4 }
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

Received on 2024-11-07 15:35:22