Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

New chunk should be equal/larger than the one it replaces#17219

Merged
jkotas merged 5 commits into
dotnet:masterfrom
maryamariyan:bug-blocks-clean-ci
Mar 27, 2018
Merged

New chunk should be equal/larger than the one it replaces#17219
jkotas merged 5 commits into
dotnet:masterfrom
maryamariyan:bug-blocks-clean-ci

Conversation

@maryamariyan
Copy link
Copy Markdown

@maryamariyan maryamariyan commented Mar 26, 2018

Fixes #17205

In PR #16926 we changed the behavior on a non-empty StringBuilder such that it will not always grow the last chunk array length on sb.Length=value operation where value is smaller than current Length.

The goal for this change was to keep Capacity within finite range as we loop multiple times to Clear and Append and Insert into the StringBuilder.

This PR removes the assertion that makes sure we always grow it's last chunk array.

cc: @jkotas @danmosemsft @benaadams

@benaadams
Copy link
Copy Markdown
Member

@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Ubuntu x64 Checked corefx_baseline

@maryamariyan maryamariyan requested a review from jkotas March 26, 2018 07:43
@@ -486,12 +486,18 @@ public int Length
// Avoid possible infinite capacity growth. See https://github.com/dotnet/coreclr/pull/16926
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this comment is now wrong?

                        // We crossed a chunk boundary when reducing the Length. We must replace this middle-chunk with a new larger chunk,
                        // to ensure the original capacity is preserved.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'll move this to the if block.

else
{
Debug.Assert(newLen == chunk.m_ChunkChars.Length, "The new chunk should be larger or equal to the one it is replacing.");
m_ChunkChars = chunk.m_ChunkChars;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

// Special case where the capacity we want to keep corresponds exactly to the size of the content.
// Just take ownership of the array.

Not sure the assert is worth having now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you check there's a test that goes down this path?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'll move this comment into the else block

{
Debug.Assert(newLen == chunk.m_ChunkChars.Length, "The new chunk should be larger or equal to the one it is replacing.");
m_ChunkChars = chunk.m_ChunkChars;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it worth asserting at the end of the method that Length == value

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Added this assert to the end:

Debug.Assert(Length == value, "Something went wrong setting Length.");

@danmoseley
Copy link
Copy Markdown
Member

Can you please add specific to StringBuilder that fails before your change and passes after? We should not rely on other test libraries for coverage necessary to protect our functionality.

Copy link
Copy Markdown
Member

@danmoseley danmoseley left a comment

Choose a reason for hiding this comment

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

thanks for fixing so quickly!

@maryamariyan
Copy link
Copy Markdown
Author

maryamariyan commented Mar 26, 2018

Will be adding test to corefx PR dotnet/corefx#28038

Array.Copy(chunk.m_ChunkChars, 0, newArray, 0, chunk.m_ChunkLength);
if (newLen > chunk.m_ChunkChars.Length)
{
// We crossed a chunk boundary when reducing the Length. We must replace this middle-chunk with a new larger chunk,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

it's not the original capacity though. it's 120% of it at most. Maybe

// to ensure the capacity we want

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

updated

Comment thread vim.exe.stackdump Outdated
@@ -0,0 +1,19 @@
Stack trace:
Frame Function Args
00180238000 0018005D19E (00180223639, 00180223C39, 001802342F0, 000FFFFB720)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Needs license header 😸

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually you could add .exe.stackdump to our .gitignore at the root.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

oh no! didn't mean to add this :)

@maryamariyan maryamariyan self-assigned this Mar 26, 2018
@benaadams
Copy link
Copy Markdown
Member

@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Ubuntu x64 Checked corefx_baseline

@maryamariyan maryamariyan changed the title The new chunk should be equal or larger than the one it is replacing New chunk should be equal/larger than the one it replaces Mar 27, 2018
@maryamariyan
Copy link
Copy Markdown
Author

About the Use Case:

The code snippet below is taken from failing test: System.Security.Tests.SecureStringTests.GrowAndContract_Small.
The last statement in the code snippet below will exercise the code path in this PR.

            var rand = new Random(42);
            var sb = new StringBuilder(string.Empty);
            for (int i = 0; i < 100; i++)
            {
                char c = (char)('a' + rand.Next(0, 26));
                int addPos = rand.Next(0, sb.Length);
                sb.Insert(addPos, c);
            }
            while (sb.Length > 1)
            {
                int removePos = rand.Next(0, sb.Length);
                sb.Remove(removePos, 1);
            }
            sb.Clear(); //sb.Length = 0;//sb.Remove(0, 1);

This will result in a StringBuilder with with length of 1 and two chunks.
The first chunk has offset=0, length=1, array length=16, and the second chunk has offset=1 and length=0, array length=16.
The Capacity is 17. (=last chunk array size, 16 + last chunk offset, 1).

In this case, it should be OK to reduce the Capacity value to 16 as we set Length to 0 and keep chunk array length the same value, rather than expanding the chunk length to 17 as we were doing prior to the change in PR #16926

@benaadams
Copy link
Copy Markdown
Member

Failures seem are infrastructure:

  • Ubuntu x64 Checked Build and Test (Jit - CoreFx) - time out during build
  • Tizen - usual
  • OSX10.12 x64 - downstream failure

Windows_NT x64 Checked Build and Test (Jit - CoreFx) - passes which was failing on this code before

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants