Skip to content

Commit 0143482

Browse files
Fix AllowTrailingInvalidCharacters parsing edge cases
- Reset elementsConsumed to 0 on the integer and decimal overflow paths so a failed parse never reports a nonzero consumed count. - Restrict floating-point special-value (Infinity/NaN) matching to the non-hex path and honor AllowTrailingInvalidCharacters via prefix matching, reporting leading whitespace plus the matched symbol as consumed. - Apply the same special-value consistency fix to the IEEE-754 decimal path (Decimal32/64/128) and share the matching logic via a helper. - Add AllowTrailingInvalidCharacters to the hex and binary allowed styles. - Remove redundant string null guards from TryParse overloads and rely on ReadOnlySpan null-tolerance; relax internal asserts to tolerate empty spans. - Restore the UTF-8 BOM inadvertently stripped from Double/Single and the IEEE-754 decimal source files. - Add and correct AllowTrailingInvalidCharacters tests, using CultureInfo.InvariantCulture for culture-dependent special values. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 6f0aa99 commit 0143482

35 files changed

Lines changed: 609 additions & 334 deletions

src/libraries/Common/src/System/Number.Parsing.Common.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ internal static partial class Number
1313
private static unsafe bool TryParseNumber<TChar>(TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, out int elementsConsumed)
1414
where TChar : unmanaged, IUtfChar<TChar>
1515
{
16-
Debug.Assert(str != null);
17-
Debug.Assert(strEnd != null);
16+
// str/strEnd may be null when the input is an empty span (e.g. default(ReadOnlySpan<TChar>)
17+
// originating from a null string), in which case they are both null and the range is empty.
18+
Debug.Assert((str != null) || (str == strEnd));
19+
Debug.Assert((strEnd != null) || (str == strEnd));
1820
Debug.Assert(str <= strEnd);
1921
Debug.Assert((styles & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) == 0);
2022

@@ -343,7 +345,11 @@ internal enum ParsingStatus
343345
private static unsafe TChar* MatchChars<TChar>(TChar* p, TChar* pEnd, ReadOnlySpan<TChar> value)
344346
where TChar : unmanaged, IUtfChar<TChar>
345347
{
346-
Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd));
348+
// p/pEnd may be null when the input being parsed is an empty span (e.g. from a null string),
349+
// in which case they are both null and the range is empty; the loop below never dereferences p then.
350+
Debug.Assert((p != null) || (p == pEnd));
351+
Debug.Assert((pEnd != null) || (p == pEnd));
352+
Debug.Assert(p <= pEnd);
347353

348354
fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value))
349355
{

src/libraries/System.Private.CoreLib/src/System/Byte.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,6 @@ public static byte Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles
124124
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result)
125125
{
126126
NumberFormatInfo.ValidateParseStyleInteger(style);
127-
128-
if (s is null)
129-
{
130-
result = 0;
131-
return false;
132-
}
133127
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
134128
}
135129

@@ -1108,13 +1102,6 @@ static bool INumberBase<byte>.TryConvertToTruncating<TOther>(byte value, [MaybeN
11081102
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed)
11091103
{
11101104
NumberFormatInfo.ValidateParseStyleInteger(style);
1111-
1112-
if (s is null)
1113-
{
1114-
result = 0;
1115-
charsConsumed = 0;
1116-
return false;
1117-
}
11181105
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
11191106
}
11201107

src/libraries/System.Private.CoreLib/src/System/Decimal.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -551,12 +551,6 @@ public static decimal Parse(ReadOnlySpan<char> s, NumberStyles style = NumberSty
551551
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result)
552552
{
553553
NumberFormatInfo.ValidateParseStyleDecimal(style);
554-
555-
if (s == null)
556-
{
557-
result = 0;
558-
return false;
559-
}
560554
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
561555
}
562556

@@ -1785,13 +1779,6 @@ private static bool TryConvertTo<TOther>(decimal value, [MaybeNullWhen(false)] o
17851779
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
17861780
{
17871781
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
1788-
1789-
if (s is null)
1790-
{
1791-
result = 0;
1792-
charsConsumed = 0;
1793-
return false;
1794-
}
17951782
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
17961783
}
17971784

src/libraries/System.Private.CoreLib/src/System/Double.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -427,12 +427,6 @@ public static double Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyl
427427
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result)
428428
{
429429
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
430-
431-
if (s == null)
432-
{
433-
result = 0;
434-
return false;
435-
}
436430
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _);
437431
}
438432

@@ -1491,13 +1485,6 @@ private static bool TryConvertTo<TOther>(double value, [MaybeNullWhen(false)] ou
14911485
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed)
14921486
{
14931487
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
1494-
1495-
if (s is null)
1496-
{
1497-
result = 0;
1498-
charsConsumed = 0;
1499-
return false;
1500-
}
15011488
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
15021489
}
15031490

src/libraries/System.Private.CoreLib/src/System/Half.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -392,12 +392,6 @@ public static Half Parse(ReadOnlySpan<char> s, NumberStyles style = DefaultParse
392392
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Half result)
393393
{
394394
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
395-
396-
if (s == null)
397-
{
398-
result = Zero;
399-
return false;
400-
}
401395
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _);
402396
}
403397

@@ -2171,13 +2165,6 @@ private static bool TryConvertTo<TOther>(Half value, [MaybeNullWhen(false)] out
21712165
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Half result, out int charsConsumed)
21722166
{
21732167
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
2174-
2175-
if (s is null)
2176-
{
2177-
result = Zero;
2178-
charsConsumed = 0;
2179-
return false;
2180-
}
21812168
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
21822169
}
21832170

src/libraries/System.Private.CoreLib/src/System/Int128.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,6 @@ public static Int128 Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyl
157157
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result)
158158
{
159159
NumberFormatInfo.ValidateParseStyleInteger(style);
160-
161-
if (s is null)
162-
{
163-
result = 0;
164-
return false;
165-
}
166160
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
167161
}
168162

@@ -1919,13 +1913,6 @@ static bool INumberBase<Int128>.TryConvertToTruncating<TOther>(Int128 value, [Ma
19191913
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out Int128 result, out int charsConsumed)
19201914
{
19211915
NumberFormatInfo.ValidateParseStyleInteger(style);
1922-
1923-
if (s is null)
1924-
{
1925-
result = 0;
1926-
charsConsumed = 0;
1927-
return false;
1928-
}
19291916
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
19301917
}
19311918

src/libraries/System.Private.CoreLib/src/System/Int16.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,6 @@ public static short Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyle
157157
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out short result)
158158
{
159159
NumberFormatInfo.ValidateParseStyleInteger(style);
160-
161-
if (s is null)
162-
{
163-
result = 0;
164-
return false;
165-
}
166160
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
167161
}
168162

@@ -1298,13 +1292,6 @@ static bool INumberBase<short>.TryConvertToTruncating<TOther>(short value, [Mayb
12981292
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out short result, out int charsConsumed)
12991293
{
13001294
NumberFormatInfo.ValidateParseStyleInteger(style);
1301-
1302-
if (s is null)
1303-
{
1304-
result = 0;
1305-
charsConsumed = 0;
1306-
return false;
1307-
}
13081295
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
13091296
}
13101297

src/libraries/System.Private.CoreLib/src/System/Int32.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,6 @@ public static int Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles.
173173
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out int result)
174174
{
175175
NumberFormatInfo.ValidateParseStyleInteger(style);
176-
177-
if (s is null)
178-
{
179-
result = 0;
180-
return false;
181-
}
182176
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
183177
}
184178

@@ -1365,13 +1359,6 @@ static bool INumberBase<int>.TryConvertToTruncating<TOther>(int value, [MaybeNul
13651359
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out int result, out int charsConsumed)
13661360
{
13671361
NumberFormatInfo.ValidateParseStyleInteger(style);
1368-
1369-
if (s is null)
1370-
{
1371-
result = 0;
1372-
charsConsumed = 0;
1373-
return false;
1374-
}
13751362
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
13761363
}
13771364

src/libraries/System.Private.CoreLib/src/System/Int64.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,6 @@ public static long Parse(ReadOnlySpan<char> s, NumberStyles style = NumberStyles
170170
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out long result)
171171
{
172172
NumberFormatInfo.ValidateParseStyleInteger(style);
173-
174-
if (s is null)
175-
{
176-
result = 0;
177-
return false;
178-
}
179173
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
180174
}
181175

@@ -1368,13 +1362,6 @@ static bool INumberBase<long>.TryConvertToTruncating<TOther>(long value, [MaybeN
13681362
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out long result, out int charsConsumed)
13691363
{
13701364
NumberFormatInfo.ValidateParseStyleInteger(style);
1371-
1372-
if (s is null)
1373-
{
1374-
result = 0;
1375-
charsConsumed = 0;
1376-
return false;
1377-
}
13781365
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
13791366
}
13801367

0 commit comments

Comments
 (0)