Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 30, 2026

Description

Debug.Assert(_bufferedBytes >= 0) in StreamPipeReader.AdvanceTo is reachable from public API when passing a SequencePosition from a different PipeReader. Debug assertions should never be reachable from public APIs, even under misuse.

PipeReader reader1 = PipeReader.Create(new MemoryStream(new byte[10]));
PipeReader reader2 = PipeReader.Create(new MemoryStream(new byte[1000]));

ReadResult result1 = await reader1.ReadAsync();
ReadResult result2 = await reader2.ReadAsync();

// Triggers Debug.Assert in debug builds, silently corrupts state in release
reader1.AdvanceTo(result2.Buffer.End);

Pre-change behavior in release builds: The Debug.Assert was stripped out, allowing the code to continue with corrupted state. _bufferedBytes would become negative after subtracting an invalid consumedBytes value calculated from segments with unrelated RunningIndex values. This led to the reader having an invalid _readHead pointing to another reader's segment, negative _bufferedBytes, and potentially leaked/corrupted buffer segments.

Changes:

  • Replace Debug.Assert with validation that throws InvalidOperationException when consumedBytes is negative or exceeds _bufferedBytes
  • Add test AdvanceWithPositionFromDifferentReaderThrows covering the reported scenario

Note: This fix validates the symptom (invalid consumedBytes values) rather than explicitly checking segment ownership, which would add O(n) overhead. The Pipe class has a similar Debug.Assert(_unconsumedBytes >= 0) pattern that may warrant similar treatment.

Original prompt

This section details on the original issue you should resolve

<issue_title>Reachable Debug.Assert in StreamPipeReader</issue_title>
<issue_description>The following unit test intentionally misuses PipeReader, however it does hit a Debug.Assert.

Debug.Assert is generally meant to hold invariants to be true, meaning misuse or not, they should not be reachable from public APIs.

[Fact]
public async Task PipeReaderPositionMisuseDebugAssert()
{
    PipeReader reader1 = PipeReader.Create(new MemoryStream(new byte[10]));
    PipeReader reader2 = PipeReader.Create(new MemoryStream(new byte[1000]));

    ReadResult result1 = await reader1.ReadAsync();
    ReadResult result2 = await reader2.ReadAsync();

    SequencePosition posFrom2 = result2.Buffer.End;
    reader1.AdvanceTo(posFrom2);
}

Will result in the following test failure:

Stack Trace:
     at Microsoft.VisualStudio.TestPlatform.TestHost.TestHostTraceListener.GetException(String message)
   at Microsoft.VisualStudio.TestPlatform.TestHost.TestHostTraceListener.Fail(String message, String detailMessage)
   at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) in /Users/vcsjones/Projects/runtime/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceInternal.cs:line 261
   at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) in /Users/vcsjones/Projects/runtime/src/libraries/System.Diagnostics.TraceSource/src/System/Diagnostics/TraceInternal.cs:line 17
   at System.Diagnostics.Debug.Fail(String message, String detailMessage) in /Users/vcsjones/Projects/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs:line 135
   at System.Diagnostics.Debug.Assert(Boolean condition, String message, String detailMessage) in /Users/vcsjones/Projects/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs:line 99
   at System.Diagnostics.Debug.Assert(Boolean condition, String message) in /Users/vcsjones/Projects/runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs:line 88
   at System.IO.Pipelines.StreamPipeReader.AdvanceTo(BufferSegment consumedSegment, Int32 consumedIndex, BufferSegment examinedSegment, Int32 examinedIndex) in /Users/vcsjones/Projects/runtime/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs:line 111
   at System.IO.Pipelines.StreamPipeReader.AdvanceTo(SequencePosition consumed, SequencePosition examined) in /Users/vcsjones/Projects/runtime/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs:line 89
   at System.IO.Pipelines.StreamPipeReader.AdvanceTo(SequencePosition consumed) in /Users/vcsjones/Projects/runtime/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs:line 70
```</issue_description>

<agent_instructions>Determine whether the Debug.Assert in question is testing the wrong condition, whether it's completely erroneous, whether there's some missing error handling, whether there's a production bug, etc. Then fix it accordingly and ensure there are tests.</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Replace Debug.Assert with proper validation that throws
InvalidOperationException when consumed position is invalid.
This handles the case when a SequencePosition from a different
PipeReader is passed to AdvanceTo.

Add test for the scenario described in the issue.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
@stephentoub stephentoub marked this pull request as ready for review January 31, 2026 04:06
Copilot AI review requested due to automatic review settings January 31, 2026 04:06
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request fixes a critical issue where Debug.Assert(_bufferedBytes >= 0) in StreamPipeReader.AdvanceTo was reachable from public API when passing a SequencePosition from a different PipeReader. Debug assertions should never be reachable from public APIs, even under misuse scenarios.

Changes:

  • Replace Debug.Assert with runtime validation that throws InvalidOperationException when consumedBytes is invalid
  • Add test coverage for the scenario where a position from one reader is passed to another reader's AdvanceTo method
  • Move the _bufferedBytes subtraction to after validation to prevent state corruption

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/StreamPipeReader.cs Replaces Debug.Assert with proper runtime validation that checks if consumedBytes is negative or exceeds _bufferedBytes, throwing InvalidOperationException for invalid values
src/libraries/System.IO.Pipelines/tests/StreamPipeReaderTests.cs Adds test case verifying that passing a SequencePosition from a different PipeReader to AdvanceTo correctly throws InvalidOperationException

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reachable Debug.Assert in StreamPipeReader

2 participants