Skip to content

Commit

Permalink
Remove unnecessary encoding allocations in System.Private.DataContrac…
Browse files Browse the repository at this point in the history
…tSerialization (#76366)

* Remove unnecessary encoding allocations in System.Private.DataContractSerialization

* Address PR feedback
  • Loading branch information
krwq authored Nov 7, 2022
1 parent 264d739 commit bfa4812
Show file tree
Hide file tree
Showing 14 changed files with 70 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization.DataContracts;
using System.Text;
using System.Xml;

using DataContractDictionary = System.Collections.Generic.Dictionary<System.Xml.XmlQualifiedName, System.Runtime.Serialization.DataContracts.DataContract>;
Expand All @@ -32,6 +32,18 @@ public sealed class DataContractSerializer : XmlObjectSerializer

private static SerializationOption s_option = IsReflectionBackupAllowed() ? SerializationOption.ReflectionAsBackup : SerializationOption.CodeGenOnly;
private static bool s_optionAlreadySet;

internal static UTF8Encoding UTF8NoBom { get; } = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);
internal static UTF8Encoding ValidatingUTF8 { get; } = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);

internal static UnicodeEncoding UTF16NoBom { get; } = new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: false);
internal static UnicodeEncoding BEUTF16NoBom { get; } = new UnicodeEncoding(bigEndian: true, byteOrderMark: false, throwOnInvalidBytes: false);
internal static UnicodeEncoding ValidatingUTF16 { get; } = new UnicodeEncoding(bigEndian: false, byteOrderMark: false, throwOnInvalidBytes: true);
internal static UnicodeEncoding ValidatingBEUTF16 { get; } = new UnicodeEncoding(bigEndian: true, byteOrderMark: false, throwOnInvalidBytes: true);

internal static Base64Encoding Base64Encoding { get; } = new Base64Encoding();
internal static BinHexEncoding BinHexEncoding { get; } = new BinHexEncoding();

internal static SerializationOption Option
{
get { return RuntimeFeature.IsDynamicCodeSupported ? s_option : SerializationOption.ReflectionOnly; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ namespace System.Runtime.Serialization.Json
// ASSUMPTION (Microsoft): This class will only be used for EITHER reading OR writing. It can be done, it would just mean more buffers.
internal sealed class JsonEncodingStreamWrapper : Stream
{
private static readonly UnicodeEncoding s_validatingBEUTF16 = new UnicodeEncoding(true, false, true);

private static readonly UnicodeEncoding s_validatingUTF16 = new UnicodeEncoding(false, false, true);

private static readonly UTF8Encoding s_validatingUTF8 = new UTF8Encoding(false, true);
private const int BufferLength = 128;

private readonly byte[] _byteBuffer = new byte[1];
Expand Down Expand Up @@ -154,7 +149,7 @@ public static ArraySegment<byte> ProcessBuffer(byte[] buffer, int offset, int co

// Convert to UTF-8
return
new ArraySegment<byte>(s_validatingUTF8.GetBytes(GetEncoding(dataEnc).GetChars(buffer, offset, count)));
new ArraySegment<byte>(DataContractSerializer.ValidatingUTF8.GetBytes(GetEncoding(dataEnc).GetChars(buffer, offset, count)));
}
catch (DecoderFallbackException e)
{
Expand Down Expand Up @@ -286,9 +281,9 @@ public override void WriteByte(byte b)
private static Encoding GetEncoding(SupportedEncoding e) =>
e switch
{
SupportedEncoding.UTF8 => s_validatingUTF8,
SupportedEncoding.UTF16LE => s_validatingUTF16,
SupportedEncoding.UTF16BE => s_validatingBEUTF16,
SupportedEncoding.UTF8 => DataContractSerializer.ValidatingUTF8,
SupportedEncoding.UTF16LE => DataContractSerializer.ValidatingUTF16,
SupportedEncoding.UTF16BE => DataContractSerializer.ValidatingBEUTF16,
_ => throw new XmlException(SR.JsonEncodingNotSupported),
};

Expand All @@ -307,15 +302,15 @@ private static SupportedEncoding GetSupportedEncoding(Encoding? encoding)
{
return SupportedEncoding.None;
}
if (encoding.WebName == s_validatingUTF8.WebName)
if (encoding.WebName == DataContractSerializer.ValidatingUTF8.WebName)
{
return SupportedEncoding.UTF8;
}
else if (encoding.WebName == s_validatingUTF16.WebName)
else if (encoding.WebName == DataContractSerializer.ValidatingUTF16.WebName)
{
return SupportedEncoding.UTF16LE;
}
else if (encoding.WebName == s_validatingBEUTF16.WebName)
else if (encoding.WebName == DataContractSerializer.ValidatingBEUTF16.WebName)
{
return SupportedEncoding.UTF16BE;
}
Expand Down Expand Up @@ -451,7 +446,7 @@ private void InitForReading(Stream inputStream, Encoding? expectedEncoding)
CleanupCharBreak();
int count = _encoding.GetChars(_bytes, _byteOffset, _byteCount, _chars, 0);
_byteOffset = 0;
_byteCount = s_validatingUTF8.GetBytes(_chars, 0, count, _bytes, 0);
_byteCount = DataContractSerializer.ValidatingUTF8.GetBytes(_chars, 0, count, _bytes, 0);
}
}
catch (DecoderFallbackException ex)
Expand All @@ -471,7 +466,7 @@ private void InitForWriting(Stream outputStream, Encoding writeEncoding)
if (_encodingCode != SupportedEncoding.UTF8)
{
EnsureBuffers();
_dec = s_validatingUTF8.GetDecoder();
_dec = DataContractSerializer.ValidatingUTF8.GetDecoder();
_enc = _encoding.GetEncoder();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ internal static class JsonGlobals
public static readonly int DataContractXsdBaseNamespaceLength = Globals.DataContractXsdBaseNamespace.Length;
public static readonly long unixEpochTicks = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
public static readonly SecurityException SecurityException = new SecurityException();
public static readonly UnicodeEncoding ValidatingBEUTF16 = new UnicodeEncoding(true, false, true);
public static readonly UnicodeEncoding ValidatingUTF16 = new UnicodeEncoding(false, false, true);
public static readonly UTF8Encoding ValidatingUTF8 = new UTF8Encoding(false, true);
public const string PositiveInf = "INF";
public const string NegativeInf = "-INF";
public const string typeString = "type";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ internal sealed class XmlJsonWriter : XmlDictionaryWriter, IXmlJsonWriterInitial
"\\u001f"
};

private static BinHexEncoding? s_binHexEncoding;

private string? _attributeText;
private JsonDataType _dataType;
private int _depth;
Expand Down Expand Up @@ -166,9 +164,6 @@ public override XmlSpace XmlSpace
get { return XmlSpace.None; }
}

private static BinHexEncoding BinHexEncoding =>
s_binHexEncoding ??= new BinHexEncoding();

private bool HasOpenAttribute => (_isWritingDataTypeAttribute || _isWritingServerTypeAttribute || IsWritingNameAttribute || _isWritingXmlnsAttribute);

private bool IsClosed => (WriteState == WriteState.Closed);
Expand Down Expand Up @@ -401,7 +396,7 @@ public override void WriteBinHex(byte[] buffer, int index, int count)
}

StartText();
WriteEscapedJsonString(BinHexEncoding.GetString(buffer, index, count));
WriteEscapedJsonString(DataContractSerializer.BinHexEncoding.GetString(buffer, index, count));
}

public override void WriteCData(string? text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ namespace System.Xml
internal sealed class EncodingStreamWrapper : Stream
{
private enum SupportedEncoding { UTF8, UTF16LE, UTF16BE, None }
private static readonly UTF8Encoding s_safeUTF8 = new UTF8Encoding(false, false);
private static readonly UnicodeEncoding s_safeUTF16 = new UnicodeEncoding(false, false, false);
private static readonly UnicodeEncoding s_safeBEUTF16 = new UnicodeEncoding(true, false, false);
private static readonly UTF8Encoding s_validatingUTF8 = new UTF8Encoding(false, true);
private static readonly UnicodeEncoding s_validatingUTF16 = new UnicodeEncoding(false, false, true);
private static readonly UnicodeEncoding s_validatingBEUTF16 = new UnicodeEncoding(true, false, true);
private const int BufferLength = 128;

// UTF-8 is fastpath, so that's how these are stored
Expand Down Expand Up @@ -91,7 +85,7 @@ public EncodingStreamWrapper(Stream stream, Encoding? encoding)
CleanupCharBreak();
int count = _encoding.GetChars(_bytes, _byteOffset, _byteCount, _chars, 0);
_byteOffset = 0;
_byteCount = s_validatingUTF8.GetBytes(_chars, 0, count, _bytes, 0);
_byteCount = DataContractSerializer.ValidatingUTF8.GetBytes(_chars, 0, count, _bytes, 0);

// Check for declaration
if (_bytes[1] == '?' && _bytes[0] == '<')
Expand Down Expand Up @@ -123,18 +117,18 @@ private void SetReadDocumentEncoding(SupportedEncoding e)
private static Encoding GetEncoding(SupportedEncoding e) =>
e switch
{
SupportedEncoding.UTF8 => s_validatingUTF8,
SupportedEncoding.UTF16LE => s_validatingUTF16,
SupportedEncoding.UTF16BE => s_validatingBEUTF16,
SupportedEncoding.UTF8 => DataContractSerializer.ValidatingUTF8,
SupportedEncoding.UTF16LE => DataContractSerializer.ValidatingUTF16,
SupportedEncoding.UTF16BE => DataContractSerializer.ValidatingBEUTF16,
_ => throw new XmlException(SR.XmlEncodingNotSupported),
};

private static Encoding GetSafeEncoding(SupportedEncoding e) =>
e switch
{
SupportedEncoding.UTF8 => s_safeUTF8,
SupportedEncoding.UTF16LE => s_safeUTF16,
SupportedEncoding.UTF16BE => s_safeBEUTF16,
SupportedEncoding.UTF8 => DataContractSerializer.UTF8NoBom,
SupportedEncoding.UTF16LE => DataContractSerializer.UTF16NoBom,
SupportedEncoding.UTF16BE => DataContractSerializer.BEUTF16NoBom,
_ => throw new XmlException(SR.XmlEncodingNotSupported),
};

Expand All @@ -151,11 +145,11 @@ private static SupportedEncoding GetSupportedEncoding(Encoding? encoding)
{
if (encoding == null)
return SupportedEncoding.None;
else if (encoding.WebName == s_validatingUTF8.WebName)
else if (encoding.WebName == DataContractSerializer.ValidatingUTF8.WebName)
return SupportedEncoding.UTF8;
else if (encoding.WebName == s_validatingUTF16.WebName)
else if (encoding.WebName == DataContractSerializer.ValidatingUTF16.WebName)
return SupportedEncoding.UTF16LE;
else if (encoding.WebName == s_validatingBEUTF16.WebName)
else if (encoding.WebName == DataContractSerializer.ValidatingBEUTF16.WebName)
return SupportedEncoding.UTF16BE;
else
throw new XmlException(SR.XmlEncodingNotSupported);
Expand All @@ -174,7 +168,7 @@ public EncodingStreamWrapper(Stream stream, Encoding encoding, bool emitBOM)
if (_encodingCode != SupportedEncoding.UTF8)
{
EnsureBuffers();
_dec = s_validatingUTF8.GetDecoder();
_dec = DataContractSerializer.ValidatingUTF8.GetDecoder();
_enc = _encoding.GetEncoder();

// Emit BOM
Expand Down Expand Up @@ -400,15 +394,15 @@ private static void CheckUTF8DeclarationEncoding(byte[] buffer, int offset, int
else if (encCount == s_encodingUnicode.Length && CompareCaseInsensitive(s_encodingUnicode, buffer, encStart))
{
if (e == SupportedEncoding.UTF8)
ThrowEncodingMismatch(s_safeUTF8.GetString(buffer, encStart, encCount), s_safeUTF8.GetString(s_encodingUTF8, 0, s_encodingUTF8.Length));
ThrowEncodingMismatch(DataContractSerializer.UTF8NoBom.GetString(buffer, encStart, encCount), DataContractSerializer.UTF8NoBom.GetString(s_encodingUTF8, 0, s_encodingUTF8.Length));
}
else
{
ThrowEncodingMismatch(s_safeUTF8.GetString(buffer, encStart, encCount), e);
ThrowEncodingMismatch(DataContractSerializer.UTF8NoBom.GetString(buffer, encStart, encCount), e);
}

if (e != declEnc)
ThrowEncodingMismatch(s_safeUTF8.GetString(buffer, encStart, encCount), e);
ThrowEncodingMismatch(DataContractSerializer.UTF8NoBom.GetString(buffer, encStart, encCount), e);
}

private static bool CompareCaseInsensitive(byte[] key, byte[] buffer, int offset)
Expand Down Expand Up @@ -470,8 +464,8 @@ internal static ArraySegment<byte> ProcessBuffer(byte[] buffer, int offset, int
int inputCount = Math.Min(count, BufferLength * 2);
chars = new char[localEnc.GetMaxCharCount(inputCount)];
int ccount = localEnc.GetChars(buffer, offset, inputCount, chars, 0);
bytes = new byte[s_validatingUTF8.GetMaxByteCount(ccount)];
int bcount = s_validatingUTF8.GetBytes(chars, 0, ccount, bytes, 0);
bytes = new byte[DataContractSerializer.ValidatingUTF8.GetMaxByteCount(ccount)];
int bcount = DataContractSerializer.ValidatingUTF8.GetBytes(chars, 0, ccount, bytes, 0);

// Check for declaration
if (bytes[1] == '?' && bytes[0] == '<')
Expand All @@ -485,7 +479,7 @@ internal static ArraySegment<byte> ProcessBuffer(byte[] buffer, int offset, int
throw new XmlException(SR.XmlDeclarationRequired);
}

seg = new ArraySegment<byte>(s_validatingUTF8.GetBytes(GetEncoding(declEnc).GetChars(buffer, offset, count)));
seg = new ArraySegment<byte>(DataContractSerializer.ValidatingUTF8.GetBytes(GetEncoding(declEnc).GetChars(buffer, offset, count)));
return seg;
}
catch (DecoderFallbackException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.Serialization;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Diagnostics.CodeAnalysis;

Expand Down Expand Up @@ -71,7 +69,6 @@ internal sealed class ValueHandle
private ValueHandleType _type;
private int _offset;
private int _length;
private static Base64Encoding? s_base64Encoding;
private static readonly string[] s_constStrings = {
"string",
"number",
Expand All @@ -87,8 +84,6 @@ public ValueHandle(XmlBufferReader bufferReader)
_type = ValueHandleType.Empty;
}

private static Base64Encoding Base64Encoding => s_base64Encoding ??= new Base64Encoding();

public void SetConstantValue(ValueHandleConstStringType constStringType)
{
_type = ValueHandleType.ConstString;
Expand Down Expand Up @@ -444,7 +439,7 @@ public byte[] ToByteArray()
}
}
byte[] buffer = new byte[expectedLength];
int actualLength = Base64Encoding.GetBytes(_bufferReader.Buffer, _offset, _length, buffer, 0);
int actualLength = DataContractSerializer.Base64Encoding.GetBytes(_bufferReader.Buffer, _offset, _length, buffer, 0);
if (actualLength != buffer.Length)
{
byte[] newBuffer = new byte[actualLength];
Expand All @@ -460,7 +455,7 @@ public byte[] ToByteArray()
}
try
{
return Base64Encoding.GetBytes(XmlConverter.StripWhitespace(GetString()));
return DataContractSerializer.Base64Encoding.GetBytes(XmlConverter.StripWhitespace(GetString()));
}
catch (FormatException exception)
{
Expand Down Expand Up @@ -513,7 +508,7 @@ public string GetString()
case ValueHandleType.Base64:
byte[] bytes = ToByteArray();
DiagnosticUtility.DebugAssert(bytes != null, "");
return Base64Encoding.GetString(bytes, 0, bytes.Length);
return DataContractSerializer.Base64Encoding.GetString(bytes, 0, bytes.Length);
case ValueHandleType.List:
return XmlConverter.ToString(ToList());
case ValueHandleType.UniqueId:
Expand Down Expand Up @@ -675,7 +670,7 @@ public bool TryReadBase64(byte[] buffer, int offset, int count, out int actual)
try
{
int charCount = Math.Min(count / 3 * 4, _length);
actual = Base64Encoding.GetBytes(_bufferReader.Buffer, _offset, charCount, buffer, offset);
actual = DataContractSerializer.Base64Encoding.GetBytes(_bufferReader.Buffer, _offset, charCount, buffer, offset);
_offset += charCount;
_length -= charCount;
return true;
Expand Down Expand Up @@ -709,7 +704,7 @@ public bool TryReadChars(char[] chars, int offset, int count, out int actual)
int byteCount = _length;
bool insufficientSpaceInCharsArray = false;

var encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
var encoding = DataContractSerializer.ValidatingUTF8;
while (true)
{
while (charCount > 0 && byteCount > 0)
Expand Down
Loading

0 comments on commit bfa4812

Please sign in to comment.