Skip to content

Commit fb54698

Browse files
committed
fixed: Bug where array pool is not returned and could even be overwritten
1 parent 7419c98 commit fb54698

11 files changed

+118
-87
lines changed

src/LinkDotNet.StringBuilder/ValueStringBuilder.Replace.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue, int start
9090
var tempBuffer = ArrayPool<char>.Shared.Rent(24);
9191
if (spanFormattable.TryFormat(tempBuffer, out var written, default, null))
9292
{
93-
Replace(oldValue, tempBuffer.AsSpan()[..written], startIndex, count);
93+
Replace(oldValue, tempBuffer.AsSpan(0, written), startIndex, count);
9494
}
9595

9696
ArrayPool<char>.Shared.Return(tempBuffer);

src/LinkDotNet.StringBuilder/ValueStringBuilder.cs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public ref partial struct ValueStringBuilder
1515
{
1616
private int bufferPosition;
1717
private Span<char> buffer;
18+
private char[]? arrayFromPool;
1819

1920
/// <summary>
2021
/// Initializes a new instance of the <see cref="ValueStringBuilder"/> struct.
@@ -23,7 +24,9 @@ public ref partial struct ValueStringBuilder
2324
public ValueStringBuilder()
2425
{
2526
bufferPosition = 0;
26-
buffer = new char[32];
27+
buffer = default;
28+
arrayFromPool = null;
29+
Grow(32);
2730
}
2831

2932
/// <summary>
@@ -35,6 +38,7 @@ public ValueStringBuilder(Span<char> initialBuffer)
3538
{
3639
bufferPosition = 0;
3740
buffer = initialBuffer;
41+
arrayFromPool = null;
3842
}
3943

4044
/// <summary>
@@ -79,7 +83,7 @@ public ValueStringBuilder(Span<char> initialBuffer)
7983
/// <remarks>
8084
/// This method is used for use-cases where the user wants to use "fixed" calls like the following:
8185
/// <code>
82-
/// var stringBuilder = new ValueStringBuilder();
86+
/// using var stringBuilder = new ValueStringBuilder();
8387
/// stringBuilder.Append("Hello World");
8488
/// fixed (var* buffer = stringBuilder) { ... }
8589
/// </code>
@@ -238,6 +242,18 @@ public int LastIndexOf(ReadOnlySpan<char> word, int startIndex)
238242
[MethodImpl(MethodImplOptions.AggressiveInlining)]
239243
public bool Contains(ReadOnlySpan<char> word) => IndexOf(word) != -1;
240244

245+
/// <summary>
246+
/// Disposes the instance and returns rented buffer from an array pool if needed.
247+
/// </summary>
248+
public void Dispose()
249+
{
250+
var toReturn = arrayFromPool;
251+
if (toReturn != null)
252+
{
253+
ArrayPool<char>.Shared.Return(toReturn);
254+
}
255+
}
256+
241257
[MethodImpl(MethodImplOptions.AggressiveInlining)]
242258
private void Grow(int capacity = 0)
243259
{
@@ -248,7 +264,12 @@ private void Grow(int capacity = 0)
248264
var newSize = capacity > 0 ? capacity : currentSize * 2;
249265
var rented = ArrayPool<char>.Shared.Rent(newSize);
250266
buffer.CopyTo(rented);
251-
buffer = rented;
252-
ArrayPool<char>.Shared.Return(rented);
267+
var toReturn = arrayFromPool;
268+
buffer = arrayFromPool = rented;
269+
270+
if (toReturn != null)
271+
{
272+
ArrayPool<char>.Shared.Return(toReturn);
273+
}
253274
}
254275
}

tests/LinkDotNet.StringBuilder.Benchmarks/AppendBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public string DotNetStringBuilder()
2020
[Benchmark]
2121
public string ValueStringBuilder()
2222
{
23-
var builder = new ValueStringBuilder();
23+
using var builder = new ValueStringBuilder();
2424
builder.AppendLine("That is the first line of our benchmark.");
2525
builder.AppendLine("We can multiple stuff in here if want.");
2626
builder.AppendLine("We can multiple stuff in here if want.");

tests/LinkDotNet.StringBuilder.Benchmarks/AppendValueTypesBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public string DotNetStringBuilder()
2727
[Benchmark]
2828
public string ValueStringBuilder()
2929
{
30-
var builder = new ValueStringBuilder();
30+
using var builder = new ValueStringBuilder();
3131

3232
for (var i = 0; i < 25; i++)
3333
{

tests/LinkDotNet.StringBuilder.Benchmarks/ReplaceBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public string DotNetStringBuilder()
5555
[Benchmark]
5656
public string ValueStringBuilder()
5757
{
58-
var builder = new ValueStringBuilder();
58+
using var builder = new ValueStringBuilder();
5959
builder.Append(Text);
6060
builder.Replace("arcu", "some long word");
6161
builder.Replace("some long word", "arcu");

tests/LinkDotNet.StringBuilder.UnitTests/LinkDotNet.StringBuilder.UnitTests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11-
<PackageReference Include="FluentAssertions" Version="6.6.0" />
11+
<PackageReference Include="FluentAssertions" Version="6.7.0" />
1212
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
1313
<PackageReference Include="xunit" Version="2.4.1" />
14-
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
14+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1616
<PrivateAssets>all</PrivateAssets>
1717
</PackageReference>
18-
<PackageReference Include="coverlet.collector" Version="3.1.0">
18+
<PackageReference Include="coverlet.collector" Version="3.1.2">
1919
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2020
<PrivateAssets>all</PrivateAssets>
2121
</PackageReference>

tests/LinkDotNet.StringBuilder.UnitTests/ValueStringBuilder.Append.Tests.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public class ValueStringBuilderAppendTests
77
[Fact]
88
public void ShouldAddString()
99
{
10-
var stringBuilder = new ValueStringBuilder();
10+
using var stringBuilder = new ValueStringBuilder();
1111

1212
stringBuilder.Append("That is a string");
1313

@@ -17,7 +17,7 @@ public void ShouldAddString()
1717
[Fact]
1818
public void ShouldAddMultipleStrings()
1919
{
20-
var stringBuilder = new ValueStringBuilder();
20+
using var stringBuilder = new ValueStringBuilder();
2121

2222
stringBuilder.Append("This");
2323
stringBuilder.Append("is");
@@ -30,7 +30,7 @@ public void ShouldAddMultipleStrings()
3030
[Fact]
3131
public void ShouldAddLargeStrings()
3232
{
33-
var stringBuilder = new ValueStringBuilder();
33+
using var stringBuilder = new ValueStringBuilder();
3434

3535
stringBuilder.Append(new string('c', 99));
3636

@@ -40,7 +40,7 @@ public void ShouldAddLargeStrings()
4040
[Fact]
4141
public void ShouldAppendLine()
4242
{
43-
var stringBuilder = new ValueStringBuilder();
43+
using var stringBuilder = new ValueStringBuilder();
4444

4545
stringBuilder.AppendLine("Hello");
4646

@@ -50,7 +50,7 @@ public void ShouldAppendLine()
5050
[Fact]
5151
public void ShouldOnlyAddNewline()
5252
{
53-
var stringBuilder = new ValueStringBuilder();
53+
using var stringBuilder = new ValueStringBuilder();
5454

5555
stringBuilder.AppendLine();
5656

@@ -60,7 +60,7 @@ public void ShouldOnlyAddNewline()
6060
[Fact]
6161
public void ShouldGetIndexIfGiven()
6262
{
63-
var stringBuilder = new ValueStringBuilder();
63+
using var stringBuilder = new ValueStringBuilder();
6464

6565
stringBuilder.Append("Hello");
6666

@@ -70,7 +70,7 @@ public void ShouldGetIndexIfGiven()
7070
[Fact]
7171
public void ShouldAppendFloat()
7272
{
73-
var builder = new ValueStringBuilder();
73+
using var builder = new ValueStringBuilder();
7474

7575
builder.Append(2.2f);
7676

@@ -80,7 +80,7 @@ public void ShouldAppendFloat()
8080
[Fact]
8181
public void ShouldAppendDouble()
8282
{
83-
var builder = new ValueStringBuilder();
83+
using var builder = new ValueStringBuilder();
8484

8585
builder.Append(2.2d);
8686

@@ -90,7 +90,7 @@ public void ShouldAppendDouble()
9090
[Fact]
9191
public void ShouldAppendDecimal()
9292
{
93-
var builder = new ValueStringBuilder();
93+
using var builder = new ValueStringBuilder();
9494

9595
builder.Append(2.2m);
9696

@@ -100,7 +100,7 @@ public void ShouldAppendDecimal()
100100
[Fact]
101101
public void ShouldAppendInteger()
102102
{
103-
var builder = new ValueStringBuilder();
103+
using var builder = new ValueStringBuilder();
104104

105105
builder.Append(-2);
106106

@@ -110,7 +110,7 @@ public void ShouldAppendInteger()
110110
[Fact]
111111
public void ShouldAppendUnsignedInteger()
112112
{
113-
var builder = new ValueStringBuilder();
113+
using var builder = new ValueStringBuilder();
114114

115115
builder.Append(2U);
116116

@@ -120,7 +120,7 @@ public void ShouldAppendUnsignedInteger()
120120
[Fact]
121121
public void ShouldAppendLong()
122122
{
123-
var builder = new ValueStringBuilder();
123+
using var builder = new ValueStringBuilder();
124124

125125
builder.Append(2L);
126126

@@ -130,7 +130,7 @@ public void ShouldAppendLong()
130130
[Fact]
131131
public void ShouldAppendChar()
132132
{
133-
var builder = new ValueStringBuilder();
133+
using var builder = new ValueStringBuilder();
134134

135135
builder.Append('2');
136136

@@ -140,7 +140,7 @@ public void ShouldAppendChar()
140140
[Fact]
141141
public void ShouldAppendShort()
142142
{
143-
var builder = new ValueStringBuilder();
143+
using var builder = new ValueStringBuilder();
144144

145145
builder.Append((short)2);
146146

@@ -150,7 +150,7 @@ public void ShouldAppendShort()
150150
[Fact]
151151
public void ShouldAppendBool()
152152
{
153-
var builder = new ValueStringBuilder();
153+
using var builder = new ValueStringBuilder();
154154

155155
builder.Append(true);
156156

@@ -160,7 +160,7 @@ public void ShouldAppendBool()
160160
[Fact]
161161
public void ShouldAppendByte()
162162
{
163-
var builder = new ValueStringBuilder();
163+
using var builder = new ValueStringBuilder();
164164

165165
builder.Append((byte)2);
166166

@@ -170,7 +170,7 @@ public void ShouldAppendByte()
170170
[Fact]
171171
public void ShouldAppendSignedByte()
172172
{
173-
var builder = new ValueStringBuilder();
173+
using var builder = new ValueStringBuilder();
174174

175175
builder.Append((sbyte)2);
176176

@@ -180,7 +180,7 @@ public void ShouldAppendSignedByte()
180180
[Fact]
181181
public void ShouldAppendMultipleChars()
182182
{
183-
var builder = new ValueStringBuilder();
183+
using var builder = new ValueStringBuilder();
184184

185185
for (var i = 0; i < 64; i++)
186186
{
@@ -193,7 +193,7 @@ public void ShouldAppendMultipleChars()
193193
[Fact]
194194
public void ShouldAppendMultipleDoubles()
195195
{
196-
var builder = new ValueStringBuilder();
196+
using var builder = new ValueStringBuilder();
197197

198198
builder.Append(1d / 3d);
199199
builder.Append(1d / 3d);

tests/LinkDotNet.StringBuilder.UnitTests/ValueStringBuilder.AppendJoin.Tests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public class ValueStringBuilderAppendJoinTests
99
[MemberData(nameof(StringSeparatorTestData))]
1010
public void ShouldAppendWithStringSeparator(string separator, IEnumerable<string?> values, string expected)
1111
{
12-
var stringBuilder = new ValueStringBuilder();
12+
using var stringBuilder = new ValueStringBuilder();
1313

1414
stringBuilder.AppendJoin(separator, values);
1515

@@ -20,7 +20,7 @@ public void ShouldAppendWithStringSeparator(string separator, IEnumerable<string
2020
[MemberData(nameof(CharSeparatorTestData))]
2121
public void ShouldAppendWithCharSeparator(char separator, IEnumerable<string?> values, string expected)
2222
{
23-
var stringBuilder = new ValueStringBuilder();
23+
using var stringBuilder = new ValueStringBuilder();
2424

2525
stringBuilder.AppendJoin(separator, values);
2626

@@ -30,7 +30,7 @@ public void ShouldAppendWithCharSeparator(char separator, IEnumerable<string?> v
3030
[Fact]
3131
public void ShouldAddDataWithStringSeparator()
3232
{
33-
var stringBuilder = new ValueStringBuilder();
33+
using var stringBuilder = new ValueStringBuilder();
3434

3535
stringBuilder.AppendJoin(",", new object[] { 1, new DateTime(1900, 1, 1) });
3636

@@ -40,7 +40,7 @@ public void ShouldAddDataWithStringSeparator()
4040
[Fact]
4141
public void ShouldAddDataWithCharSeparator()
4242
{
43-
var stringBuilder = new ValueStringBuilder();
43+
using var stringBuilder = new ValueStringBuilder();
4444

4545
stringBuilder.AppendJoin(',', new object[] { 1, new DateTime(1900, 1, 1) });
4646

0 commit comments

Comments
 (0)