public inbox for gcc-bugs@sourceware.org
help / color / mirror / Atom feed
* [Bug libstdc++/106547] New: std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types
@ 2022-08-06 18:27 the.invisible.phantom at gmail dot com
  2022-08-06 22:10 ` [Bug libstdc++/106547] " redi at gcc dot gnu.org
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: the.invisible.phantom at gmail dot com @ 2022-08-06 18:27 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106547

            Bug ID: 106547
           Summary: std::variant::emplace goes into an infinite recursion
                    with certain weird trivially copyable types
           Product: gcc
           Version: unknown
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: the.invisible.phantom at gmail dot com
  Target Milestone: ---

Created attachment 53421
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=53421&action=edit
Preprocessed source file

Consider the following source code (preprocessed version attached):

#include <concepts>
#include <variant>

struct weird {
  weird() {}
  weird(std::same_as<weird> auto&&) {}
  weird(const weird&) = default;
  weird& operator=(const weird&) = default;
};

int main() {
  std::variant<std::monostate, weird> data;
  data.emplace<weird>();
}

When executing, the emplace call goes into an infinite recursion.

The emplace function has a number of different implementations, selected using
`if constexpr`. The first `if constexpr` branch has the condition
`is_nothrow_constructible_v<type, _Args...>`; since the constructor that we're
using is not noexcept, that branch is not taken. The second branch has the
condition `is_scalar_v<type>`, which does not apply, either.

The third branch has the condition
`__detail::__variant::_Never_valueless_alt<type>() && _Traits::_S_move_assign`.
The second part simply requires that all alternatives be move-assignable, which
is true here. The first part checks two things: that the type's size not exceed
256 bytes, which is true, and that the type be trivially copyable. The `weird`
type has a trivial destructor, a trivial copy constructor, a trivial copy
assignment operator, no move constructor, and no move assignment operator;
therefore, it is trivially copyable and the third branch is chosen.

The implementation in the third branch constructs a temporary variant on the
stack and then move-assigns it to `*this`. The move assignment ultimately calls
emplace again, this time constructing the alternative from `weird&&`. The text
in the comment appears to expect that the first `if constexpr` branch --
corresponding to the noexcept constructor -- will be chosen.

However, construction from `weird&&` actually calls `weird`'s second
constructor, which is not noexcept. (It does not prevent `weird` from being
trivially copyable, since it is a template and therefore is not a move
constructor.) Therefore, this actually chooses the third branch once again,
which, once again, constructs a temporary variant on the stack and move-assigns
it to `*this`. This causes an infinite recursion.

To sum up, the key problem here is that the `weird` type is trivially copyable,
but its move construction is not noexcept, because it invokes a constructor
template that is not a move constructor. (A real-world example of such a weird
type is gsl::not_null from Microsoft's GSL implementation
<https://github.com/Microsoft/GSL>).

Full invocation of GCC is below. (I also reproduced it on godbolt.org using the
"gcc (trunk)" compiler and checked the source code in the git repository to
make sure the offending lines are still there.)

$ g++ -v -save-temps -std=c++20 test.cpp -o test
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu
11.2.0-19ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs
--enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr
--with-gcc-major-version-only --program-suffix=-11
--program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu
--enable-libstdcxx-debug --enable-libstdcxx-time=yes
--with-default-libstdcxx-abi=new --enable-gnu-unique-object
--disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib
--enable-libphobos-checking=release --with-target-system-zlib=auto
--enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet
--with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --with-tune=generic
--enable-offload-targets=nvptx-none=/build/gcc-11-gBFGDP/gcc-11-11.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-11-gBFGDP/gcc-11-11.2.0/debian/tmp-gcn/usr
--without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu
--with-build-config=bootstrap-lto-lean --enable-link-serialization=2
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 11.2.0 (Ubuntu 11.2.0-19ubuntu1)
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-o' 'test'
'-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/11/cc1plus -E -quiet -v -imultiarch
x86_64-linux-gnu -D_GNU_SOURCE test.cpp -mtune=generic -march=x86-64 -std=c++20
-fpch-preprocess -fasynchronous-unwind-tables -fstack-protector-strong -Wformat
-Wformat-security -fstack-clash-protection -fcf-protection -o test.ii
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/11"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/11/include-fixed"
ignoring nonexistent directory
"/usr/lib/gcc/x86_64-linux-gnu/11/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/11
 /usr/include/x86_64-linux-gnu/c++/11
 /usr/include/c++/11/backward
 /usr/lib/gcc/x86_64-linux-gnu/11/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-o' 'test'
'-shared-libgcc' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/11/cc1plus -fpreprocessed test.ii -quiet
-dumpbase test.cpp -dumpbase-ext .cpp -mtune=generic -march=x86-64 -std=c++20
-version -fasynchronous-unwind-tables -fstack-protector-strong -Wformat
-Wformat-security -fstack-clash-protection -fcf-protection -o test.s
GNU C++20 (Ubuntu 11.2.0-19ubuntu1) version 11.2.0 (x86_64-linux-gnu)
        compiled by GNU C version 11.2.0, GMP version 6.2.1, MPFR version
4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
GNU C++20 (Ubuntu 11.2.0-19ubuntu1) version 11.2.0 (x86_64-linux-gnu)
        compiled by GNU C version 11.2.0, GMP version 6.2.1, MPFR version
4.1.0, MPC version 1.2.1, isl version isl-0.24-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 619370d92bf6efc3023abfa7394da0c1
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-o' 'test'
'-shared-libgcc' '-mtune=generic' '-march=x86-64'
 as -v --64 -o test.o test.s
GNU assembler version 2.38 (x86_64-linux-gnu) using BFD version (GNU Binutils
for Ubuntu) 2.38
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/11/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/11/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-o' 'test'
'-shared-libgcc' '-mtune=generic' '-march=x86-64' '-dumpdir' 'test.'
 /usr/lib/gcc/x86_64-linux-gnu/11/collect2 -plugin
/usr/lib/gcc/x86_64-linux-gnu/11/liblto_plugin.so
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/11/lto-wrapper
-plugin-opt=-fresolution=test.res -plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lc
-plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lgcc --build-id
--eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker
/lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o test
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/11/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/11
-L/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/11/../../../../lib -L/lib/x86_64-linux-gnu
-L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/11/../../.. test.o -lstdc++ -lm -lgcc_s -lgcc
-lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/11/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-v' '-save-temps' '-std=c++20' '-o' 'test'
'-shared-libgcc' '-mtune=generic' '-march=x86-64' '-dumpdir' 'test.'

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [Bug libstdc++/106547] std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types
  2022-08-06 18:27 [Bug libstdc++/106547] New: std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types the.invisible.phantom at gmail dot com
@ 2022-08-06 22:10 ` redi at gcc dot gnu.org
  2023-09-11 12:38 ` de34 at live dot cn
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: redi at gcc dot gnu.org @ 2022-08-06 22:10 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106547

Jonathan Wakely <redi at gcc dot gnu.org> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
             Status|UNCONFIRMED                 |NEW
     Ever confirmed|0                           |1
   Last reconfirmed|                            |2022-08-06

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [Bug libstdc++/106547] std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types
  2022-08-06 18:27 [Bug libstdc++/106547] New: std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types the.invisible.phantom at gmail dot com
  2022-08-06 22:10 ` [Bug libstdc++/106547] " redi at gcc dot gnu.org
@ 2023-09-11 12:38 ` de34 at live dot cn
  2023-09-11 13:27 ` the.invisible.phantom at gmail dot com
  2023-09-11 13:53 ` de34 at live dot cn
  3 siblings, 0 replies; 5+ messages in thread
From: de34 at live dot cn @ 2023-09-11 12:38 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106547

Jiang An <de34 at live dot cn> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |de34 at live dot cn

--- Comment #1 from Jiang An <de34 at live dot cn> ---
Well, is_trivially_copyable looks like a red herring - it doesn't really mean
that we can trivially copy such an object in some way.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [Bug libstdc++/106547] std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types
  2022-08-06 18:27 [Bug libstdc++/106547] New: std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types the.invisible.phantom at gmail dot com
  2022-08-06 22:10 ` [Bug libstdc++/106547] " redi at gcc dot gnu.org
  2023-09-11 12:38 ` de34 at live dot cn
@ 2023-09-11 13:27 ` the.invisible.phantom at gmail dot com
  2023-09-11 13:53 ` de34 at live dot cn
  3 siblings, 0 replies; 5+ messages in thread
From: the.invisible.phantom at gmail dot com @ 2023-09-11 13:27 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106547

--- Comment #2 from Valentine Anderson <the.invisible.phantom at gmail dot com> ---
From what I understand, the key feature of trivially copyable types is that
memcpy‘ing an object of such a type onto another object is equivalent to a copy
assignment. So it is possible to trivially copy such an object, using memcpy.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [Bug libstdc++/106547] std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types
  2022-08-06 18:27 [Bug libstdc++/106547] New: std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types the.invisible.phantom at gmail dot com
                   ` (2 preceding siblings ...)
  2023-09-11 13:27 ` the.invisible.phantom at gmail dot com
@ 2023-09-11 13:53 ` de34 at live dot cn
  3 siblings, 0 replies; 5+ messages in thread
From: de34 at live dot cn @ 2023-09-11 13:53 UTC (permalink / raw)
  To: gcc-bugs

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106547

--- Comment #3 from Jiang An <de34 at live dot cn> ---
(In reply to Valentine Anderson from comment #2)
> From what I understand, the key feature of trivially copyable types is that
> memcpy‘ing an object of such a type onto another object is equivalent to a
> copy assignment. So it is possible to trivially copy such an object, using
> memcpy.

The current standard wording only guarantees that such copy is OK when the
destination object is already created.

A trivially copyable class is usually also an implicit-lifetime class, so
memcpy is usually sufficient to create that object. But there're also weird
trivially copyable class that is not an implicit-lifetime class (e.g. the class
may have deleted copy/move ctors and trivial assignment operators).

Moreover, it doesn't seem suitable to use memcpy in the cases involved in this
issue...

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2023-09-11 13:53 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-08-06 18:27 [Bug libstdc++/106547] New: std::variant::emplace goes into an infinite recursion with certain weird trivially copyable types the.invisible.phantom at gmail dot com
2022-08-06 22:10 ` [Bug libstdc++/106547] " redi at gcc dot gnu.org
2023-09-11 12:38 ` de34 at live dot cn
2023-09-11 13:27 ` the.invisible.phantom at gmail dot com
2023-09-11 13:53 ` de34 at live dot cn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).