Understanding the Coverity std::variant False Positive

Static analysis tools like Coverity are highly valuable for catching edge-case bugs, but they can occasionally get overly conservative. A common pain point in modern C++ projects is Coverity flagging std::get and std::visit with UNCAUGHT_EXCEPTION warnings (specifically for std::bad_variant_access).

This happens because Coverity's engine often assumes that any std::variant can theoretically enter a valueless_by_exception() state, or it fails to track the path-sensitive state-guarantees provided by std::holds_alternative or manual checks. Even if you assert that the variant is valid, Coverity may still flag the line as a potential source of an unhandled exception.

If you are dealing with this noise—especially in shared or library code like Qt components—here are the most effective ways to silence these false positives without compromising code quality.

1. The Cleanest Fix for std::get: Use std::get_if

As noted in the original problem, replacing std::holds_alternative and std::get with std::get_if is highly recommended. It is not only cleaner but also bypasses Coverity's exception analysis because std::get_if returns a pointer (which can be null) rather than throwing an exception.

// Instead of this (which triggers Coverity):
if (std::holds_alternative<std::string>(v)) {
    use(std::get<std::string>(v));
}

// Use this:
if (auto* p = std::get_if<std::string>(&v)) {
    use(*p);
}

2. The Best Fix for std::visit: Create a Safe Wrapper

For std::visit, you cannot easily replace the call with a chain of std::get_if statements without defeating the purpose of the visitor pattern. To resolve this globally or in a central component without infecting downstream users, you can create a safe_visit wrapper utility.

This wrapper catches std::bad_variant_access and marks the catch block as unreachable. Compiler optimizers will strip this block entirely in production builds, while Coverity will recognize that the exception is handled.

#include <variant>
#include <utility>

template <typename Visitor, typename... Variants>
decltype(auto) safe_visit(Visitor&& vis, Variants&&... vars) {
    try {
        return std::visit(std::forward<Visitor>(vis), std::forward<Variants>(vars)...);
    } catch (const std::bad_variant_access&) {
#if defined(__GNUC__) || defined(__clang__)
        __builtin_unreachable();
#elif defined(_MSC_VER)
        __assume(0);
#endif
    }
}

By replacing your internal calls to std::visit with safe_visit, you immediately eliminate the false positives while maintaining optimal runtime performance.

3. Coverity-Specific Annotations

If you prefer not to change your C++ code structure, you can suppress the warning directly using Coverity annotations. Place the // coverity[uncaught_exception] comment directly above the line triggering the warning:

// coverity[uncaught_exception]
std::visit(visitor, my_variant);

While this is a quick fix, it can become tedious if you have dozens of std::visit calls across your codebase. It is best used for isolated instances.

4. Using Coverity Modeling Files

If you want a hands-off approach that doesn't require modifying your codebase at all, you can instruct Coverity to treat std::visit or std::get as functions that do not throw. You can achieve this by writing a Coverity user model file.

Create a file named user_models.cpp with the following simplified prototype:

namespace std {
    template <typename T, typename... Types>
    T& get(variant<Types...>& v) {
        // Model get as never throwing
        return *static_cast<T*>(nullptr); 
    }
}

Feed this model file into your Coverity build pipeline using the cov-make-library command. This teaches the static analyzer's engine to assume these standard library functions are safe, eliminating the false positives globally.

Summary

  • For std::get: Refactor to std::get_if for safer, cleaner code that naturally avoids the warning.
  • For std::visit: Implement a lightweight safe_visit wrapper that catches the exception and invokes undefined behavior optimization hints like __builtin_unreachable().
  • For legacy codebases: Use Coverity suppression comments or modeling files to eliminate the noise without touching production logic.