Hello Thrift folks,

I think that the Thrift 0.17.0 C++ generator might generate - even for
simple examples - code that contains undefined behavior, and which
indeed stops compiling with Clang 15 in C++20 mode. Consider this simple
thrift specification:

┌────
│ struct StructA {
│         11:required list<StructB> myBs
│ }
│
│ struct StructB
│ {
│         1:required string someString
│ }
└────

I translate this to C++ using this command (and thrift v. 0.17.0):
┌────
│ thrift --gen cpp:no_skeleton -o out ./file.thrift
└────

The interesting parts are in the generated file `file_types.h'. You can
find the full generated file at
<https://gist.github.com/tinloaf/1f7ae57df383d7b87221598e4f7a700d>, but
these are the crucial parts:

┌────
│ class StructA;
│
│ class StructB;
│
│
│ class StructA : … {
│  public:
│
│     …
│     StructA() noexcept {
│     }
│
│     …
│     std::vector<StructB>  myBs;
│     …
│ };
│
│ …
│
│ class StructB : … {
│     …
│ };
└────

The important part is that there is a _definition_ (not just a
declaration) of `StructA''s default constructor *before* `StructB' is
complete. This default constructor will implicitly call
`std::vector<StructB>''s default constructor.

The C++20 standard states in [vector.overview]/4 [1]:

      An incomplete type T may be used when instantiating vector
      if the allocator meets the allocator completeness
      requirements.  T shall be complete before any member of the
      resulting specialization of vector is referenced.

So, having a member of type `std::vector<StructB>' (with `StructB' being
an incomplete type at that point) should be fine. However, implicitly
using its default constructor in `StructA()' is probably not. This is
also what Clang 15 tells me when I try to compile this with
`--std=c++20':

┌────
│ In file included from ./file_types.h:12:
│ In file included from
/opt/mentz/packages/x64-linux/include/thrift/Thrift.h:41:
│ In file included from
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/vector:64:
│
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_vector.h:367:35:
error: arithmetic on a pointer to an incomplete type 'StructB'
│                                             _M_impl._M_end_of_storage -
_M_impl._M_start);
│                                             ~~~~~~~~~~~~~~~~~~~~~~~~~ ^
│
/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../include/c++/12/bits/stl_vector.h:526:7:
note: in instantiation of member function 'std::_Vector_base<StructB,
std::allocator<StructB>>::~_Vector_base' requested here
│             vector() = default;
│             ^
│ ./file_types.h:34:3: note: in defaulted default constructor for
'std::vector<StructB>' first required here
│     StructA() noexcept {
│     ^
│ ./file_types.h:26:7: note: forward declaration of 'StructB'
│ class StructB;
└────

Here is a (closed as invalid) LLVM issue that is more or less the same
problem (it's the move constructor being used, not the default
constructor): <https://github.com/llvm/llvm-project/issues/57700>

I think this should be fixed by moving all member function definitions
from `*_types.h' into `*_types.cpp' (all types should be complete in the
`.cpp' files). It is not sufficient to just move the default
constructor, in my generated file I see at least `StructA::operator=='
and `StructA::operator!=' which also use members of
`std::vector<StructB>'.

I will gladly open an issue for this, but I think I would need somebody
to create a Jira account for me.

All the best, Lukas


[1]: <https://eel.is/c++draft/vector#overview-4>


-- 
________________________________________________________

Lukas Barth


MENTZ GmbH, Grillparzerstr. 18, 81675 Munich

P:+49 (0)89 4 18 68-258, F:+49 (0)89 4 18 68-160

E: ba...@mentz.net, www.mentz.net

Registered Office: Grillparzerstraße 18, 81675 Munich, Germany
President: Christoph Mentz, District Court Munich, HRB 91898

________________________________________________________

Reply via email to