Skip to content

RingBuffer, SpscRingBuffer and DelayLine throw an undocumented OverflowException for sizes above 2^30, and a failed RingBuffer.Resize corrupts the buffer #89

Description

@matt-edmondson

What's wrong

All three power-of-two buffers round their size up with the same NextPower2 helper (RingBuffer.cs ~L214, SpscRingBuffer.cs ~L202, DelayLine.cs ~L180). For any input above 2^30 the result wraps to int.MinValue, and new T[int.MinValue] throws OverflowException. The constructors only validate the lower bound, and their docs promise ArgumentOutOfRangeException for bad sizes.

  • RingBuffer<T>(length) and Resize(length) with length > 2^30: NextPower2(length) overflows (~L139).
  • SpscRingBuffer<T>(capacity) with capacity >= 2^30: NextPower2(capacity + 1) overflows (~L90). With int.MaxValue, the + 1 itself also wraps.
  • DelayLine(maxDelaySamples) with maxDelaySamples >= 2^30: the same as SpscRingBuffer (~L80).

RingBuffer.Resize is worse. AllocateBuffer (~L134-144) assigns Length and Capacity before allocating. When the allocation throws, the old Buffer stays in place but Capacity is int.MinValue, so the index mask becomes int.MaxValue. Subsequent PushBack calls index past the end of the array.

Repro (verified on net10.0)

new RingBuffer<byte>((1 << 30) + 1);   // OverflowException
new SpscRingBuffer<byte>(1 << 30);     // OverflowException
new DelayLine(1 << 30);                // OverflowException

RingBuffer<int> r = new(4); r.PushBack(1); r.PushBack(2);
try { r.Resize((1 << 30) + 1); } catch (OverflowException) { }
r.PushBack(0); r.PushBack(1); /* ... */ // IndexOutOfRangeException within a few pushes

Suggested fix

  • Validate the upper bound up front with ArgumentOutOfRangeException.ThrowIfGreaterThan, and document the limits:
    • RingBuffer: length ≤ 1 << 30.
    • SpscRingBuffer and DelayLine: capacity ≤ (1 << 30) - 1, because they need the extra slot.
  • In RingBuffer.AllocateBuffer, compute and allocate the new array before assigning any field, so a failure leaves the buffer unchanged.

Acceptance criteria

  • Oversized constructor and Resize calls throw ArgumentOutOfRangeException.
  • After a rejected Resize, the buffer still holds its previous contents and PushBack still works.

Same family as the closed #73 (the Resample index overflow) and #50 (the lower-bound check).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions