Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices;

/// <summary>
/// Used to indicate to the compiler that the <c>.locals init</c> flag should not be set in method headers.
/// </summary>
/// <remarks>
/// Downlevel polyfill of <see href="https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.skiplocalsinitattribute">System.Runtime.CompilerServices.SkipLocalsInitAttribute</see>
/// for target frameworks that do not provide it (.NET Standard 2.0, .NET Framework). The C# compiler recognizes the
/// attribute by full type name, so providing this internal definition is enough to enable the optimization.
/// </remarks>
[AttributeUsage(AttributeTargets.Module
| AttributeTargets.Class
| AttributeTargets.Struct
| AttributeTargets.Interface
| AttributeTargets.Constructor
| AttributeTargets.Method
| AttributeTargets.Property
| AttributeTargets.Event, Inherited = false)]
internal sealed class SkipLocalsInitAttribute : Attribute
{
public SkipLocalsInitAttribute()
{
}
}
4 changes: 4 additions & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

The System.Text.Json library is built-in as part of the shared framework in .NET Runtime. The package can be installed when you need to use it in other target frameworks.</PackageDescription>
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
<!-- SkipLocalsInit is applied per-method via [SkipLocalsInit] on the specific
functions that use stackalloc rather than module-wide. -->
<SkipLocalsInit>false</SkipLocalsInit>
</PropertyGroup>

<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
Expand Down Expand Up @@ -402,6 +405,7 @@ The System.Text.Json library is built-in as part of the shared framework in .NET
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(CommonPath)Polyfills\SkipLocalsInitAttributePolyfill.cs" Link="Common\Polyfills\SkipLocalsInitAttributePolyfill.cs" />
<Compile Include="$(CommonPath)Polyfills\StreamMemoryPolyfills.cs" Link="Common\Polyfills\StreamMemoryPolyfills.cs" />
<Compile Include="$(CommonPath)Polyfills\StringBuilderPolyfills.cs" Link="Common\Polyfills\StringBuilderPolyfills.cs" />
<Compile Include="$(CommonPath)Polyfills\StackPolyfills.cs" Link="Common\Polyfills\StackPolyfills.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace System.Text.Json
{
public sealed partial class JsonDocument
{
[SkipLocalsInit]
internal unsafe bool TryGetNamedPropertyValue(int index, ReadOnlySpan<char> propertyName, out JsonElement value)
{
CheckNotDisposed();
Expand Down Expand Up @@ -132,6 +134,7 @@ internal bool TryGetNamedPropertyValue(int index, ReadOnlySpan<byte> propertyNam
out value);
}

[SkipLocalsInit]
private unsafe bool TryGetNamedPropertyValue(
int startIndex,
int endIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;

namespace System.Text.Json
Expand Down Expand Up @@ -302,6 +303,7 @@ private ReadOnlyMemory<byte> GetPropertyRawValue(int valueIndex)
: JsonReaderHelper.TranscodeHelper(segment);
}

[SkipLocalsInit]
internal unsafe bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropertyName)
{
CheckNotDisposed();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;

namespace System.Text.Json
Expand Down Expand Up @@ -73,6 +74,7 @@ public static JsonEncodedText Encode(ReadOnlySpan<char> value, JavaScriptEncoder
return TranscodeAndEncode(value, encoder);
}

[SkipLocalsInit]
private static unsafe JsonEncodedText TranscodeAndEncode(ReadOnlySpan<char> value, JavaScriptEncoder? encoder)
{
JsonWriterHelper.ValidateValue(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan<byte> utf8Value,
}
}

[SkipLocalsInit]
public static unsafe byte[] EscapeValue(
ReadOnlySpan<byte> utf8Value,
int firstEscapeIndexVal,
Expand Down Expand Up @@ -53,6 +54,7 @@ public static unsafe byte[] EscapeValue(
return escapedString;
}

[SkipLocalsInit]
private static unsafe byte[] GetEscapedPropertyNameSection(
ReadOnlySpan<byte> utf8Value,
int firstEscapeIndexVal,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ public static bool TrySkipPartial(this ref Utf8JsonReader reader)
return reader.TrySkipPartial(reader.CurrentDepth);
}

[SkipLocalsInit]
public static unsafe bool TryLookupUtf8Key<TValue>(
this Dictionary<string, TValue> dictionary,
ReadOnlySpan<byte> utf8Key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Converters;
using System.Threading;

Expand Down Expand Up @@ -238,6 +239,7 @@ private protected override void SetItem(int index, JsonNode? value)
List[index] = value;
}

[SkipLocalsInit]
internal override unsafe void GetPath(ref ValueStringBuilder path, JsonNode? child)
{
Parent?.GetPath(ref path, this);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Converters;
using System.Text.Json.Serialization.Metadata;

Expand Down Expand Up @@ -135,6 +136,7 @@ internal set
/// Gets the JSON path.
/// </summary>
/// <returns>The JSON Path value.</returns>
[SkipLocalsInit]
public unsafe string GetPath()
{
if (Parent == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
using System.Buffers.Text;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Unicode;

namespace System.Text.Json
{
internal static partial class JsonReaderHelper
{
[SkipLocalsInit]
public static unsafe bool TryGetUnescapedBase64Bytes(ReadOnlySpan<byte> utf8Source, [NotNullWhen(true)] out byte[]? bytes)
{
byte[]? unescapedArray = null;
Expand Down Expand Up @@ -39,6 +41,7 @@ public static unsafe bool TryGetUnescapedBase64Bytes(ReadOnlySpan<byte> utf8Sour
public static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

// TODO: Similar to escaping, replace the unescaping logic with publicly shipping APIs from https://github.com/dotnet/runtime/issues/27919
[SkipLocalsInit]
public static unsafe string GetUnescapedString(ReadOnlySpan<byte> utf8Source)
{
// The escaped name is always >= than the unescaped, so it is safe to use escaped name for the buffer length.
Expand Down Expand Up @@ -66,6 +69,7 @@ public static unsafe string GetUnescapedString(ReadOnlySpan<byte> utf8Source)
return utf8String;
}

[SkipLocalsInit]
public static unsafe byte[] GetUnescaped(ReadOnlySpan<byte> utf8Source)
{
// The escaped name is always >= than the unescaped, so it is safe to use escaped name for the buffer length.
Expand All @@ -91,6 +95,7 @@ public static unsafe byte[] GetUnescaped(ReadOnlySpan<byte> utf8Source)
return propertyName;
}

[SkipLocalsInit]
public static unsafe bool UnescapeAndCompare(ReadOnlySpan<byte> utf8Source, ReadOnlySpan<byte> other)
{
Debug.Assert(utf8Source.Length >= other.Length && utf8Source.Length / JsonConstants.MaxExpansionFactorWhileEscaping <= other.Length);
Expand Down Expand Up @@ -118,6 +123,7 @@ public static unsafe bool UnescapeAndCompare(ReadOnlySpan<byte> utf8Source, Read
return result;
}

[SkipLocalsInit]
public static unsafe bool UnescapeAndCompare(ReadOnlySequence<byte> utf8Source, ReadOnlySpan<byte> other)
{
Debug.Assert(!utf8Source.IsSingleSegment);
Expand Down Expand Up @@ -159,6 +165,7 @@ public static unsafe bool UnescapeAndCompare(ReadOnlySequence<byte> utf8Source,
return result;
}

[SkipLocalsInit]
public static unsafe bool UnescapeAndCompareBothInputs(ReadOnlySpan<byte> utf8Source1, ReadOnlySpan<byte> utf8Source2)
{
int index1 = utf8Source1.IndexOf(JsonConstants.BackSlash);
Expand Down Expand Up @@ -215,6 +222,7 @@ public static bool TryDecodeBase64InPlace(Span<byte> utf8Unescaped, [NotNullWhen
return true;
}

[SkipLocalsInit]
public static unsafe bool TryDecodeBase64(ReadOnlySpan<byte> utf8Unescaped, [NotNullWhen(true)] out byte[]? bytes)
{
byte[]? pooledArray = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out D
return false;
}

[SkipLocalsInit]
public static unsafe bool TryGetEscapedDateTime(ReadOnlySpan<byte> source, out DateTime value)
{
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
Expand Down Expand Up @@ -224,6 +225,7 @@ public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out D
return false;
}

[SkipLocalsInit]
public static unsafe bool TryGetEscapedDateTimeOffset(ReadOnlySpan<byte> source, out DateTimeOffset value)
{
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
Expand Down Expand Up @@ -273,6 +275,7 @@ public static bool TryGetValue(ReadOnlySpan<byte> segment, bool isEscaped, out G
return false;
}

[SkipLocalsInit]
public static unsafe bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value)
{
Debug.Assert(source.Length <= JsonConstants.MaximumEscapedGuidLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ private bool ConsumeLiteralMultiSegment(ReadOnlySpan<byte> literal, JsonTokenTyp
return true;
}

[SkipLocalsInit]
private unsafe bool CheckLiteralMultiSegment(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal, out int consumed)
{
Debug.Assert(span.Length > 0 && span[0] == literal[0] && literal.Length <= JsonConstants.MaximumLiteralLength);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public readonly int CopyString(Span<char> destination)
return CopyValue(destination);
}

[SkipLocalsInit]
internal readonly unsafe int CopyValue(Span<char> destination)
{
Debug.Assert(_tokenType is JsonTokenType.String or JsonTokenType.PropertyName or JsonTokenType.Number);
Expand Down Expand Up @@ -192,6 +193,7 @@ internal readonly unsafe int CopyValue(Span<char> destination)
return charsWritten;
}

[SkipLocalsInit]
private readonly unsafe bool TryCopyEscapedString(Span<byte> destination, out int bytesWritten)
{
Debug.Assert(_tokenType is JsonTokenType.String or JsonTokenType.PropertyName);
Expand Down Expand Up @@ -1241,6 +1243,7 @@ public bool TryGetDateTime(out DateTime value)
return TryGetDateTimeCore(out value);
}

[SkipLocalsInit]
internal unsafe bool TryGetDateTimeCore(out DateTime value)
{
scoped ReadOnlySpan<byte> span;
Expand Down Expand Up @@ -1286,6 +1289,7 @@ public bool TryGetDateTimeOffset(out DateTimeOffset value)
return TryGetDateTimeOffsetCore(out value);
}

[SkipLocalsInit]
internal unsafe bool TryGetDateTimeOffsetCore(out DateTimeOffset value)
{
scoped ReadOnlySpan<byte> span;
Expand Down Expand Up @@ -1332,6 +1336,7 @@ public bool TryGetGuid(out Guid value)
return TryGetGuidCore(out value);
}

[SkipLocalsInit]
internal unsafe bool TryGetGuidCore(out Guid value)
{
scoped ReadOnlySpan<byte> span;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ private readonly bool TextEqualsHelper(ReadOnlySpan<byte> otherUtf8Text)
/// if required. The look up text is matched as is, without any modifications to it.
/// </para>
/// </remarks>
[SkipLocalsInit]
public readonly unsafe bool ValueTextEquals(ReadOnlySpan<char> text)
{
if (!IsTokenTypeString(TokenType))
Expand Down
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.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text.Json.Schema;

namespace System.Text.Json.Serialization.Converters
Expand All @@ -10,6 +11,7 @@ internal sealed class CharConverter : JsonPrimitiveConverter<char>
{
private const int MaxEscapedCharacterLength = JsonConstants.MaxExpansionFactorWhileEscaping;

[SkipLocalsInit]
public override unsafe char Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType is not (JsonTokenType.String or JsonTokenType.PropertyName))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Json.Schema;

namespace System.Text.Json.Serialization.Converters
Expand All @@ -28,6 +29,7 @@ internal override DateOnly ReadAsPropertyNameCore(ref Utf8JsonReader reader, Typ
return ReadCore(ref reader);
}

[SkipLocalsInit]
private static unsafe DateOnly ReadCore(ref Utf8JsonReader reader)
{
if (!JsonHelpers.IsInRangeInclusive(reader.ValueLength, FormatLength, MaxEscapedFormatLength))
Expand Down Expand Up @@ -62,6 +64,7 @@ private static unsafe DateOnly ReadCore(ref Utf8JsonReader reader)
return value;
}

[SkipLocalsInit]
public override unsafe void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
{
Span<byte> buffer = stackalloc byte[FormatLength];
Expand All @@ -70,6 +73,7 @@ public override unsafe void Write(Utf8JsonWriter writer, DateOnly value, JsonSer
writer.WriteStringValue(buffer);
}

[SkipLocalsInit]
internal override unsafe void WriteAsPropertyNameCore(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options, bool isWritingExtensionDataProperty)
{
Span<byte> buffer = stackalloc byte[FormatLength];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, J
}
}

[SkipLocalsInit]
private unsafe bool TryParseEnumFromString(ref Utf8JsonReader reader, out T result)
{
Debug.Assert(reader.TokenType is JsonTokenType.String or JsonTokenType.PropertyName);
Expand Down Expand Up @@ -393,6 +394,7 @@ private static T ConvertFromUInt64(ulong value)
/// <summary>
/// Attempt to format the enum value as a comma-separated string of flag values, or returns false if not a valid flag combination.
/// </summary>
[SkipLocalsInit]
private unsafe string FormatEnumAsString(ulong key, T value, JsonNamingPolicy? dictionaryKeyPolicy)
{
Debug.Assert(IsDefinedValueOrCombinationOfValues(key), "must only be invoked against valid enum values.");
Expand Down
Loading
Loading