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.
GCC 14, 15 and 16 emit
-Wstringop-overflowwhenenqueue_bulkis called with anexplicit 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 askedfor extra warnings, and
-Werrorbuilds fail outright.Reproduction
Self-contained, v1.0.5,
-O2 -std=c++17: https://godbolt.org/z/sde6qWae3A plain
enqueue_bulk(tok, int*, 64)does not trigger it -- the move-only elementtype and the generating iterator both appear to be necessary.
Diagnostic
The load is
this->tailIndex.load(...)on line 2082, reached from line 1404:token.produceris dereferenced unchecked, so GCC's value-range pass admits a pathwhere it is null and concludes the atomic load runs on a null
this. A valid tokenalways has a producer, so this looks like a false positive rather than a real
defect -- the compiler simply cannot see the invariant.
Scope
-O2and-O3-O1; clang (all versions tried)-std=c++14vsc++20;-isystemdoes not suppress it, since the diagnostic is attributed through the inlining chain rather than to the header it lands inTwo 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:
2. Tell the optimiser the producer is non-null:
Worth noting on the second:
ProducerToken::valid()exists, so a token can hold anull producer (for example if construction failed to allocate). Passing such a token
to
enqueue_bulkalready 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.