From 43f6ac52320ef55e060fef5d831ada04fa02750e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 16 Sep 2026 19:25:31 +0000 Subject: [PATCH] fix: reject non-positive lengths in RingBuffer [patch] RingBuffer passed its length straight to AllocateBuffer with no guard, unlike DelayLine and SpscRingBuffer in the same directory. new RingBuffer(0) succeeded silently and threw IndexOutOfRangeException on the first PushBack; new RingBuffer(-1) did the same while leaving a negative internal Length. The RingBuffer(T value, int length) overload was worse still: a negative length skipped the prefill loop entirely and reported no error at all. Guard in AllocateBuffer so all three constructors, Resize and Resample reject length <= 0 at the call site, matching the sibling buffers. Resize and Resample share the same allocation path and had the identical hole. Fixes #50 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019Z6jU64bBLVThqNZiXkL3c --- Containers.Test/RingBufferTests.cs | 49 ++++++++++++++++++++++++++++++ Containers/RingBuffer.cs | 8 +++++ 2 files changed, 57 insertions(+) diff --git a/Containers.Test/RingBufferTests.cs b/Containers.Test/RingBufferTests.cs index b0b5247..569cb26 100644 --- a/Containers.Test/RingBufferTests.cs +++ b/Containers.Test/RingBufferTests.cs @@ -259,4 +259,53 @@ public void Clear_AllowsReuseAfterClearing() Assert.AreEqual(1, buffer.Count); Assert.AreEqual(7, buffer.At(0)); } + + [TestMethod] + public void Constructor_NonPositiveLength_Throws() + { + Assert.ThrowsExactly(() => new RingBuffer(0)); + Assert.ThrowsExactly(() => new RingBuffer(-1)); + } + + [TestMethod] + public void Constructor_PrefillValue_NonPositiveLength_Throws() + { + Assert.ThrowsExactly(() => new RingBuffer(42, 0)); + Assert.ThrowsExactly(() => new RingBuffer(42, -1)); + } + + [TestMethod] + public void Constructor_PrefillItems_NonPositiveLength_Throws() + { + int[] items = [1, 2, 3]; + Assert.ThrowsExactly(() => new RingBuffer(items, 0)); + Assert.ThrowsExactly(() => new RingBuffer(items, -1)); + } + + [TestMethod] + public void Resize_NonPositiveLength_Throws() + { + RingBuffer buffer = new(3); + Assert.ThrowsExactly(() => buffer.Resize(0)); + Assert.ThrowsExactly(() => buffer.Resize(-1)); + } + + [TestMethod] + public void Resample_NonPositiveLength_Throws() + { + RingBuffer buffer = new(3); + buffer.PushBack(1); + Assert.ThrowsExactly(() => buffer.Resample(0)); + Assert.ThrowsExactly(() => buffer.Resample(-1)); + } + + [TestMethod] + public void Constructor_MinimumLength_IsUsable() + { + RingBuffer buffer = new(1); + buffer.PushBack(1); + buffer.PushBack(2); + Assert.AreEqual(1, buffer.Count); + Assert.AreEqual(2, buffer.At(0)); + } } diff --git a/Containers/RingBuffer.cs b/Containers/RingBuffer.cs index cc0497b..c4a0f1f 100644 --- a/Containers/RingBuffer.cs +++ b/Containers/RingBuffer.cs @@ -78,6 +78,7 @@ public class RingBuffer : IReadOnlyList /// Initializes a new instance of the class with the specified length. /// /// The number of elements the buffer should store. + /// Thrown when is less than one. public RingBuffer(int length) => AllocateBuffer(length); /// @@ -86,6 +87,7 @@ public class RingBuffer : IReadOnlyList /// /// The items to prefill the buffer with. /// The number of elements the buffer should store. + /// Thrown when is less than one. public RingBuffer(IEnumerable items, int length) : this(length) { @@ -111,6 +113,7 @@ public RingBuffer(IEnumerable items, int length) /// /// The value to prefill the buffer with. /// The number of elements the buffer should store. + /// Thrown when is less than one. public RingBuffer(T value, int length) : this(length) { @@ -124,8 +127,11 @@ public RingBuffer(T value, int length) /// Allocates and initializes the internal buffer with the specified length. /// /// The number of elements to allocate space for. + /// Thrown when is less than one. private void AllocateBuffer(int length) { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(length); + Length = length; Capacity = NextPower2(Length); Buffer = new T[Capacity]; @@ -218,6 +224,7 @@ private static int NextPower2(int v) /// Resizes the buffer to the specified length, discarding all current contents. /// /// The new length of the buffer. + /// Thrown when is less than one. public void Resize(int length) => AllocateBuffer(length); /// @@ -234,6 +241,7 @@ private static int NextPower2(int v) /// If the buffer is empty, this method will resize the buffer without adding any elements. /// /// The new length of the buffer. + /// Thrown when is less than one. public void Resample(int length) { // Save the current count of valid elements