Skip to content
Open
28 changes: 20 additions & 8 deletions src/libraries/System.Private.CoreLib/src/System/Version.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

namespace System
{
Expand Down Expand Up @@ -387,15 +388,15 @@ static bool IUtf8SpanParsable<Version>.TryParse(ReadOnlySpan<byte> utf8Text, IFo
int minor, build, revision;

// Parse the major version
if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, out int major))
if (!TryParseComponent(input.Slice(0, majorEnd), nameof(input), throwOnFailure, input, out int major))
{
return null;
}

if (minorEnd != -1)
{
// If there's more than a major and minor, parse the minor, too.
if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, out minor))
if (!TryParseComponent(input.Slice(majorEnd + 1, minorEnd - majorEnd - 1), nameof(input), throwOnFailure, input, out minor))
{
return null;
}
Expand All @@ -404,34 +405,45 @@ static bool IUtf8SpanParsable<Version>.TryParse(ReadOnlySpan<byte> utf8Text, IFo
{
// major.minor.build.revision
return
TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, out build) &&
TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, out revision) ?
TryParseComponent(input.Slice(minorEnd + 1, buildEnd - minorEnd - 1), nameof(build), throwOnFailure, input, out build) &&
TryParseComponent(input.Slice(buildEnd + 1), nameof(revision), throwOnFailure, input, out revision) ?
new Version(major, minor, build, revision) :
null;
}
else
{
// major.minor.build
return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, out build) ?
return TryParseComponent(input.Slice(minorEnd + 1), nameof(build), throwOnFailure, input, out build) ?
new Version(major, minor, build) :
null;
}
}
else
{
// major.minor
return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, out minor) ?
return TryParseComponent(input.Slice(majorEnd + 1), nameof(input), throwOnFailure, input, out minor) ?
new Version(major, minor) :
null;
}
}

private static bool TryParseComponent<TChar>(ReadOnlySpan<TChar> component, string componentName, bool throwOnFailure, out int parsedComponent)
private static bool TryParseComponent<TChar>(ReadOnlySpan<TChar> component, string componentName, bool throwOnFailure, ReadOnlySpan<TChar> originalInput, out int parsedComponent)
where TChar : unmanaged, IUtfChar<TChar>
{
if (throwOnFailure)
{
parsedComponent = Number.ParseBinaryInteger<TChar, int>(component, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
try
{
parsedComponent = Number.ParseBinaryInteger<TChar, int>(component, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
}
catch (FormatException) when (typeof(TChar) == typeof(char))
{
throw new FormatException(SR.Format(SR.Format_InvalidStringWithValue, Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<char>>(originalInput).ToString()));
}
catch (FormatException) when (typeof(TChar) == typeof(byte))
{
throw new FormatException(SR.Format(SR.Format_InvalidStringWithValue, Encoding.UTF8.GetString(Unsafe.BitCast<ReadOnlySpan<TChar>, ReadOnlySpan<byte>>(originalInput))));
}
ArgumentOutOfRangeException.ThrowIfNegative(parsedComponent, componentName);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,50 @@ public static void Parse_InvalidInput_ThrowsException(string input, Type excepti
Assert.Null(version);
}

[Theory]
[InlineData(".")]
[InlineData("1.")]
[InlineData("1.0.")]
[InlineData("1.0.0.")]
public static void Parse_TrailingDot_ThrowsFormatExceptionWithOriginalInput(string input)
{
FormatException ex = Assert.Throws<FormatException>(() => Version.Parse(input));
Assert.Contains(input, ex.Message);

Assert.False(Version.TryParse(input, out Version version));
Assert.Null(version);
}

[Theory]
[InlineData(".")]
[InlineData("1.")]
[InlineData("1.0.")]
[InlineData("1.0.0.")]
public static void Parse_Span_TrailingDot_ThrowsFormatExceptionWithOriginalInput(string input)
{
FormatException ex = Assert.Throws<FormatException>(() => Version.Parse(input.AsSpan()));
Assert.Contains(input, ex.Message);

Assert.False(Version.TryParse(input.AsSpan(), out Version version));
Assert.Null(version);
}

[Theory]
[InlineData(".")]
[InlineData("1.")]
[InlineData("1.0.")]
[InlineData("1.0.0.")]
public static void Parse_Utf8_TrailingDot_ThrowsFormatExceptionWithOriginalInput(string input)
{
byte[] utf8Bytes = Encoding.UTF8.GetBytes(input);

FormatException ex = Assert.Throws<FormatException>(() => Version.Parse(utf8Bytes));
Assert.Contains(input, ex.Message);

Assert.False(Version.TryParse(utf8Bytes, out Version version));
Assert.Null(version);
}

public static IEnumerable<object[]> Parse_ValidWithOffsetCount_TestData()
{
foreach (object[] inputs in Parse_Valid_TestData())
Expand Down
Loading