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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to **ValueStringBuilder** will be documented in this file. T

### Fixed

- `AsSpan(int startIndex, int length)` and `ToString(int startIndex, int length)` throw an `ArgumentOutOfRangeException` when the requested range ends after `Length`, instead of returning characters from the unused part of the buffer.
- The indexer throws an `IndexOutOfRangeException` for an index at or after `Length`, instead of giving access to the unused part of the buffer.
- `Append`/`AppendLine` with an interpolated string could return the rented array to the `ArrayPool` twice when an interpolation hole threw after the buffer grew. Reported by [@dhhoang](https://github.com/dhhoang) in #324

## [3.7.1] - 2026-09-25
Expand Down
2 changes: 1 addition & 1 deletion docs/site/articles/exceptions_and_edge_cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ uid: exceptions_and_edge_cases
| `Insert(int, ...)` overloads | `ArgumentOutOfRangeException` | `index` is negative, or greater than `Length`. |
| `Remove(int startIndex, int length)` | `ArgumentOutOfRangeException` | `length` is negative, `startIndex` is negative, or `startIndex + length` is greater than `Length`. |
| `Append(char value, int repeatCount)` | `ArgumentOutOfRangeException` | `repeatCount` is negative. |
| `AsSpan(int startIndex, int length)` / `ToString(int startIndex, int length)` | `ArgumentOutOfRangeException` | `length` is greater than `Length`. |
| `AsSpan(int startIndex, int length)` / `ToString(int startIndex, int length)` | `ArgumentOutOfRangeException` | `startIndex` or `length` is negative, or `startIndex + length` is greater than `Length`. |
| `AsSpan(Range)` / `ToString(Range)` | `ArgumentOutOfRangeException` | The resolved range falls outside `0..Length`. |
| `AppendFormat` | `FormatException` | A placeholder's argument index is not a valid non-negative integer within range of the supplied arguments, or a custom format specifier (e.g. `{0:00}`) is used - see [`AppendFormat` is intentionally limited](xref:best_practices#appendformat-is-intentionally-limited). |
| `Insert<T>` (the `ISpanFormattable` overload) | `InvalidOperationException` | `value.TryFormat` does not fit into the temporary buffer sized by `bufferSize` (default 36 characters) - increase `bufferSize` for large custom `ISpanFormattable` types. |
Expand Down
9 changes: 2 additions & 7 deletions src/LinkDotNet.StringBuilder/ValueStringBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public readonly bool IsEmpty
public readonly ref char this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref buffer[index];
get => ref buffer[..bufferPosition][index];
}

/// <summary>
Expand Down Expand Up @@ -192,12 +192,7 @@ public readonly ref char this[int index]
/// <param name="length">The length of the substring.</param>
/// <returns>The filled array as <see cref="ReadOnlySpan{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ReadOnlySpan<char> AsSpan(int startIndex, int length)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(length, bufferPosition);

return buffer.Slice(startIndex, length);
}
public readonly ReadOnlySpan<char> AsSpan(int startIndex, int length) => buffer[..bufferPosition].Slice(startIndex, length);

/// <summary>
/// Returns the string as an <see cref="ReadOnlySpan{T}"/>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ public void ShouldThrowIndexOutOfRangeWhenStringShorterThanIndex()
Assert.False(true);
}

[Fact]
public void ShouldThrowIndexOutOfRangeWhenIndexIsBetweenLengthAndCapacity()
{
using var stringBuilder = new ValueStringBuilder();
stringBuilder.Append("Hello");

try
{
_ = stringBuilder[5];
}
catch (IndexOutOfRangeException)
{
Assert.True(true);
return;
}

Assert.Fail();
}

[Fact]
public void ShouldTryToCopySpan()
{
Expand Down Expand Up @@ -418,6 +437,25 @@ public void ReturnSubstring()
result.ShouldBe("ell");
}

[Fact]
public void ShouldThrowWhenSubstringEndsAfterLength()
{
using var stringBuilder = new ValueStringBuilder();
stringBuilder.Append("Hello");

try
{
stringBuilder.ToString(3, 5);
}
catch (ArgumentOutOfRangeException)
{
Assert.True(true);
return;
}

Assert.Fail();
}

[Fact]
public void ImplicitCastFromStringToValueStringBuilder()
{
Expand Down
Loading