cameron314 / cameron314/concurrentqueue

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

Open
#456 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
12.5k
Forks
1.9k
PR merge metrics
No merged PRs in 30d

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in concurrentqueue.h at the enqueue_bulk call chain around lines 1065, 1404, and 2082, then reproduce the warning with the linked GCC cases and the provided move-only generating iterator. Compare the two documented diagnostic-suppression approaches and verify that the warning is gone on the affected GCC versions without runtime-cost changes or regressions.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.