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

Fix CompareTo/Equals when dealing with Empty Span or Span wrapping a null string #17115

Merged
merged 4 commits into from
Mar 25, 2018

Conversation

ahsonkhan
Copy link
Member

@ahsonkhan ahsonkhan commented Mar 22, 2018

  • Added missing Debug.Asserts to catch when an empty span is being passed to Interop code (which could result in NullReferenceException).
  • Fixed Span.CompareTo and Span.Equals by adding checks for length == 0 and addressing them ahead of time. Resolved based on the discussion in https://github.com/dotnet/corefx/issues/27350.
  • Fixed the implementation of Trim, TrimStart, TrimEnd so that its behaviour is identical to the string APIs when trimChars is null/empty.

From https://msdn.microsoft.com/en-us/library/d4tt83f9(v=vs.110).aspx

The string that remains after all occurrences of the characters in the trimChars parameter are removed from the start and end of the current string. If trimChars is null or an empty array, white-space characters are removed instead.

Added tests in corefx: dotnet/corefx#28347

cc @KrzysztofCwalina, @jkotas, @tarekgh, @stephentoub, @joshfree, @VSadov

@@ -181,6 +181,8 @@ internal static unsafe int LastIndexOfOrdinalCore(string source, string value, i
private static unsafe int CompareStringOrdinalIgnoreCase(char* string1, int count1, char* string2, int count2)
{
Debug.Assert(!GlobalizationMode.Invariant);
Debug.Assert(string1 != null);
Copy link
Member

@jkotas jkotas Mar 22, 2018

Choose a reason for hiding this comment

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

The asserts in this file are unnecessary. The Unix globalizations API handle NULL with zero-sized buffer just fine.

Copy link
Member

Choose a reason for hiding this comment

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

(You can keep them if you would like though.)

@@ -393,13 +393,33 @@ internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions

internal virtual int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
Copy link
Member

Choose a reason for hiding this comment

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

virtual is not necessary here. You can remove it.

Copy link
Member

Choose a reason for hiding this comment

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

(It will be more efficient without it.)

}
if (string2.IsEmpty)
return 1;

return _invariantMode ?
string.CompareOrdinal(string1, string2) :
CompareString(string1, string2, CompareOptions.None);
}

internal virtual int CompareOptionIgnoreCase(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
Copy link
Member

Choose a reason for hiding this comment

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

Same here. Virtual is unnecessary.

@@ -895,12 +917,16 @@ public unsafe virtual int IndexOf(string source, string value, int startIndex, i
internal virtual int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
Copy link
Member

Choose a reason for hiding this comment

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

Virtual is unnecessary.

@jkotas
Copy link
Member

jkotas commented Mar 22, 2018

If trimChars is null or an empty array, white-space characters are removed instead.

I think it is pretty questionable behavior. I would consider it a design bug. I am not sure whether we want to be replicating it...

@@ -393,13 +393,33 @@ internal int Compare(ReadOnlySpan<char> string1, string string2, CompareOptions

internal virtual int CompareOptionNone(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2)
{
// Check for empty span or span from a null string
if (string1.IsEmpty)
Copy link
Member

Choose a reason for hiding this comment

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

You can just do this as:

                    if (span.Length == 0 || string2.Length == 0)
                        return string1.Length - string2.Length;

It is how the same check is done in MemoryExtensions.Fast.cs.

@@ -895,12 +917,16 @@ public unsafe virtual int IndexOf(string source, string value, int startIndex, i
internal virtual int IndexOfOrdinal(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
{
Debug.Assert(!_invariantMode);
Debug.Assert(!source.IsEmpty);
Debug.Assert(!value.IsEmpty);
return IndexOfOrdinalCore(source, value, ignoreCase);
}

internal unsafe virtual int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value, CompareOptions options)
Copy link
Member

Choose a reason for hiding this comment

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

virtual is unnecessary.

@ahsonkhan
Copy link
Member Author

@dotnet-bot test Windows_NT arm Cross Checked corefx_baseline Build and Test
@dotnet-bot test Windows_NT x64_arm64_altjit Checked corefx_baseline
@dotnet-bot test Ubuntu x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x64 Checked corefx_baseline
@dotnet-bot test Windows_NT x86_arm_altjit Checked corefx_baseline
@dotnet-bot test Windows_NT x86 Checked corefx_baseline

@ahsonkhan
Copy link
Member Author

@dotnet-bot test Tizen armel Cross Checked Innerloop Build and Test

@ahsonkhan ahsonkhan merged commit 48c5b21 into dotnet:master Mar 25, 2018
@ahsonkhan ahsonkhan deleted the FixNullString branch March 25, 2018 22:15
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants