cameron314 / cameron314/concurrentqueue
-Wstringop-overflow false positive in enqueue_bulk with a producer token (GCC 14-16, -O2+)
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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