Compare commits

...

10 Commits

Author SHA1 Message Date
Peter Dimov
155ed414ab Disable variant_issue_55.cpp on clang-cl 32 bit 2026-01-02 12:29:11 +02:00
Peter Dimov
e6b336ec68 Call boost::core::lwt_init() in variant_issue_55.cpp, because it doesn't invoke any BOOST_TEST macros and clang-win 32 bit hangs w/ assertion 2026-01-02 02:51:46 +02:00
Peter Dimov
799a69eccc Disable -Wmaybe-uninitialized false positive. Refs #55. 2026-01-02 02:21:29 +02:00
Peter Dimov
74f6f0cd96 Fix issue link; fix __cplusplus check 2026-01-01 22:28:25 +02:00
Peter Dimov
fc786d39b3 Rename variant_gcc_false_positive.cpp 2026-01-01 22:21:34 +02:00
Vinnie Falco
382cf33824 Add test for GCC 12+ -Wmaybe-uninitialized false positive
Add variant_gcc_false_positive.cpp to reproduce GCC 12+ spurious
-Wmaybe-uninitialized warnings when copying/moving variant containing
non-trivially-copyable types like std::exception_ptr and std::string.

Tests use boost::system::result which internally uses variant2, and
include coroutine-based tests when C++20 coroutine support is available.

The test uses -O3 optimization to trigger the aggressive inlining that
exposes the false positive where GCC's dataflow analysis cannot prove
the discriminator ensures only initialized alternatives are accessed.

See https://github.com/boostorg/variant2/issues/33
2026-01-01 22:16:48 +02:00
Peter Dimov
4632862a4b msvc-14.5 still needs two more workarounds 2026-01-01 20:04:58 +02:00
Peter Dimov
48ed0a243f Update .drone.jsonnet 2026-01-01 18:45:22 +02:00
Peter Dimov
936a1a95b5 msvc-14.5 still needs the workarounds. sad! 2026-01-01 18:15:20 +02:00
Peter Dimov
fcdaadb0f5 Update .drone.jsonnet 2026-01-01 18:05:28 +02:00
7 changed files with 288 additions and 28 deletions

View File

@@ -34,7 +34,6 @@ local linux_pipeline(name, image, environment, packages = "", sources = [], arch
'set -e',
'uname -a',
'echo $DRONE_STAGE_MACHINE',
'wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -',
] +
(if sources != [] then [ ('apt-add-repository "' + source + '"') for source in sources ] else []) +
(if packages != "" then [ 'apt-get update', 'apt-get -y install ' + packages ] else []) +
@@ -213,6 +212,13 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
"g++-14-multilib",
),
linux_pipeline(
"Linux 25.04 GCC 15 32/64",
"cppalliance/droneubuntu2504:1",
{ TOOLSET: 'gcc', COMPILER: 'g++-15', CXXSTD: '11,14,17,20,23,2c', ADDRMD: '32,64' },
"g++-15-multilib",
),
linux_pipeline(
"Linux 16.04 Clang 3.5",
"cppalliance/droneubuntu1604:1",
@@ -340,40 +346,47 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
),
linux_pipeline(
"Linux 24.04 Clang 17 UBSAN",
"Linux 24.04 Clang 17",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-17', CXXSTD: '11,14,17,20,2b' } + ubsan,
{ TOOLSET: 'clang', COMPILER: 'clang++-17', CXXSTD: '11,14,17,20,2b' },
"clang-17",
),
linux_pipeline(
"Linux 24.04 Clang 17 ASAN",
"Linux 24.04 Clang 18",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-17', CXXSTD: '11,14,17,20,2b' } + asan,
"clang-17",
),
linux_pipeline(
"Linux 24.04 Clang 18 UBSAN",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-18', CXXSTD: '11,14,17,20,2b' } + ubsan,
{ TOOLSET: 'clang', COMPILER: 'clang++-18', CXXSTD: '11,14,17,20,2b' },
"clang-18",
),
linux_pipeline(
"Linux 24.04 Clang 18 ASAN",
"Linux 24.04 Clang 19",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-18', CXXSTD: '11,14,17,20,2b' } + asan,
"clang-18",
),
linux_pipeline(
"Linux 24.10 Clang 19",
"cppalliance/droneubuntu2410:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-19', CXXSTD: '11,14,17,20,2b' } + asan,
{ TOOLSET: 'clang', COMPILER: 'clang++-19', CXXSTD: '11,14,17,20,2b' },
"clang-19",
),
linux_pipeline(
"Linux 24.04 Clang 20 UBSAN",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-20', CXXSTD: '11,14,17,20,23,2c' } + ubsan,
"clang-20",
),
linux_pipeline(
"Linux 24.04 Clang 20 ASAN",
"cppalliance/droneubuntu2404:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-20', CXXSTD: '11,14,17,20,23,2c' } + asan,
"clang-20",
),
linux_pipeline(
"Linux 25.10 Clang 21",
"cppalliance/droneubuntu2510:1",
{ TOOLSET: 'clang', COMPILER: 'clang++-21', CXXSTD: '11,14,17,20,23,2c' },
"clang-21",
),
macos_pipeline(
"MacOS 10.15 Xcode 12.2 UBSAN",
{ TOOLSET: 'clang', COMPILER: 'clang++', CXXSTD: '11,14,1z' } + ubsan,
@@ -437,4 +450,16 @@ local windows_pipeline(name, image, environment, arch = "amd64") =
"cppalliance/dronevs2022:1",
{ TOOLSET: 'msvc-14.3', CXXSTD: '14,17', CXXFLAGS: '/permissive-' },
),
windows_pipeline(
"Windows VS2026 msvc-14.5",
"cppalliance/dronevs2026:1",
{ TOOLSET: 'msvc-14.5', CXXSTD: '14,17,20,latest' },
),
windows_pipeline(
"Windows VS2026 msvc-14.5 permissive-",
"cppalliance/dronevs2026:1",
{ TOOLSET: 'msvc-14.5', CXXSTD: '14,17', CXXFLAGS: '/permissive-' },
),
]

View File

@@ -93,7 +93,7 @@ struct monostate
{
};
#if !BOOST_WORKAROUND(BOOST_MSVC, < 1950)
#if !BOOST_WORKAROUND(BOOST_MSVC, < 1960)
constexpr bool operator<(monostate, monostate) noexcept { return false; }
constexpr bool operator>(monostate, monostate) noexcept { return false; }
@@ -558,10 +558,20 @@ template<class T1, class... T> union variant_storage_impl<mp11::mp_false, T1, T.
T1 first_;
variant_storage<T...> rest_;
#if defined(BOOST_GCC) && (__GNUC__ >= 12)
// false positive, see https://github.com/boostorg/variant2/issues/55
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
template<class... A> constexpr variant_storage_impl( mp11::mp_size_t<0>, A&&... a ): first_( std::forward<A>(a)... )
{
}
#if defined(BOOST_GCC) && (__GNUC__ >= 12)
# pragma GCC diagnostic pop
#endif
template<std::size_t I, class... A> constexpr variant_storage_impl( mp11::mp_size_t<I>, A&&... a ): rest_( mp11::mp_size_t<I-1>(), std::forward<A>(a)... )
{
}
@@ -704,6 +714,7 @@ template<class T1, class... T> union variant_storage_impl<mp11::mp_true, T1, T..
# pragma GCC diagnostic ignored "-Wuninitialized"
#endif
#endif
*this = variant_storage_impl( mp11::mp_size_t<I>(), std::forward<A>(a)... );
#if defined(BOOST_GCC) && (__GNUC__ >= 7)
@@ -1071,7 +1082,18 @@ template<class... T> struct variant_base_impl<false, true, T...>
template<class I> BOOST_CXX14_CONSTEXPR void operator()( I ) const noexcept
{
using U = mp11::mp_at<mp11::mp_list<none, T...>, I>;
#if defined(BOOST_GCC) && (__GNUC__ >= 12)
// false positive, see https://github.com/boostorg/variant2/issues/55
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
this_->st_.get( I() ).~U();
#if defined(BOOST_GCC) && (__GNUC__ >= 12)
# pragma GCC diagnostic pop
#endif
}
};
@@ -1119,8 +1141,18 @@ template<class... T> struct variant_base_impl<false, true, T...>
static_assert( std::is_nothrow_move_constructible<U>::value, "Logic error: U must be nothrow move constructible" );
#if defined(BOOST_GCC) && (__GNUC__ >= 12)
// false positive, see https://github.com/boostorg/variant2/issues/55
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
U tmp( std::forward<A>(a)... );
#if defined(BOOST_GCC) && (__GNUC__ >= 12)
# pragma GCC diagnostic pop
#endif
_destroy();
st_.emplace( mp11::mp_size_t<J>(), std::move(tmp) );
@@ -1689,7 +1721,7 @@ public:
class Ud = typename std::decay<U>::type,
class E1 = typename std::enable_if< !std::is_same<Ud, variant>::value && !std::is_base_of<variant, Ud>::value && !detail::is_in_place_index<Ud>::value && !detail::is_in_place_type<Ud>::value >::type,
#if BOOST_WORKAROUND(BOOST_MSVC, < 1950)
#if BOOST_WORKAROUND(BOOST_MSVC, < 1960)
class V = mp11::mp_apply_q< mp11::mp_bind_front<detail::resolve_overload_type, U&&>, variant >,

View File

@@ -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()

View File

@@ -172,3 +172,12 @@ 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_issue_55.cpp
: : :
<library>/boost/system//boost_system
# clang-cl 32 bit fails with an assertion in mp_with_index, likely due to a codegen bug
"<toolset>clang-win,<address-model>32:<build>no"
;

View File

@@ -10,9 +10,9 @@
BOOST_PRAGMA_MESSAGE("Skipping constexpr destructor test because __cpp_constexpr_dynamic_alloc < 201907L")
int main() {}
#elif defined(BOOST_MSVC) && BOOST_MSVC < 1950
#elif defined(BOOST_MSVC) && BOOST_MSVC < 1960
BOOST_PRAGMA_MESSAGE("Skipping constexpr destructor test because BOOST_MSVC < 1950")
BOOST_PRAGMA_MESSAGE("Skipping constexpr destructor test because BOOST_MSVC < 1960")
int main() {}
#elif defined(BOOST_CLANG) && BOOST_CLANG_VERSION < 180000

194
test/variant_issue_55.cpp Normal file
View File

@@ -0,0 +1,194 @@
// 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/55
//
// 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 <boost/config.hpp>
#include <exception>
#include <string>
// Check for C++17 std::optional support
#if BOOST_CXX_VERSION >= 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()
{
boost::core::lwt_init();
testGccUninitialized();
return boost::report_errors();
}

View File

@@ -10,9 +10,9 @@
BOOST_PRAGMA_MESSAGE("Skipping test because __cpp_constexpr_dynamic_alloc < 201907L")
int main() {}
#elif defined(BOOST_MSVC) && BOOST_MSVC < 1950
#elif defined(BOOST_MSVC) && BOOST_MSVC < 1960
BOOST_PRAGMA_MESSAGE("Skipping test because BOOST_MSVC < 1950")
BOOST_PRAGMA_MESSAGE("Skipping test because BOOST_MSVC < 1960")
int main() {}
#elif defined(BOOST_CLANG) && BOOST_CLANG_VERSION < 180000