From f68d8e164fac3cced29afae43ba8dbc6832eadc3 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 23 Jul 2024 19:28:54 -0400 Subject: [PATCH] Fix handling of '\0' in Ascii.Trim (#105350) --- .../src/System/Text/Ascii.Trimming.cs | 13 ++++++++----- .../System.Text.Encoding.Tests/Ascii/TrimTests.cs | 11 +++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Ascii.Trimming.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Ascii.Trimming.cs index f175db0b2d8262..a1e22d1760296d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Ascii.Trimming.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Ascii.Trimming.cs @@ -40,8 +40,11 @@ public static partial class Ascii private static Range TrimHelper(ReadOnlySpan value, TrimType trimType) where T : unmanaged, IBinaryInteger { + // A bitmap with a bit set for each ASCII whitespace character. The set bit is at the + // index of the character minus 1, since we're using a 32-bit value and space would otherwise + // be at index 32; with -1, it's at index 31. const uint TrimMask = - (1u << (0x09 - 1)) + (1u << (0x09 - 1)) | (1u << (0x0A - 1)) | (1u << (0x0B - 1)) | (1u << (0x0C - 1)) @@ -53,8 +56,8 @@ private static Range TrimHelper(ReadOnlySpan value, TrimType trimType) { for (; start < value.Length; start++) { - uint elementValue = uint.CreateTruncating(value[start]); - if ((elementValue > 0x20) || ((TrimMask & (1u << ((int)elementValue - 1))) == 0)) + uint elementValueM1 = uint.CreateTruncating(value[start]) - 1; + if ((elementValueM1 > 0x1F) || ((TrimMask & (1u << ((int)elementValueM1))) == 0)) { break; } @@ -66,8 +69,8 @@ private static Range TrimHelper(ReadOnlySpan value, TrimType trimType) { for (; start <= end; end--) { - uint elementValue = uint.CreateTruncating(value[end]); - if ((elementValue > 0x20) || ((TrimMask & (1u << ((int)elementValue - 1))) == 0)) + uint elementValueM1 = uint.CreateTruncating(value[end]) - 1; + if ((elementValueM1 > 0x1F) || ((TrimMask & (1u << ((int)elementValueM1))) == 0)) { break; } diff --git a/src/libraries/System.Runtime/tests/System.Text.Encoding.Tests/Ascii/TrimTests.cs b/src/libraries/System.Runtime/tests/System.Text.Encoding.Tests/Ascii/TrimTests.cs index 5873942d87a5dd..d7066536589cea 100644 --- a/src/libraries/System.Runtime/tests/System.Text.Encoding.Tests/Ascii/TrimTests.cs +++ b/src/libraries/System.Runtime/tests/System.Text.Encoding.Tests/Ascii/TrimTests.cs @@ -22,6 +22,7 @@ public static void EmptyInput() [InlineData("1")] [InlineData("abc")] [InlineData("a\tb c\rd\ne")] + [InlineData("\0abcde\0")] public static void NothingToTrimNonEmptyInput(string text) { ReadOnlySpan bytes = Encoding.ASCII.GetBytes(text); @@ -113,5 +114,15 @@ public static void StartingAndEndingWithWhitespace(string text, int leadingWhite Assert.Equal(0..(text.Length - trailingWhitespaceCount), Ascii.TrimEnd(bytes)); Assert.Equal(0..(text.Length - trailingWhitespaceCount), Ascii.TrimEnd(text)); } + + [Fact] + public static void OnlyAsciiWhitespaceCharactersAreTrimmed() + { + for (int i = 0; i <= char.MaxValue; i++) + { + bool trimmed = Ascii.Trim(((char)i).ToString()).Equals(1..1); + Assert.True(((char)i is '\t' or '\n' or '\v' or '\f' or '\r' or ' ') == trimmed, $"Failed at {i}"); + } + } } }