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.

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