mirror of
https://github.com/xtaci/kcp-go.git
synced 2026-10-19 16:14:38 +00:00
fix(ringbuffer): fix off-by-one in Discard() causing index out of range panic
RingBuffer.Discard() used `end <= cap` to decide whether the discard range is contiguous. When head + n equals exactly len(elements), this incorrectly set head to len(elements) instead of wrapping it to 0. A subsequent Peek() or Push() would then access elements[len(elements)], triggering a runtime panic: "index out of range [64] with length 64". The crash path in production: KCP.Input -> parse_una -> snd_buf.Discard(count) -> shrink_buf -> snd_buf.Peek() -> panic Fix: change the boundary condition from `end <= cap` to `end < cap`, so the exact-boundary case falls through to the wrap-around branch which correctly computes head = end - cap = 0. Add TestRingBufferDiscardBoundary to cover this edge case.
This commit is contained in:
+1
-1
@@ -101,7 +101,7 @@ func (r *RingBuffer[T]) Discard(n int) int {
|
||||
}
|
||||
cap := len(r.elements)
|
||||
end := r.head + n
|
||||
if end <= cap {
|
||||
if end < cap {
|
||||
// no wrap: clear contiguous range
|
||||
clear(r.elements[r.head:end])
|
||||
r.head = end
|
||||
|
||||
@@ -236,6 +236,38 @@ func TestRingForEach(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRingBufferDiscardBoundary tests discarding elements that land exactly
|
||||
// at the end of the backing array. This previously caused head to equal
|
||||
// len(elements), leading to an index-out-of-range panic in Peek().
|
||||
func TestRingBufferDiscardBoundary(t *testing.T) {
|
||||
// Create a ring buffer with backing array length 64 (MaxLen = 63).
|
||||
r := NewRingBuffer[int](64)
|
||||
|
||||
// Fill to capacity (63 elements), head=0, tail=63.
|
||||
for i := range 63 {
|
||||
r.Push(i)
|
||||
}
|
||||
|
||||
// Pop 1 element so head=1, push 1 so tail wraps to 0. Still 63 elements.
|
||||
r.Pop()
|
||||
r.Push(63)
|
||||
// Now head=1, tail=0, len=63.
|
||||
|
||||
// Discard exactly 63 elements. head + 63 = 64 = len(elements).
|
||||
// This must wrap head to 0, not leave it at 64.
|
||||
r.Discard(63)
|
||||
|
||||
if r.Len() != 0 {
|
||||
t.Fatalf("expected empty ring, got Len()=%d", r.Len())
|
||||
}
|
||||
|
||||
// Push a new element and Peek — this panicked before the fix.
|
||||
r.Push(99)
|
||||
if v, ok := r.Peek(); !ok || *v != 99 {
|
||||
t.Fatalf("expected Peek()=99, got %v (ok=%v)", v, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRingForEachReverse(t *testing.T) {
|
||||
r := NewRingBuffer[int](10)
|
||||
for i := range 10 {
|
||||
|
||||
Reference in New Issue
Block a user