Skip to content

MemoryExtensions ToUpper / ToLower don't properly support overlapping buffers #28494

Closed

Description

@GrabYourPitchforks

Per the devdoc on MemoryExtensions.ToUpper / ToLower and similar methods, these APIs are supposed to behave correctly if the source and destination buffers partially overlap.

/// <remarks>If the source and destinations overlap, this method behaves as if the original values are in
/// a temporary location before the destination is overwritten.</remarks>

However, I don't think any shipped version of ToUpper or ToLower ever had this behavior. At least, the most recent version just blasts forward without checking to see if the buffers are overlapped.

Span<char> chars = new char[] { 'a', 'b', 'c', 'd' };
((ReadOnlySpan<char>)chars).Slice(0, 2).ToUpperInvariant(chars.Slice(1, 2));
Console.WriteLine(chars.ToString()); // prints "aAAd", not "aABd"

We should update the documentation to reflect the actual behavior or change the implementation to satisfy the documented behavior, whichever is appropriate.

Activity

  1. ahsonkhan commented on Jan 29, 2019

    @ahsonkhan
    Contributor

    Hmm, that's strange. These tests would have failed if overlapping spans don't work:
    https://github.com/dotnet/corefx/blob/db7daabdb592da062d8a80f27bbad1178c364530/src/Common/tests/Tests/System/StringTests.cs#L5185-L5214

    FYI, here is when we added the comment + tests: dotnet/corefx@3131eef (as part of dotnet/corefx#27193)

  2. ahsonkhan commented on Jan 29, 2019

    @ahsonkhan
    Contributor

    // prints "aAAd", not "aABd"

    It prints "aABd" for me (.NET Core 3.0).

    Span<char> chars = new char[] { 'a', 'b', 'c', 'd' };
    ((ReadOnlySpan<char>)chars).Slice(0, 2).ToUpperInvariant(chars.Slice(1, 2));
    Console.WriteLine(chars.ToString()); // aABd
  3. ahsonkhan commented on Feb 14, 2019

    @ahsonkhan
    Contributor

    It turns out the above example just happens to work on .NET Core 3.0 due to recent changes like dotnet/coreclr#20275 since the input is small.

    On .NET Core 2.1, the above example would fail (i.e. print aAAd).

    We do not support overlapping spans for ToUpper/ToLower and hence we should change the documentation.

    For example, a test like this fails:

    [Fact]
    public static void ToUpperOverlappingLarger()
    {
        var expectedSource = new char[4] { 'a', 'A', 'B', 'C' };
        var expectedDestination = new char[4] { 'A', 'B', 'C', 'D' };
        var expectedWhole = new char[5] { 'a', 'A', 'B', 'C', 'D' };
    
        char[] a = { 'a', 'b', 'c', 'd', 'e' };
    
        string s1 = new string(a, 0, 4);                
        Assert.Equal(expectedDestination, s1.ToUpperInvariant().ToArray());
    
        var source = new ReadOnlySpan<char>(a, 0, 4);
        var destination = a.AsSpan(1, 4);
        Assert.Equal(source.Length, source.ToUpperInvariant(destination));
        Assert.Equal(expectedWhole, a.ToArray());   // Char[] ['a', 'A', 'B', 'B', 'D']
        Assert.Equal(expectedDestination, destination.ToArray()); // Char[] ['A', 'B', 'B', 'D']
        Assert.Equal(expectedSource, source.ToArray()); // Char[] ['a', 'A', 'B', 'B']
    }
  4. GSPP commented on Feb 15, 2019

    @GSPP

    Can overlapping be determined reliably? If it can be, it should be part of argument validation.

  5. ahsonkhan commented on Feb 15, 2019

    @ahsonkhan
    Contributor

    Can overlapping be determined reliably? If it can be, it should be part of argument validation.

    We should definitely consider it. However, one thing we should measure is potential performance regression that might be introduced due to this check, especially for operations on small payloads. Given that its going through TextInfo anyway, it may not matter all that much. We did recently change the methods being called to statics to avoid the overhead: dotnet/coreclr#17890

    cc @jkotas, @tarekgh

  6. jkotas commented on Feb 15, 2019

    @jkotas
    Member

    owever, one thing we should measure is potential performance regression that might be introduced due to this check, especially for operations on small payloads

    This is very cheap check. I am not worried about its cost.

  7. MarcoRossignoli commented on Feb 27, 2019

    @MarcoRossignoli
    Member

    We should definitely consider it.

    @ahsonkhan do you mean check and throw exception?

  8. ahsonkhan commented on Feb 27, 2019

    @ahsonkhan
    Contributor

    do you mean check and throw exception?

    Yes.

  9. self-assigned this
    on May 14, 2019
  10. ahsonkhan commented on Jun 15, 2019

    @ahsonkhan
    Contributor

    @adamsitnik, are you still planning on working on this issue?

  11. removed their assignment
    on Jun 17, 2019
  12. adamsitnik commented on Jun 17, 2019

    @adamsitnik
    Member

    @ahsonkhan I was hoping to do it when I was flying from Seattle to Europe, but I did not. I have unassigned myself

  13. ahsonkhan commented on Jun 18, 2019

    @ahsonkhan
    Contributor

    @GSPP, @ektrah, @MarcoRossignoli, are any of you interested in working on this issue?

  14. MarcoRossignoli commented on Jun 19, 2019

    @MarcoRossignoli
    Member

    Some specs methods to fix

    public static int ToLower(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
    public static int ToLowerInvariant(this ReadOnlySpan<char> source, Span<char> destination)
    public static int ToUpper(this ReadOnlySpan<char> source, Span<char> destination, CultureInfo culture)
    public static int ToUpperInvariant(this ReadOnlySpan<char> source, Span<char> destination)

    Should throw internal static void ThrowNotSupportedException(ExceptionResource resource) with new ExceptionResource.NotSupported_SpanOverlappedOperation ?
    Error message Operation on overlapped buffers not supported?
    Tests on corefx System.Memory close to https://github.com/dotnet/corefx/blob/master/src/System.Memory/tests/Memory/IndexAndRange.cs#L38?

  15. ahsonkhan commented on Jun 22, 2019

    @ahsonkhan
    Contributor

    Some specs methods to fix

    Yes.

    Should throw internal static void ThrowNotSupportedException(ExceptionResource resource) with new ExceptionResource.NotSupported_SpanOverlappedOperation ?
    Error message Operation on overlapped buffers not supported?

    I would throw ArgumentException (or maybe InvalidOperationException, but probably not NotSupportedException). The message seems fine to me.
    Maybe This operation is invalid on overlapping buffers.

    Tests on corefx System.Memory close to https://github.com/dotnet/corefx/blob/master/src/System.Memory/tests/Memory/IndexAndRange.cs#L38?

    Most of the ToLower/ToUpper tests live next to the StringTests so that would probably be better.
    https://github.com/dotnet/corefx/blob/42ecf717478504fd7cc072f3586c6edd1ba57daa/src/Common/tests/Tests/System/StringTests.cs#L5040-L5053

    I would also be OK with them being in a new file next to since they are specifically about spans/overlapping (which isn't relevant in the string case):
    https://github.com/dotnet/corefx/blob/master/src/System.Memory/tests/ReadOnlySpan/Overlaps.cs

    Let me know if you are planning to submit a fix by Monday, because we want to get the fix in before 6/26. Otherwise, just give me a heads up and I will try to address this issue myself.

    Thanks.

  16. MarcoRossignoli commented on Jun 22, 2019

    @MarcoRossignoli
    Member

    I'll work to have a fix by monday, if something unexpected will happen I'll tell you asap.

  17. ahsonkhan commented on Jun 25, 2019

    @ahsonkhan
    Contributor
  18. transferred this issue fromdotnet/corefxon Feb 1, 2020
  19. added this to the 3.0 milestone on Feb 1, 2020
  20. ghost locked as resolved and limited conversation to collaborators on Dec 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions