diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 660d31f98575b1..51d4647bafa726 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -208,14 +208,10 @@ public static unsafe string ToString(ReadOnlySpan bytes, Casing casing = C } return result.ToString(); #else - fixed (byte* bytesPtr = bytes) - { - return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), static (chars, args) => - { - var ros = new ReadOnlySpan((byte*)args.Ptr, args.Length); - EncodeToUtf16(ros, chars, args.casing); - }); - } +#pragma warning disable CS8500 // takes address of managed type + return string.Create(bytes.Length * 2, (RosPtr: (IntPtr)(&bytes), casing), static (chars, args) => + EncodeToUtf16(*(ReadOnlySpan*)args.RosPtr, chars, args.casing)); +#pragma warning restore CS8500 #endif } diff --git a/src/libraries/Common/src/System/IO/Archiving.Utils.Windows.cs b/src/libraries/Common/src/System/IO/Archiving.Utils.Windows.cs index 73eddcca7eac1f..4a3f3728134f33 100644 --- a/src/libraries/Common/src/System/IO/Archiving.Utils.Windows.cs +++ b/src/libraries/Common/src/System/IO/Archiving.Utils.Windows.cs @@ -61,22 +61,22 @@ public static unsafe string EntryFromPath(ReadOnlySpan path, bool appendPa string.Empty; } - fixed (char* pathPtr = &MemoryMarshal.GetReference(path)) +#pragma warning disable CS8500 // takes address of managed type + ReadOnlySpan tmpPath = path; // avoid address exposing the span and impacting the other code in the method that uses it + return string.Create(appendPathSeparator ? tmpPath.Length + 1 : tmpPath.Length, (appendPathSeparator, RosPtr: (IntPtr)(&tmpPath)), static (dest, state) => { - return string.Create(appendPathSeparator ? path.Length + 1 : path.Length, (appendPathSeparator, (IntPtr)pathPtr, path.Length), static (dest, state) => + var path = *(ReadOnlySpan*)state.RosPtr; + path.CopyTo(dest); + if (state.appendPathSeparator) { - ReadOnlySpan path = new ReadOnlySpan((char*)state.Item2, state.Length); - path.CopyTo(dest); - if (state.appendPathSeparator) - { - dest[^1] = '/'; - } + dest[^1] = '/'; + } - // To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1, - // all slashes should be forward slashes. - dest.Replace('\\', '/'); - }); - } + // To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1, + // all slashes should be forward slashes. + dest.Replace('\\', '/'); + }); +#pragma warning restore CS8500 } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs index e7cab2f203fae0..be8ea99c30cb72 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Path.cs @@ -686,132 +686,91 @@ private static unsafe string JoinInternal(ReadOnlySpan first, ReadOnlySpan { Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths"); - bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) - || PathInternal.IsDirectorySeparator(second[0]); + bool hasSeparator = PathInternal.IsDirectorySeparator(first[^1]) || PathInternal.IsDirectorySeparator(second[0]); return hasSeparator ? string.Concat(first, second) : string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second); } - private readonly unsafe struct Join3Payload - { - public Join3Payload(char* first, int firstLength, char* second, int secondLength, char* third, int thirdLength, byte separators) - { - First = first; - FirstLength = firstLength; - Second = second; - SecondLength = secondLength; - Third = third; - ThirdLength = thirdLength; - Separators = separators; - } - - public readonly char* First; - public readonly int FirstLength; - public readonly char* Second; - public readonly int SecondLength; - public readonly char* Third; - public readonly int ThirdLength; - public readonly byte Separators; - } - private static unsafe string JoinInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third) { Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths"); - byte firstNeedsSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) - || PathInternal.IsDirectorySeparator(second[0]) ? (byte)0 : (byte)1; - byte secondNeedsSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1]) - || PathInternal.IsDirectorySeparator(third[0]) ? (byte)0 : (byte)1; + bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[^1]) || PathInternal.IsDirectorySeparator(second[0]); + bool secondHasSeparator = PathInternal.IsDirectorySeparator(second[^1]) || PathInternal.IsDirectorySeparator(third[0]); - fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third)) + return (firstHasSeparator, secondHasSeparator) switch { - var payload = new Join3Payload( - f, first.Length, s, second.Length, t, third.Length, - (byte)(firstNeedsSeparator | secondNeedsSeparator << 1)); - - return string.Create( - first.Length + second.Length + third.Length + firstNeedsSeparator + secondNeedsSeparator, - (IntPtr)(&payload), - static (destination, statePtr) => - { - ref Join3Payload state = ref *(Join3Payload*)statePtr; - new Span(state.First, state.FirstLength).CopyTo(destination); - if ((state.Separators & 0b1) != 0) - destination[state.FirstLength] = PathInternal.DirectorySeparatorChar; - new Span(state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.Separators & 0b1))); - if ((state.Separators & 0b10) != 0) - destination[destination.Length - state.ThirdLength - 1] = PathInternal.DirectorySeparatorChar; - new Span(state.Third, state.ThirdLength).CopyTo(destination.Slice(destination.Length - state.ThirdLength)); - }); - } + (false, false) => string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second, PathInternal.DirectorySeparatorCharAsString, third), + (false, true) => string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second, third), + (true, false) => string.Concat(first, second, PathInternal.DirectorySeparatorCharAsString, third), + (true, true) => string.Concat(first, second, third), + }; } - private readonly unsafe struct Join4Payload + private static unsafe string JoinInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third, ReadOnlySpan fourth) { - public Join4Payload(char* first, int firstLength, char* second, int secondLength, char* third, int thirdLength, char* fourth, int fourthLength, byte separators) + Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths"); + +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + var state = new JoinInternalState { - First = first; - FirstLength = firstLength; - Second = second; - SecondLength = secondLength; - Third = third; - ThirdLength = thirdLength; - Fourth = fourth; - FourthLength = fourthLength; - Separators = separators; - } + ReadOnlySpanPtr1 = (IntPtr)(&first), + ReadOnlySpanPtr2 = (IntPtr)(&second), + ReadOnlySpanPtr3 = (IntPtr)(&third), + ReadOnlySpanPtr4 = (IntPtr)(&fourth), + NeedSeparator1 = PathInternal.IsDirectorySeparator(first[^1]) || PathInternal.IsDirectorySeparator(second[0]) ? (byte)0 : (byte)1, + NeedSeparator2 = PathInternal.IsDirectorySeparator(second[^1]) || PathInternal.IsDirectorySeparator(third[0]) ? (byte)0 : (byte)1, + NeedSeparator3 = PathInternal.IsDirectorySeparator(third[^1]) || PathInternal.IsDirectorySeparator(fourth[0]) ? (byte)0 : (byte)1, + }; - public readonly char* First; - public readonly int FirstLength; - public readonly char* Second; - public readonly int SecondLength; - public readonly char* Third; - public readonly int ThirdLength; - public readonly char* Fourth; - public readonly int FourthLength; - public readonly byte Separators; - } + return string.Create( + first.Length + second.Length + third.Length + fourth.Length + state.NeedSeparator1 + state.NeedSeparator2 + state.NeedSeparator3, + state, + static (destination, state) => + { + ReadOnlySpan first = *(ReadOnlySpan*)state.ReadOnlySpanPtr1; + first.CopyTo(destination); + destination = destination.Slice(first.Length); - private static unsafe string JoinInternal(ReadOnlySpan first, ReadOnlySpan second, ReadOnlySpan third, ReadOnlySpan fourth) - { - Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths"); + if (state.NeedSeparator1 != 0) + { + destination[0] = PathInternal.DirectorySeparatorChar; + destination = destination.Slice(1); + } - byte firstNeedsSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) - || PathInternal.IsDirectorySeparator(second[0]) ? (byte)0 : (byte)1; - byte secondNeedsSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1]) - || PathInternal.IsDirectorySeparator(third[0]) ? (byte)0 : (byte)1; - byte thirdNeedsSeparator = PathInternal.IsDirectorySeparator(third[third.Length - 1]) - || PathInternal.IsDirectorySeparator(fourth[0]) ? (byte)0 : (byte)1; + ReadOnlySpan second = *(ReadOnlySpan*)state.ReadOnlySpanPtr2; + second.CopyTo(destination); + destination = destination.Slice(second.Length); - fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third), u = &MemoryMarshal.GetReference(fourth)) - { - var payload = new Join4Payload( - f, first.Length, s, second.Length, t, third.Length, u, fourth.Length, - (byte)(firstNeedsSeparator | secondNeedsSeparator << 1 | thirdNeedsSeparator << 2)); - - return string.Create( - first.Length + second.Length + third.Length + fourth.Length + firstNeedsSeparator + secondNeedsSeparator + thirdNeedsSeparator, - (IntPtr)(&payload), - static (destination, statePtr) => + if (state.NeedSeparator2 != 0) { - ref Join4Payload state = ref *(Join4Payload*)statePtr; - new Span(state.First, state.FirstLength).CopyTo(destination); - int insertionPoint = state.FirstLength; - if ((state.Separators & 0b1) != 0) - destination[insertionPoint++] = PathInternal.DirectorySeparatorChar; - new Span(state.Second, state.SecondLength).CopyTo(destination.Slice(insertionPoint)); - insertionPoint += state.SecondLength; - if ((state.Separators & 0b10) != 0) - destination[insertionPoint++] = PathInternal.DirectorySeparatorChar; - new Span(state.Third, state.ThirdLength).CopyTo(destination.Slice(insertionPoint)); - insertionPoint += state.ThirdLength; - if ((state.Separators & 0b100) != 0) - destination[insertionPoint++] = PathInternal.DirectorySeparatorChar; - new Span(state.Fourth, state.FourthLength).CopyTo(destination.Slice(insertionPoint)); - }); - } + destination[0] = PathInternal.DirectorySeparatorChar; + destination = destination.Slice(1); + } + + ReadOnlySpan third = *(ReadOnlySpan*)state.ReadOnlySpanPtr3; + third.CopyTo(destination); + destination = destination.Slice(third.Length); + + if (state.NeedSeparator3 != 0) + { + destination[0] = PathInternal.DirectorySeparatorChar; + destination = destination.Slice(1); + } + + ReadOnlySpan fourth = *(ReadOnlySpan*)state.ReadOnlySpanPtr4; + Debug.Assert(fourth.Length == destination.Length); + fourth.CopyTo(destination); + }); +#pragma warning restore CS8500 + } + + private struct JoinInternalState // used to avoid rooting ValueTuple`7 + { + public IntPtr ReadOnlySpanPtr1, ReadOnlySpanPtr2, ReadOnlySpanPtr3, ReadOnlySpanPtr4; + public byte NeedSeparator1, NeedSeparator2, NeedSeparator3; } private static ReadOnlySpan Base32Char => "abcdefghijklmnopqrstuvwxyz012345"u8; diff --git a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs index 8522fd72c411f1..ebaf304908840a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs +++ b/src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs @@ -379,6 +379,34 @@ public static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, Re return result; } + internal static string Concat(ReadOnlySpan str0, ReadOnlySpan str1, ReadOnlySpan str2, ReadOnlySpan str3, ReadOnlySpan str4) + { + int length = checked(str0.Length + str1.Length + str2.Length + str3.Length + str4.Length); + if (length == 0) + { + return Empty; + } + + string result = FastAllocateString(length); + Span resultSpan = new Span(ref result._firstChar, result.Length); + + str0.CopyTo(resultSpan); + resultSpan = resultSpan.Slice(str0.Length); + + str1.CopyTo(resultSpan); + resultSpan = resultSpan.Slice(str1.Length); + + str2.CopyTo(resultSpan); + resultSpan = resultSpan.Slice(str2.Length); + + str3.CopyTo(resultSpan); + resultSpan = resultSpan.Slice(str3.Length); + + str4.CopyTo(resultSpan); + + return result; + } + public static string Concat(params string?[] values) { ArgumentNullException.ThrowIfNull(values); diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 2cff5cb16def19..60d2612cf366b1 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -3840,15 +3840,14 @@ private static unsafe ParsingError CheckSchemeSyntax(ReadOnlySpan span, re } // Then look up the syntax in a string-based table. - string str; - fixed (char* pSpan = span) +#pragma warning disable CS8500 // takes address of managed type + ReadOnlySpan tmpSpan = span; // avoid address exposing the span and impacting the other code in the method that uses it + string str = string.Create(tmpSpan.Length, (IntPtr)(&tmpSpan), (buffer, spanPtr) => { - str = string.Create(span.Length, (ip: (IntPtr)pSpan, length: span.Length), (buffer, state) => - { - int charsWritten = new ReadOnlySpan((char*)state.ip, state.length).ToLowerInvariant(buffer); - Debug.Assert(charsWritten == buffer.Length); - }); - } + int charsWritten = (*(ReadOnlySpan*)spanPtr).ToLowerInvariant(buffer); + Debug.Assert(charsWritten == buffer.Length); + }); +#pragma warning restore CS8500 syntax = UriParser.FindOrFetchAsUnknownV1Syntax(str); return ParsingError.None; } diff --git a/src/libraries/System.Private.Uri/src/System/UriHelper.cs b/src/libraries/System.Private.Uri/src/System/UriHelper.cs index f66587707f7fb6..bcd8c0dfde3329 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -543,22 +543,21 @@ internal static unsafe string StripBidiControlCharacters(ReadOnlySpan strT return backingString ?? new string(strToClean); } - fixed (char* pStrToClean = &MemoryMarshal.GetReference(strToClean)) +#pragma warning disable CS8500 // takes address of managed type + ReadOnlySpan tmpStrToClean = strToClean; // avoid address exposing the span and impacting the other code in the method that uses it + return string.Create(tmpStrToClean.Length - charsToRemove, (IntPtr)(&tmpStrToClean), static (buffer, strToCleanPtr) => { - return string.Create(strToClean.Length - charsToRemove, (StrToClean: (IntPtr)pStrToClean, strToClean.Length), static (buffer, state) => + int destIndex = 0; + foreach (char c in *(ReadOnlySpan*)strToCleanPtr) { - var strToClean = new ReadOnlySpan((char*)state.StrToClean, state.Length); - int destIndex = 0; - foreach (char c in strToClean) + if (!IsBidiControlCharacter(c)) { - if (!IsBidiControlCharacter(c)) - { - buffer[destIndex++] = c; - } + buffer[destIndex++] = c; } - Debug.Assert(buffer.Length == destIndex); - }); - } + } + Debug.Assert(buffer.Length == destIndex); + }); +#pragma warning restore CS8500 } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs index 4382d5ab05e85b..6e157c2fc59e5d 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs @@ -572,26 +572,24 @@ public static unsafe string WriteString(ReadOnlySpan label, ReadOnlySpan - { - ReadOnlySpan data = new ReadOnlySpan(state.DataPointer.ToPointer(), state.DataLength); - ReadOnlySpan label = new ReadOnlySpan(state.LabelPointer.ToPointer(), state.LabelLength); +#pragma warning disable CS8500 // takes address of managed type + return string.Create( + encodedSize, + (LabelPointer: (IntPtr)(&label), DataPointer: (IntPtr)(&data)), + static (destination, state) => + { + ReadOnlySpan label = *(ReadOnlySpan*)state.LabelPointer; + ReadOnlySpan data = *(ReadOnlySpan*)state.DataPointer; - int charsWritten = WriteCore(label, data, destination); + int charsWritten = WriteCore(label, data, destination); - if (charsWritten != destination.Length) - { - Debug.Fail("WriteCore wrote the wrong amount of data"); - throw new CryptographicException(); - } - }); - } + if (charsWritten != destination.Length) + { + Debug.Fail("WriteCore wrote the wrong amount of data"); + throw new CryptographicException(); + } + }); +#pragma warning restore CS8500 } } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs index 70d2e6a72619a6..ea47cb5b4dc3f6 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexCharClass.cs @@ -1536,34 +1536,31 @@ internal static unsafe string CharsToStringClass(ReadOnlySpan chars) } // Get the pointer/length of the span to be able to pass it into string.Create. - fixed (char* charsPtr = chars) - { +#pragma warning disable CS8500 // takes address of managed type + ReadOnlySpan tmpChars = chars; // avoid address exposing the span and impacting the other code in the method that uses it #if REGEXGENERATOR - return StringExtensions.Create( + return StringExtensions.Create( #else - return string.Create( + return string.Create( #endif - SetStartIndex + count, ((IntPtr)charsPtr, chars.Length), static (span, state) => + SetStartIndex + count, (IntPtr)(&tmpChars), static (span, charsPtr) => + { + // Fill in the set string + span[FlagsIndex] = (char)0; + span[SetLengthIndex] = (char)(span.Length - SetStartIndex); + span[CategoryLengthIndex] = (char)0; + int i = SetStartIndex; + foreach (char c in *(ReadOnlySpan*)charsPtr) { - // Reconstruct the span now that we're inside of the lambda. - ReadOnlySpan chars = new ReadOnlySpan((char*)state.Item1, state.Length); - - // Fill in the set string - span[FlagsIndex] = (char)0; - span[SetLengthIndex] = (char)(span.Length - SetStartIndex); - span[CategoryLengthIndex] = (char)0; - int i = SetStartIndex; - foreach (char c in chars) + span[i++] = c; + if (c != LastChar) { - span[i++] = c; - if (c != LastChar) - { - span[i++] = (char)(c + 1); - } + span[i++] = (char)(c + 1); } - Debug.Assert(i == span.Length); - }); - } + } + Debug.Assert(i == span.Length); + }); +#pragma warning restore CS8500 } /// diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/SegmentStringBuilder.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/SegmentStringBuilder.cs index c23e1ce83d28b6..2c603815cd1577 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/SegmentStringBuilder.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/SegmentStringBuilder.cs @@ -63,7 +63,7 @@ private void GrowAndAdd(ReadOnlyMemory segment) public Span> AsSpan() => new Span>(_array, 0, _count); /// Creates a string from all the segments in the builder and then disposes of the builder. - public override string ToString() + public override unsafe string ToString() { ReadOnlyMemory[] array = _array; var span = new Span>(array, 0, _count); @@ -74,16 +74,19 @@ public override string ToString() length += span[i].Length; } - string result = string.Create(length, this, static (dest, builder) => +#pragma warning disable CS8500 // takes address of managed type + ReadOnlySpan> tmpSpan = span; // avoid address exposing the span and impacting the other code in the method that uses it + string result = string.Create(length, (IntPtr)(&tmpSpan), static (dest, spanPtr) => { - Span> localSpan = builder.AsSpan(); - for (int i = 0; i < localSpan.Length; i++) + Span> span = *(Span>*)spanPtr; + for (int i = 0; i < span.Length; i++) { - ReadOnlySpan segment = localSpan[i].Span; + ReadOnlySpan segment = span[i].Span; segment.CopyTo(dest); dest = dest.Slice(segment.Length); } }); +#pragma warning restore CS8500 span.Clear(); this = default;