Repository: dotnet/aspnetcore (language: python)

Issue:
Blazor static SSR ExpressionFormatter.FormatLambda adds zero symbols \0\0\0\0 into an input name attribute value when it's size exceeds 128 symbols due to a bug in a ReverseStringBuilder
### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the bug

I tried to use a complicated model structure. In a result I got stuck with an issue when input name attribute includes zero symbols inside...
For example, this model path: 

`Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders`

`Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate` - this model part is bound to the intermediate `Editor<AutomaticCreditTemplate>` component

`<AutomaticOrderCreditTemplate @bind-Value="Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate" />`
This component generates this component inside:
`<InputCheckbox @bind-Value="Value.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders" />`

InputCheckbox name attribute is generated as:

`Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders`

### Expected Behavior

A generated name attribute value must be 
`Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders`

### Steps To Reproduce

I simply reproduced it when copied actual ReverseStringBuilder code into the local project and executed these lines of code

```
int StackAllocBufferSize = 128;

var builder = new ReverseStringBuilder(stackalloc char[StackAllocBufferSize]);

builder.InsertFront("UseForRegistrationOrders");
builder.InsertFront(".");
builder.InsertFront("CreditOrderSubjectsSettingsModel");
builder.InsertFront(".");
builder.InsertFront("Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate");

var result = builder.ToString();
```

`result == "Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders"`

### Exceptions (if any)

_No response_

### .NET Version

8.0.403

### Anything else?

I installed the latest .Net 8 SDK

.NET SDK:
 Version:           8.0.403
 Commit:            c64aa40a71
 Workload version:  8.0.400-manifests.e99c892e
 MSBuild version:   17.11.9+a69bbaaf5

Host:
  Version:      8.0.10
  Architecture: x64
  Commit:       81cabf2857



Discussion / hints:
I fixed bug in a local version of the ReverseStringBuilder struct in this block of code:

[https://github.com/dotnet/aspnetcore/blob/1d98312ff65d4e668bc93a843e985fdacb7d2e7c/src/Components/Shared/src/ExpressionFormatting/ReverseStringBuilder.cs#L53](https://github.com/dotnet/aspnetcore/blob/1d98312ff65d4e668bc93a843e985fdacb7d2e7c/src/Components/Shared/src/ExpressionFormatting/ReverseStringBuilder.cs#L53)

```
        if (_fallbackSequenceSegment is null)
        {
            // We've been writing to a stack-allocated buffer, but there is no more room on the stack.
            // We rent new memory with a length sufficiently larger than the initial buffer
            // and copy the contents over.
            var remainingLength = -startIndex;
            var sizeToRent = _currentBuffer.Length + Math.Max(MinimumRentedArraySize, remainingLength * 2);
            var newBuffer = s_arrayPool.Rent(sizeToRent);
            _fallbackSequenceSegment = new(newBuffer);

            _nextEndIndex = newBuffer.Length - _currentBuffer.Length;
            _currentBuffer.CopyTo(newBuffer.AsSpan()[_nextEndIndex..]);
            _currentBuffer = newBuffer;

            startIndex = _nextEndIndex - span.Length;
            span.CopyTo(_currentBuffer[startIndex..]);
            _nextEndIndex = startIndex;
        }
```

changes are here:

```
            if (_fallbackSequenceSegment is null)
            {
                // We've been writing to a stack-allocated buffer, but there is no more room on the stack.
                // We rent new memory with a length sufficiently larger than the initial buffer
                // and copy the contents over.
                var remainingLength = -startIndex;
                var sizeToRent = _currentBuffer.Length + Math.Max(MinimumRentedArraySize, remainingLength * 2);
                var newBuffer = s_arrayPool.Rent(sizeToRent);
                _fallbackSequenceSegment = new(newBuffer);
```
this one line added
```
                // gets a real data length in the original buffer
                var originalBufferUsedLength = _currentBuffer.Length - _nextEndIndex;

```
```
   _nextEndIndex = newBuffer.Length - _currentBuffer.Length;
                _currentBuffer.CopyTo(newBuffer.AsSpan()[_nextEndIndex..]);
                _currentBuffer = newBuffer;
```

and this one line added to fix the _nextEndIndex, otherwise the original code keeps all preceding leading null characters in the original 128 length span
```
                // fixes the _nextEndIndex value to take into account a real data size in the original buffer
                _nextEndIndex = _currentBuffer.Length - originalBufferUsedLength;

```
```
 startIndex = _nextEndIndex - span.Length;
                span.CopyTo(_currentBuffer[startIndex..]);
                _nextEndIndex = startIndex;
            }

```


Also generated a few lines of code to validate the fixed ReverseStringBuilder works fine in all modes:

```
var builder = new ReverseStringBuilder(stackalloc char[StackAllocBufferSize]);

var testLines = new string[]
{
    "UseForRegistrationOrders",
    ".",
    "CreditOrderSubjectsSettingsModel",
    ".",
    "Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate",
    ".",
    "[1_MyTestModelPath12345678901234567890123456789012345678901234666666777777901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234ffffff567890123456789012345678901234567890]",
    ".",
    "[2_MyTestModelPath12345678901234567890123456789012345678yyyyyyyyy12345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901ddddd234567890123456789012345678901234567890]",
    ".",
    "[3_MyTestModelPath12345678901234567890123456789012345678901234ttttt012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|123456789012345678901234567890123456789012345678901234567ssss8901234567890123456789012345678901234567890]",
    ".",
    "[4_MyTestModelPath123456789012345678901234567890123456789012345rrrr012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890|123456789012345678901234567890123456789012345678901234567eeee8901234567890123456789012345678901234567890]",
    ".",
    "LongModelStart",
};

for (var i = 0; i < testLines.Length; i++)
    builder.InsertFront(testLines[i]);

var testStringBuilder = new StringBuilder();
for (var i = testLines.Length - 1; i >= 0; i--)
    testStringBuilder.Append(testLines[i]);

if (testStringBuilder.ToString() != builder.ToString())
    throw new InvalidDataException("The MyReverseStringBuilder's InsertFront method doesn't work as expected.");
```
A local workaround will be just to remove zero symbols inside NameAttributeValue for each used Input component.
If you have all basic input components are overridden in your project - it is not a problem to apply.

I haven't found any working and simple solution to patch internal classes in the framework code yet.

This issue is also related to using a generic Editor<T> while binding a complicated/nested forms, and we have been waiting for a fix here too. 
[Nested Editor<T> crashing because of bug in HtmlFieldPrefix.Combine method](https://github.com/dotnet/aspnetcore/issues/53956)
It is also reproduced when Editor is not used.

`<InputCheckbox @bind-Value="Model.ReferralMarketingSettings.ReferringCustomerCreditOptions.AutomaticCreditTemplate.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders" />`

Name value is in this case:  `Model.ReferralMarketingSettings\0\0\0\0\0\0\0\0\0\0\0\0\0\0.ReferringCustomerCreditOptions.AutomaticCreditTemplate.CreditOrderSubjectsSettingsModel.UseForRegistrationOrders`

Fix patch:
diff --git a/src/Components/Shared/src/ExpressionFormatting/ReverseStringBuilder.cs b/src/Components/Shared/src/ExpressionFormatting/ReverseStringBuilder.cs
index 223942c9ad9f..8cedb6b715a6 100644
--- a/src/Components/Shared/src/ExpressionFormatting/ReverseStringBuilder.cs
+++ b/src/Components/Shared/src/ExpressionFormatting/ReverseStringBuilder.cs
@@ -17,7 +17,7 @@ internal ref struct ReverseStringBuilder
     private SequenceSegment? _fallbackSequenceSegment;
 
     // For testing.
-    internal int SequenceSegmentCount => _fallbackSequenceSegment?.Count() ?? 0;
+    internal readonly int SequenceSegmentCount => _fallbackSequenceSegment?.Count() ?? 0;
 
     public ReverseStringBuilder(int conservativeEstimatedStringLength)
     {
@@ -33,7 +33,7 @@ public ReverseStringBuilder(Span<char> initialBuffer)
         _nextEndIndex = _currentBuffer.Length;
     }
 
-    public bool Empty => _nextEndIndex == _currentBuffer.Length;
+    public readonly bool Empty => _nextEndIndex == _currentBuffer.Length;
 
     public void InsertFront(scoped ReadOnlySpan<char> span)
     {
@@ -60,13 +60,13 @@ public void InsertFront(scoped ReadOnlySpan<char> span)
             var newBuffer = s_arrayPool.Rent(sizeToRent);
             _fallbackSequenceSegment = new(newBuffer);
 
-            _nextEndIndex = newBuffer.Length - _currentBuffer.Length;
-            _currentBuffer.CopyTo(newBuffer.AsSpan()[_nextEndIndex..]);
-            _currentBuffer = newBuffer;
+            var newEndIndex = newBuffer.Length - _currentBuffer.Length + _nextEndIndex;
+            _currentBuffer[_nextEndIndex..].CopyTo(newBuffer.AsSpan(newEndIndex));
+            newEndIndex -= span.Length;
+            span.CopyTo(newBuffer.AsSpan(newEndIndex));
 
-            startIndex = _nextEndIndex - span.Length;
-            span.CopyTo(_currentBuffer[startIndex..]);
-            _nextEndIndex = startIndex;
+            _currentBuffer = newBuffer;
+            _nextEndIndex = newEndIndex;
         }
         else
         {


Test patch:
diff --git a/src/Components/Forms/test/ReverseStringBuilderTest.cs b/src/Components/Forms/test/ReverseStringBuilderTest.cs
index df2209305bb1..d3013b2544ab 100644
--- a/src/Components/Forms/test/ReverseStringBuilderTest.cs
+++ b/src/Components/Forms/test/ReverseStringBuilderTest.cs
@@ -92,6 +92,24 @@ public void ToString_Works_AfterExceedingStackAllocatedBuffer()
         Assert.Equal(1, builder.SequenceSegmentCount);
     }
 
+    [Fact]
+    public void ToString_Works_AfterExceedingStackAllocatedBuffer_WithUnusedSpaceInInitialBuffer()
+    {
+        const int InitialBufferSize = 64;
+        var builder = new ReverseStringBuilder(stackalloc char[InitialBufferSize]);
+
+        var firstChunk = new string('A', InitialBufferSize - 10);
+        var secondChunk = new string('B', 20);
+
+        builder.InsertFront(firstChunk);
+        builder.InsertFront(secondChunk);
+
+        var expected = secondChunk + firstChunk;
+        var actual = builder.ToString();
+
+        Assert.Equal(expected, actual);
+    }
+
     [Fact]
     public void ToString_Works_AfterExpandingIntoMultipleBuffersFromEstimatedStringSize()
     {