Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ on:
env:
UBSAN_OPTIONS: print_stacktrace=1

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
posix:
strategy:
Expand Down
35 changes: 35 additions & 0 deletions include/boost/variant2/variant.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@
# define BOOST_VARIANT2_CXX20_CONSTEXPR
#endif

// GCC 12+ false positive -Wmaybe-uninitialized workaround
// https://github.com/boostorg/variant2/issues/33
#ifndef BOOST_VARIANT2_GCC12_WORKAROUND
# if defined(BOOST_GCC) && __GNUC__ >= 12
# define BOOST_VARIANT2_GCC12_WORKAROUND 1
# endif
#endif

#if BOOST_VARIANT2_GCC12_WORKAROUND
# define BOOST_VARIANT2_DISABLE_GCC12_WARNINGS \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
# define BOOST_VARIANT2_RESTORE_GCC12_WARNINGS \
_Pragma("GCC diagnostic pop")
#else
# define BOOST_VARIANT2_DISABLE_GCC12_WARNINGS
# define BOOST_VARIANT2_RESTORE_GCC12_WARNINGS
#endif

//

namespace boost
Expand Down Expand Up @@ -1420,13 +1439,17 @@ template<class... T> struct variant_cc_base_impl<true, false, T...>: public vari

public:

BOOST_VARIANT2_DISABLE_GCC12_WARNINGS

BOOST_CXX14_CONSTEXPR variant_cc_base_impl( variant_cc_base_impl const& r )
noexcept( mp11::mp_all<std::is_nothrow_copy_constructible<T>...>::value )
: variant_base()
{
mp11::mp_with_index<sizeof...(T)>( r.index(), L1{ this, r } );
}

BOOST_VARIANT2_RESTORE_GCC12_WARNINGS

// move constructor

variant_cc_base_impl( variant_cc_base_impl && ) = default;
Expand Down Expand Up @@ -1500,13 +1523,17 @@ template<class... T> struct variant_ca_base_impl<true, false, T...>: public vari

public:

BOOST_VARIANT2_DISABLE_GCC12_WARNINGS

BOOST_CXX14_CONSTEXPR variant_ca_base_impl& operator=( variant_ca_base_impl const & r )
noexcept( mp11::mp_all<std::is_nothrow_copy_constructible<T>...>::value )
{
mp11::mp_with_index<sizeof...(T)>( r.index(), L3{ this, r } );
return *this;
}

BOOST_VARIANT2_RESTORE_GCC12_WARNINGS

// move assignment

variant_ca_base_impl& operator=( variant_ca_base_impl && ) = default;
Expand Down Expand Up @@ -1574,12 +1601,16 @@ template<class... T> struct variant_mc_base_impl<true, false, T...>: public vari

public:

BOOST_VARIANT2_DISABLE_GCC12_WARNINGS

BOOST_CXX14_CONSTEXPR variant_mc_base_impl( variant_mc_base_impl && r )
noexcept( mp11::mp_all<std::is_nothrow_move_constructible<T>...>::value )
{
mp11::mp_with_index<sizeof...(T)>( r.index(), L2{ this, r } );
}

BOOST_VARIANT2_RESTORE_GCC12_WARNINGS

// assignment

variant_mc_base_impl& operator=( variant_mc_base_impl const & ) = default;
Expand Down Expand Up @@ -1653,12 +1684,16 @@ template<class... T> struct variant_ma_base_impl<true, false, T...>: public vari

public:

BOOST_VARIANT2_DISABLE_GCC12_WARNINGS

BOOST_CXX14_CONSTEXPR variant_ma_base_impl& operator=( variant_ma_base_impl && r )
noexcept( mp11::mp_all<std::is_nothrow_move_constructible<T>...>::value )
{
mp11::mp_with_index<sizeof...(T)>( r.index(), L4{ this, r } );
return *this;
}

BOOST_VARIANT2_RESTORE_GCC12_WARNINGS
};

} // namespace detail
Expand Down
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ include(BoostTestJamfile OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST)

if(HAVE_BOOST_TEST)

boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::variant2 Boost::core Boost::container_hash)
boost_test_jamfile(FILE Jamfile LINK_LIBRARIES Boost::variant2 Boost::core Boost::container_hash Boost::system)

endif()
7 changes: 7 additions & 0 deletions test/Jamfile
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,10 @@ compile variant_default_construct_cx_5.cpp ;
compile variant_value_construct_cx_2.cpp ;
compile variant_value_construct_cx_3.cpp ;
compile variant_value_construct_cx_4.cpp ;

# GCC 12+ false positive -Wmaybe-uninitialized with non-trivially-copyable types
run variant_gcc_false_positive.cpp : : :
<library>/boost/system//boost_system
<toolset>gcc:<cxxflags>-O3
<toolset>clang:<cxxflags>-O3
;
191 changes: 191 additions & 0 deletions test/variant_gcc_false_positive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@

// Copyright 2025 Peter Dimov
// Copyright 2025 Vinnie Falco
//
// Distributed under the Boost Software License, Version 1.0.
//
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt

// GCC 12+ -Wmaybe-uninitialized false positive tests
// https://github.com/boostorg/variant2/issues/33
//
// GCC 12+'s improved dataflow analysis sees code paths for all alternatives
// in mp_with_index and warns that members may be uninitialized, even though
// the variant's discriminator guarantees only initialized alternatives are
// accessed.

#include <boost/system/result.hpp>
#include <boost/core/lightweight_test.hpp>
#include <exception>
#include <string>

// Check for C++17 std::optional support
#if __cplusplus >= 201703L
# include <optional>
# define BOOST_VARIANT2_TEST_HAS_OPTIONAL 1
#endif

// Check for C++20 coroutine support
#if defined(__cpp_impl_coroutine) && __cpp_impl_coroutine >= 201902L
# include <coroutine>
# define BOOST_VARIANT2_TEST_HAS_CORO 1
#endif

using result_void = boost::system::result<void, std::exception_ptr>;
using result_string = boost::system::result<std::string, std::exception_ptr>;

void testGccUninitialized()
{
// Test 1: Simple copy construction
{
result_void r1;
result_void r2(r1);
(void)r2;
}

// Test 2: Copy assignment
{
result_void r1;
result_void r2;
r2 = r1;
(void)r2;
}

#ifdef BOOST_VARIANT2_TEST_HAS_OPTIONAL
// Test 3: std::optional assignment (matches spawn pattern)
{
std::optional<result_void> opt;
opt = result_void{};
(void)opt;
}
#endif

// Test 4: Pass to function via copy
{
auto fn = [](result_void r) { (void)r; };
fn(result_void{});
}

#ifdef BOOST_VARIANT2_TEST_HAS_OPTIONAL
// Test 5: Lambda capture + optional (closest to spawn)
{
auto fn = [](result_void r) {
std::optional<result_void> opt;
opt = r;
return opt.has_value();
};
(void)fn(result_void{});
}
#endif

// Test 6: Non-void result with string (triggers string warning)
{
result_string r1;
result_string r2(r1);
(void)r2;
}

// Test 7: Assign exception to result holding value
{
result_string r1{"hello"};
r1 = std::make_exception_ptr(std::runtime_error("test"));
(void)r1;
}

#ifdef BOOST_VARIANT2_TEST_HAS_OPTIONAL
// Test 8: Optional with string result
{
std::optional<result_string> opt;
opt = result_string{};
(void)opt;
}
#endif

#ifdef BOOST_VARIANT2_TEST_HAS_CORO
// Minimal fire-and-forget coroutine for testing
struct fire_and_forget
{
struct promise_type
{
fire_and_forget get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};

// Test 9: Coroutine returning result (mimics spawn)
{
auto coro = []() -> fire_and_forget {
result_void r{};
(void)r;
co_return;
};
coro();
}

// Test 10: Coroutine with handler call (closest to actual spawn)
{
std::optional<result_void> received;
auto handler = [&](result_void r) {
received = r;
};
auto coro = [&]() -> fire_and_forget {
handler(result_void{});
co_return;
};
coro();
(void)received;
}

// Test 11: Coroutine with try/catch like spawn
{
std::optional<result_void> received;
auto handler = [&](result_void r) {
received = r;
};
auto coro = [&]() -> fire_and_forget {
try
{
handler(result_void{});
}
catch (...)
{
handler(result_void{std::current_exception()});
}
co_return;
};
coro();
(void)received;
}

// Test 12: Coroutine with string result
{
std::optional<result_string> received;
auto handler = [&](result_string r) {
received = r;
};
auto coro = [&]() -> fire_and_forget {
try
{
handler(result_string{"test"});
}
catch (...)
{
handler(result_string{std::current_exception()});
}
co_return;
};
coro();
(void)received;
}
#endif
}

int main()
{
testGccUninitialized();
return boost::report_errors();
}
Loading