Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix StringSegment.Buffer can be null #57858

Merged
merged 11 commits into from
Aug 24, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ public partial interface IChangeToken
bool HasChanged { get; }
System.IDisposable RegisterChangeCallback(System.Action<object?> callback, object? state);
}
public readonly partial struct StringSegment : System.IEquatable<Microsoft.Extensions.Primitives.StringSegment>, System.IEquatable<string>
public readonly partial struct StringSegment : System.IEquatable<Microsoft.Extensions.Primitives.StringSegment>, System.IEquatable<string?>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
public static readonly Microsoft.Extensions.Primitives.StringSegment Empty;
public StringSegment(string buffer) { throw null; }
public StringSegment(string? buffer) { throw null; }
public StringSegment(string buffer, int offset, int length) { throw null; }
public string Buffer { get { throw null; } }
public string? Buffer { get { throw null; } }
[System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, nameof(Buffer))]
public bool HasValue { get { throw null; } }
public char this[int index] { get { throw null; } }
Expand Down Expand Up @@ -74,7 +74,7 @@ public partial interface IChangeToken
public static bool operator ==(Microsoft.Extensions.Primitives.StringSegment left, Microsoft.Extensions.Primitives.StringSegment right) { throw null; }
public static implicit operator System.ReadOnlyMemory<char>(Microsoft.Extensions.Primitives.StringSegment segment) { throw null; }
public static implicit operator System.ReadOnlySpan<char>(Microsoft.Extensions.Primitives.StringSegment segment) { throw null; }
public static implicit operator Microsoft.Extensions.Primitives.StringSegment(string value) { throw null; }
public static implicit operator Microsoft.Extensions.Primitives.StringSegment(string? value) { throw null; }
public static bool operator !=(Microsoft.Extensions.Primitives.StringSegment left, Microsoft.Extensions.Primitives.StringSegment right) { throw null; }
public Microsoft.Extensions.Primitives.StringTokenizer Split(char[] chars) { throw null; }
public bool StartsWith(string text, System.StringComparison comparisonType) { throw null; }
Expand Down
38 changes: 10 additions & 28 deletions src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

Expand All @@ -10,7 +11,7 @@ namespace Microsoft.Extensions.Primitives
/// <summary>
/// An optimized representation of a substring.
/// </summary>
public readonly struct StringSegment : IEquatable<StringSegment>, IEquatable<string>
public readonly struct StringSegment : IEquatable<StringSegment>, IEquatable<string?>
{
/// <summary>
/// A <see cref="StringSegment"/> for <see cref="string.Empty"/>.
Expand All @@ -23,7 +24,7 @@ namespace Microsoft.Extensions.Primitives
/// <param name="buffer">
/// The original <see cref="string"/>. The <see cref="StringSegment"/> includes the whole <see cref="string"/>.
/// </param>
public StringSegment(string buffer)
public StringSegment(string? buffer)
{
Buffer = buffer;
Offset = 0;
Expand Down Expand Up @@ -62,7 +63,7 @@ public StringSegment(string buffer, int offset, int length)
/// <summary>
/// Gets the <see cref="string"/> buffer for this <see cref="StringSegment"/>.
/// </summary>
public string Buffer { get; }
public string? Buffer { get; }

/// <summary>
/// Gets the offset within the buffer for this <see cref="StringSegment"/>.
Expand Down Expand Up @@ -102,6 +103,7 @@ public char this[int index]
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index);
}

Debug.Assert(Buffer is not null);
return Buffer[Offset + index];
}
}
Expand Down Expand Up @@ -231,20 +233,14 @@ public bool Equals(StringSegment other, StringComparison comparisonType)
/// <param name="b">The second <see cref="StringSegment"/> to compare.</param>
/// <param name="comparisonType">One of the enumeration values that specifies the rules for the comparison.</param>
/// <returns><see langword="true" /> if the objects are equal; otherwise, <see langword="false" />.</returns>
public static bool Equals(StringSegment a, StringSegment b, StringComparison comparisonType)
{
return a.Equals(b, comparisonType);
}
public static bool Equals(StringSegment a, StringSegment b, StringComparison comparisonType) => a.Equals(b, comparisonType);

/// <summary>
/// Checks if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>.
/// </summary>
/// <param name="text">The <see cref="string"/> to compare with the current <see cref="StringSegment"/>.</param>
/// <returns><see langword="true" /> if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>; otherwise, <see langword="false" />.</returns>
public bool Equals([NotNullWhen(true)] string? text)
{
return Equals(text, StringComparison.Ordinal);
}
public bool Equals([NotNullWhen(true)] string? text) => Equals(text, StringComparison.Ordinal);

/// <summary>
/// Checks if the specified <see cref="string"/> is equal to the current <see cref="StringSegment"/>.
Expand All @@ -255,15 +251,10 @@ public bool Equals([NotNullWhen(true)] string? text)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals([NotNullWhen(true)] string? text, StringComparison comparisonType)
{
if (text == null)
{
return false;
}

if (!HasValue)
if (!HasValue || text == null)
{
CheckStringComparison(comparisonType); // must arg check before returning
return false;
return text == Buffer; // return true if both are null
}

return AsSpan().Equals(text.AsSpan(), comparisonType);
Expand Down Expand Up @@ -311,7 +302,7 @@ public override int GetHashCode()
/// Creates a new <see cref="StringSegment"/> from the given <see cref="string"/>.
/// </summary>
/// <param name="value">The <see cref="string"/> to convert to a <see cref="StringSegment"/></param>
public static implicit operator StringSegment(string value) => new StringSegment(value);
public static implicit operator StringSegment(string? value) => new StringSegment(value);

/// <summary>
/// Creates a see <see cref="ReadOnlySpan{T}"/> from the given <see cref="StringSegment"/>.
Expand Down Expand Up @@ -732,14 +723,5 @@ Exception GetInvalidArgumentsException(bool hasValue)
return ThrowHelper.GetArgumentException(ExceptionResource.Argument_InvalidOffsetLengthStringSegment);
}
}

/// <inheritdoc />
bool IEquatable<string>.Equals(string? other)
{
// Explicit interface implementation for IEquatable<string> because
// the interface's Equals method allows null strings, which we return
// as not-equal.
return other != null && Equals(other);
}
}
}
Loading