Skip to content

Commit

Permalink
StringBuilder: use Span.Fill in Append repeating char (#86287)
Browse files Browse the repository at this point in the history
  • Loading branch information
yesmey authored Jul 7, 2023
1 parent 5a03596 commit ff57624
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -659,36 +659,57 @@ public StringBuilder Append(char value, int repeatCount)
return this;
}

// this is where we can check if the repeatCount will put us over m_MaxCapacity
// We are doing the check here to prevent the corruption of the StringBuilder.
int newLength = Length + repeatCount;
if (newLength > m_MaxCapacity || newLength < repeatCount)
char[] chunkChars = m_ChunkChars;
int chunkLength = m_ChunkLength;

// Try to fit the whole repeatCount in the current chunk
// Use the same check as Span<T>.Slice for 64-bit so it can be folded
// Since repeatCount can't be negative, there's no risk for it to overflow on 32 bit
if (((nuint)(uint)chunkLength + (nuint)(uint)repeatCount) <= (nuint)(uint)chunkChars.Length)
{
throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
chunkChars.AsSpan(chunkLength, repeatCount).Fill(value);
m_ChunkLength += repeatCount;
}

int index = m_ChunkLength;
while (repeatCount > 0)
else
{
if (index < m_ChunkChars.Length)
{
m_ChunkChars[index++] = value;
--repeatCount;
}
else
{
m_ChunkLength = index;
ExpandByABlock(repeatCount);
Debug.Assert(m_ChunkLength == 0);
index = 0;
}
AppendWithExpansion(value, repeatCount);
}

m_ChunkLength = index;
AssertInvariants();
return this;
}

private void AppendWithExpansion(char value, int repeatCount)
{
Debug.Assert(repeatCount > 0, "Invalid length; should have been validated by caller.");

// Check if the repeatCount will put us over m_MaxCapacity
if ((uint)(repeatCount + Length) > (uint)m_MaxCapacity)
{
throw new ArgumentOutOfRangeException(nameof(repeatCount), SR.ArgumentOutOfRange_LengthGreaterThanCapacity);
}

char[] chunkChars = m_ChunkChars;
int chunkLength = m_ChunkLength;

// Fill the rest of the current chunk
int firstLength = chunkChars.Length - chunkLength;
if (firstLength > 0)
{
chunkChars.AsSpan(chunkLength, firstLength).Fill(value);
m_ChunkLength = chunkChars.Length;
}

// Expand the builder to add another chunk
int restLength = repeatCount - firstLength;
ExpandByABlock(restLength);
Debug.Assert(m_ChunkLength == 0, "A new block was not created.");

// Fill the new chunk with the remaining part of repeatCount
m_ChunkChars.AsSpan(0, restLength).Fill(value);
m_ChunkLength = restLength;
}

/// <summary>
/// Appends a range of characters to the end of this builder.
/// </summary>
Expand Down Expand Up @@ -990,12 +1011,21 @@ public StringBuilder Append(char value)
}
else
{
Append(value, 1);
AppendWithExpansion(value);
}

return this;
}

[MethodImpl(MethodImplOptions.NoInlining)]
private void AppendWithExpansion(char value)
{
ExpandByABlock(1);
Debug.Assert(m_ChunkLength == 0, "A new block was not created.");
m_ChunkChars[0] = value;
m_ChunkLength++;
}

[CLSCompliant(false)]
public StringBuilder Append(sbyte value) => AppendSpanFormattable(value);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ public static void Append_Char_NoSpareCapacity_ThrowsArgumentOutOfRangeException
var builder = new StringBuilder(0, 5);
builder.Append("Hello");

AssertExtensions.Throws<ArgumentOutOfRangeException>("repeatCount", "requiredLength", () => builder.Append('a'));
AssertExtensions.Throws<ArgumentOutOfRangeException>("requiredLength", () => builder.Append('a'));
AssertExtensions.Throws<ArgumentOutOfRangeException>("repeatCount", "requiredLength", () => builder.Append('a', 1));
}

Expand Down

0 comments on commit ff57624

Please sign in to comment.