Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ Testing/Temporary
_codeql_build_dir
_codeql_detected_source_root

# CodeQL build artifacts
_codeql_build_dir
_codeql_detected_source_root

18 changes: 11 additions & 7 deletions RingBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,27 @@ namespace buffers {
ring_buffer_iterator& operator=(ring_buffer_iterator const& ) noexcept = default;
template<bool Z = C, typename std::enable_if<(!Z), int>::type* = nullptr>
[[nodiscard]] reference operator*() noexcept {
return (*source_)[index_ % N];
return (*source_)[index_];
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the modulo operation from the dereference operator creates a critical bug. When an iterator has index_ set to the sentinel value N (which happens for end() iterators), dereferencing will access (*source_)[N], which is out of bounds. The valid range is [0, N-1].

While dereferencing end() iterators is undefined behavior in standard iterators, the previous implementation would not cause out-of-bounds access. Consider restoring the modulo operation to maintain memory safety, or add bounds checking to prevent accessing index N.

Copilot uses AI. Check for mistakes.
}
template<bool Z = C, typename std::enable_if<(Z), int>::type* = nullptr>
[[nodiscard]] const_reference operator*() const noexcept {
return (*source_)[index_ % N];
return (*source_)[index_];
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the modulo operation from the dereference operator creates a critical bug. When an iterator has index_ set to the sentinel value N (which happens for end() iterators), dereferencing will access (*source_)[N], which is out of bounds. The valid range is [0, N-1].

While dereferencing end() iterators is undefined behavior in standard iterators, the previous implementation would not cause out-of-bounds access. Consider restoring the modulo operation to maintain memory safety, or add bounds checking to prevent accessing index N.

Copilot uses AI. Check for mistakes.
}
template<bool Z = C, typename std::enable_if<(!Z), int>::type* = nullptr>
[[nodiscard]] pointer operator->() noexcept {
return &((*source_)[index_ % N]);
return &((*source_)[index_]);
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the modulo operation from the arrow operator creates a critical bug. When an iterator has index_ set to the sentinel value N (which happens for end() iterators), dereferencing will access (*source_)[N], which is out of bounds. The valid range is [0, N-1].

While dereferencing end() iterators is undefined behavior in standard iterators, the previous implementation would not cause out-of-bounds access. Consider restoring the modulo operation to maintain memory safety, or add bounds checking to prevent accessing index N.

Copilot uses AI. Check for mistakes.
}
template<bool Z = C, typename std::enable_if<(Z), int>::type* = nullptr>
[[nodiscard]] const_pointer operator->() const noexcept {
return &((*source_)[index_ % N]);
return &((*source_)[index_]);
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the modulo operation from the arrow operator creates a critical bug. When an iterator has index_ set to the sentinel value N (which happens for end() iterators), dereferencing will access (*source_)[N], which is out of bounds. The valid range is [0, N-1].

While dereferencing end() iterators is undefined behavior in standard iterators, the previous implementation would not cause out-of-bounds access. Consider restoring the modulo operation to maintain memory safety, or add bounds checking to prevent accessing index N.

Copilot uses AI. Check for mistakes.
}
self_type& operator++() noexcept {
++index_;
++count_;
if (count_ >= source_->size()) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果你这样实现,那么上面的 [[nodiscard]] reference operator*() noexcept {
return (source_)[index_ % N];
}
template<bool Z = C, typename std::enable_if<(Z), int>::type
= nullptr>
[[nodiscard]] const_reference operator*() const noexcept {
return (source_)[index_ % N];
}
template<bool Z = C, typename std::enable_if<(!Z), int>::type
= nullptr>
[[nodiscard]] pointer operator->() noexcept {
return &((source_)[index_ % N]);
}
template<bool Z = C, typename std::enable_if<(Z), int>::type
= nullptr>
[[nodiscard]] const_pointer operator->() const noexcept {
return &((*source_)[index_ % N]);
}是不是就多余%N了 @copilot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right! Fixed in bd7f23a. Since the operator++ keeps index_ in [0, N-1] during valid iteration and only sets it to N when reaching end, the % N in the dereference operators was redundant. I've also updated end() and cend() to consistently use N as the sentinel value.

index_ = N; // Set to sentinel value (out of valid range [0, N-1]) when reaching end
} else {
index_ = (index_ + 1) % N;
}
return *this;
}
self_type operator++(int) noexcept {
Expand Down Expand Up @@ -214,11 +218,11 @@ using std::bool_constant;
// Iterator to oldest element.
[[nodiscard]] iterator begin() noexcept { return iterator{this, tail_, 0};}
// Iterator to one past newest element.
[[nodiscard]] iterator end() noexcept { return iterator{this, tail_ + size_, size_};}
[[nodiscard]] iterator end() noexcept { return iterator{this, N, size_};}
Comment on lines 219 to +221

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make begin() equal end() for empty buffers

With end() now returning an iterator at index N, an empty buffer (size_ == 0) produces begin() at tail_ (typically 0) and end() at N. Because iterator equality only compares source() and index(), begin() != end() for empty buffers, so range-based for/for(it!=end) loops will perform one dereference of uninitialized storage. This is a regression from the prior end() definition (tail_ + size_) where empty buffers produced matching iterators. Consider returning end() from begin() when size_ == 0, or otherwise aligning the sentinel/index comparison so empty buffers don’t iterate.

Useful? React with 👍 / 👎.

// Const iterator to oldest element.
[[nodiscard]] const_iterator cbegin() const noexcept { return const_iterator{this, tail_, 0};}
// Const iterator to one past newest element.
[[nodiscard]] const_iterator cend() const noexcept { return const_iterator{this, tail_ + size_, size_};}
[[nodiscard]] const_iterator cend() const noexcept { return const_iterator{this, N, size_};}
// Check if buffer has no elements.
[[nodiscard]] bool empty() const noexcept { return size_ == 0; }
// Check if buffer is at capacity.
Expand Down