diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2010163..6a491d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,10 @@ on: env: UBSAN_OPTIONS: print_stacktrace=1 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: posix: strategy: diff --git a/include/boost/variant2/variant.hpp b/include/boost/variant2/variant.hpp index fe93a78..82f52f8 100644 --- a/include/boost/variant2/variant.hpp +++ b/include/boost/variant2/variant.hpp @@ -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 @@ -1420,6 +1439,8 @@ template struct variant_cc_base_impl: 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...>::value ) : variant_base() @@ -1427,6 +1448,8 @@ template struct variant_cc_base_impl: public vari mp11::mp_with_index( r.index(), L1{ this, r } ); } + BOOST_VARIANT2_RESTORE_GCC12_WARNINGS + // move constructor variant_cc_base_impl( variant_cc_base_impl && ) = default; @@ -1500,6 +1523,8 @@ template struct variant_ca_base_impl: 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...>::value ) { @@ -1507,6 +1532,8 @@ template struct variant_ca_base_impl: public vari return *this; } + BOOST_VARIANT2_RESTORE_GCC12_WARNINGS + // move assignment variant_ca_base_impl& operator=( variant_ca_base_impl && ) = default; @@ -1574,12 +1601,16 @@ template struct variant_mc_base_impl: public vari public: + BOOST_VARIANT2_DISABLE_GCC12_WARNINGS + BOOST_CXX14_CONSTEXPR variant_mc_base_impl( variant_mc_base_impl && r ) noexcept( mp11::mp_all...>::value ) { mp11::mp_with_index( r.index(), L2{ this, r } ); } + BOOST_VARIANT2_RESTORE_GCC12_WARNINGS + // assignment variant_mc_base_impl& operator=( variant_mc_base_impl const & ) = default; @@ -1653,12 +1684,16 @@ template struct variant_ma_base_impl: 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...>::value ) { mp11::mp_with_index( r.index(), L4{ this, r } ); return *this; } + + BOOST_VARIANT2_RESTORE_GCC12_WARNINGS }; } // namespace detail diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0c317a2..203b9e2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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() diff --git a/test/Jamfile b/test/Jamfile index 344e1d0..dea2c95 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -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 : : : + /boost/system//boost_system + gcc:-O3 + clang:-O3 + ; \ No newline at end of file diff --git a/test/variant_gcc_false_positive.cpp b/test/variant_gcc_false_positive.cpp new file mode 100644 index 0000000..6ad30c0 --- /dev/null +++ b/test/variant_gcc_false_positive.cpp @@ -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 +#include +#include +#include + +// Check for C++17 std::optional support +#if __cplusplus >= 201703L +# include +# define BOOST_VARIANT2_TEST_HAS_OPTIONAL 1 +#endif + +// Check for C++20 coroutine support +#if defined(__cpp_impl_coroutine) && __cpp_impl_coroutine >= 201902L +# include +# define BOOST_VARIANT2_TEST_HAS_CORO 1 +#endif + +using result_void = boost::system::result; +using result_string = boost::system::result; + +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 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 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 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 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 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 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(); +}