Skip to content

-Wstringop-overflow false positive in enqueue_bulk with a producer token (GCC 14-16, -O2+) #456

Description

@graphicsMan

GCC 14, 15 and 16 emit -Wstringop-overflow when enqueue_bulk is called with an
explicit producer token and an iterator that materialises elements on dereference.
The warning is enabled by default at -O2, so it reaches users who have not asked
for extra warnings, and -Werror builds fail outright.

Reproduction

Self-contained, v1.0.5, -O2 -std=c++17: https://godbolt.org/z/sde6qWae3

struct Task {                       // move-only, with inline storage
  void (*fn)(void*) = nullptr;
  alignas(16) char storage[48] = {};
  Task() = default;
  Task(Task&&) noexcept = default;
  Task& operator=(Task&&) noexcept = default;
  Task(const Task&) = delete;
};

template <typename Gen>             // materialises each element on deref
struct GenIter {
  Gen* gen; std::size_t i;
  Task operator*() const { return (*gen)(i); }
  GenIter& operator++() { ++i; return *this; }
};

moodycamel::ConcurrentQueue<Task> q;
moodycamel::ProducerToken tok(q);
auto gen = [](std::size_t) { return Task{}; };
GenIter<decltype(gen)> it{&gen, 0};
q.enqueue_bulk(tok, it, 64);

A plain enqueue_bulk(tok, int*, 64) does not trigger it -- the move-only element
type and the generating iterator both appear to be necessary.

Diagnostic

bits/atomic_base.h:501:31: warning: '__atomic_load_8' writing 8 bytes into a
  region of size 0 overflows the destination [-Wstringop-overflow=]
  inlined from ExplicitProducer::enqueue_bulk(...) at concurrentqueue.h:2082:49
  inlined from inner_enqueue_bulk(...)             at concurrentqueue.h:1404:124
  inlined from enqueue_bulk(...)                   at concurrentqueue.h:1065:38
cc1plus: note: destination object is likely at address zero

The load is this->tailIndex.load(...) on line 2082, reached from line 1404:

return static_cast<ExplicitProducer*>(token.producer)->...enqueue_bulk<canAlloc>(itemFirst, count);

token.producer is dereferenced unchecked, so GCC's value-range pass admits a path
where it is null and concludes the atomic load runs on a null this. A valid token
always has a producer, so this looks like a false positive rather than a real
defect -- the compiler simply cannot see the invariant.

Scope

Affected GCC 14.2, 15.3, 16.2 at -O2 and -O3
Clean -O1; clang (all versions tried)
Not a factor -std=c++14 vs c++20; -isystem does not suppress it, since the diagnostic is attributed through the inlining chain rather than to the header it lands in

Two zero-cost options

Both verified to silence it on GCC 14.2, 15.3 and 16.2, with no runtime cost:

1. Localised pragma -- purely diagnostic, no codegen change:

#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overflow"
#endif
return static_cast<ExplicitProducer*>(token.producer)->...enqueue_bulk<canAlloc>(itemFirst, count);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif

2. Tell the optimiser the producer is non-null:

auto* const producer = static_cast<ExplicitProducer*>(token.producer);
#if defined(__GNUC__) || defined(__clang__)
if (producer == nullptr) { __builtin_unreachable(); }
#endif
return producer->...enqueue_bulk<canAlloc>(itemFirst, count);

Worth noting on the second: ProducerToken::valid() exists, so a token can hold a
null producer (for example if construction failed to allocate). Passing such a token
to enqueue_bulk already dereferences null today, so the hint introduces no new UB --
but it does let the optimiser assume validity, which turns a likely crash into
arbitrary behaviour. That is a design call for you rather than one I would make.

Happy to send a PR for whichever you prefer, or neither if you would rather handle
it differently.

Found via dispenso, which vendors
v1.0.5 and hits this in its thread pool's bulk-enqueue path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions