diff --git a/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj b/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj index fca56efcdfe..aed2d867893 100644 --- a/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj +++ b/src/System.Private.Windows.Core/src/System.Private.Windows.Core.csproj @@ -28,9 +28,7 @@ - - - + diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs deleted file mode 100644 index 92f0c4ba20a..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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.Serialization.BinaryFormat; - -[Flags] -internal enum AllowedRecordTypes : uint -{ - None = 0, - SerializedStreamHeader = 1 << RecordType.SerializedStreamHeader, - ClassWithId = 1 << RecordType.ClassWithId, - SystemClassWithMembers = 1 << RecordType.SystemClassWithMembers, - ClassWithMembers = 1 << RecordType.ClassWithMembers, - SystemClassWithMembersAndTypes = 1 << RecordType.SystemClassWithMembersAndTypes, - ClassWithMembersAndTypes = 1 << RecordType.ClassWithMembersAndTypes, - BinaryObjectString = 1 << RecordType.BinaryObjectString, - BinaryArray = 1 << RecordType.BinaryArray, - MemberPrimitiveTyped = 1 << RecordType.MemberPrimitiveTyped, - MemberReference = 1 << RecordType.MemberReference, - ObjectNull = 1 << RecordType.ObjectNull, - MessageEnd = 1 << RecordType.MessageEnd, - BinaryLibrary = 1 << RecordType.BinaryLibrary, - ObjectNullMultiple256 = 1 << RecordType.ObjectNullMultiple256, - ObjectNullMultiple = 1 << RecordType.ObjectNullMultiple, - ArraySinglePrimitive = 1 << RecordType.ArraySinglePrimitive, - ArraySingleObject = 1 << RecordType.ArraySingleObject, - ArraySingleString = 1 << RecordType.ArraySingleString, - - Nulls = ObjectNull | ObjectNullMultiple256 | ObjectNullMultiple, - - /// - /// Any .NET object (a primitive, a reference type, a reference or single null). - /// - AnyObject = MemberPrimitiveTyped - | ArraySingleObject | ArraySinglePrimitive | ArraySingleString | BinaryArray - | ClassWithId | ClassWithMembers | ClassWithMembersAndTypes | SystemClassWithMembers | SystemClassWithMembersAndTypes - | BinaryObjectString - | MemberReference - | ObjectNull, -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs deleted file mode 100644 index 16a0a9bd89f..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Binary array type. -/// -/// -/// -/// -/// [MS-NRBF] 2.4.1.1 -/// -/// -/// -public enum ArrayType : byte -{ - /// - /// A single-dimensional array. - /// - Single = 0, - - /// - /// An array whose elements are arrays. The elements of a jagged array can be of different dimensions and sizes. - /// - Jagged = 1, - - /// - /// A multi-dimensional rectangular array. - /// - Rectangular = 2, - - /// - /// A single-dimensional array where the lower bound index is greater than 0. - /// - SingleOffset = 3, - - /// - /// A jagged array where the lower bound index is greater than 0. - /// - JaggedOffset = 4, - - /// - /// Multi-dimensional arrays where the lower bound index of at least one of the dimensions is greater than 0. - /// - RectangularOffset = 5, -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs deleted file mode 100644 index 2ec5da67e83..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Identifies the remoting type of a class member or array item. -/// -/// -/// -/// -/// [MS-NRBF] 2.1.2.2 -/// -/// -/// -internal enum BinaryType : byte -{ - /// - /// Type is defined by and it is not a string. - /// - Primitive, - - /// - /// Type is - /// length prefixed string. - /// - String, - - /// - /// Type is System.Object. - /// - Object, - - /// - /// Type is a standard .NET object. - /// - SystemClass, - - /// - /// Type is an object. - /// - Class, - - /// - /// Type is a single-dimensional array of objects. - /// - ObjectArray, - - /// - /// Type is a single-dimensional array of strings. - /// - StringArray, - - /// - /// Types is a single-dimensional array of a primitive type. - /// - PrimitiveArray -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs deleted file mode 100644 index 1859ddd0e66..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Primitive type. -/// -/// -/// -/// -/// [MS-NRBF] 2.1.2.3 -/// -/// -/// -internal enum PrimitiveType : byte -{ - /// - /// Used internally to express no value - /// - None = 0, - Boolean = 1, - Byte = 2, - Char = 3, - // 4 is not used in the protocol - Decimal = 5, - Double = 6, - Int16 = 7, - Int32 = 8, - Int64 = 9, - SByte = 10, - Single = 11, - TimeSpan = 12, - DateTime = 13, - UInt16 = 14, - UInt32 = 15, - UInt64 = 16, - Null = 17, - String = 18 -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs deleted file mode 100644 index 213587af06e..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Record type. -/// -/// -/// -/// The enumeration does not contain all values supported by the -/// [MS-NRBF] 2.1.2.1, but only those supported by the . -/// -/// -public enum RecordType : byte -{ - SerializedStreamHeader, - ClassWithId, - SystemClassWithMembers, - ClassWithMembers, - SystemClassWithMembersAndTypes, - ClassWithMembersAndTypes, - BinaryObjectString, - BinaryArray, - MemberPrimitiveTyped, - MemberReference, - ObjectNull, - MessageEnd, - BinaryLibrary, - ObjectNullMultiple256, - ObjectNullMultiple, - ArraySinglePrimitive, - ArraySingleObject, - ArraySingleString -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs deleted file mode 100644 index 38cc6b5557f..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Array information structure. -/// -/// -/// -/// -/// [MS-NRBF] 2.4.2.1 -/// -/// -/// -[DebuggerDisplay("Length={Length}, {ArrayType}, rank={Rank}")] -internal readonly struct ArrayInfo -{ - internal ArrayInfo(int objectId, uint length, ArrayType arrayType = ArrayType.Single, int rank = 1) - { - ObjectId = objectId; - Length = length; - ArrayType = arrayType; - Rank = rank; - } - - internal int ObjectId { get; } - - internal uint Length { get; } - - internal ArrayType ArrayType { get; } - - internal int Rank { get; } - - internal static ArrayInfo Parse(BinaryReader reader) - => new(reader.ReadInt32(), (uint)ParseValidArrayLength(reader)); - - internal static int ParseValidArrayLength(BinaryReader reader) - { - int length = reader.ReadInt32(); - - if (length is < 0 or > 2147483591) // Array.MaxLength - { - throw new SerializationException($"Invalid array length: {length}"); - } - - return length; - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs deleted file mode 100644 index ecbd70b13c6..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Class info. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.1.1 -/// -/// -/// -[DebuggerDisplay("{Name}")] -internal sealed class ClassInfo -{ - private ClassInfo(int objectId, TypeName name, Dictionary memberNames) - { - ObjectId = objectId; - Name = name; - MemberNames = memberNames; - } - - internal int ObjectId { get; } - - internal TypeName Name { get; } - - internal Dictionary MemberNames { get; } - - internal static ClassInfo Parse(BinaryReader reader, PayloadOptions payloadOptions) - { - int objectId = reader.ReadInt32(); - TypeName typeName = reader.ReadTypeName(payloadOptions); - int memberCount = reader.ReadInt32(); - - // The attackers could create an input with MANY member names. - // If we were storing them in a list, then searching for the index - // of given member name (done by ClassRecord indexer) would take - // O(m * n), where m = memberCount and n = memberNameLength. - // To prevent this from happening, we are using a Dictionary instead, - // which has O(1) lookup time. - Dictionary memberNames = new(StringComparer.Ordinal); - for (int i = 0; i < memberCount; i++) - { - // The NRBF specification does not prohibit multiple members with the same names, - // however it's impossible to get such output with BinaryFormatter, - // so we prohibit that on purpose (Add is going to throw on duplicates). - memberNames.Add(reader.ReadString(), i); - } - - return new(objectId, typeName, memberNames); - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs deleted file mode 100644 index c06dd3ba715..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Identifies a class by it's name and library id. -/// -/// -/// -/// -/// [MS-NRBF] 2.1.1.8 -/// -/// -/// -[DebuggerDisplay("{TypeName}")] -internal sealed class ClassTypeInfo -{ - internal ClassTypeInfo(TypeName typeName, int libraryId) - { - TypeName = typeName; - LibraryId = libraryId; - } - - internal TypeName TypeName { get; } - - internal int LibraryId { get; } - - internal static ClassTypeInfo Parse(BinaryReader reader, PayloadOptions options) - => new(reader.ReadTypeName(options), reader.ReadInt32()); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs deleted file mode 100644 index f1827932e73..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs +++ /dev/null @@ -1,197 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Member type info. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.1.2 -/// -/// -/// -internal readonly struct MemberTypeInfo -{ - internal MemberTypeInfo(IReadOnlyList<(BinaryType BinaryType, object? AdditionalInfo)> infos) => _infos = infos; - - private readonly IReadOnlyList<(BinaryType BinaryType, object? AdditionalInfo)> _infos; - - internal IReadOnlyList<(BinaryType BinaryType, object? AdditionalInfo)> Infos => _infos; - - internal static MemberTypeInfo Parse(BinaryReader reader, int count, PayloadOptions options) - { - List<(BinaryType BinaryType, object? AdditionalInfo)> info = []; - - // [MS-NRBF] 2.3.1.2 - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/aa509b5a-620a-4592-a5d8-7e9613e0a03e - - // All of the BinaryTypeEnumeration values come before all of the AdditionalInfo values. - // There's not necessarily a 1:1 mapping; some enum values don't have associated AdditionalInfo. - for (int i = 0; i < count; i++) - { - info.Add(((BinaryType)reader.ReadByte(), null)); - } - - // Check for more clarifying information - for (int i = 0; i < info.Count; i++) - { - BinaryType type = info[i].BinaryType; - switch (type) - { - case BinaryType.Primitive: - case BinaryType.PrimitiveArray: - info[i] = (type, (PrimitiveType)reader.ReadByte()); - break; - case BinaryType.SystemClass: - info[i] = (type, reader.ReadTypeName(options)); - break; - case BinaryType.Class: - info[i] = (type, ClassTypeInfo.Parse(reader, options)); - break; - case BinaryType.String: - case BinaryType.ObjectArray: - case BinaryType.StringArray: - case BinaryType.Object: - // Other types have no additional data. - break; - default: - throw new SerializationException("Unexpected binary type."); - } - } - - return new MemberTypeInfo(info); - } - - internal (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType(int currentValuesCount) - { - (BinaryType binaryType, object? additionalInfo) = Infos[currentValuesCount]; - - // Every array can be either an array itself, a null or a reference (to an array) - const AllowedRecordTypes stringArray = AllowedRecordTypes.ArraySingleString - | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference; - const AllowedRecordTypes primitiveArray = AllowedRecordTypes.ArraySinglePrimitive - | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference; - const AllowedRecordTypes objectArray = AllowedRecordTypes.ArraySingleObject - | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference; - - // Every string can be a string, a null or a reference (to a string) - const AllowedRecordTypes strings = AllowedRecordTypes.BinaryObjectString - | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference; - - // Every class can be a null or a reference and a ClassWithId (TODO: verify ClassId) - const AllowedRecordTypes classes = AllowedRecordTypes.ClassWithId - | AllowedRecordTypes.ObjectNull | AllowedRecordTypes.MemberReference - | AllowedRecordTypes.MemberPrimitiveTyped - | AllowedRecordTypes.BinaryLibrary; // classes may be preceded with a library record (System too!) - // but System classes can be expressed only by System records - const AllowedRecordTypes systemClass = classes - | AllowedRecordTypes.SystemClassWithMembers | AllowedRecordTypes.SystemClassWithMembersAndTypes; - const AllowedRecordTypes nonSystemClass = classes - | AllowedRecordTypes.ClassWithMembers | AllowedRecordTypes.ClassWithMembersAndTypes; - - return binaryType switch - { - BinaryType.Primitive => (default, (PrimitiveType)additionalInfo!), - BinaryType.String => (strings, default), - BinaryType.Object => (AllowedRecordTypes.AnyObject, default), - BinaryType.StringArray => (stringArray, default), - BinaryType.PrimitiveArray => (primitiveArray, default), - BinaryType.Class => (nonSystemClass, default), - BinaryType.SystemClass => (systemClass, default), - BinaryType.ObjectArray => (objectArray, default), - _ => throw new SerializationException($"Invalid binary type: {binaryType}.") - }; - } - - internal bool IsElementType(Type typeElement, RecordMap recordMap) - { - (BinaryType binaryType, object? additionalInfo) = Infos[0]; - - switch (binaryType) - { - case BinaryType.String: - return typeElement == typeof(string); - case BinaryType.StringArray: - return typeElement == typeof(string[]); - case BinaryType.Object: - return typeElement == typeof(object); - case BinaryType.ObjectArray: - return typeElement == typeof(object[]); - case BinaryType.Primitive: - case BinaryType.PrimitiveArray: - if (binaryType is BinaryType.PrimitiveArray) - { - if (!typeElement.IsArray) - { - return false; - } - - typeElement = typeElement.GetElementType()!; - } - - return ((PrimitiveType)additionalInfo!) switch - { - PrimitiveType.Boolean => typeElement == typeof(bool), - PrimitiveType.Byte => typeElement == typeof(byte), - PrimitiveType.Char => typeElement == typeof(char), - PrimitiveType.Decimal => typeElement == typeof(decimal), - PrimitiveType.Double => typeElement == typeof(double), - PrimitiveType.Int16 => typeElement == typeof(short), - PrimitiveType.Int32 => typeElement == typeof(int), - PrimitiveType.Int64 => typeElement == typeof(long), - PrimitiveType.SByte => typeElement == typeof(sbyte), - PrimitiveType.Single => typeElement == typeof(float), - PrimitiveType.TimeSpan => typeElement == typeof(TimeSpan), - PrimitiveType.DateTime => typeElement == typeof(DateTime), - PrimitiveType.UInt16 => typeElement == typeof(ushort), - PrimitiveType.UInt32 => typeElement == typeof(uint), - PrimitiveType.UInt64 => typeElement == typeof(ulong), - _ => false - }; - case BinaryType.SystemClass: - if (typeElement.Assembly != typeof(object).Assembly) - { - return false; - } - - TypeName typeName = (TypeName)additionalInfo!; - string fullSystemClassName = FormatterServices.GetTypeFullNameIncludingTypeForwards(typeElement); - return typeName.FullName == fullSystemClassName; - case BinaryType.Class: - ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!; - string fullClassName = FormatterServices.GetTypeFullNameIncludingTypeForwards(typeElement); - if (typeInfo.TypeName.FullName != fullClassName) - { - return false; - } - - BinaryLibraryRecord libraryRecord = (BinaryLibraryRecord)recordMap[typeInfo.LibraryId]; - string assemblyName = FormatterServices.GetAssemblyNameIncludingTypeForwards(typeElement); - return assemblyName == libraryRecord.LibraryName.FullName; - default: - throw new NotSupportedException(); - } - } - - internal bool ShouldBeRepresentedAsArrayOfClassRecords() - { - (BinaryType binaryType, object? additionalInfo) = Infos[0]; - - switch (binaryType) - { - case BinaryType.SystemClass: - TypeName typeName = (TypeName)additionalInfo!; - return !typeName.IsSZArray; - case BinaryType.Class: - ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!; - return !typeInfo.TypeName.IsSZArray; - default: - return false; - } - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs deleted file mode 100644 index 31fdefc3d6c..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Serialization.BinaryFormat; - -[DebuggerDisplay("{Parent.RecordType}, {Allowed}, {PrimitiveType}")] -internal readonly struct NextInfo -{ - internal NextInfo(AllowedRecordTypes allowed, SerializationRecord parent, - Stack stack, PrimitiveType primitiveType = default) - { - Allowed = allowed; - Parent = parent; - Stack = stack; - PrimitiveType = primitiveType; - } - - internal AllowedRecordTypes Allowed { get; } - - internal SerializationRecord Parent { get; } - - internal Stack Stack { get; } - - internal PrimitiveType PrimitiveType { get; } - - internal NextInfo With(AllowedRecordTypes allowed, PrimitiveType primitiveType) - => allowed == Allowed && primitiveType == PrimitiveType - ? this // previous record was of the same type - : new(allowed, Parent, Stack, primitiveType); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs deleted file mode 100644 index 90e736d7f48..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -public sealed class PayloadOptions -{ - public PayloadOptions() { } - - public TypeNameParseOptions? TypeNameParseOptions { get; set; } - - // public int MaxMemberCount { get; set; } = 100; - - // public bool AreCustomOffsetArraysAllowed { get; set; } = false; - - // public bool AreJaggedArraysAllowed { get; set; } = false; - - // public int MaxArrayLength { get; set; } - - // public int MaxArrayRank { get; set; } - - // public HashSet AllowedRootRecordTypes { get; } = new(); - - // public HashSet AllowedTypes { get; } = new(); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs deleted file mode 100644 index 11ba19fe01f..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs +++ /dev/null @@ -1,285 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Text; - -namespace System.Runtime.Serialization.BinaryFormat; - -public static class PayloadReader -{ - private static UTF8Encoding ThrowOnInvalidUtf8Encoding { get; } = new(false, throwOnInvalidBytes: true); - - /// - /// Checks if given buffer contains only Binary Formatter payload. - /// - /// The buffer to inspect - /// True if the first and last bytes indicate Binary Format, otherwise false. - public static bool ContainsBinaryFormatterPayload(ReadOnlySpan bytes) - => bytes.Length >= SerializedStreamHeaderRecord.Size + 2 - && bytes[0] == (byte)RecordType.SerializedStreamHeader - && bytes[bytes.Length - 1] == (byte)RecordType.MessageEnd; - - /// - /// Checks if given stream contains only Binary Formatter payload. - /// - /// The readable and seekable stream to inspect. - /// True if the first and last bytes indicate Binary Format, otherwise false. - /// The stream is null. - /// The stream does not support reading or seeking. - /// The stream was closed. - /// It does not modify the position of the stream. - public static bool ContainsBinaryFormatterPayload(Stream stream) - { - ArgumentNullException.ThrowIfNull(stream); - - // TODO: discuss an alternative approach, where we would parse SerializedStreamHeaderRecord - // here and return false on failure - - long beginning = stream.Position; - if (stream.Length - beginning < SerializedStreamHeaderRecord.Size + 2) - { - return false; - } - - try - { - int firstByte = stream.ReadByte(); - if (firstByte != (byte)RecordType.SerializedStreamHeader) - { - return false; - } - - stream.Seek(-1, SeekOrigin.End); - int lastByte = stream.ReadByte(); - return lastByte == (byte)RecordType.MessageEnd; - } - finally - { - stream.Position = beginning; - } - } - - /// - /// Reads the provided Binary Format payload. - /// - /// The Binary Format payload. - /// An object that describes optional parameters to use. - /// True to leave the payload open - /// after the reading is finished, otherwise, false. - /// A that represents the root object. - /// It can be either , - /// a or an . - /// When is null. - /// The payload does not support reading or is already closed. - /// When reading input from encounters invalid Binary Format data. - /// When reading input from - /// encounters invalid sequence of UTF8 characters. - public static SerializationRecord Read(Stream payload, PayloadOptions? options = default, bool leaveOpen = false) - { - ArgumentNullException.ThrowIfNull(payload); - - using BinaryReader reader = new(payload, ThrowOnInvalidUtf8Encoding, leaveOpen: leaveOpen); - return Read(reader, options ?? new()); - } - - /// - /// Reads the provided Binary Format payload that is expected to contain an instance of any class (or struct) that is not an or a primitive type. - /// - /// A that represents the root object. - /// - public static ClassRecord ReadClassRecord(Stream payload, PayloadOptions? options = default, bool leaveOpen = false) - => (ClassRecord)Read(payload, options, leaveOpen); - - private static SerializationRecord Read(BinaryReader reader, PayloadOptions options) - { - Stack readStack = new(); - RecordMap recordMap = new(); - - // Everything has to start with a header - var header = (SerializedStreamHeaderRecord)ReadNext(reader, recordMap, AllowedRecordTypes.SerializedStreamHeader, options, out _); - // and can be followed by any Object, BinaryLibrary and a MessageEnd. - const AllowedRecordTypes allowed = AllowedRecordTypes.AnyObject - | AllowedRecordTypes.BinaryLibrary | AllowedRecordTypes.MessageEnd; - - RecordType recordType; - SerializationRecord nextRecord; - do - { - while (readStack.Count > 0) - { - NextInfo nextInfo = readStack.Pop(); - - if (nextInfo.Allowed != AllowedRecordTypes.None) - { - // Read the next Record - do - { - nextRecord = ReadNext(reader, recordMap, nextInfo.Allowed, options, out _); - // BinaryLibrary often precedes class records. - // It has been already added to the RecordMap and it must not be added - // to the array record, so simply read next record. - // It's possible to read multiple BinaryLibraryRecord in a row, hence the loop. - } - while (nextRecord is BinaryLibraryRecord); - - // Handle it: - // - add to the parent records list, - // - push next info if there are remaining nested records to read. - nextInfo.Parent.HandleNextRecord(nextRecord, nextInfo); - // Push on the top of the stack the first nested record of last read record, - // so it gets read as next record. - PushFirstNestedRecordInfo(nextRecord, readStack); - } - else - { - object value = reader.ReadPrimitiveType(nextInfo.PrimitiveType); - - nextInfo.Parent.HandleNextValue(value, nextInfo); - } - } - - nextRecord = ReadNext(reader, recordMap, allowed, options, out recordType); - PushFirstNestedRecordInfo(nextRecord, readStack); - } - while (recordType != RecordType.MessageEnd); - - SerializationRecord rootRecord = recordMap[header.RootId]; - return rootRecord is SystemClassWithMembersAndTypesRecord systemClass - ? systemClass.TryToMapToUserFriendly() - : rootRecord; - } - - private static SerializationRecord ReadNext(BinaryReader reader, RecordMap recordMap, - AllowedRecordTypes allowed, PayloadOptions options, out RecordType recordType) - { - recordType = (RecordType)reader.ReadByte(); - - if (((uint)allowed & (1u << (int)recordType)) == 0) - { - throw new SerializationException($"Unexpected type seen: {recordType}."); - } - - // Following values are not part of the public API surface, as they are not supported - // and users don't need to handle these values. - // 18~20 are from the reference source but aren't in the OpenSpecs doc - const RecordType CrossAppDomainMap = (RecordType)18; - const RecordType CrossAppDomainString = (RecordType)19; - const RecordType CrossAppDomainAssembly = (RecordType)20; - const RecordType MethodCall = (RecordType)21; - const RecordType MethodReturn = (RecordType)22; - - SerializationRecord record = recordType switch - { - RecordType.ArraySingleObject => ArraySingleObjectRecord.Parse(reader), - RecordType.ArraySinglePrimitive => ParseArraySinglePrimitiveRecord(reader), - RecordType.ArraySingleString => ArraySingleStringRecord.Parse(reader), - RecordType.BinaryArray => BinaryArrayRecord.Parse(reader, recordMap, options), - RecordType.BinaryLibrary => BinaryLibraryRecord.Parse(reader), - RecordType.BinaryObjectString => BinaryObjectStringRecord.Parse(reader), - RecordType.ClassWithId => ClassWithIdRecord.Parse(reader, recordMap), - RecordType.ClassWithMembers => ClassWithMembersRecord.Parse(reader, recordMap, options), - RecordType.ClassWithMembersAndTypes => ClassWithMembersAndTypesRecord.Parse(reader, recordMap, options), - RecordType.MemberPrimitiveTyped => ParseMemberPrimitiveTypedRecord(reader), - RecordType.MemberReference => MemberReferenceRecord.Parse(reader, recordMap), - RecordType.MessageEnd => MessageEndRecord.Singleton, - RecordType.ObjectNull => ObjectNullRecord.Instance, - RecordType.ObjectNullMultiple => ObjectNullMultipleRecord.Parse(reader), - RecordType.ObjectNullMultiple256 => ObjectNullMultiple256Record.Parse(reader), - RecordType.SerializedStreamHeader => SerializedStreamHeaderRecord.Parse(reader), - RecordType.SystemClassWithMembers => SystemClassWithMembersRecord.Parse(reader, options), - RecordType.SystemClassWithMembersAndTypes => SystemClassWithMembersAndTypesRecord.Parse(reader, options), - CrossAppDomainAssembly or CrossAppDomainMap or CrossAppDomainString - => throw new NotSupportedException("Cross domain is not supported by design"), - MethodCall or MethodReturn - => throw new NotSupportedException("Remote invocation is not supported by design"), - _ => throw new SerializationException($"Invalid RecordType value: {recordType}") - }; - - recordMap.Add(record); - - return record; - } - - private static SerializationRecord ParseMemberPrimitiveTypedRecord(BinaryReader reader) - { - PrimitiveType primitiveType = (PrimitiveType)reader.ReadByte(); - - return primitiveType switch - { - PrimitiveType.Boolean => new MemberPrimitiveTypedRecord(reader.ReadBoolean()), - PrimitiveType.Byte => new MemberPrimitiveTypedRecord(reader.ReadByte()), - PrimitiveType.SByte => new MemberPrimitiveTypedRecord(reader.ReadSByte()), - PrimitiveType.Char => new MemberPrimitiveTypedRecord(reader.ReadChar()), - PrimitiveType.Int16 => new MemberPrimitiveTypedRecord(reader.ReadInt16()), - PrimitiveType.UInt16 => new MemberPrimitiveTypedRecord(reader.ReadUInt16()), - PrimitiveType.Int32 => new MemberPrimitiveTypedRecord(reader.ReadInt32()), - PrimitiveType.UInt32 => new MemberPrimitiveTypedRecord(reader.ReadUInt32()), - PrimitiveType.Int64 => new MemberPrimitiveTypedRecord(reader.ReadInt64()), - PrimitiveType.UInt64 => new MemberPrimitiveTypedRecord(reader.ReadUInt64()), - PrimitiveType.Single => new MemberPrimitiveTypedRecord(reader.ReadSingle()), - PrimitiveType.Double => new MemberPrimitiveTypedRecord(reader.ReadDouble()), - PrimitiveType.Decimal => new MemberPrimitiveTypedRecord(decimal.Parse(reader.ReadString(), CultureInfo.InvariantCulture)), - PrimitiveType.DateTime => new MemberPrimitiveTypedRecord(BinaryReaderExtensions.CreateDateTimeFromData(reader.ReadInt64())), - PrimitiveType.TimeSpan => new MemberPrimitiveTypedRecord(new TimeSpan(reader.ReadInt64())), - // String is handled with a record, never on it's own - _ => throw new SerializationException($"Failure trying to read primitive '{primitiveType}'"), - }; - } - - private static SerializationRecord ParseArraySinglePrimitiveRecord(BinaryReader reader) - { - ArrayInfo info = ArrayInfo.Parse(reader); - PrimitiveType primitiveType = (PrimitiveType)reader.ReadByte(); - - return primitiveType switch - { - PrimitiveType.Boolean => Read(info, reader), - PrimitiveType.Byte => Read(info, reader), - PrimitiveType.SByte => Read(info, reader), - PrimitiveType.Char => Read(info, reader), - PrimitiveType.Int16 => Read(info, reader), - PrimitiveType.UInt16 => Read(info, reader), - PrimitiveType.Int32 => Read(info, reader), - PrimitiveType.UInt32 => Read(info, reader), - PrimitiveType.Int64 => Read(info, reader), - PrimitiveType.UInt64 => Read(info, reader), - PrimitiveType.Single => Read(info, reader), - PrimitiveType.Double => Read(info, reader), - PrimitiveType.Decimal => Read(info, reader), - PrimitiveType.DateTime => Read(info, reader), - PrimitiveType.TimeSpan => Read(info, reader), - _ => throw new SerializationException($"Failure trying to read primitive '{primitiveType}'"), - }; - - static SerializationRecord Read(ArrayInfo info, BinaryReader reader) where T : unmanaged - => new ArraySinglePrimitiveRecord(info, ArraySinglePrimitiveRecord.ReadPrimitiveTypes(reader, (int)info.Length)); - } - - /// - /// This method is responsible for pushing only the FIRST read info - /// of the NESTED record into the . - /// It's not pushing all of them, because it could be used as a vector of attack. - /// Example: BinaryArrayRecord with length, - /// where first item turns out to be - /// that provides nulls. - /// - private static void PushFirstNestedRecordInfo(SerializationRecord record, Stack readStack) - { - if (record is ClassRecord classRecord) - { - if (classRecord.ExpectedValuesCount > 0) - { - (AllowedRecordTypes allowed, PrimitiveType primitiveType) = classRecord.GetNextAllowedRecordType(); - - readStack.Push(new(allowed, classRecord, readStack, primitiveType)); - } - } - else if (record is ArrayRecord arrayRecord && arrayRecord.ValuesToRead > 0) - { - (AllowedRecordTypes allowed, PrimitiveType primitiveType) = arrayRecord.GetAllowedRecordType(); - - readStack.Push(new(allowed, arrayRecord, readStack, primitiveType)); - } - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs deleted file mode 100644 index a6856e2b4ee..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs +++ /dev/null @@ -1,76 +0,0 @@ -// 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.Serialization.BinaryFormat; - -internal sealed class ArrayOfClassesRecord : ArrayRecord -{ - internal ArrayOfClassesRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, RecordMap recordMap) - : base(arrayInfo) - { - MemberTypeInfo = memberTypeInfo; - RecordMap = recordMap; - Records = []; - } - - public override RecordType RecordType => RecordType.BinaryArray; - - internal List Records { get; } - - private MemberTypeInfo MemberTypeInfo { get; } - - private RecordMap RecordMap { get; } - - protected override ClassRecord?[] ToArrayOfT(bool allowNulls) - { - ClassRecord?[] result = new ClassRecord?[Length]; - - int resultIndex = 0; - foreach (SerializationRecord record in Records) - { - SerializationRecord actual = record is MemberReferenceRecord referenceRecord - ? referenceRecord.GetReferencedRecord() - : record; - - if (actual is ClassRecord classRecord) - { - result[resultIndex++] = classRecord; - } - else - { - if (!allowNulls) - { - ThrowHelper.ThrowArrayContainedNull(); - } - - int nullCount = ((NullsRecord)actual).NullCount; - do - { - result[resultIndex++] = null; - nullCount--; - } - while (nullCount > 0); - } - } - - return result; - } - - private protected override void AddValue(object value) => Records.Add((SerializationRecord)value); - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType() - { - (AllowedRecordTypes allowed, PrimitiveType primitiveType) = MemberTypeInfo.GetNextAllowedRecordType(0); - - if (allowed != AllowedRecordTypes.None) - { - // It's an array, it can also contain multiple nulls - return (allowed | AllowedRecordTypes.Nulls, primitiveType); - } - - return (allowed, primitiveType); - } - - internal override bool IsElementType(Type typeElement) - => MemberTypeInfo.IsElementType(typeElement, RecordMap); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs deleted file mode 100644 index 6a692876067..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs +++ /dev/null @@ -1,122 +0,0 @@ -// 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.Serialization.BinaryFormat; - -public abstract class ArrayRecord : SerializationRecord -{ - internal const int DefaultMaxArrayLength = 64_000; - - private protected ArrayRecord(ArrayInfo arrayInfo) - { - ArrayInfo = arrayInfo; - ValuesToRead = arrayInfo.Length; - } - - /// - /// Length of the array. - /// - public uint Length => ArrayInfo.Length; - - /// - /// Rank of the array. - /// - public int Rank => ArrayInfo.Rank; - - /// - /// Type of the array. - /// - public ArrayType ArrayType => ArrayInfo.ArrayType; - - internal override int ObjectId => ArrayInfo.ObjectId; - - internal long ValuesToRead { get; private protected set; } - - private protected ArrayInfo ArrayInfo { get; } - - public Array ToArray(Type expectedArrayType, bool allowNulls = true, int maxLength = DefaultMaxArrayLength) - { - if (!IsTypeNameMatching(expectedArrayType)) - { - throw new InvalidOperationException(); - } - - return Deserialize(expectedArrayType, allowNulls, maxLength); - } - - private protected abstract Array Deserialize(Type arrayType, bool allowNulls, int maxLength); - - public override bool IsTypeNameMatching(Type type) - => type.IsArray - && type.GetArrayRank() == ArrayInfo.Rank - && IsElementType(type.GetElementType()!); - - internal sealed override void HandleNextValue(object value, NextInfo info) - => HandleNext(value, info, size: 1); - - internal sealed override void HandleNextRecord(SerializationRecord nextRecord, NextInfo info) - => HandleNext(nextRecord, info, size: nextRecord is NullsRecord nullsRecord ? nullsRecord.NullCount : 1); - - private protected abstract void AddValue(object value); - - internal abstract bool IsElementType(Type typeElement); - - private void HandleNext(object value, NextInfo info, int size) - { - ValuesToRead -= size; - - if (ValuesToRead < 0) - { - // The only way to get here is to read a multiple null item with Count - // larger than the number of array items that were left to read. - ThrowHelper.ThrowUnexpectedNullRecordCount(); - } - else if (ValuesToRead > 0) - { - info.Stack.Push(info); - } - - AddValue(value); - } - - internal abstract (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType(); -} - -public abstract class ArrayRecord : ArrayRecord -{ - private protected ArrayRecord(ArrayInfo arrayInfo) : base(arrayInfo) - { - } - - /// - /// Allocates an array of and fills it with the data provided in the serialized records (in case of primitive types like or ) or the serialized records themselves. - /// - /// Specifies whether null values are allowed. - /// Specifies the max length of an array that can be allocated. - /// - /// - /// The array has elements and can be used as a vector of attack. - /// Example: an array with elements that contains only nulls - /// takes 15 bytes to serialize and more than 2 GB to deserialize! - /// - /// - /// A new array is allocated every time this method is called. - /// - /// - public T?[] ToArray(bool allowNulls = true, int maxLength = DefaultMaxArrayLength) - { - if (Length > maxLength) - { - ThrowHelper.ThrowMaxArrayLength(maxLength, Length); - } - - return ToArrayOfT(allowNulls); - } - - // PERF: if allocating new arrays is not acceptable, then we could introduce CopyTo method - - private protected override Array Deserialize(Type arrayType, bool allowNulls, int maxLength) - => ToArray(allowNulls, maxLength); - - protected abstract T?[] ToArrayOfT(bool allowNulls); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs deleted file mode 100644 index 98dba85559d..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Single dimensional array of objects. -/// -/// -/// -/// -/// [MS-NRBF] 2.4.3.2 -/// -/// -/// -internal sealed class ArraySingleObjectRecord : ArrayRecord -{ - private ArraySingleObjectRecord(ArrayInfo arrayInfo) : base(arrayInfo) => Records = []; - - public override RecordType RecordType => RecordType.ArraySingleObject; - - private List Records { get; } - - public override bool IsTypeNameMatching(Type type) => type == typeof(object[]); - - internal override bool IsElementType(Type typeElement) => typeElement == typeof(object); - - protected override object?[] ToArrayOfT(bool allowNulls) - { - object?[] values = new object?[Length]; - - for (int recordIndex = 0, valueIndex = 0; recordIndex < Records.Count; recordIndex++) - { - SerializationRecord record = Records[recordIndex]; - - int nullCount = record is NullsRecord nullsRecord ? nullsRecord.NullCount : 0; - if (nullCount == 0) - { - values[valueIndex++] = record is MemberReferenceRecord referenceRecord && referenceRecord.Reference == ObjectId - ? values // a reference to self, and a way to get StackOverflow exception ;) - : record.GetValue(); - continue; - } - - if (!allowNulls) - { - ThrowHelper.ThrowArrayContainedNull(); - } - - do - { - values[valueIndex++] = null; - nullCount--; - } - while (nullCount > 0); - } - - return values; - } - - internal static ArraySingleObjectRecord Parse(BinaryReader reader) - => new(ArrayInfo.Parse(reader)); - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType() - { - // An array of objects can contain any Object or multiple nulls. - const AllowedRecordTypes allowed = AllowedRecordTypes.AnyObject | AllowedRecordTypes.Nulls; - - return (allowed, default); - } - - private protected override void AddValue(object value) => Records.Add((SerializationRecord)value); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs deleted file mode 100644 index cccd839d2dd..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs +++ /dev/null @@ -1,154 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.Globalization; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Single dimensional array of a primitive type. -/// -/// -/// -/// -/// [MS-NRBF] 2.4.3.3 -/// -/// -/// -internal class ArraySinglePrimitiveRecord : ArrayRecord - where T : unmanaged -{ - internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values) : base(arrayInfo) - { - Values = values; - ValuesToRead = 0; // there is nothing to read anymore - } - - public override RecordType RecordType => RecordType.ArraySinglePrimitive; - - internal IReadOnlyList Values { get; } - - public override bool IsTypeNameMatching(Type type) => typeof(T[]) == type; - - internal override bool IsElementType(Type typeElement) => typeElement == typeof(T); - - protected override T[] ToArrayOfT(bool allowNulls) => [.. Values]; - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType() - => throw new InvalidOperationException("This should never happen"); - - private protected override void AddValue(object value) - => throw new InvalidOperationException("This should never happen"); - - internal static IReadOnlyList ReadPrimitiveTypes(BinaryReader reader, int count) - { - if (typeof(T) == typeof(byte)) - { - return (IReadOnlyList)(object)ReadBytes(reader, count); - } - - List values = []; - for (int i = 0; i < count; i++) - { - if (typeof(T) == typeof(bool)) - { - values.Add((T)(object)reader.ReadBoolean()); - } - else if (typeof(T) == typeof(sbyte)) - { - values.Add((T)(object)reader.ReadSByte()); - } - else if (typeof(T) == typeof(char)) - { - values.Add((T)(object)reader.ReadChar()); - } - else if (typeof(T) == typeof(short)) - { - values.Add((T)(object)reader.ReadInt16()); - } - else if (typeof(T) == typeof(ushort)) - { - values.Add((T)(object)reader.ReadUInt16()); - } - else if (typeof(T) == typeof(int)) - { - values.Add((T)(object)reader.ReadInt32()); - } - else if (typeof(T) == typeof(uint)) - { - values.Add((T)(object)reader.ReadUInt32()); - } - else if (typeof(T) == typeof(long)) - { - values.Add((T)(object)reader.ReadInt64()); - } - else if (typeof(T) == typeof(ulong)) - { - values.Add((T)(object)reader.ReadUInt64()); - } - else if (typeof(T) == typeof(float)) - { - values.Add((T)(object)reader.ReadSingle()); - } - else if (typeof(T) == typeof(double)) - { - values.Add((T)(object)reader.ReadDouble()); - } - else if (typeof(T) == typeof(decimal)) - { - values.Add((T)(object)decimal.Parse(reader.ReadString(), CultureInfo.InvariantCulture)); - } - else if (typeof(T) == typeof(DateTime)) - { - values.Add((T)(object)BinaryReaderExtensions.CreateDateTimeFromData(reader.ReadInt64())); - } - else if (typeof(T) == typeof(TimeSpan)) - { - values.Add((T)(object)new TimeSpan(reader.ReadInt64())); - } - else - { - throw new SerializationException($"Failure trying to read primitive '{typeof(T)}'"); - } - } - - return values; - } - - private static IReadOnlyList ReadBytes(BinaryReader reader, int count) - { - // Special casing byte for performance. - if (count <= DefaultMaxArrayLength) - { - return reader.ReadBytes(count); - } - - // But only to certain degree, as the input is untrusted. - List result = new(DefaultMaxArrayLength); - byte[] bytes = ArrayPool.Shared.Rent(DefaultMaxArrayLength); - - while (count > 0) - { - int bytesRead = reader.Read(bytes, 0, Math.Min(count, bytes.Length)); - - if (bytesRead <= 0) - { - ThrowHelper.ThrowEndOfStreamException(); - } - else if (bytesRead == bytes.Length) - { - result.AddRange(bytes); - } - else - { - result.AddRange(new ArraySegment(bytes, 0, bytesRead)); - } - - count -= bytesRead; - } - - ArrayPool.Shared.Return(bytes); - return result; - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs deleted file mode 100644 index 400eef1383b..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Single dimensional array of strings. -/// -/// -/// -/// -/// [MS-NRBF] 2.4.3.4 -/// -/// -/// -internal sealed class ArraySingleStringRecord : ArrayRecord -{ - private ArraySingleStringRecord(ArrayInfo arrayInfo) : base(arrayInfo) => Records = []; - - public override RecordType RecordType => RecordType.ArraySingleString; - - private List Records { get; } - - public override bool IsTypeNameMatching(Type type) => type == typeof(string[]); - - internal override bool IsElementType(Type typeElement) => typeElement == typeof(string); - - internal static ArraySingleStringRecord Parse(BinaryReader reader) - => new(ArrayInfo.Parse(reader)); - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType() - { - // An array of string can consist of string(s), null(s) and reference(s) to string(s). - const AllowedRecordTypes allowedTypes = AllowedRecordTypes.BinaryObjectString - | AllowedRecordTypes.Nulls | AllowedRecordTypes.MemberReference; - - return (allowedTypes, default); - } - - private protected override void AddValue(object value) => Records.Add((SerializationRecord)value); - - protected override string?[] ToArrayOfT(bool allowNulls) - { - string?[] values = new string?[Length]; - - for (int recordIndex = 0, valueIndex = 0; recordIndex < Records.Count; recordIndex++) - { - SerializationRecord record = Records[recordIndex]; - - if (record is MemberReferenceRecord memberReference) - { - record = memberReference.GetReferencedRecord(); - - if (record is not BinaryObjectStringRecord) - { - // TODO: consider throwing this exception as soon as we read the referenced record. - // It would require registering reference validation checks. - throw new SerializationException("The string array contained a reference to non-string."); - } - } - - if (record is BinaryObjectStringRecord stringRecord) - { - values[valueIndex++] = stringRecord.Value; - continue; - } - - if (!allowNulls) - { - throw new SerializationException("The array contained null(s)."); - } - - int nullCount = ((NullsRecord)record).NullCount; - do - { - values[valueIndex++] = null; - nullCount--; - } - while (nullCount > 0); - } - - return values; - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs deleted file mode 100644 index abdb497955c..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs +++ /dev/null @@ -1,217 +0,0 @@ -// 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.Serialization.BinaryFormat; - -internal sealed class BinaryArrayRecord : ArrayRecord -{ - private static HashSet PrimitiveTypes { get; } = - [ - typeof(bool), typeof(char), typeof(byte), typeof(sbyte), - typeof(short), typeof(ushort), typeof(int), typeof(uint), - typeof(long), typeof(ulong), typeof(IntPtr), typeof(UIntPtr), - typeof(float), typeof(double), typeof(decimal), typeof(DateTime), - typeof(TimeSpan), typeof(string), typeof(object) - ]; - - private BinaryArrayRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, RecordMap recordMap) - : base(arrayInfo) - { - MemberTypeInfo = memberTypeInfo; - RecordMap = recordMap; - Values = []; - } - - public override RecordType RecordType => RecordType.BinaryArray; - - private MemberTypeInfo MemberTypeInfo { get; } - - private RecordMap RecordMap { get; } - - private List Values { get; } - - private protected override Array Deserialize(Type arrayType, bool allowNulls, int maxLength) - { - Type elementType = MapElementType(arrayType); - Type actualElementType = arrayType.GetElementType()!; - Array array = Array.CreateInstance(elementType, Length); - - int resultIndex = 0; - foreach (object value in Values) - { - object item = value is MemberReferenceRecord referenceRecord - ? referenceRecord.GetReferencedRecord() - : value; - - if (item is not SerializationRecord record) - { - array.SetValue(item, resultIndex++); - continue; - } - - switch (record.RecordType) - { - case RecordType.BinaryArray: - case RecordType.ArraySinglePrimitive: - case RecordType.ArraySingleObject: - case RecordType.ArraySingleString: - ArrayRecord nestedArrayRecord = (ArrayRecord)record; - Array nestedArray = nestedArrayRecord.ToArray(actualElementType, allowNulls, maxLength); - array.SetValue(nestedArray, resultIndex++); - break; - case RecordType.ObjectNull: - case RecordType.ObjectNullMultiple256: - case RecordType.ObjectNullMultiple: - if (!allowNulls) - { - ThrowHelper.ThrowArrayContainedNull(); - } - - int nullCount = ((NullsRecord)item).NullCount; - do - { - array.SetValue(null, resultIndex++); - nullCount--; - } - while (nullCount > 0); - break; - default: - array.SetValue(record.GetValue(), resultIndex++); - break; - } - } - - return array; - } - - internal static ArrayRecord Parse(BinaryReader reader, RecordMap recordMap, PayloadOptions options) - { - int objectId = reader.ReadInt32(); - - byte typeByte = reader.ReadByte(); - if (typeByte is < 0 or > 5) - { - throw new SerializationException($"Unknown binary array type: {typeByte}"); - } - - ArrayType arrayType = (ArrayType)typeByte; - int rank = reader.ReadInt32(); - - bool isRectangular = arrayType is ArrayType.Rectangular or ArrayType.RectangularOffset; - - if (rank < 1 || rank > 32 - || (rank != 1 && !isRectangular) - || (rank == 1 && isRectangular)) - { - throw new SerializationException($"Invalid array rank ({rank}) for {arrayType}."); - } - - int[] lengths = new int[rank]; // adversary-controlled, but acceptable since upper limit of 32 - for (int i = 0; i < lengths.Length; i++) - { - lengths[i] = ArrayInfo.ParseValidArrayLength(reader); - } - - long totalElementCount = lengths[0]; - for (int i = 1; i < lengths.Length; i++) - { - totalElementCount *= lengths[i]; - - if (totalElementCount > uint.MaxValue) - { - throw new SerializationException("Max array size exceeded"); // max array size exceeded - } - } - - int[] offsets = new int[rank]; // zero-init; adversary-controlled, but acceptable since upper limit of 32 - bool hasCustomOffset = false; - if (arrayType is ArrayType.SingleOffset or ArrayType.JaggedOffset or ArrayType.RectangularOffset) - { - for (int i = 0; i < offsets.Length; i++) - { - int offset = reader.ReadInt32(); - - if (offset < 0) - { - throw new SerializationException("Invalid offset"); - } - else if (offset > 0) - { - hasCustomOffset = true; - - long maxIndex = lengths[i] + offset; - if (maxIndex > int.MaxValue) - { - throw new SerializationException("Invalid length and offset"); - } - } - - offsets[i] = offset; - } - } - - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(reader, 1, options); - ArrayInfo arrayInfo = new(objectId, (uint)totalElementCount, arrayType, rank); - - if (isRectangular || hasCustomOffset) - { - return RectangularOrCustomOffsetArrayRecord.Create(arrayInfo, memberTypeInfo, lengths, offsets, recordMap); - } - - return memberTypeInfo.ShouldBeRepresentedAsArrayOfClassRecords() - ? new ArrayOfClassesRecord(arrayInfo, memberTypeInfo, recordMap) - : new BinaryArrayRecord(arrayInfo, memberTypeInfo, recordMap); - } - - private protected override void AddValue(object value) => Values.Add(value); - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType() - { - (AllowedRecordTypes allowed, PrimitiveType primitiveType) = MemberTypeInfo.GetNextAllowedRecordType(0); - - if (allowed != AllowedRecordTypes.None) - { - // It's an array, it can also contain multiple nulls - return (allowed | AllowedRecordTypes.Nulls, primitiveType); - } - - return (allowed, primitiveType); - } - - internal override bool IsElementType(Type typeElement) - => MemberTypeInfo.IsElementType(typeElement, RecordMap); - - /// - /// Complex types must not be instantiated, but represented as ClassRecord. - /// For arrays of primitive types like int, string and object this method returns the element type. - /// For array of complex types, it returns ClassRecord. - /// It takes arrays of arrays into account: - /// - int[][] => int[] - /// - MyClass[][][] => ClassRecord[][] - /// - private static Type MapElementType(Type arrayType) - { - Type elementType = arrayType; - int arrayNestingDepth = 0; - - while (elementType.IsArray) - { - elementType = elementType.GetElementType()!; - arrayNestingDepth++; - } - - if (PrimitiveTypes.Contains(elementType)) - { - return arrayNestingDepth == 1 ? elementType : arrayType.GetElementType()!; - } - - // Complex types are never instantiated, but represented as ClassRecord - Type complexType = typeof(ClassRecord); - for (int i = 1; i < arrayNestingDepth; i++) - { - complexType = complexType.MakeArrayType(); - } - - return complexType; - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs deleted file mode 100644 index 31f069c5cfa..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Library full name information. -/// -/// -/// -/// -/// [MS-NRBF] 2.6.2 -/// -/// -/// -internal sealed class BinaryLibraryRecord : SerializationRecord -{ - private BinaryLibraryRecord(int libraryId, AssemblyNameInfo libraryName) - { - ObjectId = libraryId; - LibraryName = libraryName; - } - - public override RecordType RecordType => RecordType.BinaryLibrary; - - internal AssemblyNameInfo LibraryName { get; } - - internal override int ObjectId { get; } - - internal static BinaryLibraryRecord Parse(BinaryReader reader) - => new(reader.ReadInt32(), reader.ReadLibraryName()); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs deleted file mode 100644 index 90aab196fde..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// String record. -/// -/// -/// -/// -/// [MS-NRBF] 2.5.7 -/// -/// -/// -[DebuggerDisplay("{Value}, {ObjectId}")] -internal sealed class BinaryObjectStringRecord : PrimitiveTypeRecord -{ - private BinaryObjectStringRecord(int objectId, string value) : base(value) - { - ObjectId = objectId; - } - - public override RecordType RecordType => RecordType.BinaryObjectString; - - internal override int ObjectId { get; } - - internal static BinaryObjectStringRecord Parse(BinaryReader reader) - => new(reader.ReadInt32(), reader.ReadString()); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs deleted file mode 100644 index 298db43295d..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs +++ /dev/null @@ -1,174 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Base class for class records. -/// -/// -/// -/// Includes the values for the class (which trail the record) -/// -/// [MS-NRBF] 2.3 -/// . -/// -/// -public abstract class ClassRecord : SerializationRecord -{ - private const int MaxLength = ArrayRecord.DefaultMaxArrayLength; - - private protected ClassRecord(ClassInfo classInfo) - { - ClassInfo = classInfo; - MemberValues = []; - } - - public TypeName TypeName => ClassInfo.Name; - - public abstract AssemblyNameInfo LibraryName { get; } - - // Currently we don't expose raw values, so we are not preserving the order here. - public IEnumerable MemberNames => ClassInfo.MemberNames.Keys; - - internal override int ObjectId => ClassInfo.ObjectId; - - internal abstract int ExpectedValuesCount { get; } - - internal ClassInfo ClassInfo { get; } - - internal List MemberValues { get; } - - /// - /// Checks if member of given name was present in the payload. - /// - /// The name of the member. - /// True if it was present, otherwise false. - /// - /// - /// It's recommended to use this method when dealing with payload that may contain - /// different versions of the same type.s - /// - /// - public bool HasMember(string memberName) => ClassInfo.MemberNames.ContainsKey(memberName); - - /// - /// Retrieves the value of the provided . - /// - /// The name of the member. - /// The value. - /// Member of such name does not exist. You can use to check if given member exists. - /// Member of such name has value of a different type. - public ClassRecord? GetClassRecord(string memberName) => GetMember(memberName); - - /// - public string? GetString(string memberName) => GetMember(memberName); - /// - public bool GetBoolean(string memberName) => GetMember(memberName); - /// - public byte GetByte(string memberName) => GetMember(memberName); - /// - public sbyte GetSByte(string memberName) => GetMember(memberName); - /// - public short GetInt16(string memberName) => GetMember(memberName); - /// - public ushort GetUInt16(string memberName) => GetMember(memberName); - /// - public char GetChar(string memberName) => GetMember(memberName); - /// - public int GetInt32(string memberName) => GetMember(memberName); - /// - public uint GetUInt32(string memberName) => GetMember(memberName); - /// - public float GetSingle(string memberName) => GetMember(memberName); - /// - public long GetInt64(string memberName) => GetMember(memberName); - /// - public ulong GetUInt64(string memberName) => GetMember(memberName); - /// - public double GetDouble(string memberName) => GetMember(memberName); - /// - public decimal GetDecimal(string memberName) => GetMember(memberName); - /// - public TimeSpan GetTimeSpan(string memberName) => GetMember(memberName); - /// - public DateTime GetDateTime(string memberName) => GetMember(memberName); - - /// - /// For primitive types like , or returns their value. - /// For nulls, returns a null. - /// For other types that are not arrays, returns an instance of . - /// For single-dimensional arrays returns where the generic type is the primitive type or . - /// For jagged and multi-dimensional arrays, returns an instance of . - /// - /// - public object? GetObject(string memberName) => GetMember(memberName); - - /// - /// Retrieves an array for the provided . - /// - /// The name of the field. - /// Specifies whether null values are allowed. - /// Specifies the max length of an array that can be allocated. - /// The array itself or null. - /// Member of such name does not exist. - /// Member of such name has value of a different type. - public T?[]? GetArrayOfPrimitiveType(string memberName, bool allowNulls = true, int maxLength = MaxLength) - => GetMember>(memberName)?.ToArray(allowNulls, maxLength); - - /// - /// Retrieves the of the provided . - /// - /// The name of the field. - /// The serialization record which can be either , - /// a , an or a null. - /// - /// Member of such name does not exist. - /// Member of such name has value of a different type or was a primitive value. - public SerializationRecord? GetSerializationRecord(string memberName) - => MemberValues[ClassInfo.MemberNames[memberName]] switch - { - null or NullsRecord => null, - MemberReferenceRecord referenceRecord => referenceRecord.GetReferencedRecord(), - SerializationRecord serializationRecord => serializationRecord, - _ => throw new InvalidOperationException() - }; - - private T? GetMember(string memberName) - { - int index = ClassInfo.MemberNames[memberName]; - - object? value = MemberValues[index]; - if (value is SerializationRecord record) - { - value = record.GetValue(); - } - - return value is null - ? default - : value is not T ? throw new InvalidOperationException() : (T)value!; - } - - internal abstract (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType(); - - internal override void HandleNextRecord(SerializationRecord nextRecord, NextInfo info) - { - Debug.Assert(!(nextRecord is NullsRecord nullsRecord && nullsRecord.NullCount > 1)); - - HandleNextValue(nextRecord, info); - } - - internal override void HandleNextValue(object value, NextInfo info) - { - MemberValues.Add(value); - - if (MemberValues.Count < ExpectedValuesCount) - { - (AllowedRecordTypes allowed, PrimitiveType primitiveType) = GetNextAllowedRecordType(); - - info.Stack.Push(info.With(allowed, primitiveType)); - } - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs deleted file mode 100644 index a7388255d83..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Class information that references another class record's metadata. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.2.5 -/// -/// -/// -internal sealed class ClassWithIdRecord : ClassRecord -{ - private ClassWithIdRecord(int objectId, ClassRecord metadataClass) : base(metadataClass.ClassInfo) - { - ObjectId = objectId; - MetadataClass = metadataClass; - } - - public override RecordType RecordType => RecordType.ClassWithId; - - public override AssemblyNameInfo LibraryName => MetadataClass.LibraryName; - - internal override int ObjectId { get; } - - internal ClassRecord MetadataClass { get; } - - internal override int ExpectedValuesCount => MetadataClass.ExpectedValuesCount; - - internal static ClassWithIdRecord Parse( - BinaryReader reader, - RecordMap recordMap) - { - int objectId = reader.ReadInt32(); - int metadataId = reader.ReadInt32(); - - if (recordMap[metadataId] is not ClassRecord referencedRecord) - { - throw new SerializationException(); - } - - return new(objectId, referencedRecord); - } - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType() - => MetadataClass switch - { - ClassWithMembersAndTypesRecord classWithMembersAndTypes - => classWithMembersAndTypes.MemberTypeInfo.GetNextAllowedRecordType(MemberValues.Count), - SystemClassWithMembersAndTypesRecord systemClassWithMembersAndTypes - => systemClassWithMembersAndTypes.MemberTypeInfo.GetNextAllowedRecordType(MemberValues.Count), - // ClassWithMembersRecord and SystemClassWithMembersRecord allow for AnyData - _ => (AllowedRecordTypes.AnyObject, PrimitiveType.None) - }; -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs deleted file mode 100644 index 0a6e0863b6f..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Class information with type info and the source library. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.2.1 -/// -/// -/// -internal sealed class ClassWithMembersAndTypesRecord : ClassRecord -{ - private ClassWithMembersAndTypesRecord(ClassInfo classInfo, BinaryLibraryRecord library, MemberTypeInfo memberTypeInfo) - : base(classInfo) - { - Library = library; - MemberTypeInfo = memberTypeInfo; - } - - public override RecordType RecordType => RecordType.ClassWithMembersAndTypes; - - public override AssemblyNameInfo LibraryName => Library.LibraryName; - - internal BinaryLibraryRecord Library { get; } - - internal MemberTypeInfo MemberTypeInfo { get; } - - internal override int ExpectedValuesCount => MemberTypeInfo.Infos.Count; - - public override bool IsTypeNameMatching(Type type) - => FormatterServices.GetTypeFullNameIncludingTypeForwards(type) == ClassInfo.Name.FullName - && FormatterServices.GetAssemblyNameIncludingTypeForwards(type) == Library.LibraryName.FullName; - - internal static ClassWithMembersAndTypesRecord Parse(BinaryReader reader, RecordMap recordMap, PayloadOptions options) - { - ClassInfo classInfo = ClassInfo.Parse(reader, options); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(reader, classInfo.MemberNames.Count, options); - int libraryId = reader.ReadInt32(); - - BinaryLibraryRecord library = (BinaryLibraryRecord)recordMap[libraryId]; - - return new(classInfo, library, memberTypeInfo); - } - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType() - => MemberTypeInfo.GetNextAllowedRecordType(MemberValues.Count); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs deleted file mode 100644 index 308979a36c4..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// Class information with the source library. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.2.2 -/// -/// -/// -internal sealed class ClassWithMembersRecord : ClassRecord -{ - private ClassWithMembersRecord(ClassInfo classInfo, BinaryLibraryRecord library) : base(classInfo) - { - Library = library; - } - - public override RecordType RecordType => RecordType.ClassWithMembers; - - public override AssemblyNameInfo LibraryName => Library.LibraryName; - - internal BinaryLibraryRecord Library { get; } - - internal override int ExpectedValuesCount => ClassInfo.MemberNames.Count; - - public override bool IsTypeNameMatching(Type type) - => FormatterServices.GetTypeFullNameIncludingTypeForwards(type) == ClassInfo.Name.FullName - && FormatterServices.GetAssemblyNameIncludingTypeForwards(type) == Library.LibraryName.FullName; - - internal static ClassWithMembersRecord Parse(BinaryReader reader, RecordMap recordMap, PayloadOptions options) - { - ClassInfo classInfo = ClassInfo.Parse(reader, options); - int libraryId = reader.ReadInt32(); - - BinaryLibraryRecord library = (BinaryLibraryRecord)recordMap[libraryId]; - - return new(classInfo, library); - } - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType() - => (AllowedRecordTypes.AnyObject, PrimitiveType.None); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs deleted file mode 100644 index d4853c4288d..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Primitive value other than . -/// -/// -/// -/// -/// [MS-NRBF] 2.5.1 -/// -/// -/// -internal sealed class MemberPrimitiveTypedRecord : PrimitiveTypeRecord - where T : unmanaged -{ - internal MemberPrimitiveTypedRecord(T value, bool pretend = false) : base(value) - { - RecordType = pretend ? RecordType.MemberPrimitiveTyped : RecordType.SystemClassWithMembersAndTypes; - } - - public override RecordType RecordType { get; } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs deleted file mode 100644 index 29a5db28c55..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// The record contains a reference to another record that contains the actual value. -/// -/// -/// -/// -/// [MS-NRBF] 2.5.3 -/// -/// -/// -internal sealed class MemberReferenceRecord : SerializationRecord -{ - // This type has no ObjectId, so it's impossible to create a reference to a reference - // and get into issues with cycles or unbounded recursion. - private MemberReferenceRecord(int reference, RecordMap recordMap) - { - Reference = reference; - RecordMap = recordMap; - } - - public override RecordType RecordType => RecordType.MemberReference; - - internal int Reference { get; } - - private RecordMap RecordMap { get; } - - internal override object? GetValue() => GetReferencedRecord().GetValue(); - - public override bool IsTypeNameMatching(Type type) => RecordMap[Reference].IsTypeNameMatching(type); - - internal static MemberReferenceRecord Parse(BinaryReader reader, RecordMap recordMap) - => new(reader.ReadInt32(), recordMap); - - internal SerializationRecord GetReferencedRecord() => RecordMap[Reference]; -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs deleted file mode 100644 index 8779aa26e68..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Record that marks the end of the binary format stream. -/// -/// -/// -/// -/// [MS-NRBF] 2.6.3 -/// -/// -/// -internal sealed class MessageEndRecord : SerializationRecord -{ - internal static MessageEndRecord Singleton { get; } = new(); - - private MessageEndRecord() - { - } - - public override RecordType RecordType => RecordType.MessageEnd; -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs deleted file mode 100644 index e14d9f61c2d..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs +++ /dev/null @@ -1,9 +0,0 @@ -// 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.Serialization.BinaryFormat; - -internal abstract class NullsRecord : SerializationRecord -{ - internal abstract int NullCount { get; } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs deleted file mode 100644 index 73b6c0f3de0..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Multiple null object record (less than 256). -/// -/// -/// -/// -/// [MS-NRBF] 2.5.5 -/// -/// -/// -internal sealed class ObjectNullMultiple256Record : NullsRecord -{ - private ObjectNullMultiple256Record(byte count) => NullCount = count; - - public override RecordType RecordType => RecordType.ObjectNullMultiple256; - - internal override int NullCount { get; } - - internal static ObjectNullMultiple256Record Parse(BinaryReader reader) - => new(reader.ReadByte()); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs deleted file mode 100644 index 824c69fac4d..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Multiple null object record. -/// -/// -/// -/// -/// [MS-NRBF] 2.5.5 -/// -/// -/// -internal sealed class ObjectNullMultipleRecord : NullsRecord -{ - private ObjectNullMultipleRecord(int count) => NullCount = count; - - public override RecordType RecordType => RecordType.ObjectNullMultiple; - - internal override int NullCount { get; } - - internal static ObjectNullMultipleRecord Parse(BinaryReader reader) - { - int count = reader.ReadInt32(); - if (count <= byte.MaxValue) // BinaryFormatter would have used ObjectNullMultiple256Record - { - throw new SerializationException($"Unexpected Null Record count: {count}"); - } - - return new(count); - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs deleted file mode 100644 index deec1c92b8a..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Null object record. -/// -/// -/// -/// -/// [MS-NRBF] 2.5.4 -/// -/// -/// -internal sealed class ObjectNullRecord : NullsRecord -{ - internal static ObjectNullRecord Instance { get; } = new(); - - public override RecordType RecordType => RecordType.ObjectNull; - - internal override int NullCount => 1; - - public override bool IsTypeNameMatching(Type type) => !type.IsValueType; // TODO: ensure this is expected - - internal override object? GetValue() => null; -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs deleted file mode 100644 index 8e546c1f97e..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// A record that represents the primitive value of type. -/// -/// Primitive type. -/// -/// -/// can be one of the following types: -/// , , , -/// , , , -/// , , , , -/// , , , -/// or . -/// -/// Other serialization records are represented with or . -/// -[DebuggerDisplay("{Value}")] -public abstract class PrimitiveTypeRecord : SerializationRecord -{ - private protected PrimitiveTypeRecord(T value) => Value = value; - - public T Value { get; } - - public override bool IsTypeNameMatching(Type type) => type == typeof(T); - - internal override object? GetValue() => Value; -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs deleted file mode 100644 index 60767f24a6e..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace System.Runtime.Serialization.BinaryFormat; - -internal sealed class RectangularOrCustomOffsetArrayRecord : ArrayRecord -{ - private RectangularOrCustomOffsetArrayRecord(Type elementType, ArrayInfo arrayInfo, - MemberTypeInfo memberTypeInfo, int[] lengths, int[] offsets, RecordMap recordMap) : base(arrayInfo) - { - ElementType = elementType; - MemberTypeInfo = memberTypeInfo; - Lengths = lengths; - Offsets = offsets; - RecordMap = recordMap; - Values = new(); - } - - public override RecordType RecordType => RecordType.BinaryArray; - - private Type ElementType { get; } - - private MemberTypeInfo MemberTypeInfo { get; } - - private int[] Lengths { get; } - - private int[] Offsets { get; } - - private RecordMap RecordMap { get; } - - // This is the only array type that may have more elements than Array.MaxLength, - // that is why we use Linked instead of regular List here. - // TODO: verify my assumptions as I doubt them myself - private LinkedList Values { get; } - - internal override bool IsElementType(Type typeElement) - => MemberTypeInfo.IsElementType(typeElement, RecordMap); - - private protected override Array Deserialize(Type arrayType, bool allowNulls, int maxLength) - { - if (Length > maxLength) - { - ThrowHelper.ThrowMaxArrayLength(maxLength, Length); - } - - Array result = Array.CreateInstance(ElementType, Lengths, Offsets); - -#if !NET8_0_OR_GREATER - int[] indices = new int[Offsets.Length]; - Offsets.CopyTo(indices, 0); // respect custom offsets - - foreach (object value in Values) - { - result.SetValue(GetActualValue(value), indices); - - int dimension = indices.Length - 1; - while (dimension >= 0) - { - indices[dimension]++; - if (indices[dimension] < Offsets[dimension] + Lengths[dimension]) - { - break; - } - indices[dimension] = Offsets[dimension]; - dimension--; - } - - if (dimension < 0) - { - break; - } - } - - return result; -#else - // I took this idea from Array.CoreCLR that maps an array of int indices into - // an internal flat index. - // Yes, I know I'll most likely burn in hell for doing that. - if (ElementType.IsValueType) - { - if (ElementType == typeof(bool)) CopyTo(Values, result); - else if (ElementType == typeof(byte)) CopyTo(Values, result); - else if (ElementType == typeof(sbyte)) CopyTo(Values, result); - else if (ElementType == typeof(short)) CopyTo(Values, result); - else if (ElementType == typeof(ushort)) CopyTo(Values, result); - else if (ElementType == typeof(char)) CopyTo(Values, result); - else if (ElementType == typeof(int)) CopyTo(Values, result); - else if (ElementType == typeof(float)) CopyTo(Values, result); - else if (ElementType == typeof(long)) CopyTo(Values, result); - else if (ElementType == typeof(ulong)) CopyTo(Values, result); - else if (ElementType == typeof(double)) CopyTo(Values, result); - else if (ElementType == typeof(TimeSpan)) CopyTo(Values, result); - else if (ElementType == typeof(DateTime)) CopyTo(Values, result); - else if (ElementType == typeof(decimal)) CopyTo(Values, result); - } - else - { - ref byte arrayDataRef = ref MemoryMarshal.GetArrayDataReference(result); - ref object elementRef = ref Unsafe.As(ref arrayDataRef); - nuint flattenedIndex = 0; - foreach (object value in Values) - { - ref object offsetElementRef = ref Unsafe.Add(ref elementRef, flattenedIndex); - offsetElementRef = GetActualValue(value)!; - flattenedIndex++; - } - } - - return result; - - static void CopyTo(LinkedList list, Array array) where T : unmanaged - { - ref byte arrayDataRef = ref MemoryMarshal.GetArrayDataReference(array); - ref T elementRef = ref Unsafe.As(ref arrayDataRef); - nuint flattenedIndex = 0; - foreach (object value in list) - { - ref T targetIndex = ref Unsafe.Add(ref elementRef, flattenedIndex); - targetIndex = (T)GetActualValue(value)!; - flattenedIndex++; - } - } -#endif - } - - private protected override void AddValue(object value) => Values.AddLast(value); - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetAllowedRecordType() - { - (AllowedRecordTypes allowed, PrimitiveType primitiveType) = MemberTypeInfo.GetNextAllowedRecordType(0); - - if (allowed != AllowedRecordTypes.None) - { - // It's an array, it can also contain multiple nulls - return (allowed | AllowedRecordTypes.Nulls, primitiveType); - } - - return (allowed, primitiveType); - } - - internal static RectangularOrCustomOffsetArrayRecord Create(ArrayInfo arrayInfo, - MemberTypeInfo memberTypeInfo, int[] lengths, int[] offsets, RecordMap recordMap) - { - return memberTypeInfo.Infos[0].BinaryType switch - { - BinaryType.Primitive => new(MapPrimitive((PrimitiveType)memberTypeInfo.Infos[0].AdditionalInfo!), arrayInfo, memberTypeInfo, lengths, offsets, recordMap), - BinaryType.String => new(typeof(string), arrayInfo, memberTypeInfo, lengths, offsets, recordMap), - BinaryType.Object => new(typeof(object), arrayInfo, memberTypeInfo, lengths, offsets, recordMap), - BinaryType.SystemClass or BinaryType.Class => new(typeof(ClassRecord), arrayInfo, memberTypeInfo, lengths, offsets, recordMap), - _ => throw ThrowHelper.InvalidBinaryType(memberTypeInfo.Infos[0].BinaryType), - }; - } - - private static Type MapPrimitive(PrimitiveType primitiveType) - => primitiveType switch - { - PrimitiveType.Boolean => typeof(bool), - PrimitiveType.Byte => typeof(byte), - PrimitiveType.Char => typeof(char), - PrimitiveType.Decimal => typeof(decimal), - PrimitiveType.Double => typeof(double), - PrimitiveType.Int16 => typeof(short), - PrimitiveType.Int32 => typeof(int), - PrimitiveType.Int64 => typeof(long), - PrimitiveType.SByte => typeof(sbyte), - PrimitiveType.Single => typeof(float), - PrimitiveType.TimeSpan => typeof(TimeSpan), - PrimitiveType.DateTime => typeof(DateTime), - PrimitiveType.UInt16 => typeof(ushort), - PrimitiveType.UInt32 => typeof(uint), - PrimitiveType.UInt64 => typeof(ulong), - _ => throw ThrowHelper.InvalidPrimitiveType(primitiveType), - }; - - private static object? GetActualValue(object value) - => value is SerializationRecord serializationRecord - ? serializationRecord.GetValue() - : value; // it must be a primitive type -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs deleted file mode 100644 index 3157e90c086..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Abstract class that represents the serialization record. -/// -/// -/// -/// Every instance returned to the end user can be either , -/// a or an . -/// -/// -[DebuggerDisplay("{RecordType}, {ObjectId}")] -public abstract class SerializationRecord -{ - internal const int NoId = 0; - - internal SerializationRecord() // others can't derive from this type - { - } - - public abstract RecordType RecordType { get; } - - internal virtual int ObjectId => NoId; - - /// - /// Compares the type and assembly name read from the payload against the specified type. - /// - /// - /// It takes type forwarding into account. - /// It does NOT take into account member names and their types. - /// - /// The to compare against. - /// True if the serialized type and assembly name match provided type. - public virtual bool IsTypeNameMatching(Type type) => false; - - /// - /// Gets the primitive, string or null record value. - /// For reference records, it returns the referenced record. - /// For other records, it returns the records themselves. - /// - internal virtual object? GetValue() => this; - - internal virtual void HandleNextRecord(SerializationRecord nextRecord, NextInfo info) - => throw new InvalidOperationException("This should never happen"); - - internal virtual void HandleNextValue(object value, NextInfo info) - => throw new InvalidOperationException("This should never happen"); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs deleted file mode 100644 index 290914d655b..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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.Serialization.BinaryFormat; - -/// -/// Binary format header. -/// -/// -/// -/// -/// [MS-NRBF] 2.6.1 -/// -/// -/// -internal sealed class SerializedStreamHeaderRecord : SerializationRecord -{ - internal const int Size = sizeof(int) * 4; - - internal SerializedStreamHeaderRecord(int rootId, int headerId, int majorVersion, int minorVersion) - { - RootId = rootId; - HeaderId = headerId; - MajorVersion = majorVersion; - MinorVersion = minorVersion; - } - - public override RecordType RecordType => RecordType.SerializedStreamHeader; - - internal int RootId { get; } - - internal int HeaderId { get; } - - internal int MajorVersion { get; } - - internal int MinorVersion { get; } - - internal static SerializedStreamHeaderRecord Parse(BinaryReader reader) - { - int rootId = reader.ReadInt32(); - int headerId = reader.ReadInt32(); - int majorVersion = reader.ReadInt32(); - int minorVersion = reader.ReadInt32(); - - if (majorVersion != 1 || minorVersion != 0) - { - throw new SerializationException(); - } - - return new(rootId, headerId, majorVersion, minorVersion); - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs deleted file mode 100644 index be40332d8f2..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -internal sealed class SystemClassWithMembersAndTypesRecord : ClassRecord -{ - private SystemClassWithMembersAndTypesRecord(ClassInfo classInfo, MemberTypeInfo memberTypeInfo) - : base(classInfo) - { - MemberTypeInfo = memberTypeInfo; - } - - public override RecordType RecordType => RecordType.SystemClassWithMembersAndTypes; - - public override AssemblyNameInfo LibraryName => FormatterServices.CoreLibAssemblyName; - - public MemberTypeInfo MemberTypeInfo { get; } - - internal override int ExpectedValuesCount => MemberTypeInfo.Infos.Count; - - public override bool IsTypeNameMatching(Type type) - => type.Assembly == typeof(object).Assembly - && FormatterServices.GetTypeFullNameIncludingTypeForwards(type) == ClassInfo.Name.FullName; - - internal static SystemClassWithMembersAndTypesRecord Parse(BinaryReader reader, PayloadOptions options) - { - ClassInfo classInfo = ClassInfo.Parse(reader, options); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(reader, classInfo.MemberNames.Count, options); - // the only difference with ClassWithMembersAndTypesRecord is that we don't read library id here - return new(classInfo, memberTypeInfo); - } - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType() - => MemberTypeInfo.GetNextAllowedRecordType(MemberValues.Count); - - // For the root records that turn out to be primitive types, we map them to - // PrimitiveTypeRecord so the users don't need to learn the BF internals - // to get a single primitive value! - internal SerializationRecord TryToMapToUserFriendly() - { - // It could be implemented with way fewer ifs, but perf is important - // and we want to bail out as soon as possible, but also don't convert something - // that is not an exact match. - if (MemberValues.Count == 1 && HasMember("m_value")) - { - return MemberValues[0] switch - { - // there can be a value match, but no TypeName match - bool value when IsTypeNameMatching(typeof(bool)) => Create(value), - byte value when IsTypeNameMatching(typeof(byte)) => Create(value), - sbyte value when IsTypeNameMatching(typeof(sbyte)) => Create(value), - char value when IsTypeNameMatching(typeof(char)) => Create(value), - short value when IsTypeNameMatching(typeof(short)) => Create(value), - ushort value when IsTypeNameMatching(typeof(ushort)) => Create(value), - int value when IsTypeNameMatching(typeof(int)) => Create(value), - uint value when IsTypeNameMatching(typeof(uint)) => Create(value), - long value when IsTypeNameMatching(typeof(long)) => Create(value), - ulong value when IsTypeNameMatching(typeof(ulong)) => Create(value), - float value when IsTypeNameMatching(typeof(float)) => Create(value), - double value when IsTypeNameMatching(typeof(double)) => Create(value), - _ => this - }; - } - else if (MemberValues.Count == 1 && HasMember("_ticks") && MemberValues[0] is long ticks - && IsTypeNameMatching(typeof(TimeSpan))) - { - return Create(new TimeSpan(ticks)); - } - else if (MemberValues.Count == 2 - && HasMember("ticks") && HasMember("dateData") - && MemberValues[0] is long value && MemberValues[1] is ulong - && IsTypeNameMatching(typeof(DateTime))) - { - return Create(BinaryReaderExtensions.CreateDateTimeFromData(value)); - } - else if(MemberValues.Count == 4 - && HasMember("lo") && HasMember("mid") && HasMember("hi") && HasMember("flags") - && MemberValues[0] is int && MemberValues[1] is int && MemberValues[2] is int && MemberValues[3] is int - && IsTypeNameMatching(typeof(decimal))) - { - int[] bits = - [ - GetInt32("lo"), - GetInt32("mid"), - GetInt32("hi"), - GetInt32("flags") - ]; - - return Create(new decimal(bits)); - } - - return this; - - static SerializationRecord Create(T value) where T : unmanaged - => new MemberPrimitiveTypedRecord(value, pretend: true); - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs deleted file mode 100644 index 2c231d78cce..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; - -namespace System.Runtime.Serialization.BinaryFormat; - -/// -/// System class information. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.2.4 -/// -/// -/// -internal sealed class SystemClassWithMembersRecord : ClassRecord -{ - private SystemClassWithMembersRecord(ClassInfo classInfo) : base(classInfo) - { - } - - public override RecordType RecordType => RecordType.SystemClassWithMembers; - - public override AssemblyNameInfo LibraryName => FormatterServices.CoreLibAssemblyName; - - internal override int ExpectedValuesCount => ClassInfo.MemberNames.Count; - - public override bool IsTypeNameMatching(Type type) - => type.Assembly == typeof(object).Assembly - && FormatterServices.GetTypeFullNameIncludingTypeForwards(type) == ClassInfo.Name.FullName; - - internal static SystemClassWithMembersRecord Parse(BinaryReader reader, PayloadOptions options) - { - ClassInfo classInfo = ClassInfo.Parse(reader, options); - // the only difference with ClassWithMembersRecord is that we don't read library id here - return new(classInfo); - } - - internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetNextAllowedRecordType() - => (AllowedRecordTypes.AnyObject, PrimitiveType.None); -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs deleted file mode 100644 index 285d99046a4..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Reflection.Metadata; -using System.Runtime.CompilerServices; - -namespace System.Runtime.Serialization.BinaryFormat; - -internal static class BinaryReaderExtensions -{ - /// - /// Reads a primitive of from the given . - /// - internal static object ReadPrimitiveType(this BinaryReader reader, PrimitiveType primitiveType) - => primitiveType switch - { - PrimitiveType.Boolean => reader.ReadBoolean(), - PrimitiveType.Byte => reader.ReadByte(), - PrimitiveType.SByte => reader.ReadSByte(), - PrimitiveType.Char => reader.ReadChar(), - PrimitiveType.Int16 => reader.ReadInt16(), - PrimitiveType.UInt16 => reader.ReadUInt16(), - PrimitiveType.Int32 => reader.ReadInt32(), - PrimitiveType.UInt32 => reader.ReadUInt32(), - PrimitiveType.Int64 => reader.ReadInt64(), - PrimitiveType.UInt64 => reader.ReadUInt64(), - PrimitiveType.Single => reader.ReadSingle(), - PrimitiveType.Double => reader.ReadDouble(), - PrimitiveType.Decimal => decimal.Parse(reader.ReadString(), CultureInfo.InvariantCulture), - PrimitiveType.DateTime => CreateDateTimeFromData(reader.ReadInt64()), - PrimitiveType.TimeSpan => new TimeSpan(reader.ReadInt64()), - // String is handled with a record, never on it's own - _ => throw new SerializationException($"Failure trying to read primitive '{primitiveType}'"), - }; - - // TODO: fix https://github.com/adamsitnik/SafePayloadReader/issues/2 - /// - /// Creates a object from raw data with validation. - /// - /// was invalid. - internal static DateTime CreateDateTimeFromData(long data) - { - // Copied from System.Runtime.Serialization.Formatters.Binary.BinaryParser - - // Use DateTime's public constructor to validate the input, but we - // can't return that result as it strips off the kind. To address - // that, store the value directly into a DateTime via an unsafe cast. - // See BinaryFormatterWriter.WriteDateTime for details. - - try - { - const long TicksMask = 0x3FFFFFFFFFFFFFFF; - _ = new DateTime(data & TicksMask); - } - catch (ArgumentException ex) - { - // Bad data - throw new SerializationException(ex.Message, ex); - } - - return Unsafe.As(ref data); - } - - internal static TypeName ReadTypeName(this BinaryReader binaryReader, PayloadOptions options) - { - string name = binaryReader.ReadString(); - if (!TypeName.TryParse(name.AsSpan(), out TypeName? typeName, options.TypeNameParseOptions)) - { - throw new SerializationException($"Invalid type name: '{name}'"); - } - else if (typeName.AssemblyName is not null) - { - throw new SerializationException("Type names must not contain assembly names"); - } - - return typeName; - } - - internal static AssemblyNameInfo ReadLibraryName(this BinaryReader binaryReader) - { - string name = binaryReader.ReadString(); - if (!AssemblyNameInfo.TryParse(name.AsSpan(), out AssemblyNameInfo? libraryName)) - { - throw new SerializationException($"Invalid library name: '{name}'"); - } - - return libraryName; - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs deleted file mode 100644 index fcb487e625d..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Metadata; -using System.Runtime.CompilerServices; -using System.Text; - -namespace System.Runtime.Serialization.BinaryFormat; - -// code copied from libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs -// we need it to handle TypeForwardedFromAttribute! -internal static class FormatterServices -{ - private static AssemblyNameInfo? s_coreLibAssemblyName; - - internal static AssemblyNameInfo CoreLibAssemblyName => s_coreLibAssemblyName ??= AssemblyNameInfo.Parse(GetAssemblyNameIncludingTypeForwards(typeof(object)).AsSpan()); - - internal static string GetAssemblyNameIncludingTypeForwards(Type type) - { - // Special case types like arrays - Type attributedType = type; - while (attributedType.HasElementType) - { - attributedType = attributedType.GetElementType()!; - } - - foreach (Attribute first in attributedType.GetCustomAttributes(typeof(TypeForwardedFromAttribute), false)) - { - return ((TypeForwardedFromAttribute)first).AssemblyFullName; - } - - return type.Assembly.FullName!; - } - - internal static string GetTypeFullNameIncludingTypeForwards(Type type) - { - return type.IsArray ? - GetClrTypeFullNameForArray(type) : - GetClrTypeFullNameForNonArrayTypes(type); - } - - private static string GetClrTypeFullNameForArray(Type type) - { - int rank = type.GetArrayRank(); - - string typeName = GetTypeFullNameIncludingTypeForwards(type.GetElementType()!); - return rank == 1 ? - typeName + "[]" : - typeName + "[" + new string(',', rank - 1) + "]"; - } - - private static string GetClrTypeFullNameForNonArrayTypes(Type type) - { - if (!type.IsGenericType) - { - return type.FullName!; - } - - var builder = new StringBuilder(type.GetGenericTypeDefinition().FullName).Append('['); - - foreach (Type genericArgument in type.GetGenericArguments()) - { - builder.Append('[').Append(GetTypeFullNameIncludingTypeForwards(genericArgument)).Append(", "); - builder.Append(GetAssemblyNameIncludingTypeForwards(genericArgument)).Append("],"); - } - - // remove the last comma and close typename for generic with a close bracket - return builder.Remove(builder.Length - 1, 1).Append(']').ToString(); - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs deleted file mode 100644 index 7478ce7c998..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO.Hashing; -using System.Runtime.InteropServices; - -namespace System.Runtime.Serialization.BinaryFormat; - -internal sealed class RecordMap -{ - private readonly List _records = []; // TODO: verify whether we actually need that - private readonly Dictionary _map = new(CollisionResistantInt32Comparer.Instance); - - internal void Add(SerializationRecord record) - { - _records.Add(record); - - // From https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/0a192be0-58a1-41d0-8a54-9c91db0ab7bf: - // "If the ObjectId is not referenced by any MemberReference in the serialization stream, - // then the ObjectId SHOULD be positive, but MAY be negative." - if (record.ObjectId != SerializationRecord.NoId) - { - // use Add on purpose, so in case of duplicate Ids we get an exception - _map.Add(record.ObjectId, record); - } - } - - internal SerializationRecord this[int objectId] => _map[objectId]; - - // keys (32-bit integer ids) are adversary-provided so we need a collision-resistant comparer - private sealed class CollisionResistantInt32Comparer : IEqualityComparer - { - internal static CollisionResistantInt32Comparer Instance { get; } = new(); - - private CollisionResistantInt32Comparer() { } - - public bool Equals(int x, int y) => x == y; - - public int GetHashCode(int obj) - { -#if NETCOREAPP - Span integers = new(ref obj); -#else - Span integers = stackalloc int[1] { obj }; -#endif - return (int)XxHash32.HashToUInt32(MemoryMarshal.AsBytes(integers)); - } - } -} diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs b/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs deleted file mode 100644 index 365ae58745c..00000000000 --- a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.Serialization.BinaryFormat; - -internal static class ThrowHelper -{ - internal static void ThrowUnexpectedNullRecordCount() - => throw new SerializationException("Unexpected Null Record count."); - - internal static Exception InvalidPrimitiveType(PrimitiveType primitiveType) - => new SerializationException($"Invalid primitive type: {primitiveType}"); - - internal static Exception InvalidBinaryType(BinaryType binaryType) - => new SerializationException($"Invalid binary type: {binaryType}"); - - internal static void ThrowMaxArrayLength(int limit, uint actual) - => throw new SerializationException( - $"The serialized array length ({actual}) was larger that the configured limit {limit}"); - - internal static void ThrowArrayContainedNull() - => throw new SerializationException("The array contained null(s)"); - - internal static void ThrowTypeMismatch(Type expected) - => throw new SerializationException($"The payload was expected to contain an instance of {expected}, while it contained TODO"); - - internal static void ThrowEndOfStreamException() - => throw new EndOfStreamException(); -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecordExtensions.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecordExtensions.cs deleted file mode 100644 index 67132012e83..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecordExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization; - -namespace System.Windows.Forms.BinaryFormat; - -internal static class ArrayRecordExtensions -{ - internal static Array GetPrimitiveArray(this IPrimitiveTypeRecord primitiveArray) => primitiveArray.PrimitiveType switch - { - PrimitiveType.Boolean => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Byte => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Char => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Decimal => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Double => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Int16 => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Int32 => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Int64 => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.SByte => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.Single => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.TimeSpan => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.DateTime => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.UInt16 => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.UInt32 => ((ArrayRecord)primitiveArray).AsArray(), - PrimitiveType.UInt64 => ((ArrayRecord)primitiveArray).AsArray(), - _ => throw new SerializationException($"Unexpected primitive array type: '{primitiveArray.PrimitiveType}'") - }; - - /// - /// Convert the source to an array, if it is not already an array. - /// - private static Array AsArray(this ArrayRecord record) where T : unmanaged - { - if (record.ArrayObjects is not T[] rawArray) - { - Debug.Fail("Should not have any primitive arrays that are not already arrays."); - throw new InvalidOperationException(); - } - - if (record is not IBinaryArray binaryArray || binaryArray.ArrayType is BinaryArrayType.Single or BinaryArrayType.Jagged) - { - return rawArray; - } - - if (binaryArray.ArrayType is not BinaryArrayType.Rectangular) - { - // This should not be possible. - throw new NotSupportedException(); - } - - Array array = Array.CreateInstance(typeof(T), binaryArray.Lengths.ToArray()); - Span flatSpan = array.GetArrayData(); - rawArray.AsSpan().CopyTo(flatSpan); - return array; - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.ObjectBinaryArray.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.ObjectBinaryArray.cs deleted file mode 100644 index 59a7f64d03e..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.ObjectBinaryArray.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -internal static partial class BinaryArray -{ - /// - /// of objects. - /// - /// - private sealed class ObjectBinaryArray : ArrayRecord, IRecord, IBinaryArray - { - public Count Rank { get; } - public BinaryArrayType ArrayType { get; } - public MemberTypeInfo TypeInfo { get; } - public IReadOnlyList Lengths { get; } - - internal ObjectBinaryArray( - Count rank, - BinaryArrayType type, - IReadOnlyList lengths, - ArrayInfo arrayInfo, - MemberTypeInfo typeInfo, - BinaryFormattedObject.IParseState state) - : base(arrayInfo, ReadObjectArrayValues(state, typeInfo[0].Type, typeInfo[0].Info, arrayInfo.Length)) - { - Rank = rank; - ArrayType = type; - TypeInfo = typeInfo; - Lengths = lengths; - } - - public override void Write(BinaryWriter writer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.PrimitiveBinaryArray.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.PrimitiveBinaryArray.cs deleted file mode 100644 index cb58010edb2..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.PrimitiveBinaryArray.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -internal static partial class BinaryArray -{ - /// - /// of primitive values. - /// - /// - private sealed class PrimitiveBinaryArray : ArrayRecord, IRecord>, IBinaryArray, IPrimitiveTypeRecord where T : unmanaged - { - public Count Rank { get; } - public BinaryArrayType ArrayType { get; } - public MemberTypeInfo TypeInfo { get; } - public IReadOnlyList Lengths { get; } - public PrimitiveType PrimitiveType { get; } - - internal PrimitiveBinaryArray( - Count rank, - BinaryArrayType arrayType, - IReadOnlyList lengths, - ArrayInfo arrayInfo, - MemberTypeInfo typeInfo, - BinaryReader reader) - : base(arrayInfo, reader.ReadPrimitiveArray(arrayInfo.Length)) - { - Rank = rank; - ArrayType = arrayType; - TypeInfo = typeInfo; - Lengths = lengths; - PrimitiveType = (PrimitiveType)typeInfo[0].Info!; - } - - public override void Write(BinaryWriter writer) - { - throw new NotSupportedException(); - } - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.cs deleted file mode 100644 index 80fbd1ce039..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization; - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Array of objects. -/// -/// -/// -/// -/// [MS-NRBF] 2.4.3.1 -/// -/// -/// -internal static partial class BinaryArray -{ - private const int MaxRanks = 32; - - public static RecordType RecordType => RecordType.BinaryArray; - - internal static IBinaryArray Parse(BinaryFormattedObject.IParseState state) - { - Id objectId = state.Reader.ReadInt32(); - BinaryArrayType arrayType = (BinaryArrayType)state.Reader.ReadByte(); - - if (arrayType is not (BinaryArrayType.Single or BinaryArrayType.Rectangular or BinaryArrayType.Jagged)) - { - if (arrayType is BinaryArrayType.SingleOffset or BinaryArrayType.RectangularOffset or BinaryArrayType.JaggedOffset) - { - throw new NotSupportedException("Offset arrays are not supported."); - } - - throw new SerializationException("Invalid array type."); - } - - int rank = state.Reader.ReadInt32(); - - if (arrayType is BinaryArrayType.Single or BinaryArrayType.Jagged) - { - // Jagged array is an array of arrays, there should always be one rank - // for the "outer" array. - if (rank != 1) - { - throw new SerializationException("Invalid array rank."); - } - } - else if (arrayType is BinaryArrayType.Rectangular) - { - // Multidimensional array - if (rank is < 2 or > MaxRanks) - { - throw new SerializationException("Invalid array rank."); - } - } - - int[] lengths = new int[rank]; - int length; - - if (arrayType is not BinaryArrayType.Rectangular) - { - length = state.Reader.ReadInt32(); - if (length < 0) - { - throw new SerializationException("Invalid array length."); - } - - lengths[0] = length; - } - else - { - length = 1; - - for (int i = 0; i < rank; i++) - { - int rankLength = state.Reader.ReadInt32(); - if (rankLength < 0) - { - throw new SerializationException("Invalid array length."); - } - - // Rectangular (multidimensional) length is product of lengths. - // - // It is technically possible to have multidimensional arrays with - // a total length over int.MaxValue. Even with just bytes this would - // be a very large array (2GB). To constrain this reader we'll reject - // anything that goes over this. - length = checked(length * rankLength); - lengths[i] = rankLength; - } - } - - MemberTypeInfo typeInfo = MemberTypeInfo.Parse(state.Reader, 1); - (BinaryType type, object? info) = typeInfo[0]; - - IBinaryArray array = type is not BinaryType.Primitive - ? new ObjectBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state) - : (PrimitiveType)info! switch - { - PrimitiveType.Boolean => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Byte => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.SByte => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Char => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Int16 => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.UInt16 => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Int32 => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.UInt32 => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Int64 => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.UInt64 => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Single => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Double => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.Decimal => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.DateTime => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - PrimitiveType.TimeSpan => new PrimitiveBinaryArray(rank, arrayType, lengths, new(objectId, length), typeInfo, state.Reader), - _ => throw new SerializationException($"Invalid primitive type '{(PrimitiveType)info}'"), - }; - - return array; - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.IParseState.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.IParseState.cs index 944556e9f92..36d9fb4a6c9 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.IParseState.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.IParseState.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; + namespace System.Windows.Forms.BinaryFormat; internal sealed partial class BinaryFormattedObject @@ -11,7 +13,7 @@ internal sealed partial class BinaryFormattedObject internal interface IParseState { BinaryReader Reader { get; } - RecordMap RecordMap { get; } + IReadOnlyDictionary RecordMap { get; } Options Options { get; } ITypeResolver TypeResolver { get; } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ITypeResolver.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ITypeResolver.cs index 9be78764f7e..b49cd736672 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ITypeResolver.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ITypeResolver.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Reflection.Metadata; + namespace System.Windows.Forms.BinaryFormat; internal sealed partial class BinaryFormattedObject @@ -13,9 +15,8 @@ internal interface ITypeResolver /// /// Resolves the given type name against the specified library. /// - /// The library id, or for the "system" assembly. [RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type GetType(string typeName, Id libraryId); + Type GetType(TypeName typeName); } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ParseState.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ParseState.cs index 683f9d91883..05aaaf56450 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ParseState.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.ParseState.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; + namespace System.Windows.Forms.BinaryFormat; internal sealed partial class BinaryFormattedObject @@ -19,7 +21,7 @@ public ParseState(BinaryReader reader, BinaryFormattedObject format) } public BinaryReader Reader { get; } - public RecordMap RecordMap => _format._recordMap; + public IReadOnlyDictionary RecordMap => _format.RecordMap; public Options Options => _format._options; public ITypeResolver TypeResolver => _format.TypeResolver; } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.TypeResolver.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.TypeResolver.cs index ceb5d0ed4eb..ba371cfa387 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.TypeResolver.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.TypeResolver.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; +using System.Reflection.Metadata; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; @@ -15,54 +16,31 @@ internal sealed class DefaultTypeResolver : ITypeResolver { private readonly FormatterAssemblyStyle _assemblyMatching; private readonly SerializationBinder? _binder; - private readonly IReadOnlyRecordMap _recordMap; - // This has to be Id as we use Id.Null for mscorlib types. - private readonly Dictionary _assemblies = []; - private readonly Dictionary<(string TypeName, Id LibraryId), Type> _types = []; + private readonly Dictionary _assemblies = []; + private readonly Dictionary _types = []; - private string? _lastTypeName; - private Id _lastLibraryId; - - [AllowNull] - private Type _lastType; - - internal DefaultTypeResolver(Options options, IReadOnlyRecordMap recordMap) + internal DefaultTypeResolver(Options options) { _assemblyMatching = options.AssemblyMatching; _binder = options.Binder; - _assemblies[Id.Null] = TypeInfo.MscorlibAssembly; - _recordMap = recordMap; } /// /// Resolves the given type name against the specified library. /// - /// The library id, or for the "system" assembly. [RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - Type ITypeResolver.GetType(string typeName, Id libraryId) + Type ITypeResolver.GetType(TypeName typeName) { - if (libraryId == _lastLibraryId && string.Equals(typeName, _lastTypeName)) - { - Debug.Assert(_lastType is not null); - return _lastType; - } + Debug.Assert(typeName.AssemblyName is not null); - _lastLibraryId = libraryId; - _lastTypeName = typeName; - - if (_types.TryGetValue((typeName, libraryId), out Type? cachedType)) + if (_types.TryGetValue(typeName.AssemblyQualifiedName, out Type? cachedType)) { - _lastType = cachedType; return cachedType; } - string libraryName = libraryId.IsNull - ? TypeInfo.MscorlibAssemblyName - : ((BinaryLibrary)_recordMap[libraryId]).LibraryName; - - if (_binder?.BindToType(libraryName, typeName) is Type binderType) + if (_binder?.BindToType(typeName.AssemblyName.FullName, typeName.FullName) is Type binderType) { // BinaryFormatter is inconsistent about what caching behavior you get with binders. // It would always cache the last item from the binder, but wouldn't put the result @@ -70,16 +48,13 @@ Type ITypeResolver.GetType(string typeName, Id libraryId) // always return the same result for a given set of strings. Choosing to always cache // for performance. - _types[(typeName, libraryId)] = binderType; - _lastType = binderType; + _types[typeName.AssemblyQualifiedName] = binderType; return binderType; } - if (!_assemblies.TryGetValue(libraryId, out Assembly? assembly)) + if (!_assemblies.TryGetValue(typeName.AssemblyName.FullName, out Assembly? assembly)) { - Debug.Assert(!libraryId.IsNull); - - AssemblyName assemblyName = new(libraryName); + AssemblyName assemblyName = typeName.AssemblyName.ToAssemblyName(); try { assembly = Assembly.Load(assemblyName); @@ -94,20 +69,19 @@ Type ITypeResolver.GetType(string typeName, Id libraryId) assembly = Assembly.Load(assemblyName.Name!); } - _assemblies.Add(libraryId, assembly); + _assemblies.Add(typeName.AssemblyName.FullName, assembly); } Type? type = _assemblyMatching != FormatterAssemblyStyle.Simple - ? assembly.GetType(typeName) + ? assembly.GetType(typeName.FullName) : GetSimplyNamedTypeFromAssembly(assembly, typeName); - _types[(typeName, libraryId)] = type ?? throw new SerializationException($"Could not find type '{typeName}'."); - _lastType = type; - return _lastType; + _types[typeName.AssemblyQualifiedName] = type ?? throw new SerializationException($"Could not find type '{typeName}'."); + return type; } [RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String, Boolean, Boolean)")] - private static Type? GetSimplyNamedTypeFromAssembly(Assembly assembly, string typeName) + private static Type? GetSimplyNamedTypeFromAssembly(Assembly assembly, TypeName typeName) { // Catching any exceptions that could be thrown from a failure on assembly load // This is necessary, for example, if there are generic parameters that are qualified @@ -115,14 +89,14 @@ Type ITypeResolver.GetType(string typeName, Id libraryId) try { - return assembly.GetType(typeName, throwOnError: false, ignoreCase: false); + return assembly.GetType(typeName.FullName, throwOnError: false, ignoreCase: false); } catch (TypeLoadException) { } catch (FileNotFoundException) { } catch (FileLoadException) { } catch (BadImageFormatException) { } - return Type.GetType(typeName, ResolveSimpleAssemblyName, new TopLevelAssemblyTypeResolver(assembly).ResolveType, throwOnError: false); + return Type.GetType(typeName.FullName, ResolveSimpleAssemblyName, new TopLevelAssemblyTypeResolver(assembly).ResolveType, throwOnError: false); static Assembly? ResolveSimpleAssemblyName(AssemblyName assemblyName) { diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.cs index 6b77160a4e7..efca16830d1 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObject.cs @@ -4,7 +4,7 @@ using System.Reflection; using System.Runtime.ExceptionServices; using System.Runtime.Serialization; -using System.Text; +using System.Formats.Nrbf; namespace System.Windows.Forms.BinaryFormat; @@ -29,42 +29,20 @@ internal sealed partial class BinaryFormattedObject private static readonly Options s_defaultOptions = new(); private readonly Options _options; - private readonly RecordMap _recordMap = new(); - private ITypeResolver? _typeResolver; - private ITypeResolver TypeResolver => _typeResolver ??= new DefaultTypeResolver(_options, _recordMap); - - private readonly Id _rootRecord; + private ITypeResolver TypeResolver => _typeResolver ??= new DefaultTypeResolver(_options); /// /// Creates by parsing . /// public BinaryFormattedObject(Stream stream, Options? options = null) { - ArgumentNullException.ThrowIfNull(stream); - using BinaryReader reader = new(stream, Encoding.UTF8, leaveOpen: true); - _options = options ?? s_defaultOptions; - ParseState state = new(reader, this); - - IRecord? currentRecord; - try { - currentRecord = Record.ReadBinaryFormatRecord(state); - if (currentRecord is not SerializationHeader header) - { - throw new SerializationException("Did not find serialization header."); - } - - _rootRecord = header.RootId; - - do - { - currentRecord = Record.ReadBinaryFormatRecord(state); - } - while (currentRecord is not MessageEnd); + RootRecord = NrbfDecoder.Decode(stream, out var readonlyRecordMap, leaveOpen: true); + RecordMap = readonlyRecordMap; } catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException) { @@ -85,7 +63,7 @@ public object Deserialize() { try { - return Deserializer.Deserializer.Deserialize(RootRecord.Id, _recordMap, TypeResolver, _options); + return Deserializer.Deserializer.Deserialize(RootRecord.Id, RecordMap, TypeResolver, _options); } catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException) { @@ -101,13 +79,13 @@ public object Deserialize() /// /// The Id of the root record of the object graph. /// - public IRecord RootRecord => _recordMap[_rootRecord]; + public SerializationRecord RootRecord { get; } /// /// Gets a record by it's identifier. Not all records have identifiers, only ones that /// can be referenced by other records. /// - public IRecord this[Id id] => _recordMap[id]; + public SerializationRecord this[SerializationRecordId id] => RecordMap[id]; - public IReadOnlyRecordMap RecordMap => _recordMap; + public IReadOnlyDictionary RecordMap { get; } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObjectExtensions.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObjectExtensions.cs index 819a14576b9..f32907e9b42 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObjectExtensions.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormattedObjectExtensions.cs @@ -3,33 +3,12 @@ using System.Collections; using System.Drawing; -using System.Runtime.CompilerServices; +using System.Formats.Nrbf; namespace System.Windows.Forms.BinaryFormat; internal static class BinaryFormattedObjectExtensions { - /// - /// Type names for that are raw primitives. - /// - private static bool IsPrimitiveTypeClassName(ReadOnlySpan typeName) - => TypeInfo.GetPrimitiveType(typeName) switch - { - PrimitiveType.Boolean => true, - PrimitiveType.Byte => true, - PrimitiveType.Char => true, - PrimitiveType.Double => true, - PrimitiveType.Int32 => true, - PrimitiveType.Int64 => true, - PrimitiveType.SByte => true, - PrimitiveType.Single => true, - PrimitiveType.Int16 => true, - PrimitiveType.UInt16 => true, - PrimitiveType.UInt32 => true, - PrimitiveType.UInt64 => true, - _ => false, - }; - internal delegate bool TryGetDelegate(BinaryFormattedObject format, [NotNullWhen(true)] out object? value); internal static bool TryGet(TryGetDelegate get, BinaryFormattedObject format, [NotNullWhen(true)] out object? value) @@ -58,16 +37,15 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? va { value = default; - if (format.RootRecord is not ClassWithMembersAndTypes classInfo - || format[classInfo.LibraryId] is not BinaryLibrary binaryLibrary - || binaryLibrary.LibraryName != TypeInfo.SystemDrawingAssemblyName - || classInfo.Name != typeof(PointF).FullName - || classInfo.MemberValues.Count != 2) + if (format.RootRecord is not ClassRecord classInfo + || !classInfo.TypeNameMatches(typeof(PointF)) + || !classInfo.HasMember("x") + || !classInfo.HasMember("y")) { return false; } - value = new PointF((float)classInfo["x"]!, (float)classInfo["y"]!); + value = new PointF(classInfo.GetSingle("x"), classInfo.GetSingle("y")); return true; } @@ -84,27 +62,28 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? va { value = default; - if (format.RootRecord is not ClassWithMembersAndTypes classInfo - || format[classInfo.LibraryId] is not BinaryLibrary binaryLibrary - || binaryLibrary.LibraryName != TypeInfo.SystemDrawingAssemblyName - || classInfo.Name != typeof(RectangleF).FullName - || classInfo.MemberValues.Count != 4) + if (format.RootRecord is not ClassRecord classInfo + || !classInfo.TypeNameMatches(typeof(RectangleF)) + || !classInfo.HasMember("x") + || !classInfo.HasMember("y") + || !classInfo.HasMember("width") + || !classInfo.HasMember("height")) { return false; } value = new RectangleF( - (float)classInfo["x"]!, - (float)classInfo["y"]!, - (float)classInfo["width"]!, - (float)classInfo["height"]!); + classInfo.GetSingle("x"), + classInfo.GetSingle("y"), + classInfo.GetSingle("width"), + classInfo.GetSingle("height")); return true; } } /// - /// Trys to get this object as a primitive type or string. + /// Tries to get this object as a primitive type or string. /// /// if this represented a primitive type or string. public static bool TryGetPrimitiveType(this BinaryFormattedObject format, [NotNullWhen(true)] out object? value) @@ -113,64 +92,49 @@ public static bool TryGetPrimitiveType(this BinaryFormattedObject format, [NotNu static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? value) { - value = default; - - if (format.RootRecord is BinaryObjectString binaryString) + if (format.RootRecord.RecordType is SerializationRecordType.BinaryObjectString) { - value = binaryString.Value; + value = ((PrimitiveTypeRecord)format.RootRecord).Value; return true; } - - if (format.RootRecord is not SystemClassWithMembersAndTypes systemClass) - { - return false; - } - - if (IsPrimitiveTypeClassName(systemClass.Name) && systemClass.MemberTypeInfo[0].Type == BinaryType.Primitive) + else if (format.RootRecord.RecordType is SerializationRecordType.MemberPrimitiveTyped) { - value = systemClass.MemberValues[0]!; + value = GetMemberPrimitiveTypedValue(format.RootRecord); return true; } - if (systemClass.Name == typeof(TimeSpan).FullName) - { - value = new TimeSpan((long)systemClass.MemberValues[0]!); - return true; - } - - switch (systemClass.Name) - { - case TypeInfo.TimeSpanType: - value = new TimeSpan((long)systemClass.MemberValues[0]!); - return true; - case TypeInfo.DateTimeType: - ulong ulongValue = (ulong)systemClass["dateData"]!; - value = Unsafe.As(ref ulongValue); - return true; - case TypeInfo.DecimalType: - ReadOnlySpan bits = - [ - (int)systemClass["lo"]!, - (int)systemClass["mid"]!, - (int)systemClass["hi"]!, - (int)systemClass["flags"]! - ]; - - value = new decimal(bits); - return true; - case TypeInfo.IntPtrType: - // Rehydrating still throws even though casting doesn't any more - value = checked((nint)(long)systemClass.MemberValues[0]!); - return true; - case TypeInfo.UIntPtrType: - value = checked((nuint)(ulong)systemClass.MemberValues[0]!); - return true; - default: - return false; - } + value = null; + return false; } } + internal static object GetMemberPrimitiveTypedValue(this SerializationRecord record) + { + Debug.Assert(record.RecordType is SerializationRecordType.MemberPrimitiveTyped); + + return record switch + { + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + PrimitiveTypeRecord primitive => primitive.Value, + _ => ((PrimitiveTypeRecord)record).Value + }; + } + /// /// Tries to get this object as a of . /// @@ -182,78 +146,38 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? li { list = null; - const string ListTypeName = "System.Collections.Generic.List`1[["; - - if (format.RootRecord is not SystemClassWithMembersAndTypes classInfo - || !classInfo.Name.StartsWith(ListTypeName, StringComparison.Ordinal) - || classInfo["_items"] is not MemberReference reference - || format[reference] is not ArrayRecord array) - { - return false; - } - - int commaIndex = classInfo.Name.IndexOf(','); - if (commaIndex == -1) - { - return false; - } - - ReadOnlySpan typeName = classInfo.Name.AsSpan()[ListTypeName.Length..commaIndex]; - PrimitiveType primitiveType = TypeInfo.GetPrimitiveType(typeName); - - int size; - try - { - // Lists serialize the entire backing array. - if ((size = (int)classInfo["_size"]!) > array.Length) - { - return false; - } - } - catch (KeyNotFoundException) - { - return false; - } - - switch (primitiveType) - { - case default(PrimitiveType): - return false; - case PrimitiveType.String: - if (array is ArraySingleString stringArray) - { - List stringList = new(size); - stringList.AddRange((IEnumerable)stringArray.GetStringValues(format.RecordMap)); - list = stringList; - return true; - } - - return false; - } - - if (array is not IPrimitiveTypeRecord primitiveArray || primitiveArray.PrimitiveType != primitiveType) + if (format.RootRecord is not ClassRecord classInfo + || !classInfo.HasMember("_items") + || !classInfo.HasMember("_size") + || classInfo.GetRawValue("_size") is not int size + || !classInfo.TypeName.IsConstructedGenericType + || classInfo.TypeName.GetGenericTypeDefinition().Name != typeof(List<>).Name + || classInfo.TypeName.GetGenericArguments().Length != 1 + || classInfo.GetRawValue("_items") is not ArrayRecord arrayRecord + || !IsPrimitiveArrayRecord(arrayRecord)) { return false; } // BinaryFormatter serializes the entire backing array, so we need to trim it down to the size of the list. - list = primitiveType switch + list = arrayRecord switch { - PrimitiveType.Boolean => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Byte => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Char => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Decimal => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Double => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Int16 => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Int32 => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Int64 => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.SByte => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.Single => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.TimeSpan => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.DateTime => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.UInt16 => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.UInt32 => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), - PrimitiveType.UInt64 => ((ArrayRecord)array).ArrayObjects.CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), + SZArrayRecord ar => ar.GetArray().CreateTrimmedList(size), _ => throw new InvalidOperationException() }; @@ -272,36 +196,27 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? va { value = null; - if (format.RootRecord is not SystemClassWithMembersAndTypes classInfo - || classInfo.Name != typeof(ArrayList).FullName - || format[2] is not ArraySingleObject array) - { - return false; - } - - int size; - try - { - // Lists serialize the entire backing array. - if ((size = (int)classInfo["_size"]!) > array.Length) - { - return false; - } - } - catch (KeyNotFoundException) + if (format.RootRecord is not ClassRecord classInfo + || !classInfo.TypeNameMatches(typeof(ArrayList)) + || !classInfo.HasMember("_items") + || !classInfo.HasMember("_size") + || classInfo.GetRawValue("_size") is not int size + || classInfo.GetRawValue("_items") is not SZArrayRecord arrayRecord + || size > arrayRecord.Length) { return false; } ArrayList arrayList = new(size); + object?[] array = arrayRecord.GetArray(); for (int i = 0; i < size; i++) { - if (!format.TryGetPrimitiveRecordValueOrNull((IRecord)array[i]!, out object? item)) + if (array[i] is SerializationRecord) { return false; } - arrayList.Add(item); + arrayList.Add(array[i]); } value = arrayList; @@ -318,41 +233,31 @@ public static bool TryGetPrimitiveArray(this BinaryFormattedObject format, [NotN static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? value) { - value = null; - if (format.RootRecord is not ArrayRecord array) + if (!IsPrimitiveArrayRecord(format.RootRecord)) { + value = null; return false; } - if (array is ArraySingleString stringArray) + value = format.RootRecord switch { - value = stringArray.GetStringValues(format.RecordMap).ToArray(); - return true; - } - - if (array is not IPrimitiveTypeRecord primitiveArray) - { - return false; - } - - value = primitiveArray.PrimitiveType switch - { - PrimitiveType.Boolean => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Byte => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Char => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Decimal => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Double => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Int16 => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Int32 => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Int64 => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.SByte => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.Single => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.TimeSpan => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.DateTime => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.UInt16 => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.UInt32 => ((ArrayRecord)primitiveArray).ArrayObjects, - PrimitiveType.UInt64 => ((ArrayRecord)primitiveArray).ArrayObjects, - _ => null + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + SZArrayRecord ar => ar.GetArray(), + _ => throw new InvalidOperationException() }; return value is not null; @@ -380,22 +285,29 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? ha { hashtable = null; - // Note that hashtables with custom comparers and/or hash code providers will have that information before - // the value pair arrays. - if (format.RootRecord is not SystemClassWithMembersAndTypes classInfo - || classInfo.Name != TypeInfo.HashtableType - || format[2] is not ArraySingleObject keys - || format[3] is not ArraySingleObject values - || keys.Length != values.Length) + if (format.RootRecord.RecordType != SerializationRecordType.SystemClassWithMembersAndTypes + || format.RootRecord is not ClassRecord classInfo + || !classInfo.TypeNameMatches(typeof(Hashtable)) + || !classInfo.HasMember("Keys") + || !classInfo.HasMember("Values") + // Note that hashtables with custom comparers and/or hash code providers will have non null Comparer + || classInfo.GetSerializationRecord("Comparer") is not null + || classInfo.GetSerializationRecord("Keys") is not SZArrayRecord keysRecord + || classInfo.GetSerializationRecord("Values") is not SZArrayRecord valuesRecord + || keysRecord.Length != valuesRecord.Length) { return false; } - Hashtable temp = new(keys.Length); + Hashtable temp = new(keysRecord.Length); + object?[] keys = keysRecord.GetArray(); + object?[] values = valuesRecord.GetArray(); for (int i = 0; i < keys.Length; i++) { - if (!format.TryGetPrimitiveRecordValue((IRecord?)keys[i], out object? key) - || !format.TryGetPrimitiveRecordValueOrNull((IRecord?)values[i], out object? value)) + object? key = keys[i]; + object? value = values[i]; + + if (key is null or SerializationRecord || value is SerializationRecord) { return false; } @@ -408,43 +320,6 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? ha } } - /// - /// Tries to get the value for the given if it represents a - /// that isn't . - /// - public static bool TryGetPrimitiveRecordValue( - this BinaryFormattedObject format, - IRecord? record, - [NotNullWhen(true)] out object? value) - { - format.TryGetPrimitiveRecordValueOrNull(record, out value); - return value is not null; - } - - /// - /// Tries to get the value for the given if it represents a . - /// - public static bool TryGetPrimitiveRecordValueOrNull( - this BinaryFormattedObject format, - IRecord? record, - out object? value) - { - value = null; - if (record is ObjectNull or null) - { - return true; - } - - value = format.RecordMap.Dereference(record) switch - { - BinaryObjectString valueString => valueString.Value, - MemberPrimitiveTyped primitive => primitive.Value, - _ => null, - }; - - return value is not null; - } - /// /// Tries to get this object as a binary formatted . /// @@ -458,13 +333,13 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? ex { exception = null; - if (format.RootRecord is not SystemClassWithMembersAndTypes classInfo - || classInfo.Name != TypeInfo.NotSupportedExceptionType) + if (format.RootRecord is not ClassRecord classInfo + || classInfo.TypeNameMatches(typeof(NotSupportedException))) { return false; } - exception = new NotSupportedException(classInfo["Message"]!.ToString()); + exception = new NotSupportedException(classInfo.GetString("Message")); return true; } } @@ -484,23 +359,6 @@ public static bool TryGetFrameworkObject( || format.TryGetPointF(out value) || format.TryGetNotSupportedException(out value); - /// - /// Dereferences records. - /// - public static IRecord Dereference(this IReadOnlyRecordMap recordMap, IRecord record) => record switch - { - MemberReference reference => recordMap[reference.IdRef], - _ => record - }; - - /// - /// Gets strings from a . - /// - public static IEnumerable GetStringValues(this ArraySingleString array, IReadOnlyRecordMap recordMap) - => array.ArrayObjects.Select(record => - record is null ? null : recordMap.Dereference((IRecord)record) switch - { - BinaryObjectString stringRecord => stringRecord.Value, - _ => null - }); + private static bool IsPrimitiveArrayRecord(SerializationRecord serializationRecord) + => serializationRecord.RecordType is SerializationRecordType.ArraySingleString or SerializationRecordType.ArraySinglePrimitive; } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecordExtensions.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecordExtensions.cs deleted file mode 100644 index 721b01ccf77..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecordExtensions.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.CompilerServices; - -namespace System.Windows.Forms.BinaryFormat; - -internal static class ClassRecordExtensions -{ - private static bool IsPrimitiveType(this SystemClassWithMembersAndTypes systemClass) => - systemClass.IsPrimitiveTypeClassName() && systemClass.MemberTypeInfo[0].Type == BinaryType.Primitive; - - private static bool IsPrimitiveTypeClassName(this SystemClassWithMembersAndTypes systemClass) => TypeInfo.GetPrimitiveType(systemClass.Name) switch - { - PrimitiveType.Boolean => true, - PrimitiveType.Byte => true, - PrimitiveType.Char => true, - PrimitiveType.Double => true, - PrimitiveType.Int32 => true, - PrimitiveType.Int64 => true, - PrimitiveType.SByte => true, - PrimitiveType.Single => true, - PrimitiveType.Int16 => true, - PrimitiveType.UInt16 => true, - PrimitiveType.UInt32 => true, - PrimitiveType.UInt64 => true, - _ => false, - }; - - internal static bool TryGetSystemPrimitive(this SystemClassWithMembersAndTypes systemClass, [NotNullWhen(true)] out object? value) - { - value = null; - - if (systemClass.IsPrimitiveType()) - { - value = systemClass.MemberValues[0]; - Debug.Assert(value is not null); - return true; - } - - if (systemClass.Name == typeof(TimeSpan).FullName) - { - value = new TimeSpan((long)systemClass.MemberValues[0]!); - return true; - } - - switch (systemClass.Name) - { - case TypeInfo.TimeSpanType: - value = new TimeSpan((long)systemClass.MemberValues[0]!); - return true; - case TypeInfo.DateTimeType: - ulong ulongValue = (ulong)systemClass["dateData"]!; - value = Unsafe.As(ref ulongValue); - return true; - case TypeInfo.DecimalType: - ReadOnlySpan bits = - [ - (int)systemClass["lo"]!, - (int)systemClass["mid"]!, - (int)systemClass["hi"]!, - (int)systemClass["flags"]! - ]; - - value = new decimal(bits); - return true; - case TypeInfo.IntPtrType: - // Rehydrating still throws even though casting doesn't any more - value = checked((nint)(long)systemClass.MemberValues[0]!); - return true; - case TypeInfo.UIntPtrType: - value = checked((nuint)(ulong)systemClass.MemberValues[0]!); - return true; - default: - return false; - } - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembers.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembers.cs deleted file mode 100644 index 56ef428f1eb..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembers.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Class information with the source library. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.2.2 -/// -/// -/// -internal sealed class ClassWithMembers : ClassRecord, IRecord, IBinaryFormatParseable -{ - public override Id LibraryId { get; } - - private ClassWithMembers(ClassInfo classInfo, Id libraryId, MemberTypeInfo memberTypeInfo, IReadOnlyList memberValues) - : base(classInfo, memberTypeInfo, memberValues) - { - LibraryId = libraryId; - } - - public static RecordType RecordType => RecordType.ClassWithMembers; - - static ClassWithMembers IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - ClassInfo classInfo = ClassInfo.Parse(state.Reader, out _); - Id libraryId = state.Reader.ReadInt32(); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.CreateFromClassInfoAndLibrary(state, classInfo, libraryId); - return new( - classInfo, - libraryId, - memberTypeInfo, - ReadObjectMemberValues(state, memberTypeInfo)); - } - - public override void Write(BinaryWriter writer) - { - // Really shouldn't be writing this record type. It isn't as safe as the typed variant - // and saves very little space. - throw new NotSupportedException(); - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs index 89c544f49ef..21856ec7cf2 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayRecordDeserializer.cs @@ -1,72 +1,57 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.Serialization; +using System.Formats.Nrbf; namespace System.Windows.Forms.BinaryFormat.Deserializer; internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer { - private readonly ArrayRecord _arrayRecord; - private readonly BinaryArrayType _arrayType; + private readonly ArrayRecord _arrayRecord; private readonly Type _elementType; - private readonly Array _array; + private readonly Array _arrayOfClassRecords; + private readonly Array _arrayOfT; private int _index; private bool _hasFixups; - [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(String, Id)")] - internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer) + [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] + internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer) : base(arrayRecord, deserializer) { - _arrayRecord = arrayRecord; + // Other array types are handled directly (ArraySinglePrimitive and ArraySingleString). + Debug.Assert(arrayRecord.RecordType is not (SerializationRecordType.ArraySingleString or SerializationRecordType.ArraySinglePrimitive)); - if (arrayRecord is not IBinaryArray binaryArray) + _arrayRecord = arrayRecord; + _elementType = deserializer.TypeResolver.GetType(arrayRecord.TypeName.GetElementType()); + Type expectedArrayType = arrayRecord.Rank switch { - // Other array types are handled directly (ArraySinglePrimitive and ArraySingleString). - Debug.Assert(arrayRecord is ArraySingleObject); - _elementType = typeof(object); - _arrayType = BinaryArrayType.Single; - _array = Array.CreateInstance(_elementType, arrayRecord.Length); - } - else + 1 => _elementType.MakeArrayType(), + _ => _elementType.MakeArrayType(arrayRecord.Rank), + }; + // Tricky part: for arrays of classes/structs the following record allocates and array of class records + // (because the payload reader can not load types, instantiate objects and rehydrate them) + _arrayOfClassRecords = arrayRecord.GetArray(expectedArrayType); + // Now we need to create an array of the same length, but of a different, exact type + Type elementType = _arrayOfClassRecords.GetType(); + while (elementType.IsArray) { - (BinaryType binaryType, object? info) = binaryArray.TypeInfo[0]; - - _elementType = binaryType switch - { - BinaryType.SystemClass => info is Type type - ? type - : deserializer.TypeResolver.GetType((string)info!, Id.Null), - BinaryType.Class => info is Type type - ? type - : deserializer.TypeResolver.GetType( - ((ClassTypeInfo)info!).TypeName, - ((ClassTypeInfo)info!).LibraryId), - BinaryType.String => typeof(string), - // Jagged array - BinaryType.PrimitiveArray => ((PrimitiveType)info!).GetArrayPrimitiveTypeType(), - _ => throw new SerializationException($"Unexpected BinaryArray type: {_elementType}") - }; - - _arrayType = binaryArray.ArrayType; - if (_arrayType is not (BinaryArrayType.Single or BinaryArrayType.Jagged or BinaryArrayType.Rectangular)) - { - throw new NotSupportedException("Only arrays with zero offsets are supported."); - } + elementType = elementType.GetElementType()!; + } - _array = _arrayType is BinaryArrayType.Rectangular - ? Array.CreateInstance(_elementType, binaryArray.Lengths.ToArray()) - : Array.CreateInstance(_elementType, arrayRecord.Length); + int[] lengths = new int[arrayRecord.Rank]; + for (int dimension = 0; dimension < lengths.Length; dimension++) + { + lengths[dimension] = _arrayOfClassRecords.GetLength(dimension); } - Object = _array; + Object = _arrayOfT = Array.CreateInstance(_elementType, lengths); } - internal override Id Continue() + internal override SerializationRecordId Continue() { - while (_index < _arrayRecord.Length) + while (_index < _arrayOfT.Length) { - (object? memberValue, Id reference) = UnwrapMemberValue(_arrayRecord.ArrayObjects[_index]); + (object? memberValue, SerializationRecordId reference) = UnwrapMemberValue(_arrayOfClassRecords.GetArrayData()[_index]); if (s_missingValueSentinel == memberValue) { @@ -78,7 +63,7 @@ internal override Id Continue() { // Need to track a fixup for this index. _hasFixups = true; - Deserializer.PendValueUpdater(new ArrayUpdater(_arrayRecord.ObjectId, reference, _index)); + Deserializer.PendValueUpdater(new ArrayUpdater(_arrayRecord.Id, reference, _index)); } if (memberValue is null && !_elementType.IsValueType) @@ -90,11 +75,11 @@ internal override Id Continue() if (_elementType.IsValueType) { - _array.SetArrayValueByFlattenedIndex(memberValue, _index); + _arrayOfT.SetArrayValueByFlattenedIndex(memberValue, _index); } else { - Span flatSpan = _array.GetArrayData(); + Span flatSpan = _arrayOfT.GetArrayData(); flatSpan[_index] = memberValue; } @@ -105,9 +90,63 @@ internal override Id Continue() if (!_hasFixups) { - Deserializer.CompleteObject(_arrayRecord.ObjectId); + Deserializer.CompleteObject(_arrayRecord.Id); + } + + return default; + } + + internal static Array GetArraySinglePrimitive(SerializationRecord record) => record switch + { + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + SZArrayRecord primitiveArray => primitiveArray.GetArray(), + _ => throw new NotSupportedException(), + }; + + [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] + internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, BinaryFormattedObject.ITypeResolver typeResolver) + { + Type arrayRecordElementType = typeResolver.GetType(arrayRecord.TypeName.GetElementType()); + Type elementType = arrayRecordElementType; + while (elementType.IsArray) + { + elementType = elementType.GetElementType()!; } - return Id.Null; + if (!(HasBuiltInSupport(elementType) + || (Nullable.GetUnderlyingType(elementType) is Type nullable && HasBuiltInSupport(nullable)))) + { + return null; + } + + Type expectedArrayType = arrayRecord.Rank switch + { + 1 => arrayRecordElementType.MakeArrayType(), + _ => arrayRecordElementType.MakeArrayType(arrayRecord.Rank), + }; + + return arrayRecord.GetArray(expectedArrayType); + + static bool HasBuiltInSupport(Type elementType) + => elementType == typeof(string) + || elementType == typeof(bool) || elementType == typeof(byte) || elementType == typeof(sbyte) + || elementType == typeof(char) || elementType == typeof(short) || elementType == typeof(ushort) + || elementType == typeof(int) || elementType == typeof(uint) + || elementType == typeof(long) || elementType == typeof(ulong) + || elementType == typeof(float) || elementType == typeof(double) || elementType == typeof(decimal) + || elementType == typeof(DateTime) || elementType == typeof(TimeSpan); } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayUpdater.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayUpdater.cs index 566449979c6..602a278362c 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayUpdater.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ArrayUpdater.cs @@ -1,18 +1,20 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; + namespace System.Windows.Forms.BinaryFormat.Deserializer; internal sealed class ArrayUpdater : ValueUpdater { private readonly int _index; - internal ArrayUpdater(int objectId, int valueId, int index) : base(objectId, valueId) + internal ArrayUpdater(SerializationRecordId objectId, SerializationRecordId valueId, int index) : base(objectId, valueId) { _index = index; } - internal override void UpdateValue(IDictionary objects) + internal override void UpdateValue(IDictionary objects) { object value = objects[ValueId]; Array array = (Array)objects[ObjectId]; diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordDeserializer.cs index da07b4257cf..e238d2d3882 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordDeserializer.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Formats.Nrbf; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -33,11 +34,11 @@ private protected ClassRecordDeserializer( _onlyAllowPrimitives = @object is IObjectReference; } - [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(String, Id)")] + [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] internal static ObjectRecordDeserializer Create(ClassRecord classRecord, IDeserializer deserializer) { - Type type = deserializer.TypeResolver.GetType(classRecord.Name, classRecord.LibraryId); - Id id = classRecord.ObjectId; + Type type = deserializer.TypeResolver.GetType(classRecord.TypeName); + SerializationRecordId id = classRecord.Id; ISerializationSurrogate? surrogate = deserializer.GetSurrogate(type); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordFieldInfoDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordFieldInfoDeserializer.cs index 57a86acc5c0..1557fa64898 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordFieldInfoDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordFieldInfoDeserializer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; @@ -14,14 +15,14 @@ namespace System.Windows.Forms.BinaryFormat.Deserializer; /// internal sealed class ClassRecordFieldInfoDeserializer : ClassRecordDeserializer { - private readonly ClassRecord _classRecord; + private readonly Formats.Nrbf.ClassRecord _classRecord; private readonly MemberInfo[] _fieldInfo; private int _currentFieldIndex; private readonly bool _isValueType; private bool _hasFixups; internal ClassRecordFieldInfoDeserializer( - ClassRecord classRecord, + Formats.Nrbf.ClassRecord classRecord, object @object, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, @@ -33,7 +34,7 @@ internal ClassRecordFieldInfoDeserializer( _isValueType = type.IsValueType; } - internal override Id Continue() + internal override SerializationRecordId Continue() { // When directly setting fields we need to populate fields with primitive types before we // can add the object to the deserialized object list to handle value types. This ensures @@ -48,12 +49,7 @@ internal override Id Continue() { // FormatterServices *never* returns anything but fields. FieldInfo field = (FieldInfo)_fieldInfo[_currentFieldIndex]; - object? rawValue; - try - { - rawValue = _classRecord[field.Name]; - } - catch (KeyNotFoundException) + if (!_classRecord.HasMember(field.Name)) { if (Deserializer.Options.AssemblyMatching == FormatterAssemblyStyle.Simple || field.GetCustomAttribute() is not null) @@ -65,7 +61,7 @@ internal override Id Continue() throw new SerializationException($"Could not find field '{field.Name}' data for type '{field.DeclaringType!.Name}'."); } - (object? memberValue, Id reference) = UnwrapMemberValue(rawValue); + (object? memberValue, SerializationRecordId reference) = UnwrapMemberValue(_classRecord.GetRawValue(field.Name)); if (s_missingValueSentinel == memberValue) { // Record has not been encountered yet, need to pend iteration. @@ -78,7 +74,7 @@ internal override Id Continue() { // Need to track a fixup for this field. _hasFixups = true; - Deserializer.PendValueUpdater(new FieldValueUpdater(_classRecord.ObjectId, reference, field)); + Deserializer.PendValueUpdater(new FieldValueUpdater(_classRecord.Id, reference, field)); } _currentFieldIndex++; @@ -91,11 +87,11 @@ internal override Id Continue() { // We can be used for completion even with fixups if we're not a value type as our fixups won't need to be // copied to propogate them. Note that surrogates cannot replace our created instance for reference types. - Deserializer.CompleteObject(_classRecord.ObjectId); + Deserializer.CompleteObject(_classRecord.Id); } // No more missing member refs. - return Id.Null; + return default; } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordSerializationInfoDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordSerializationInfoDeserializer.cs index 8259c03b21a..4c10899d323 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordSerializationInfoDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordSerializationInfoDeserializer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Runtime.Serialization; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -17,13 +18,14 @@ namespace System.Windows.Forms.BinaryFormat.Deserializer; /// internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDeserializer { - private readonly ClassRecord _classRecord; + private readonly Formats.Nrbf.ClassRecord _classRecord; private readonly SerializationInfo _serializationInfo; private readonly ISerializationSurrogate? _surrogate; - private int _currentMemberIndex; + private readonly IEnumerator _memberNamesIterator; + private bool _canIterate; internal ClassRecordSerializationInfoDeserializer( - ClassRecord classRecord, + Formats.Nrbf.ClassRecord classRecord, object @object, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, ISerializationSurrogate? surrogate, @@ -32,33 +34,39 @@ internal ClassRecordSerializationInfoDeserializer( _classRecord = classRecord; _surrogate = surrogate; _serializationInfo = new(type, BinaryFormattedObject.DefaultConverter); + _memberNamesIterator = _classRecord.MemberNames.GetEnumerator(); + _canIterate = _memberNamesIterator.MoveNext(); // start the iterator } - internal override Id Continue() + internal override SerializationRecordId Continue() { - // There is no benefit to changing order here so we can keep the same order as the serialized data. - while (_currentMemberIndex < _classRecord.MemberNames.Count) + if (_canIterate) { - string memberName = _classRecord.MemberNames[_currentMemberIndex]; - (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.MemberValues[_currentMemberIndex]); - - if (s_missingValueSentinel == memberValue) + do { - // Record has not been encountered yet, need to pend iteration. - return reference; - } + string memberName = _memberNamesIterator.Current; + (object? memberValue, SerializationRecordId reference) = UnwrapMemberValue(_classRecord.GetRawValue(memberName)); - if (memberValue is not null && DoesValueNeedUpdated(memberValue, reference)) - { - Deserializer.PendValueUpdater(new SerializationInfoValueUpdater( - _classRecord.ObjectId, - reference, - _serializationInfo, - memberName)); + if (s_missingValueSentinel == memberValue) + { + // Record has not been encountered yet, need to pend iteration. + return reference; + } + + if (memberValue is not null && DoesValueNeedUpdated(memberValue, reference)) + { + Deserializer.PendValueUpdater(new SerializationInfoValueUpdater( + _classRecord.Id, + reference, + _serializationInfo, + memberName)); + } + + _serializationInfo.AddValue(memberName, memberValue); } + while (_memberNamesIterator.MoveNext()); - _serializationInfo.AddValue(memberName, memberValue); - _currentMemberIndex++; + _canIterate = false; } // Register after all members are parsed, to ensure that completion events are triggered in depth-first order. @@ -75,11 +83,11 @@ internal override Id Continue() // If we were confident that there were no cycles in the graph to this point we could apply directly // if there were no pending value types (which should also not happen if there are no cycles). - PendingSerializationInfo pending = new(_classRecord.ObjectId, _serializationInfo, _surrogate); + PendingSerializationInfo pending = new(_classRecord.Id, _serializationInfo, _surrogate); Deserializer.PendSerializationInfo(pending); // No more missing member refs. - return Id.Null; + return default; } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.PendingUpdates.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.PendingUpdates.cs index 4bdefef6242..6def3aa2e5b 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.PendingUpdates.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.PendingUpdates.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; + namespace System.Windows.Forms.BinaryFormat.Deserializer; internal sealed partial class Deserializer @@ -19,17 +21,17 @@ private class PendingUpdates // dictionary, to do so we'd need to know if any referenced object is going to get to this state // even if it hasn't finished parsing, which isn't easy to do with cycles involved. private Queue? _pendingSerializationInfo; - private HashSet? _pendingSerializationInfoIds; + private HashSet? _pendingSerializationInfoIds; // For a given object id, the set of ids that it is waiting on to complete. - private Dictionary>? _incompleteDependencies; + private Dictionary>? _incompleteDependencies; // The pending value updaters. Scanned each time an object is completed. private HashSet? _pendingValueUpdates; - private readonly IDictionary _deserializedObjects; + private readonly IDictionary _deserializedObjects; - internal PendingUpdates(IDictionary deserializedObjects) => _deserializedObjects = deserializedObjects; + internal PendingUpdates(IDictionary deserializedObjects) => _deserializedObjects = deserializedObjects; /// /// Number of pending updates, if any. @@ -43,7 +45,7 @@ internal void Add(ValueUpdater updater) _incompleteDependencies ??= []; - if (_incompleteDependencies.TryGetValue(updater.ObjectId, out HashSet? dependencies)) + if (_incompleteDependencies.TryGetValue(updater.ObjectId, out HashSet? dependencies)) { dependencies.Add(updater.ValueId); } @@ -53,9 +55,9 @@ internal void Add(ValueUpdater updater) } } - internal bool HasIncompleteDependencies(int id) => _incompleteDependencies?.ContainsKey(id) ?? false; + internal bool HasIncompleteDependencies(SerializationRecordId id) => _incompleteDependencies?.ContainsKey(id) ?? false; - internal void RemoveIncompleteDependency(int id) => _incompleteDependencies?.Remove(id); + internal void RemoveIncompleteDependency(SerializationRecordId id) => _incompleteDependencies?.Remove(id); internal void Enqueue(PendingSerializationInfo pending) { @@ -78,11 +80,11 @@ internal bool TryDequeuePendingSerializationInfo([NotNullWhen(true)] out Pending return false; } - internal bool ContainsPendingSerializationInfo(int id) => _pendingSerializationInfoIds?.Contains(id) ?? false; + internal bool ContainsPendingSerializationInfo(SerializationRecordId id) => _pendingSerializationInfoIds?.Contains(id) ?? false; internal int PendingSerializationInfoCount => _pendingSerializationInfo?.Count ?? 0; - internal bool TryGetIncompleteDependencies(int id, [NotNullWhen(true)] out HashSet? dependencies) + internal bool TryGetIncompleteDependencies(SerializationRecordId id, [NotNullWhen(true)] out HashSet? dependencies) { if (_incompleteDependencies is not null) { @@ -93,14 +95,14 @@ internal bool TryGetIncompleteDependencies(int id, [NotNullWhen(true)] out HashS return false; } - internal IEnumerable CompleteDependencies(int completedId) + internal IEnumerable CompleteDependencies(SerializationRecordId completedId) { if (_incompleteDependencies is null) { yield break; } - foreach ((int incompleteId, HashSet dependencies) in _incompleteDependencies) + foreach ((SerializationRecordId incompleteId, HashSet dependencies) in _incompleteDependencies) { if (!dependencies.Remove(completedId)) { @@ -110,7 +112,7 @@ internal IEnumerable CompleteDependencies(int completedId) // Search for fixups that need to be applied for this dependency. int removals = _pendingValueUpdates!.RemoveWhere((ValueUpdater updater) => { - if (updater.ValueId != completedId) + if (!updater.ValueId.Equals(completedId)) { return false; } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.cs index e56bd106c3c..17e9be4d7f1 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/Deserializer.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Formats.Nrbf; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -47,7 +48,7 @@ namespace System.Windows.Forms.BinaryFormat.Deserializer; /// internal sealed partial class Deserializer : IDeserializer { - private readonly IReadOnlyRecordMap _recordMap; + private readonly IReadOnlyDictionary _recordMap; private readonly BinaryFormattedObject.ITypeResolver _typeResolver; BinaryFormattedObject.ITypeResolver IDeserializer.TypeResolver => _typeResolver; @@ -56,34 +57,34 @@ internal sealed partial class Deserializer : IDeserializer BinaryFormattedObject.Options IDeserializer.Options => Options; /// - private readonly Dictionary _deserializedObjects = []; - IDictionary IDeserializer.DeserializedObjects => _deserializedObjects; + private readonly Dictionary _deserializedObjects = []; + IDictionary IDeserializer.DeserializedObjects => _deserializedObjects; // Surrogate cache. private readonly Dictionary? _surrogates; // Keeping a separate stack for ids for fast infinite loop checks. - private readonly Stack _parseStack = []; + private readonly Stack _parseStack = []; private readonly Stack _parserStack = []; /// - private readonly HashSet _incompleteObjects = []; - public IReadOnlySet IncompleteObjects => _incompleteObjects; + private readonly HashSet _incompleteObjects = []; + public IReadOnlySet IncompleteObjects => _incompleteObjects; private readonly PendingUpdates _pending; // Kept as a field to avoid allocating a new one every time we complete objects. - private readonly Queue _pendingCompletions = []; + private readonly Queue _pendingCompletions = []; - private readonly Id _rootId; + private readonly SerializationRecordId _rootId; // We group individual object events here to fire them all when we complete the graph. private event Action? OnDeserialization; private event Action? OnDeserialized; private Deserializer( - Id rootId, - IReadOnlyRecordMap recordMap, + SerializationRecordId rootId, + IReadOnlyDictionary recordMap, BinaryFormattedObject.ITypeResolver typeResolver, BinaryFormattedObject.Options options) { @@ -104,8 +105,8 @@ private Deserializer( /// [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.Deserializer.Deserialize()")] internal static object Deserialize( - Id rootId, - IReadOnlyRecordMap recordMap, + SerializationRecordId rootId, + IReadOnlyDictionary recordMap, BinaryFormattedObject.ITypeResolver typeResolver, BinaryFormattedObject.Options options) { @@ -125,7 +126,7 @@ private object Deserialize() // Using pendingCount to only requeue on the first pass. if (--pendingCount >= 0 && _pending.PendingSerializationInfoCount != 0 - && _pending.TryGetIncompleteDependencies(pending.ObjectId, out HashSet? dependencies)) + && _pending.TryGetIncompleteDependencies(pending.ObjectId, out HashSet? dependencies)) { // We can get here with nested ISerializable value types. @@ -159,7 +160,7 @@ private object Deserialize() } [RequiresUnreferencedCode("Calls DeserializeNew(Id)")] - private void DeserializeRoot(Id rootId) + private void DeserializeRoot(SerializationRecordId rootId) { object root = DeserializeNew(rootId); if (root is not ObjectRecordDeserializer parser) @@ -172,15 +173,15 @@ private void DeserializeRoot(Id rootId) while (_parserStack.TryPop(out ObjectRecordDeserializer? currentParser)) { - int currentId = _parseStack.Pop(); - Debug.Assert(currentId == currentParser.ObjectRecord.ObjectId); + SerializationRecordId currentId = _parseStack.Pop(); + Debug.Assert(currentId.Equals(currentParser.ObjectRecord.Id)); - Id requiredId; - while (!(requiredId = currentParser.Continue()).IsNull) + SerializationRecordId requiredId; + while (!(requiredId = currentParser.Continue()).Equals(default)) { // A record is required to complete the current parser. Get it. object requiredObject = DeserializeNew(requiredId); - Debug.Assert(requiredObject is not IRecord); + Debug.Assert(requiredObject is not SerializationRecord); if (requiredObject is ObjectRecordDeserializer requiredParser) { @@ -207,34 +208,29 @@ private void DeserializeRoot(Id rootId) [MethodImpl(MethodImplOptions.AggressiveInlining)] [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.ObjectRecordParser.Create(Id, IRecord, IDeserializer)")] - object DeserializeNew(Id id) + object DeserializeNew(SerializationRecordId id) { // Strings, string arrays, and primitive arrays can be completed without creating a // parser object. Single primitives don't normally show up as records unless they are top // level or are boxed into an interface reference. Checking for these requires costly // string matches and as such we'll just create the parser object. - IRecord record = _recordMap[id]; - if (record is BinaryObjectString binaryString) - { - _deserializedObjects.Add(id, binaryString.Value); - return binaryString.Value; - } + SerializationRecord record = _recordMap[id]; - if (record is ArrayRecord arrayRecord) + object? value = record.RecordType switch { - Array? values = arrayRecord switch - { - ArraySingleString stringArray => stringArray.GetStringValues(_recordMap).ToArray(), - IPrimitiveTypeRecord primitiveArray => primitiveArray.GetPrimitiveArray(), - _ => null - }; - - if (values is not null) - { - _deserializedObjects.Add(arrayRecord.ObjectId, values); - return values; - } + SerializationRecordType.BinaryObjectString => ((PrimitiveTypeRecord)record).Value, + SerializationRecordType.MemberPrimitiveTyped => record.GetMemberPrimitiveTypedValue(), + SerializationRecordType.ArraySingleString => ((SZArrayRecord)record).GetArray(), + SerializationRecordType.ArraySinglePrimitive => ArrayRecordDeserializer.GetArraySinglePrimitive(record), + SerializationRecordType.BinaryArray => ArrayRecordDeserializer.GetSimpleBinaryArray((ArrayRecord)record, _typeResolver), + _ => null + }; + + if (value is not null) + { + _deserializedObjects.Add(record.Id, value); + return value; } // Not a simple case, need to do a full deserialization of the record. @@ -288,14 +284,14 @@ void IDeserializer.RegisterCompleteEvents( } } - void IDeserializer.CompleteObject(Id id) + void IDeserializer.CompleteObject(SerializationRecordId id) { // Need to use a queue as Completion is recursive. _pendingCompletions.Enqueue(id); bool recursed = false; - while (_pendingCompletions.TryDequeue(out int completedId)) + while (_pendingCompletions.TryDequeue(out SerializationRecordId completedId)) { _incompleteObjects.Remove(completedId); @@ -322,7 +318,7 @@ void IDeserializer.CompleteObject(Id id) } } - foreach (int dependentCompletion in _pending.CompleteDependencies(completedId)) + foreach (SerializationRecordId dependentCompletion in _pending.CompleteDependencies(completedId)) { _pendingCompletions.Enqueue(dependentCompletion); } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/FieldValueUpdater.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/FieldValueUpdater.cs index 0422e45a343..0cfd709026b 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/FieldValueUpdater.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/FieldValueUpdater.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Reflection; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -9,12 +10,12 @@ internal sealed class FieldValueUpdater : ValueUpdater { private readonly FieldInfo _field; - internal FieldValueUpdater(int objectId, int valueId, FieldInfo field) : base(objectId, valueId) + internal FieldValueUpdater(SerializationRecordId objectId, SerializationRecordId valueId, FieldInfo field) : base(objectId, valueId) { _field = field; } - internal override void UpdateValue(IDictionary objects) + internal override void UpdateValue(IDictionary objects) { object newValue = objects[ValueId]; _field.SetValue(objects[ObjectId], newValue); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/IDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/IDeserializer.cs index 005719c0ce2..bec0cadb79b 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/IDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/IDeserializer.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Runtime.Serialization; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -29,7 +30,7 @@ internal interface IDeserializer /// has not yet been applied. /// /// - IReadOnlySet IncompleteObjects { get; } + IReadOnlySet IncompleteObjects { get; } /// /// The set of objects that have been deserialized, indexed by record id. @@ -41,7 +42,7 @@ internal interface IDeserializer /// be ready if there are cycles in the object graph). /// /// - IDictionary DeserializedObjects { get; } + IDictionary DeserializedObjects { get; } /// /// Resolver for types. @@ -61,7 +62,7 @@ internal interface IDeserializer /// /// Mark the object id as complete. This will check dependencies and resolve relevant s. /// - void CompleteObject(Id id); + void CompleteObject(SerializationRecordId id); /// /// Check for a surrogate for the given type. If none exists, returns . diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ObjectRecordDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ObjectRecordDeserializer.cs index 5844d265409..94e10fe1fb7 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ObjectRecordDeserializer.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ObjectRecordDeserializer.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; +using System.Formats.Nrbf; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -16,14 +17,14 @@ internal abstract partial class ObjectRecordDeserializer // Used to indicate that the value is missing from the deserialized objects. private protected static object s_missingValueSentinel = new(); - internal ObjectRecord ObjectRecord { get; } + internal SerializationRecord ObjectRecord { get; } [AllowNull] internal object Object { get; private protected set; } private protected IDeserializer Deserializer { get; } - private protected ObjectRecordDeserializer(ObjectRecord objectRecord, IDeserializer deserializer) + private protected ObjectRecordDeserializer(SerializationRecord objectRecord, IDeserializer deserializer) { Deserializer = deserializer; ObjectRecord = objectRecord; @@ -32,35 +33,40 @@ private protected ObjectRecordDeserializer(ObjectRecord objectRecord, IDeseriali /// /// Continue parsing. /// - /// The id that is necessary to complete parsing or if complete. - internal abstract Id Continue(); + /// The id that is necessary to complete parsing or a default if complete. + internal abstract SerializationRecordId Continue(); /// /// Gets the actual object for a member value primitive or record. Returns if /// the object record has not been encountered yet. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected (object? value, Id id) UnwrapMemberValue(object? memberValue) + private protected (object? value, SerializationRecordId id) UnwrapMemberValue(object? memberValue) { - return memberValue switch + if (memberValue is null) // PayloadReader does not return NullRecord, just null { - // String - BinaryObjectString binaryString => (binaryString.Value, Id.Null), - // Inline record - ObjectRecord objectRecord => TryGetObject(objectRecord.ObjectId), - // Record reference - MemberReference memberReference => TryGetObject(memberReference.IdRef), - // Prmitive type record - MemberPrimitiveTyped memberPrimitiveTyped => (memberPrimitiveTyped.Value, Id.Null), - // Null - ObjectNull or null => (null, Id.Null), - // At this point should be an inline primitive - _ => TypeInfo.GetPrimitiveType(memberValue.GetType()) != default - ? (memberValue, Id.Null) - : throw new SerializationException($"Unexpected member type '{memberValue.GetType()}'."), - }; - - (object? value, Id id) TryGetObject(Id id) + return (null, default); + } + else if (memberValue is not SerializationRecord serializationRecord) // a primitive value + { + return (memberValue, default); + } + else if (serializationRecord.RecordType is SerializationRecordType.BinaryObjectString) + { + PrimitiveTypeRecord stringRecord = (PrimitiveTypeRecord)serializationRecord; + return (stringRecord.Value, stringRecord.Id); + } + else if (serializationRecord.RecordType is SerializationRecordType.MemberPrimitiveTyped) + { + return (serializationRecord.GetMemberPrimitiveTypedValue(), default); + } + else + { + // ClassRecords & ArrayRecords + return TryGetObject(serializationRecord.Id); + } + + (object? value, SerializationRecordId id) TryGetObject(SerializationRecordId id) { if (!Deserializer.DeserializedObjects.TryGetValue(id, out object? value)) { @@ -80,19 +86,19 @@ private protected virtual void ValidateNewMemberObjectValue(object value) { } /// /// Returns if the given record's value needs an updater applied. /// - private protected bool DoesValueNeedUpdated(object value, Id valueRecord) => - // Null Id is a primitive of some sort. - !valueRecord.IsNull + private protected bool DoesValueNeedUpdated(object value, SerializationRecordId valueRecord) => + // Null SerializationRecordId is a primitive of some sort. + !valueRecord.Equals(default) // IObjectReference is going to have it's object replaced. && (value is IObjectReference // Value types that aren't "complete" need to be reapplied. || (Deserializer.IncompleteObjects.Contains(valueRecord) && value.GetType().IsValueType)); [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.ClassRecordParser.Create(ClassRecord, IDeserializer)")] - internal static ObjectRecordDeserializer Create(Id id, IRecord record, IDeserializer deserializer) => record switch + internal static ObjectRecordDeserializer Create(SerializationRecordId id, SerializationRecord record, IDeserializer deserializer) => record switch { ClassRecord classRecord => ClassRecordDeserializer.Create(classRecord, deserializer), - ArrayRecord arrayRecord => new ArrayRecordDeserializer(arrayRecord, deserializer), + ArrayRecord arrayRecord => new ArrayRecordDeserializer(arrayRecord, deserializer), _ => throw new SerializationException($"Unexpected record type for {id}.") }; } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/PendingSerializationInfo.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/PendingSerializationInfo.cs index 0c55a8c8dfe..39d9d401abb 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/PendingSerializationInfo.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/PendingSerializationInfo.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Reflection; using System.Runtime.Serialization; @@ -10,12 +11,12 @@ namespace System.Windows.Forms.BinaryFormat.Deserializer; internal sealed class PendingSerializationInfo { - internal int ObjectId { get; } + internal SerializationRecordId ObjectId { get; } private readonly ISerializationSurrogate? _surrogate; private readonly SerializationInfo _info; internal PendingSerializationInfo( - int objectId, + SerializationRecordId objectId, SerializationInfo info, ISerializationSurrogate? surrogate) { @@ -28,7 +29,7 @@ internal PendingSerializationInfo( "ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern", Justification = "Not sure how to attribute this.")] - internal void Populate(IDictionary objects, StreamingContext context) + internal void Populate(IDictionary objects, StreamingContext context) { object @object = objects[ObjectId]; Type type = @object.GetType(); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/SerializationInfoValueUpdater.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/SerializationInfoValueUpdater.cs index 34a7656f595..9fda2179370 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/SerializationInfoValueUpdater.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/SerializationInfoValueUpdater.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Runtime.Serialization; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -10,13 +11,13 @@ internal sealed class SerializationInfoValueUpdater : ValueUpdater private readonly SerializationInfo _info; private readonly string _name; - internal SerializationInfoValueUpdater(int objectId, int valueId, SerializationInfo info, string name) : base(objectId, valueId) + internal SerializationInfoValueUpdater(SerializationRecordId objectId, SerializationRecordId valueId, SerializationInfo info, string name) : base(objectId, valueId) { _info = info; _name = name; } - internal override void UpdateValue(IDictionary objects) + internal override void UpdateValue(IDictionary objects) { object newValue = objects[ValueId]; _info.UpdateValue(_name, newValue, newValue.GetType()); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ValueUpdater.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ValueUpdater.cs index 3be4bae0398..3aec7a083e9 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ValueUpdater.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ValueUpdater.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; + namespace System.Windows.Forms.BinaryFormat.Deserializer; internal abstract class ValueUpdater @@ -8,18 +10,18 @@ internal abstract class ValueUpdater /// /// The value id that needs to be reapplied. /// - internal int ValueId { get; } + internal SerializationRecordId ValueId { get; } /// /// The object id that is dependent on . /// - internal int ObjectId { get; } + internal SerializationRecordId ObjectId { get; } - private protected ValueUpdater(int objectId, int valueId) + private protected ValueUpdater(SerializationRecordId objectId, SerializationRecordId valueId) { ObjectId = objectId; ValueId = valueId; } - internal abstract void UpdateValue(IDictionary objects); + internal abstract void UpdateValue(IDictionary objects); } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryFormatParseable.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryFormatParseable.cs deleted file mode 100644 index 28aca26ca95..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryFormatParseable.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Specifies that the given record type can be created from a . -/// -internal interface IBinaryFormatParseable where T : IRecord -{ - /// - /// Creates the type utilizaing the given . - /// - static abstract T Parse(BinaryFormattedObject.IParseState state); -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IPrimitiveTypeRecord.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IPrimitiveTypeRecord.cs deleted file mode 100644 index 6313255afa8..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IPrimitiveTypeRecord.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Record that represents a primitive type or an array of primitive types. -/// -internal interface IPrimitiveTypeRecord : IRecord -{ - PrimitiveType PrimitiveType { get; } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IReadOnlyRecordMap.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IReadOnlyRecordMap.cs deleted file mode 100644 index 7567b863ec8..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IReadOnlyRecordMap.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Map of records. -/// -internal interface IReadOnlyRecordMap -{ - IRecord this[Id id] { get; } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs deleted file mode 100644 index 480a1bd0dd5..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs +++ /dev/null @@ -1,155 +0,0 @@ -// 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; -using System.Reflection; -using System.Runtime.Serialization; - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Member type info. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.1.2 -/// -/// -/// -internal class MemberTypeInfo : IBinaryWriteable, IEnumerable<(BinaryType Type, object? Info)> -{ - private readonly IList<(BinaryType Type, object? Info)> _info; - - public MemberTypeInfo(IList<(BinaryType Type, object? Info)> info) => _info = info; - - public MemberTypeInfo(params (BinaryType Type, object? Info)[] info) => _info = info; - - public (BinaryType Type, object? Info) this[int index] => _info[index]; - public int Count => _info.Count; - - public static MemberTypeInfo Parse(BinaryReader reader, Count expectedCount) - { - List<(BinaryType Type, object? Info)> info = new(expectedCount); - - // Get all of the BinaryTypes - for (int i = 0; i < expectedCount; i++) - { - info.Add(((BinaryType)reader.ReadByte(), null)); - } - - // Check for more clarifying information - - for (int i = 0; i < expectedCount; i++) - { - BinaryType type = info[i].Type; - switch (type) - { - case BinaryType.Primitive: - case BinaryType.PrimitiveArray: - info[i] = (type, (PrimitiveType)reader.ReadByte()); - break; - case BinaryType.SystemClass: - info[i] = (type, reader.ReadString()); - break; - case BinaryType.Class: - info[i] = (type, ClassTypeInfo.Parse(reader)); - break; - case BinaryType.String: - case BinaryType.ObjectArray: - case BinaryType.StringArray: - case BinaryType.Object: - // Other types have no additional data. - break; - default: - throw new SerializationException("Unexpected binary type."); - } - } - - return new MemberTypeInfo(info); - } - - [UnconditionalSuppressMessage( - "Trimming", - "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", - Justification = """ - Incoming type names are coming off of the formatted stream. There is no way for user code to pass compile - time context for preserialized data. If a type can't be found on deserialization it won't matter any more - than any other case where the type can't be found (e.g. a missing assembly). The deserializer will fail - with information on the missing type that can be used to attribute to keep said type. - """)] - internal static MemberTypeInfo CreateFromClassInfoAndLibrary(BinaryFormattedObject.IParseState state, ClassInfo classInfo, Id libraryId) - { - Type type = state.TypeResolver.GetType(classInfo.Name, libraryId); - - if (typeof(ISerializable).IsAssignableFrom(type) - || state.Options.SurrogateSelector?.GetSurrogate(type, state.Options.StreamingContext, out _) is not null) - { - throw new SerializationException("Cannot intuit type information for ISerializable types."); - } - -#pragma warning disable SYSLIB0050 // Type or member is obsolete - MemberInfo[] memberInfo = FormatterServices.GetSerializableMembers(type); -#pragma warning restore SYSLIB0050 - IReadOnlyList memberNames = classInfo.MemberNames; - var info = new (BinaryType Type, object? Info)[memberInfo.Length]; - - for (int i = 0; i < memberNames.Count; i++) - { - // FormatterServices never returns anything other than FieldInfo. - FieldInfo? field = (FieldInfo?)memberInfo.FirstOrDefault(m => m.Name == memberNames[i]) - ?? throw new SerializationException($"Could not find member '{memberNames[i]}' on type '{classInfo.Name}'."); - - BinaryType binaryType = TypeInfo.GetBinaryType(field.FieldType); - - info[i] = (binaryType, binaryType switch - { - BinaryType.PrimitiveArray => TypeInfo.GetPrimitiveArrayType(field.FieldType), - BinaryType.Primitive => TypeInfo.GetPrimitiveType(field.FieldType), - BinaryType.SystemClass => field.FieldType, - BinaryType.Class => field.FieldType, - _ => null - }); - } - - return new(info); - } - - public void Write(BinaryWriter writer) - { - foreach ((BinaryType type, _) in this) - { - writer.Write((byte)type); - } - - foreach ((BinaryType type, object? info) in this) - { - switch (type) - { - case BinaryType.Primitive: - case BinaryType.PrimitiveArray: - writer.Write((byte)info!); - break; - case BinaryType.SystemClass: - writer.Write((string)info!); - break; - case BinaryType.Class: - ((ClassTypeInfo)info!).Write(writer); - break; - case BinaryType.String: - case BinaryType.ObjectArray: - case BinaryType.StringArray: - case BinaryType.Object: - // Other types have no additional data. - break; - default: - throw new SerializationException("Unexpected binary type."); - } - } - } - - IEnumerator<(BinaryType Type, object? Info)> IEnumerable<(BinaryType Type, object? Info)>.GetEnumerator() - => _info.GetEnumerator(); - - IEnumerator IEnumerable.GetEnumerator() => _info.GetEnumerator(); -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Record.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Record.cs deleted file mode 100644 index 2a0a6b3e5b4..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Record.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Globalization; -using System.Runtime.Serialization; - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Base record class. -/// -internal abstract class Record : IRecord -{ - Id IRecord.Id => Id; - - private protected virtual Id Id => Id.Null; - - /// - /// Reads a primitive of from the given . - /// - private protected static object ReadPrimitiveType(BinaryReader reader, PrimitiveType primitiveType) => primitiveType switch - { - PrimitiveType.Boolean => reader.ReadBoolean(), - PrimitiveType.Byte => reader.ReadByte(), - PrimitiveType.SByte => reader.ReadSByte(), - PrimitiveType.Char => reader.ReadChar(), - PrimitiveType.Int16 => reader.ReadInt16(), - PrimitiveType.UInt16 => reader.ReadUInt16(), - PrimitiveType.Int32 => reader.ReadInt32(), - PrimitiveType.UInt32 => reader.ReadUInt32(), - PrimitiveType.Int64 => reader.ReadInt64(), - PrimitiveType.UInt64 => reader.ReadUInt64(), - PrimitiveType.Single => reader.ReadSingle(), - PrimitiveType.Double => reader.ReadDouble(), - PrimitiveType.Decimal => decimal.Parse(reader.ReadString(), CultureInfo.InvariantCulture), - PrimitiveType.DateTime => reader.ReadDateTime(), - PrimitiveType.TimeSpan => new TimeSpan(reader.ReadInt64()), - // String is handled with a record, never on it's own - _ => throw new SerializationException($"Failure trying to read primitive '{primitiveType}'"), - }; - - /// - /// Writes as to the given . - /// - private protected static unsafe void WritePrimitiveType(BinaryWriter writer, PrimitiveType primitiveType, object value) - { - switch (primitiveType) - { - case PrimitiveType.Boolean: - writer.Write((bool)value); - break; - case PrimitiveType.Byte: - writer.Write((byte)value); - break; - case PrimitiveType.Char: - writer.Write((char)value); - break; - case PrimitiveType.Decimal: - writer.Write(((decimal)value).ToString(CultureInfo.InvariantCulture)); - break; - case PrimitiveType.Double: - writer.Write((double)value); - break; - case PrimitiveType.Int16: - writer.Write((short)value); - break; - case PrimitiveType.Int32: - writer.Write((int)value); - break; - case PrimitiveType.Int64: - writer.Write((long)value); - break; - case PrimitiveType.SByte: - writer.Write((sbyte)value); - break; - case PrimitiveType.Single: - writer.Write((float)value); - break; - case PrimitiveType.TimeSpan: - writer.Write(((TimeSpan)value).Ticks); - break; - case PrimitiveType.DateTime: - writer.Write((DateTime)value); - break; - case PrimitiveType.UInt16: - writer.Write((ushort)value); - break; - case PrimitiveType.UInt32: - writer.Write((uint)value); - break; - case PrimitiveType.UInt64: - writer.Write((ulong)value); - break; - // String is handled with a record, never on it's own - case PrimitiveType.Null: - case PrimitiveType.String: - default: - throw new ArgumentException("Invalid primitive type.", nameof(primitiveType)); - } - } - - /// - /// Reads the next record. - /// - /// Found a multidimensional array. - /// Found a remote method invocation record. - /// Unknown or corrupted data. - internal static IRecord ReadBinaryFormatRecord(BinaryFormattedObject.IParseState state) - { - RecordType recordType = (RecordType)state.Reader.ReadByte(); - - IRecord record = recordType switch - { - RecordType.SerializedStreamHeader => ReadSpecificRecord(state), - RecordType.ClassWithId => ReadSpecificRecord(state), - RecordType.SystemClassWithMembers => ReadSpecificRecord(state), - RecordType.ClassWithMembers => ReadSpecificRecord(state), - RecordType.SystemClassWithMembersAndTypes => ReadSpecificRecord(state), - RecordType.ClassWithMembersAndTypes => ReadSpecificRecord(state), - RecordType.BinaryObjectString => ReadSpecificRecord(state), - RecordType.BinaryArray => BinaryArray.Parse(state), - RecordType.MemberPrimitiveTyped => ReadSpecificRecord(state), - RecordType.MemberReference => ReadSpecificRecord(state), - RecordType.ObjectNull => ReadSpecificRecord(state), - RecordType.MessageEnd => ReadSpecificRecord(state), - RecordType.BinaryLibrary => ReadSpecificRecord(state), - RecordType.ObjectNullMultiple256 => ReadSpecificRecord(state), - RecordType.ObjectNullMultiple => ReadSpecificRecord(state), - RecordType.ArraySinglePrimitive => ReadArraySinglePrimitive(state), - RecordType.ArraySingleObject => ReadSpecificRecord(state), - RecordType.ArraySingleString => ReadSpecificRecord(state), - RecordType.MethodCall => throw new NotSupportedException(), - RecordType.MethodReturn => throw new NotSupportedException(), - _ => throw new SerializationException("Invalid record type."), - }; - - // BinaryLibrary records can be dumped in front of most records, add them and continue. - if (record is BinaryLibrary library) - { - do - { - state.RecordMap.AddRecord(library); - record = ReadBinaryFormatRecord(state); - } - while (record is BinaryLibrary); - - return record; - } - - state.RecordMap.AddRecord(record); - return record; - - static IRecord ReadArraySinglePrimitive(BinaryFormattedObject.IParseState state) - { - // Special casing to avoid excessive boxing/unboxing. - Id id = ArrayInfo.Parse(state.Reader, out Count length); - PrimitiveType primitiveType = (PrimitiveType)state.Reader.ReadByte(); - - IRecord record = primitiveType switch - { - PrimitiveType.Boolean => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Byte => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.SByte => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Char => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Int16 => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.UInt16 => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Int32 => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.UInt32 => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Int64 => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.UInt64 => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Single => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Double => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.Decimal => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.DateTime => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - PrimitiveType.TimeSpan => new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)), - _ => throw new SerializationException($"Invalid primitive type '{primitiveType}'"), - }; - - return record; - } - - static TRecord ReadSpecificRecord(BinaryFormattedObject.IParseState state) - where TRecord : class, IRecord, IBinaryFormatParseable => TRecord.Parse(state); - } - - /// - /// Writes records, coalescing null records into single entries. - /// - /// - /// contained an object that isn't a record. - /// - private protected static void WriteRecords(BinaryWriter writer, IReadOnlyList objects, bool coalesceNulls) - { - int nullCount = 0; - - // Indexing for performance and avoiding writing an enumerator of ListConverter. - for (int i = 0; i < objects.Count; i++) - { - object? @object = objects[i]; - - // Aggregate consecutive null records. - if (@object is null) - { - nullCount++; - if (coalesceNulls) - { - continue; - } - } - - if (nullCount > 0) - { - NullRecord.Write(writer, nullCount); - nullCount = 0; - } - - if (@object is not IRecord record || record is NullRecord) - { - throw new ArgumentException("Invalid record.", nameof(objects)); - } - - record.Write(writer); - } - - if (nullCount > 0) - { - NullRecord.Write(writer, nullCount); - } - } - - /// - /// Reads an object member value of with optional clarifying . - /// - /// was unexpected. - private protected static object ReadValue( - BinaryFormattedObject.IParseState state, - BinaryType type, - object? typeInfo) - { - if (type == BinaryType.Primitive) - { - return ReadPrimitiveType(state.Reader, (PrimitiveType)typeInfo!); - } - - if (type == BinaryType.String) - { - IRecord stringRecord = ReadBinaryFormatRecord(state); - - return stringRecord is not (BinaryObjectString or ObjectNull or MemberReference) - ? throw new SerializationException($"Expected string record, found {stringRecord.GetType().Name}") - : (object)stringRecord; - } - - return type switch - { - BinaryType.Object - or BinaryType.StringArray - or BinaryType.PrimitiveArray - or BinaryType.Class - or BinaryType.SystemClass - or BinaryType.ObjectArray => ReadBinaryFormatRecord(state), - _ => throw new SerializationException($"Invalid binary type {type}."), - }; - } - - public abstract void Write(BinaryWriter writer); -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/RecordMap.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/RecordMap.cs deleted file mode 100644 index 526cf2ff764..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/RecordMap.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// Map of records that ensures that IDs are only entered once. -/// -internal class RecordMap : IReadOnlyRecordMap -{ - private readonly Dictionary _recordMap = []; - - public IRecord this[Id id] => _recordMap[id]; - - public void AddRecord(IRecord record) - { - Id id = record.Id; - if (id.IsNull) - { - return; - } - - if ((int)id < 0) - { - // Negative record Ids should never be referenced. Duplicate negative ids can be - // exported by the writer. The root object Id can be negative. - _recordMap.TryAdd(id, record); - return; - } - - _recordMap.Add(id, record); - } -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/TypeInfo.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/TypeInfo.cs deleted file mode 100644 index a6869e64670..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/TypeInfo.cs +++ /dev/null @@ -1,190 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection; - -namespace System.Windows.Forms.BinaryFormat; - -internal static class TypeInfo -{ - public const string BooleanType = "System.Boolean"; - public const string CharType = "System.Char"; - public const string StringType = "System.String"; - public const string SByteType = "System.SByte"; - public const string ByteType = "System.Byte"; - public const string Int16Type = "System.Int16"; - public const string UInt16Type = "System.UInt16"; - public const string Int32Type = "System.Int32"; - public const string UInt32Type = "System.UInt32"; - public const string Int64Type = "System.Int64"; - public const string DecimalType = "System.Decimal"; - public const string UInt64Type = "System.UInt64"; - public const string SingleType = "System.Single"; - public const string DoubleType = "System.Double"; - public const string TimeSpanType = "System.TimeSpan"; - public const string DateTimeType = "System.DateTime"; - public const string IntPtrType = "System.IntPtr"; - public const string UIntPtrType = "System.UIntPtr"; - - public const string HashtableType = "System.Collections.Hashtable"; - public const string IDictionaryType = "System.Collections.IDictionary"; - public const string ExceptionType = "System.Exception"; - public const string NotSupportedExceptionType = "System.NotSupportedException"; - - public const string MscorlibAssemblyName - = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; - public const string SystemDrawingAssemblyName - = "System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; - - private static Assembly? s_mscorlibFacadeAssembly; - - internal static Assembly MscorlibAssembly => s_mscorlibFacadeAssembly - ??= Assembly.Load("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); - - internal static Assembly CorelibAssembly { get; } = typeof(string).Assembly; - internal static string CorelibAssemblyString { get; } = CorelibAssembly.FullName!; - - /// - /// Returns the for the given . - /// - internal static PrimitiveType GetPrimitiveType(ReadOnlySpan typeName) => typeName switch - { - BooleanType => PrimitiveType.Boolean, - CharType => PrimitiveType.Char, - SByteType => PrimitiveType.SByte, - ByteType => PrimitiveType.Byte, - Int16Type => PrimitiveType.Int16, - UInt16Type => PrimitiveType.UInt16, - Int32Type => PrimitiveType.Int32, - UInt32Type => PrimitiveType.UInt32, - Int64Type => PrimitiveType.Int64, - UInt64Type => PrimitiveType.UInt64, - SingleType => PrimitiveType.Single, - DoubleType => PrimitiveType.Double, - DecimalType => PrimitiveType.Decimal, - DateTimeType => PrimitiveType.DateTime, - StringType => PrimitiveType.String, - TimeSpanType => PrimitiveType.TimeSpan, - _ => default, - }; - - /// - /// Returns the for the given . - /// - /// or if not a . - internal static PrimitiveType GetPrimitiveType(Type type) => type.IsEnum ? default : Type.GetTypeCode(type) switch - { - TypeCode.Boolean => PrimitiveType.Boolean, - TypeCode.Char => PrimitiveType.Char, - TypeCode.SByte => PrimitiveType.SByte, - TypeCode.Byte => PrimitiveType.Byte, - TypeCode.Int16 => PrimitiveType.Int16, - TypeCode.UInt16 => PrimitiveType.UInt16, - TypeCode.Int32 => PrimitiveType.Int32, - TypeCode.UInt32 => PrimitiveType.UInt32, - TypeCode.Int64 => PrimitiveType.Int64, - TypeCode.UInt64 => PrimitiveType.UInt64, - TypeCode.Single => PrimitiveType.Single, - TypeCode.Double => PrimitiveType.Double, - TypeCode.Decimal => PrimitiveType.Decimal, - TypeCode.DateTime => PrimitiveType.DateTime, - TypeCode.String => PrimitiveType.String, - // TypeCode.Empty => 0, - // TypeCode.Object => 0, - // TypeCode.DBNull => 0, - _ => type == typeof(TimeSpan) ? PrimitiveType.TimeSpan : default, - }; - - /// - /// Returns the for the given if it is a simple primitive array. - /// - /// or if not a primitive array. - internal static PrimitiveType GetPrimitiveArrayType(Type type) => type.IsSZArray - ? GetPrimitiveType(type.GetElementType()!) - : default; - - /// - /// Get the proper for the given . - /// - internal static BinaryType GetBinaryType(Type type) - { - if (type == typeof(string)) - { - return BinaryType.String; - } - else if (type == typeof(object)) - { - return BinaryType.Object; - } - else if (type == typeof(object[])) - { - return BinaryType.ObjectArray; - } - else if (type == typeof(string[])) - { - return BinaryType.StringArray; - } - else if (GetPrimitiveArrayType(type) != default) - { - return BinaryType.PrimitiveArray; - } - else - { - PrimitiveType primitiveType = GetPrimitiveType(type); - if (primitiveType == default) - { - return type.Assembly == MscorlibAssembly ? BinaryType.SystemClass : BinaryType.Class; - } - - return BinaryType.Primitive; - } - } - - /// - /// Returns the for the given . - /// - internal static Type GetPrimitiveTypeType(this PrimitiveType primitiveType) => primitiveType switch - { - PrimitiveType.Boolean => typeof(bool), - PrimitiveType.Char => typeof(char), - PrimitiveType.SByte => typeof(sbyte), - PrimitiveType.Byte => typeof(byte), - PrimitiveType.Int16 => typeof(short), - PrimitiveType.UInt16 => typeof(ushort), - PrimitiveType.Int32 => typeof(int), - PrimitiveType.UInt32 => typeof(uint), - PrimitiveType.Int64 => typeof(long), - PrimitiveType.UInt64 => typeof(ulong), - PrimitiveType.Single => typeof(float), - PrimitiveType.Double => typeof(double), - PrimitiveType.Decimal => typeof(decimal), - PrimitiveType.DateTime => typeof(DateTime), - PrimitiveType.String => typeof(string), - PrimitiveType.TimeSpan => typeof(TimeSpan), - _ => throw new NotSupportedException(), - }; - - /// - /// Returns the for the given . - /// - internal static Type GetArrayPrimitiveTypeType(this PrimitiveType primitiveType) => primitiveType switch - { - PrimitiveType.Boolean => typeof(bool[]), - PrimitiveType.Char => typeof(char[]), - PrimitiveType.SByte => typeof(sbyte[]), - PrimitiveType.Byte => typeof(byte[]), - PrimitiveType.Int16 => typeof(short[]), - PrimitiveType.UInt16 => typeof(ushort[]), - PrimitiveType.Int32 => typeof(int[]), - PrimitiveType.UInt32 => typeof(uint[]), - PrimitiveType.Int64 => typeof(long[]), - PrimitiveType.UInt64 => typeof(ulong[]), - PrimitiveType.Single => typeof(float[]), - PrimitiveType.Double => typeof(double[]), - PrimitiveType.Decimal => typeof(decimal[]), - PrimitiveType.DateTime => typeof(DateTime[]), - PrimitiveType.String => typeof(string[]), - PrimitiveType.TimeSpan => typeof(TimeSpan[]), - _ => throw new NotSupportedException(), - }; -} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembers.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembers.cs deleted file mode 100644 index 065f97f6022..00000000000 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembers.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Windows.Forms.BinaryFormat; - -/// -/// System class information. -/// -/// -/// -/// -/// [MS-NRBF] 2.3.2.4 -/// -/// -/// -internal sealed class SystemClassWithMembers : ClassRecord, IRecord, IBinaryFormatParseable -{ - private SystemClassWithMembers(ClassInfo classInfo, MemberTypeInfo memberTypeInfo, IReadOnlyList memberValues) - : base(classInfo, memberTypeInfo, memberValues) { } - - public static RecordType RecordType => RecordType.SystemClassWithMembers; - - static SystemClassWithMembers IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - ClassInfo classInfo = ClassInfo.Parse(state.Reader, out _); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.CreateFromClassInfoAndLibrary(state, classInfo, Id.Null); - return new( - classInfo, - memberTypeInfo, - ReadObjectMemberValues(state, memberTypeInfo)); - } - - public override void Write(BinaryWriter writer) - { - // Really shouldn't be writing this record type. It isn't as safe as the typed variant - // and saves very little space. - throw new NotSupportedException(); - } -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ArrayTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ArrayTests.cs index 6d944672433..7293ef965df 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ArrayTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ArrayTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Runtime.Serialization; using System.Windows.Forms.BinaryFormat; @@ -40,12 +41,12 @@ public void SameObjectRepeatedInArray() } [Theory] - [InlineData(-1, (byte)RecordType.ArraySingleObject)] - [InlineData(int.MinValue, (byte)RecordType.ArraySingleObject)] - [InlineData(-1, (byte)RecordType.ArraySinglePrimitive)] - [InlineData(int.MinValue, (byte)RecordType.ArraySinglePrimitive)] - [InlineData(-1, (byte)RecordType.ArraySingleString)] - [InlineData(int.MinValue, (byte)RecordType.ArraySingleString)] + [InlineData(-1, (byte)SerializationRecordType.ArraySingleObject)] + [InlineData(int.MinValue, (byte)SerializationRecordType.ArraySingleObject)] + [InlineData(-1, (byte)SerializationRecordType.ArraySinglePrimitive)] + [InlineData(int.MinValue, (byte)SerializationRecordType.ArraySinglePrimitive)] + [InlineData(-1, (byte)SerializationRecordType.ArraySingleString)] + [InlineData(int.MinValue, (byte)SerializationRecordType.ArraySingleString)] public void NegativeLength(int length, byte recordType) { MemoryStream stream = new(); @@ -77,7 +78,7 @@ public void BinaryArray_NegativeLength(int length, byte arrayType) MemoryStream stream = new(); using (BinaryFormatWriterScope scope = new(stream)) { - scope.Writer.Write((byte)RecordType.BinaryArray); + scope.Writer.Write((byte)SerializationRecordType.BinaryArray); // Id scope.Writer.Write(1); @@ -111,7 +112,7 @@ public void BinaryArray_InvalidRank(int rank, byte arrayType) MemoryStream stream = new(); using (BinaryFormatWriterScope scope = new(stream)) { - scope.Writer.Write((byte)RecordType.BinaryArray); + scope.Writer.Write((byte)SerializationRecordType.BinaryArray); // Id scope.Writer.Write(1); @@ -143,7 +144,7 @@ public virtual void BinaryArray_InvalidRank_Positive(int rank, byte arrayType) MemoryStream stream = new(); using (BinaryFormatWriterScope scope = new(stream)) { - scope.Writer.Write((byte)RecordType.BinaryArray); + scope.Writer.Write((byte)SerializationRecordType.BinaryArray); // Id scope.Writer.Write(1); diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/BasicObjectTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/BasicObjectTests.cs index bbb5cec034c..c4ae23ebc66 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/BasicObjectTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/BasicObjectTests.cs @@ -75,7 +75,7 @@ public static EnumerableTupleTheoryData -/// Helper for manually looking through records. -/// -public sealed class ManualParser : BinaryFormattedObject.IParseState, IDisposable -{ - private readonly RecordMap _recordMap; - private readonly BinaryFormattedObject.Options _options = new(); - - /// - /// Creates a parse - /// - /// - public ManualParser(object @object) - { - BinaryFormatter formatter = new(); - MemoryStream stream = new(); - formatter.Serialize(stream, @object); - stream.Position = 0; - Reader = new(stream); - - _recordMap = new(); - } - - public BinaryReader Reader { get; } - - RecordMap BinaryFormattedObject.IParseState.RecordMap => _recordMap; - - BinaryFormattedObject.Options BinaryFormattedObject.IParseState.Options => _options; - - BinaryFormattedObject.ITypeResolver BinaryFormattedObject.IParseState.TypeResolver => throw new NotSupportedException(); - - internal IRecord ReadRecord() => Record.ReadBinaryFormatRecord(this); - - public void Dispose() => Reader.Dispose(); -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/NullRecordTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/NullRecordTests.cs deleted file mode 100644 index b16abdb973d..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/NullRecordTests.cs +++ /dev/null @@ -1,256 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization; -using System.Windows.Forms; -using System.Windows.Forms.BinaryFormat; - -namespace FormatTests.Common; - -public abstract class NullRecordTests : SerializationTest where T : ISerializer -{ - [Theory] - // One over, with ObjectNullMultiple256 - [InlineData(11)] - // ObjectNullMultiple - [InlineData(256)] - public void NullObjectArray_CorruptedNullCount(int nullCount) - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.ArraySingleObject); - - // Id - scope.Writer.Write(1); - - // Length - scope.Writer.Write(10); - - NullRecord.Write(scope, nullCount); - } - - stream.Position = 0; - Action action = () => Deserialize(stream); - action.Should().Throw(); - } - - [Fact] - public virtual void NullObjectArray_NotEnoughNulls() - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.ArraySingleObject); - - // Id - scope.Writer.Write(1); - - // Length - scope.Writer.Write(10); - - NullRecord.Write(scope, 9); - } - - stream.Position = 0; - object deserialized = Deserialize(stream); - } - - [Theory] - [InlineData(-1)] - [InlineData(-2)] - [InlineData(int.MinValue)] - public void NullRecord_Negative(int count) - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.ArraySingleObject); - - // Id - scope.Writer.Write(1); - - // Length - scope.Writer.Write(1); - - scope.Writer.Write((byte)RecordType.ObjectNullMultiple); - scope.Writer.Write(count); - } - - stream.Position = 0; - - // BinaryFormattedObject blocks before it creates any objects - Action action = () => Deserialize(stream); - action.Should().Throw(); - } - - [Fact] - public virtual void NullRecord_ZeroByte() - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.ArraySingleObject); - - // Id - scope.Writer.Write(1); - - // Length - scope.Writer.Write(1); - - scope.Writer.Write((byte)RecordType.ObjectNullMultiple256); - scope.Writer.Write((byte)0); - } - - stream.Position = 0; - - object deserialized = Deserialize(stream); - } - - [Theory] - [InlineData(0)] - [InlineData(2)] - public virtual void NullRecord_WrongLength_WithTuple(int nullCount) - { - Tuple tuple = new(null); - Stream stream = Serialize(tuple); - BinaryFormattedObject format = new(stream); - - stream.Position = 0; - - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.ArraySingleObject); - - // Id - scope.Writer.Write(1); - - // Length - scope.Writer.Write(2); - - // Write a member reference to the tuple, followed by a null record with zero length. - new MemberReference(2).Write(scope); - scope.Writer.Write((byte)RecordType.ObjectNullMultiple256); - scope.Writer.Write((byte)nullCount); - - // Write the Tuple out with an updated Id - scope.Writer.Write((byte)RecordType.SystemClassWithMembersAndTypes); - var record = (SystemClassWithMembersAndTypes)format[(Id)1]; - ClassInfo classInfo = record.ClassInfo; - new ClassInfo(2, classInfo.Name, classInfo.MemberNames).Write(scope); - record.MemberTypeInfo.Write(scope); - NullRecord.Write(scope, 1); - } - - stream.Position = 0; - - object deserialized = Deserialize(stream); - } - - [Fact] - public virtual void NullRecord_ZeroInt() - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.ArraySingleObject); - - // Id - scope.Writer.Write(1); - - // Length - scope.Writer.Write(1); - - scope.Writer.Write((byte)RecordType.ObjectNullMultiple); - scope.Writer.Write(0); - } - - stream.Position = 0; - - object deserialized = Deserialize(stream); - } - - [Fact] - public virtual void NullRecord_FollowingReferenceable() - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - new ArraySingleObject(1, [null]).Write(scope); - NullRecord.Write(scope, 1); - } - - stream.Position = 0; - - // Not technically valid, the BinaryFormatter gets a null ref that it turns into SerializationException. - // (According to the specification you shouldn't have null records that don't follow object or array records.) - object deserialized = Deserialize(stream); - - deserialized.Should().BeEquivalentTo(new object?[1]); - } - - [Fact] - public virtual void NullRecord_BeforeReferenceable() - { - MemoryStream stream = new(); - using (BinaryFormatWriterScope scope = new(stream)) - { - NullRecord.Write(scope, 1); - new ArraySingleObject(1, [null]).Write(scope); - } - - stream.Position = 0; - - // Not technically valid, the BinaryFormatter gets a null ref that it turns into SerializationException. - // (According to the specification you shouldn't have null records that don't follow object or array records.) - object deserialized = Deserialize(stream); - - deserialized.Should().BeEquivalentTo(new object?[1]); - } - - [Fact] - public virtual void Tuple_WithMultipleNullCount() - { - Tuple tuple = new(null); - Stream stream = Serialize(tuple); - BinaryFormattedObject format = new(stream); - - stream.Position = 0; - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.SystemClassWithMembersAndTypes); - var record = (SystemClassWithMembersAndTypes)format[(Id)1]; - record.ClassInfo.Write(scope); - record.MemberTypeInfo.Write(scope); - NullRecord.Write(scope, 2); - } - - stream.Position = 0; - - object deserialized = Deserialize(stream); - } - - [Fact] - public virtual void Tuple_WithZeroNullCount() - { - Tuple tuple = new(null); - Stream stream = Serialize(tuple); - BinaryFormattedObject format = new(stream); - - stream.Position = 0; - using (BinaryFormatWriterScope scope = new(stream)) - { - scope.Writer.Write((byte)RecordType.SystemClassWithMembersAndTypes); - var record = (SystemClassWithMembersAndTypes)format[(Id)1]; - record.ClassInfo.Write(scope); - record.MemberTypeInfo.Write(scope); - - scope.Writer.Write((byte)RecordType.ObjectNullMultiple); - scope.Writer.Write(0); - } - - stream.Position = 0; - - object deserialized = Deserialize(stream); - } -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/PrimitiveTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/PrimitiveTests.cs index 38c56966ecb..5c5ca796ce3 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/PrimitiveTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/PrimitiveTests.cs @@ -1,18 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.Serialization.Formatters; - namespace FormatTests.Common; public abstract class PrimitiveTests : SerializationTest where T : ISerializer { - [Fact] - public void TypesWhenNeeded_Integer() - { - int value = 42; - Stream stream = Serialize(value, typeStyle: FormatterTypeStyle.TypesWhenNeeded); - object deserialized = Deserialize(stream); - deserialized.Should().Be(42); - } } diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ArrayTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ArrayTests.cs index c3d02153443..f267d8e0692 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ArrayTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ArrayTests.cs @@ -1,11 +1,9 @@ // 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; using System.Drawing; using System.Runtime.Serialization; -using System.Windows.Forms; -using System.Windows.Forms.BinaryFormat; +using System.Formats.Nrbf; namespace FormatTests.FormattedObject; @@ -17,49 +15,13 @@ public override void Roundtrip_ArrayContainingArrayAtNonZeroLowerBound() action.Should().Throw(); } - [Theory] - [MemberData(nameof(ArrayInfo_ParseSuccessData))] - public void ArrayInfo_Parse_Success(Stream stream, int expectedId, int expectedLength) - { - using BinaryReader reader = new(stream); - Id id = ArrayInfo.Parse(reader, out Count length); - id.Should().Be((Id)expectedId); - length.Should().Be(expectedLength); - length.Should().Be(expectedLength); - } - - public static TheoryData ArrayInfo_ParseSuccessData => new() - { - { new MemoryStream([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), 0, 0 }, - { new MemoryStream([0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]), 1, 1 }, - { new MemoryStream([0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]), 2, 1 }, - { new MemoryStream([0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0x7F]), int.MaxValue, int.MaxValue } - }; - - [Theory] - [MemberData(nameof(ArrayInfo_ParseNegativeData))] - public void ArrayInfo_Parse_Negative(Stream stream, Type expectedException) - { - using BinaryReader reader = new(stream); - Assert.Throws(expectedException, () => ArrayInfo.Parse(reader, out Count length)); - } - - public static TheoryData ArrayInfo_ParseNegativeData => new() - { - // Not enough data - { new MemoryStream([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), typeof(EndOfStreamException) }, - { new MemoryStream([0x00, 0x00, 0x00]), typeof(EndOfStreamException) }, - // Negative numbers - { new MemoryStream([0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF]), typeof(ArgumentOutOfRangeException) } - }; - [Theory] [MemberData(nameof(StringArray_Parse_Data))] public void StringArray_Parse(string?[] strings) { - BinaryFormattedObject format = new(Serialize(strings)); - ArraySingleString array = (ArraySingleString)format.RootRecord; - array.GetStringValues(format.RecordMap).Should().BeEquivalentTo(strings); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(strings)); + var arrayRecord = (SZArrayRecord)format.RootRecord; + arrayRecord.GetArray().Should().BeEquivalentTo(strings); } public static TheoryData StringArray_Parse_Data => new() @@ -73,9 +35,9 @@ public void StringArray_Parse(string?[] strings) [MemberData(nameof(PrimitiveArray_Parse_Data))] public void PrimitiveArray_Parse(Array array) { - BinaryFormattedObject format = new(Serialize(array)); - ArrayRecord arrayRecord = (ArrayRecord)format.RootRecord; - arrayRecord.Should().BeEquivalentTo((IEnumerable)array); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(array)); + var arrayRecord = (ArrayRecord)format.RootRecord; + arrayRecord.GetArray(expectedArrayType: array.GetType()).Should().BeEquivalentTo(array); } public static TheoryData PrimitiveArray_Parse_Data => new() diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormattedObjectTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormattedObjectTests.cs index 89a02f1b3bc..adf50d90d04 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormattedObjectTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/BinaryFormattedObjectTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Windows.Forms.BinaryFormat; +using System.Formats.Nrbf; using FormatTests.Common; namespace FormatTests.FormattedObject; @@ -12,8 +12,8 @@ public class BinaryFormattedObjectTests : SerializationTest stringRecord = (PrimitiveTypeRecord)format[format.RootRecord.Id]; stringRecord.Value.Should().Be(testString); } [Fact] public void ReadEmptyHashTable() { - BinaryFormattedObject format = new(Serialize(new Hashtable())); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new Hashtable())); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; - systemClass.ObjectId.Should().Be(1); - systemClass.Name.Should().Be("System.Collections.Hashtable"); + ClassRecord systemClass = (ClassRecord)format[format.RootRecord.Id]; + VerifyHashTable(systemClass, expectedVersion: 0, expectedHashSize: 3); + + SZArrayRecord keys = (SZArrayRecord)systemClass.GetSerializationRecord("Keys")!; + keys.Length.Should().Be(0); + SZArrayRecord values = (SZArrayRecord)systemClass.GetSerializationRecord("Values")!; + values.Length.Should().Be(0); + } + + private static void VerifyHashTable(ClassRecord systemClass, int expectedVersion, int expectedHashSize) + { + systemClass.RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); + systemClass.TypeName.FullName.Should().Be("System.Collections.Hashtable"); systemClass.MemberNames.Should().BeEquivalentTo( [ "LoadFactor", @@ -47,300 +56,212 @@ public void ReadEmptyHashTable() "Values" ]); - systemClass.MemberTypeInfo.Should().BeEquivalentTo(new (BinaryType Type, object? Info)[] - { - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.SystemClass, "System.Collections.IComparer"), - (BinaryType.SystemClass, "System.Collections.IHashCodeProvider"), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.ObjectArray, null), - (BinaryType.ObjectArray, null) - }); - - systemClass.MemberValues.Should().BeEquivalentTo(new object?[] - { - 0.72f, - 0, - null, - null, - 3, - new MemberReference(2), - new MemberReference(3) - }); - - ArraySingleObject array = (ArraySingleObject)format[2]; - array.ArrayInfo.ObjectId.Should().Be(2); - array.ArrayInfo.Length.Should().Be(0); - - array = (ArraySingleObject)format[3]; - array.ArrayInfo.ObjectId.Should().Be(3); - array.ArrayInfo.Length.Should().Be(0); + systemClass.GetSingle("LoadFactor").Should().Be(0.72f); + systemClass.GetInt32("Version").Should().Be(expectedVersion); + systemClass.GetRawValue("Comparer").Should().BeNull(); + systemClass.GetRawValue("HashCodeProvider").Should().BeNull(); + systemClass.GetInt32("HashSize").Should().Be(expectedHashSize); } [Fact] public void ReadHashTableWithStringPair() { - BinaryFormattedObject format = new(Serialize(new Hashtable() + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new Hashtable() { { "This", "That" } })); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; + ClassRecord systemClass = (ClassRecord)format[format.RootRecord.Id]; + VerifyHashTable(systemClass, expectedVersion: 1, expectedHashSize: 3); - systemClass.MemberTypeInfo.Should().BeEquivalentTo(new (BinaryType Type, object? Info)[] - { - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.SystemClass, "System.Collections.IComparer"), - (BinaryType.SystemClass, "System.Collections.IHashCodeProvider"), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.ObjectArray, null), - (BinaryType.ObjectArray, null) - }); - - systemClass.MemberValues.Should().BeEquivalentTo(new object?[] - { - 0.72f, - 1, - null, - null, - 3, - new MemberReference(2), - new MemberReference(3) - }); - - ArraySingleObject array = (ArraySingleObject)format[2]; - array.ArrayInfo.ObjectId.Should().Be(2); - array.ArrayInfo.Length.Should().Be(1); - BinaryObjectString value = (BinaryObjectString)array.ArrayObjects[0]!; - value.ObjectId.Should().Be(4); - value.Value.Should().Be("This"); - - array = (ArraySingleObject)format[3]; - array.ArrayInfo.ObjectId.Should().Be(3); - array.ArrayInfo.Length.Should().Be(1); - value = (BinaryObjectString)array.ArrayObjects[0]!; - value.ObjectId.Should().Be(5); - value.Value.Should().Be("That"); + SZArrayRecord keys = (SZArrayRecord)format[systemClass.GetArrayRecord("Keys")!.Id]; + keys.Length.Should().Be(1); + keys.GetArray().Single().Should().Be("This"); + SZArrayRecord values = (SZArrayRecord)format[systemClass.GetArrayRecord("Values")!.Id]; + values.Length.Should().Be(1); + values.GetArray().Single().Should().Be("That"); } [Fact] public void ReadHashTableWithRepeatedStrings() { - BinaryFormattedObject format = new(Serialize(new Hashtable() + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new Hashtable() { { "This", "That" }, { "TheOther", "This" }, { "That", "This" } })); + ClassRecord systemClass = (ClassRecord)format[format.RootRecord.Id]; + VerifyHashTable(systemClass, expectedVersion: 4, expectedHashSize: 7); + // The collections themselves get ids first before the strings do. - // Everything in the second array is a string reference. - ArraySingleObject array = (ArraySingleObject)format[3]; - array.ObjectId.Should().Be(3); - array[0].Should().BeOfType(); - array[1].Should().BeOfType(); - array[2].Should().BeOfType(); + // Everything in the second keys is a string reference. + SZArrayRecord array = (SZArrayRecord)systemClass.GetSerializationRecord("Keys")!; + array.GetArray().Should().BeEquivalentTo(["This", "TheOther", "That"]); } [Fact] public void ReadHashTableWithNullValues() { - BinaryFormattedObject format = new(Serialize(new Hashtable() + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new Hashtable() { { "Yowza", null }, { "Youza", null }, { "Meeza", null } })); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; + ClassRecord systemClass = (ClassRecord)format[format.RootRecord.Id]; + VerifyHashTable(systemClass, expectedVersion: 4, expectedHashSize: 7); - systemClass.MemberValues.Should().BeEquivalentTo(new object?[] - { - 0.72f, - 4, - null, - null, - 7, - new MemberReference(2), - new MemberReference(3) - }); - - ArrayRecord array = (ArrayRecord)format[(MemberReference)systemClass.MemberValues[5]!]; - - array.ArrayInfo.ObjectId.Should().Be(2); - array.ArrayInfo.Length.Should().Be(3); - BinaryObjectString value = (BinaryObjectString)array.ArrayObjects[0]; - value.ObjectId.Should().Be(4); - value.Value.Should().BeOneOf("Yowza", "Youza", "Meeza"); - - array = (ArrayRecord)format[(MemberReference)systemClass["Values"]!]; - array.ArrayInfo.ObjectId.Should().Be(3); - array.ArrayInfo.Length.Should().Be(3); - array.ArrayObjects[0].Should().BeNull(); + // The collections themselves get ids first before the strings do. + // Everything in the second keys is a string reference. + SZArrayRecord keys = (SZArrayRecord)systemClass.GetSerializationRecord("Keys")!; + keys.GetArray().Should().BeEquivalentTo(new object[] { "Yowza", "Youza", "Meeza" }); + + SZArrayRecord values = (SZArrayRecord)systemClass.GetSerializationRecord("Values")!; + values.GetArray().Should().BeEquivalentTo(new object?[] { null, null, null }); } [Fact] public void ReadObject() { - BinaryFormattedObject format = new(Serialize(new object())); - format[1].Should().BeOfType(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new object())); + format[format.RootRecord.Id].RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); } [Fact] public void ReadStruct() { ValueTuple tuple = new(355); - BinaryFormattedObject format = new(Serialize(tuple)); - format[1].Should().BeOfType(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(tuple)); + format[format.RootRecord.Id].RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); } [Fact] public void ReadSimpleSerializableObject() { - BinaryFormattedObject format = new(Serialize(new SimpleSerializableObject())); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new SimpleSerializableObject())); - ClassWithMembersAndTypes @class = (ClassWithMembersAndTypes)format.RootRecord; - @class.ObjectId.Should().Be(1); - @class.Name.Should().Be(typeof(SimpleSerializableObject).FullName); + ClassRecord @class = (ClassRecord)format.RootRecord; + @class.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + @class.Id.Should().Be(format.RootRecord.Id); + @class.TypeName.FullName.Should().Be(typeof(SimpleSerializableObject).FullName); + @class.TypeName.AssemblyName!.FullName.Should().Be(typeof(SimpleSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEmpty(); - @class.LibraryId.Should().Be(2); - @class.MemberTypeInfo.Should().BeEmpty(); - - format[@class.LibraryId].Should().BeOfType() - .Which.LibraryName.Should().Be(typeof(BinaryFormattedObjectTests).Assembly.FullName); } [Fact] public void ReadNestedSerializableObject() { - BinaryFormattedObject format = new(Serialize(new NestedSerializableObject())); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new NestedSerializableObject())); - ClassWithMembersAndTypes @class = (ClassWithMembersAndTypes)format.RootRecord; - @class.ObjectId.Should().Be(1); - @class.Name.Should().Be(typeof(NestedSerializableObject).FullName); + ClassRecord @class = (ClassRecord)format.RootRecord; + @class.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + @class.Id.Should().Be(format.RootRecord.Id); + @class.TypeName.FullName.Should().Be(typeof(NestedSerializableObject).FullName); + @class.TypeName.AssemblyName!.FullName.Should().Be(typeof(NestedSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEquivalentTo(["_object", "_meaning"]); - @class.LibraryId.Should().Be(2); - @class.MemberTypeInfo.Should().BeEquivalentTo(new (BinaryType Type, object? Info)[] - { - (BinaryType.Class, new ClassTypeInfo(typeof(SimpleSerializableObject).FullName!, 2)), - (BinaryType.Primitive, PrimitiveType.Int32) - }); - - @class.MemberValues.Should().BeEquivalentTo(new object?[] - { - new MemberReference(3), - 42 - }); - - @class = (ClassWithMembersAndTypes)format[3]; - @class.ObjectId.Should().Be(3); - @class.Name.Should().Be(typeof(SimpleSerializableObject).FullName); - @class.MemberNames.Should().BeEmpty(); - @class.LibraryId.Should().Be(2); - @class.MemberTypeInfo.Should().BeEmpty(); + @class.GetRawValue("_object").Should().NotBeNull(); + @class.GetInt32("_meaning").Should().Be(42); } [Fact] public void ReadTwoIntObject() { - BinaryFormattedObject format = new(Serialize(new TwoIntSerializableObject())); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new TwoIntSerializableObject())); - ClassWithMembersAndTypes @class = (ClassWithMembersAndTypes)format.RootRecord; - @class.ObjectId.Should().Be(1); - @class.Name.Should().Be(typeof(TwoIntSerializableObject).FullName); + ClassRecord @class = (ClassRecord)format.RootRecord; + @class.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + @class.Id.Should().Be(format.RootRecord.Id); + @class.TypeName.FullName.Should().Be(typeof(TwoIntSerializableObject).FullName); + @class.TypeName.AssemblyName!.FullName.Should().Be(typeof(TwoIntSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEquivalentTo(["_value", "_meaning"]); - @class.LibraryId.Should().Be(2); - @class.MemberTypeInfo.Should().BeEquivalentTo(new (BinaryType Type, object? Info)[] - { - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.Primitive, PrimitiveType.Int32) - }); - - @class.MemberValues.Should().BeEquivalentTo(new object?[] - { - 1970, - 42 - }); + @class.GetRawValue("_value").Should().Be(1970); + @class.GetInt32("_meaning").Should().Be(42); } [Fact] public void ReadRepeatedNestedObject() { - BinaryFormattedObject format = new(Serialize(new RepeatedNestedSerializableObject())); - ClassWithMembersAndTypes firstClass = (ClassWithMembersAndTypes)format[3]; - ClassWithId classWithId = (ClassWithId)format[4]; - classWithId.MetadataId.Should().Be(firstClass.ObjectId); - classWithId.MemberValues.Should().BeEquivalentTo(new object[] { 1970, 42 }); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new RepeatedNestedSerializableObject())); + ClassRecord classRecord = (ClassRecord)format.RootRecord; + ClassRecord firstClass = classRecord.GetClassRecord("_first")!; + firstClass.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + ClassRecord classWithId = classRecord.GetClassRecord("_second")!; + classWithId.RecordType.Should().Be(SerializationRecordType.ClassWithId); + classWithId.GetRawValue("_value").Should().Be(1970); + classWithId.GetInt32("_meaning").Should().Be(42); } [Fact] public void ReadPrimitiveArray() { - BinaryFormattedObject format = new(Serialize(new int[] { 10, 9, 8, 7 })); + int[] input = [10, 9, 8, 7]; + + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(input)); - ArraySinglePrimitive array = (ArraySinglePrimitive)format[1]; - array.ArrayInfo.Length.Should().Be(4); - array.PrimitiveType.Should().Be(PrimitiveType.Int32); - array.ArrayObjects.Should().BeEquivalentTo(new object[] { 10, 9, 8, 7 }); + SZArrayRecord array = (SZArrayRecord)format[format.RootRecord.Id]; + array.Length.Should().Be(4); + array.GetArray().Should().BeEquivalentTo(input); } [Fact] public void ReadStringArray() { - BinaryFormattedObject format = new(Serialize(new string[] { "Monday", "Tuesday", "Wednesday" })); - ArraySingleString array = (ArraySingleString)format[1]; - array.ArrayInfo.ObjectId.Should().Be(1); - array.ArrayInfo.Length.Should().Be(3); - BinaryObjectString value = (BinaryObjectString)array.ArrayObjects[0]!; + string[] input = ["Monday", "Tuesday", "Wednesday"]; + + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(input)); + + SZArrayRecord array = (SZArrayRecord)format[format.RootRecord.Id]; + array.Length.Should().Be(3); + array.GetArray().Should().BeEquivalentTo(input); + format.RecordMap.Count.Should().Be(4); } [Fact] public void ReadStringArrayWithNulls() { - BinaryFormattedObject format = new(Serialize(new string?[] { "Monday", null, "Wednesday", null, null, null })); - ArraySingleString array = (ArraySingleString)format[1]; - array.ArrayInfo.ObjectId.Should().Be(1); - array.ArrayInfo.Length.Should().Be(6); - array.ArrayObjects.Should().BeEquivalentTo(new object?[] - { - new BinaryObjectString(2, "Monday"), - null, - new BinaryObjectString(3, "Wednesday"), - null, - null, - null - }); - BinaryObjectString value = (BinaryObjectString)array.ArrayObjects[0]!; + string?[] input = ["Monday", null, "Wednesday", null, null, null]; + + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(input)); + + SZArrayRecord array = (SZArrayRecord)format[format.RootRecord.Id]; + array.Id.Should().Be(format.RootRecord.Id); + array.Length.Should().Be(6); + array.GetArray().Should().BeEquivalentTo(input); } [Fact] public void ReadDuplicatedStringArray() { - BinaryFormattedObject format = new(Serialize(new string[] { "Monday", "Tuesday", "Monday" })); - ArraySingleString array = (ArraySingleString)format[1]; - array.ArrayInfo.ObjectId.Should().Be(1); - array.ArrayInfo.Length.Should().Be(3); - BinaryObjectString value = (BinaryObjectString)array.ArrayObjects[0]!; - MemberReference reference = (MemberReference)array.ArrayObjects[2]!; - reference.IdRef.Should().Be(value.ObjectId); + string[] input = ["Monday", "Tuesday", "Monday"]; + + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(input)); + + SZArrayRecord array = (SZArrayRecord)format[format.RootRecord.Id]; + array.Id.Should().Be(format.RootRecord.Id); + array.Length.Should().Be(3); + array.GetArray().Should().BeEquivalentTo(input); + format.RecordMap.Count.Should().Be(3); } [Fact] public void ReadObjectWithNullableObjects() { - BinaryFormattedObject format = new(Serialize(new ObjectWithNullableObjects())); - ClassWithMembersAndTypes classRecord = (ClassWithMembersAndTypes)format.RootRecord; - BinaryLibrary library = (BinaryLibrary)format[classRecord.LibraryId]; + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new ObjectWithNullableObjects())); + ClassRecord classRecord = (ClassRecord)format.RootRecord; + classRecord.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + classRecord.TypeName.AssemblyName!.FullName.Should().Be(typeof(ObjectWithNullableObjects).Assembly.FullName); } [Fact] public void ReadNestedObjectWithNullableObjects() { - BinaryFormattedObject format = new(Serialize(new NestedObjectWithNullableObjects())); - ClassWithMembersAndTypes classRecord = (ClassWithMembersAndTypes)format.RootRecord; - BinaryLibrary library = (BinaryLibrary)format[classRecord.LibraryId]; + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new NestedObjectWithNullableObjects())); + ClassRecord classRecord = (ClassRecord)format.RootRecord; + classRecord.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + classRecord.TypeName.AssemblyName!.FullName.Should().Be(typeof(NestedObjectWithNullableObjects).Assembly.FullName); } [Serializable] diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ClassInfoTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ClassInfoTests.cs deleted file mode 100644 index ad3d7366670..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ClassInfoTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Windows.Forms; -using System.Windows.Forms.BinaryFormat; - -namespace FormatTests.FormattedObject; - -public class ClassInfoTests -{ - private static readonly byte[] s_hashtableClassInfo = - [ - 0x01, 0x00, 0x00, 0x00, 0x1c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, - 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x48, 0x61, 0x73, 0x68, 0x74, 0x61, 0x62, 0x6c, - 0x65, 0x07, 0x00, 0x00, 0x00, 0x0a, 0x4c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, - 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x72, 0x65, - 0x72, 0x10, 0x48, 0x61, 0x73, 0x68, 0x43, 0x6f, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x08, 0x48, 0x61, 0x73, 0x68, 0x53, 0x69, 0x7a, 0x65, 0x04, 0x4b, 0x65, 0x79, 0x73, - 0x06, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73 - ]; - - [Fact] - public void ClassInfo_ReadHashtable() - { - using BinaryReader reader = new(new MemoryStream(s_hashtableClassInfo)); - ClassInfo info = ClassInfo.Parse(reader, out Count memberCount); - - memberCount.Should().Be(7); - info.ObjectId.Should().Be(1); - info.Name.Should().Be("System.Collections.Hashtable"); - info.MemberNames.Should().BeEquivalentTo( - [ - "LoadFactor", - "Version", - "Comparer", - "HashCodeProvider", - "HashSize", - "Keys", - "Values" - ]); - } - - [Fact] - public void ClassInfo_Hashtable_RoundTrip() - { - using BinaryReader reader = new(new MemoryStream(s_hashtableClassInfo)); - ClassInfo info = ClassInfo.Parse(reader, out Count memberCount); - - MemoryStream stream = new(); - BinaryWriter writer = new(stream); - info.Write(writer); - stream.Position = 0; - - using BinaryReader reader2 = new(stream); - info = ClassInfo.Parse(reader2, out memberCount); - - memberCount.Should().Be(7); - info.ObjectId.Should().Be(1); - info.Name.Should().Be("System.Collections.Hashtable"); - info.MemberNames.Should().BeEquivalentTo( - [ - "LoadFactor", - "Version", - "Comparer", - "HashCodeProvider", - "HashSize", - "Keys", - "Values" - ]); - } - - [Fact] - public void MemberTypeInfo_ReadHashtable_TooShort() - { - MemoryStream stream = new(s_hashtableClassInfo); - stream.SetLength(stream.Length - 1); - using BinaryReader reader = new(stream); - Action action = () => ClassInfo.Parse(reader, out _); - action.Should().Throw(); - } -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ExceptionTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ExceptionTests.cs index fc94a246f9f..cdba045c3a9 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ExceptionTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ExceptionTests.cs @@ -1,8 +1,7 @@ // 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; -using System.Windows.Forms.BinaryFormat; +using System.Formats.Nrbf; using FormatTests.Common; namespace FormatTests.FormattedObject; @@ -12,9 +11,9 @@ public class ExceptionTests : SerializationTest [Fact] public void NotSupportedException_Parse() { - BinaryFormattedObject format = new(Serialize(new NotSupportedException())); - var systemClass = (SystemClassWithMembersAndTypes)format.RootRecord; - systemClass.Name.Should().Be(typeof(NotSupportedException).FullName); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new NotSupportedException())); + var systemClass = (ClassRecord)format.RootRecord; + systemClass.TypeName.FullName.Should().Be(typeof(NotSupportedException).FullName); systemClass.MemberNames.Should().BeEquivalentTo( [ "ClassName", @@ -31,36 +30,17 @@ public void NotSupportedException_Parse() "WatsonBuckets" ]); - systemClass.MemberTypeInfo.Should().BeEquivalentTo(new (BinaryType, object?)[] - { - (BinaryType.String, null), - (BinaryType.String, null), - (BinaryType.SystemClass, typeof(IDictionary).FullName), - (BinaryType.SystemClass, typeof(Exception).FullName), - (BinaryType.String, null), - (BinaryType.String, null), - (BinaryType.String, null), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.String, null), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.String, null), - (BinaryType.PrimitiveArray, PrimitiveType.Byte) - }); - - systemClass.MemberValues.Should().BeEquivalentTo(new object?[] - { - new BinaryObjectString(2, "System.NotSupportedException"), - new BinaryObjectString(3, "Specified method is not supported."), - null, - null, - null, - null, - null, - 0, - null, - unchecked((int)0x80131515), - null, - null - }); + systemClass.GetString("ClassName").Should().Be("System.NotSupportedException"); + systemClass.GetString("Message").Should().Be("Specified method is not supported."); + systemClass.GetRawValue("Data").Should().BeNull(); + systemClass.GetRawValue("InnerException").Should().BeNull(); + systemClass.GetRawValue("HelpURL").Should().BeNull(); + systemClass.GetRawValue("StackTraceString").Should().BeNull(); + systemClass.GetRawValue("RemoteStackTraceString").Should().BeNull(); + systemClass.GetInt32("RemoteStackIndex").Should().Be(0); + systemClass.GetRawValue("ExceptionMethod").Should().BeNull(); + systemClass.GetInt32("HResult").Should().Be(unchecked((int)0x80131515)); + systemClass.GetRawValue("Source").Should().BeNull(); + systemClass.GetRawValue("WatsonBuckets").Should().BeNull(); } } diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs index 8f5eb0aadfc..2e5ba6ecb01 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/HashTableTests.cs @@ -4,8 +4,8 @@ using System.Collections; using System.Drawing; using System.Runtime.Serialization; +using System.Formats.Nrbf; using System.Runtime.Serialization.Formatters.Binary; -using System.Windows.Forms.BinaryFormat; using FormatTests.Common; namespace FormatTests.FormattedObject; @@ -66,13 +66,14 @@ public void HashTable_CustomComparer() { "This", "That" } }; - BinaryFormattedObject format = new(Serialize(hashtable)); - SystemClassWithMembersAndTypes systemClass = format.RootRecord.Should().BeOfType().Subject; - systemClass.Name.Should().Be("System.Collections.Hashtable"); - format[(MemberReference)systemClass["Comparer"]!].Should().BeOfType().Which.Name.Should().Be("System.OrdinalComparer"); - format[(MemberReference)systemClass["HashCodeProvider"]!].Should().BeOfType().Which.Name.Should().Be("FormatTests.FormattedObject.HashtableTests+CustomHashCodeProvider"); - format[(MemberReference)systemClass["Keys"]!].Should().BeOfType(); - format[(MemberReference)systemClass["Values"]!].Should().BeOfType(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(hashtable)); + ClassRecord systemClass = (ClassRecord)format.RootRecord; + systemClass.RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); + systemClass.TypeName.FullName.Should().Be("System.Collections.Hashtable"); + systemClass.GetSerializationRecord("Comparer")!.Should().BeAssignableTo().Which.TypeName.FullName.Should().Be("System.OrdinalComparer"); + systemClass.GetSerializationRecord("HashCodeProvider")!.Should().BeAssignableTo().Which.TypeName.FullName.Should().Be("FormatTests.FormattedObject.HashtableTests+CustomHashCodeProvider"); + systemClass.GetSerializationRecord("Keys")!.Should().BeAssignableTo>(); + systemClass.GetSerializationRecord("Values")!.Should().BeAssignableTo>(); } [Fact] @@ -83,8 +84,8 @@ public void HashTable_CustomComparer_DoesNotRead() { "This", "That" } }; - BinaryFormattedObject format = new(Serialize(hashtable)); - format.TryGetPrimitiveHashtable(out Hashtable? deserialized).Should().BeFalse(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(hashtable)); + System.Windows.Forms.BinaryFormat.BinaryFormattedObjectExtensions.TryGetPrimitiveHashtable(format, out Hashtable? deserialized).Should().BeFalse(); deserialized.Should().BeNull(); } @@ -103,7 +104,7 @@ public void BinaryFormatWriter_WriteCustomComparerfails() }; using MemoryStream stream = new(); - BinaryFormatWriter.TryWriteHashtable(stream, hashtable).Should().BeFalse(); + System.Windows.Forms.BinaryFormat.BinaryFormatWriter.TryWriteHashtable(stream, hashtable).Should().BeFalse(); stream.Position.Should().Be(0); } @@ -112,7 +113,7 @@ public void BinaryFormatWriter_WriteCustomComparerfails() public void BinaryFormatWriter_WriteHashtables(Hashtable hashtable) { using MemoryStream stream = new(); - BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable); + System.Windows.Forms.BinaryFormat.BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable); stream.Position = 0; // cs/binary-formatter-without-binder @@ -133,7 +134,7 @@ public void BinaryFormatWriter_WriteHashtables(Hashtable hashtable) public void BinaryFormatWriter_WriteUnsupportedHashtables(Hashtable hashtable) { using MemoryStream stream = new(); - Action action = () => BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable); + Action action = () => System.Windows.Forms.BinaryFormat.BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable); action.Should().Throw(); } @@ -141,8 +142,8 @@ public void BinaryFormatWriter_WriteUnsupportedHashtables(Hashtable hashtable) [MemberData(nameof(Hashtables_TestData))] public void BinaryFormattedObjectExtensions_TryGetPrimitiveHashtable(Hashtable hashtable) { - BinaryFormattedObject format = new(Serialize(hashtable)); - format.TryGetPrimitiveHashtable(out Hashtable? deserialized).Should().BeTrue(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(hashtable)); + System.Windows.Forms.BinaryFormat.BinaryFormattedObjectExtensions.TryGetPrimitiveHashtable(format, out Hashtable? deserialized).Should().BeTrue(); deserialized!.Count.Should().Be(hashtable.Count); foreach (object? key in hashtable.Keys) @@ -156,11 +157,11 @@ public void BinaryFormattedObjectExtensions_TryGetPrimitiveHashtable(Hashtable h public void RoundTripHashtables(Hashtable hashtable) { using MemoryStream stream = new(); - BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable); + System.Windows.Forms.BinaryFormat.BinaryFormatWriter.WritePrimitiveHashtable(stream, hashtable); stream.Position = 0; - BinaryFormattedObject format = new(stream); - format.TryGetPrimitiveHashtable(out Hashtable? deserialized).Should().BeTrue(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(stream); + System.Windows.Forms.BinaryFormat.BinaryFormattedObjectExtensions.TryGetPrimitiveHashtable(format, out Hashtable? deserialized).Should().BeTrue(); deserialized!.Count.Should().Be(hashtable.Count); foreach (object? key in hashtable.Keys) { diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs index e4a07a964f5..503529c45be 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ListTests.cs @@ -3,8 +3,8 @@ using System.Collections; using System.Drawing; +using System.Formats.Nrbf; using System.Runtime.Serialization.Formatters.Binary; -using System.Windows.Forms.BinaryFormat; using FormatTests.Common; namespace FormatTests.FormattedObject; @@ -14,53 +14,53 @@ public class ListTests : SerializationTest [Fact] public void BinaryFormattedObject_ParseEmptyArrayList() { - BinaryFormattedObject format = new(Serialize(new ArrayList())); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new ArrayList())); - systemClass.Name.Should().Be(typeof(ArrayList).FullName); - systemClass.MemberNames.Should().BeEquivalentTo(["_items", "_size", "_version"]); - systemClass.MemberTypeInfo[0].Should().Be((BinaryType.ObjectArray, null)); + VerifyArrayList((ClassRecord)format[format.RootRecord.Id]); - format[2].Should().BeOfType(); + format[((ClassRecord)format.RootRecord).GetArrayRecord("_items")!.Id].Should().BeAssignableTo>(); + } + + private static void VerifyArrayList(ClassRecord systemClass) + { + systemClass.RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); + + systemClass.TypeName.FullName.Should().Be(typeof(ArrayList).FullName); + systemClass.MemberNames.Should().BeEquivalentTo(["_items", "_size", "_version"]); + systemClass.GetSerializationRecord("_items").Should().BeAssignableTo>(); } [Theory] [MemberData(nameof(ArrayList_Primitive_Data))] public void BinaryFormattedObject_ParsePrimitivesArrayList(object value) { - BinaryFormattedObject format = new(Serialize(new ArrayList() + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new ArrayList() { value })); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; + ClassRecord listRecord = (ClassRecord)format[format.RootRecord.Id]; + VerifyArrayList(listRecord); - systemClass.Name.Should().Be(typeof(ArrayList).FullName); - systemClass.MemberNames.Should().BeEquivalentTo(["_items", "_size", "_version"]); - systemClass.MemberTypeInfo[0].Should().Be((BinaryType.ObjectArray, null)); + SZArrayRecord array = (SZArrayRecord)format[listRecord.GetArrayRecord("_items")!.Id]; - ArraySingleObject array = (ArraySingleObject)format[2]; - MemberPrimitiveTyped primitve = (MemberPrimitiveTyped)array[0]!; - primitve.Value.Should().Be(value); + array.GetArray().Take(listRecord.GetInt32("_size")).Should().BeEquivalentTo(new[] { value }); } [Fact] public void BinaryFormattedObject_ParseStringArrayList() { - BinaryFormattedObject format = new(Serialize(new ArrayList() + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new ArrayList() { "JarJar" })); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; + ClassRecord listRecord = (ClassRecord)format[format.RootRecord.Id]; + VerifyArrayList(listRecord); - systemClass.Name.Should().Be(typeof(ArrayList).FullName); - systemClass.MemberNames.Should().BeEquivalentTo(["_items", "_size", "_version"]); - systemClass.MemberTypeInfo[0].Should().Be((BinaryType.ObjectArray, null)); + SZArrayRecord array = (SZArrayRecord)format[listRecord.GetArrayRecord("_items")!.Id]; - ArraySingleObject array = (ArraySingleObject)format[2]; - BinaryObjectString binaryString = (BinaryObjectString)array[0]!; - binaryString.Value.Should().Be("JarJar"); + array.GetArray().Take(listRecord.GetInt32("_size")).ToArray().Should().BeEquivalentTo(new object[] { "JarJar" }); } public static TheoryData ArrayList_Primitive_Data => new() @@ -123,14 +123,15 @@ public void BinaryFormattedObject_ParseStringArrayList() [Fact] public void BinaryFormattedObject_ParseEmptyIntList() { - BinaryFormattedObject format = new(Serialize(new List())); - SystemClassWithMembersAndTypes classInfo = (SystemClassWithMembersAndTypes)format[1]; + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new List())); + ClassRecord classInfo = (ClassRecord)format[format.RootRecord.Id]; + classInfo.RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); // Note that T types are serialized as the mscorlib type. - classInfo.Name.Should().Be( + classInfo.TypeName.FullName.Should().Be( "System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"); - classInfo.ClassInfo.MemberNames.Should().BeEquivalentTo( + classInfo.MemberNames.Should().BeEquivalentTo( [ "_items", // This is something that wouldn't be needed if List implemented ISerializable. If we format @@ -141,27 +142,25 @@ public void BinaryFormattedObject_ParseEmptyIntList() "_version" ]); - classInfo.MemberTypeInfo[0].Should().Be((BinaryType.PrimitiveArray, PrimitiveType.Int32)); - classInfo.MemberTypeInfo[1].Should().Be((BinaryType.Primitive, PrimitiveType.Int32)); - classInfo.MemberTypeInfo[2].Should().Be((BinaryType.Primitive, PrimitiveType.Int32)); - classInfo["_items"].Should().BeOfType(); - classInfo["_size"].Should().Be(0); - classInfo["_version"].Should().Be(0); + classInfo.GetSerializationRecord("_items").Should().BeAssignableTo>(); + classInfo.GetInt32("_size").Should().Be(0); + classInfo.GetInt32("_version").Should().Be(0); - ArraySinglePrimitive array = (ArraySinglePrimitive)format[2]; + SZArrayRecord array = (SZArrayRecord)format[classInfo.GetArrayRecord("_items")!.Id]; array.Length.Should().Be(0); } [Fact] public void BinaryFormattedObject_ParseEmptyStringList() { - BinaryFormattedObject format = new(Serialize(new List())); - SystemClassWithMembersAndTypes classInfo = (SystemClassWithMembersAndTypes)format[1]; - classInfo.ClassInfo.Name.Should().StartWith("System.Collections.Generic.List`1[[System.String,"); - classInfo.MemberTypeInfo[0].Should().Be((BinaryType.StringArray, null)); - classInfo["_items"].Should().BeOfType(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new List())); + + ClassRecord classInfo = (ClassRecord)format[format.RootRecord.Id]; + classInfo.RecordType.Should().Be(SerializationRecordType.SystemClassWithMembersAndTypes); + classInfo.TypeName.FullName.Should().StartWith("System.Collections.Generic.List`1[[System.String,"); + classInfo.GetSerializationRecord("_items").Should().BeAssignableTo>(); - ArraySingleString array = (ArraySingleString)format[2]; + SZArrayRecord array = (SZArrayRecord)format[classInfo.GetArrayRecord("_items")!.Id]; array.Length.Should().Be(0); } @@ -170,7 +169,7 @@ public void BinaryFormattedObject_ParseEmptyStringList() public void BinaryFormatWriter_TryWritePrimitiveList(IList list) { using MemoryStream stream = new(); - BinaryFormatWriter.TryWritePrimitiveList(stream, list).Should().BeTrue(); + System.Windows.Forms.BinaryFormat.BinaryFormatWriter.TryWritePrimitiveList(stream, list).Should().BeTrue(); stream.Position = 0; // cs/binary-formatter-without-binder @@ -186,7 +185,7 @@ public void BinaryFormatWriter_TryWritePrimitiveList(IList list) public void BinaryFormatWriter_TryWritePrimitiveList_Unsupported(IList list) { using MemoryStream stream = new(); - BinaryFormatWriter.TryWritePrimitiveList(stream, list).Should().BeFalse(); + System.Windows.Forms.BinaryFormat.BinaryFormatWriter.TryWritePrimitiveList(stream, list).Should().BeFalse(); stream.Position.Should().Be(0); } @@ -194,8 +193,8 @@ public void BinaryFormatWriter_TryWritePrimitiveList_Unsupported(IList list) [MemberData(nameof(PrimitiveLists_TestData))] public void BinaryFormattedObjectExtensions_TryGetPrimitiveList(IList list) { - BinaryFormattedObject format = new(Serialize(list)); - format.TryGetPrimitiveList(out object? deserialized).Should().BeTrue(); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(list)); + System.Windows.Forms.BinaryFormat.BinaryFormattedObjectExtensions.TryGetPrimitiveList(format, out object? deserialized).Should().BeTrue(); deserialized.Should().BeEquivalentTo(list); } diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/MemberTypeInfoTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/MemberTypeInfoTests.cs deleted file mode 100644 index 5b5b88c2aca..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/MemberTypeInfoTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Windows.Forms.BinaryFormat; - -namespace FormatTests.FormattedObject; - -public class MemberTypeInfoTests -{ - private static readonly byte[] s_hashtableMemberInfo = - [ - 0x00, 0x00, 0x03, 0x03, 0x00, 0x05, 0x05, 0x0b, 0x08, 0x1c, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x49, 0x43, 0x6f, - 0x6d, 0x70, 0x61, 0x72, 0x65, 0x72, 0x24, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x43, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x49, 0x48, 0x61, 0x73, 0x68, 0x43, - 0x6f, 0x64, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x08 - ]; - - [Fact] - public void MemberTypeInfo_ReadHashtable() - { - using BinaryReader reader = new(new MemoryStream(s_hashtableMemberInfo)); - MemberTypeInfo info = MemberTypeInfo.Parse(reader, 7); - - info.Should().BeEquivalentTo(new (BinaryType Type, object? Info)[] - { - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.SystemClass, "System.Collections.IComparer"), - (BinaryType.SystemClass, "System.Collections.IHashCodeProvider"), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.ObjectArray, null), - (BinaryType.ObjectArray, null) - }); - } - - [Fact] - public void MemberTypeInfo_HashtableRoundTrip() - { - using BinaryReader reader = new(new MemoryStream(s_hashtableMemberInfo)); - MemberTypeInfo info = MemberTypeInfo.Parse(reader, 7); - - MemoryStream stream = new(); - BinaryWriter writer = new(stream); - info.Write(writer); - stream.Position = 0; - - using BinaryReader reader2 = new(stream); - info = MemberTypeInfo.Parse(reader2, 7); - info.Should().BeEquivalentTo(new (BinaryType Type, object? Info)[] - { - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.SystemClass, "System.Collections.IComparer"), - (BinaryType.SystemClass, "System.Collections.IHashCodeProvider"), - (BinaryType.Primitive, PrimitiveType.Int32), - (BinaryType.ObjectArray, null), - (BinaryType.ObjectArray, null) - }); - } - - [Fact] - public void MemberTypeInfo_ReadHashtable_TooShort() - { - MemoryStream stream = new(s_hashtableMemberInfo); - stream.SetLength(stream.Length - 1); - using BinaryReader reader = new(stream); - Action action = () => MemberTypeInfo.Parse(reader, 7); - action.Should().Throw(); - } -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/NullRecordTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/NullRecordTests.cs deleted file mode 100644 index 161ecf86995..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/NullRecordTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization; -using System.Windows.Forms; -using System.Windows.Forms.BinaryFormat; -using FormatTests.Common; - -namespace FormatTests.FormattedObject; - -public class NullRecordTests : NullRecordTests -{ - [Fact] - public void NullObjectArrayRecords_MultipleSerializedAs() - { - object?[] items = new object?[10]; - using ManualParser parser = new(items); - - IRecord record = parser.ReadRecord(); - record.Should().BeOfType(); - - RecordType recordType = (RecordType)parser.Reader.ReadByte(); - recordType.Should().Be(RecordType.ArraySingleObject); - Id id = ArrayInfo.Parse(parser.Reader, out Count length); - id.Should().Be(1); - length.Should().Be(items.Length); - - record = parser.ReadRecord(); - record.Should().BeOfType() - .Subject.NullCount.Should().Be(items.Length); - } - - [Fact] - public void TupleWithNull_SerializedAs() - { - Tuple tuple = new(null); - using ManualParser parser = new(tuple); - - IRecord record = parser.ReadRecord(); - record.Should().BeOfType(); - - RecordType recordType = (RecordType)parser.Reader.ReadByte(); - recordType.Should().Be(RecordType.SystemClassWithMembersAndTypes); - ClassInfo classInfo = ClassInfo.Parse(parser.Reader, out Count memberCount); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(parser.Reader, memberCount); - - recordType = (RecordType)parser.Reader.ReadByte(); - recordType.Should().Be(RecordType.ObjectNull); - } - - [Fact] - public void TupleWithTwoNulls_SerializedAs() - { - Tuple tuple = new(null, null); - using ManualParser parser = new(tuple); - - IRecord record = parser.ReadRecord(); - record.Should().BeOfType(); - - RecordType recordType = (RecordType)parser.Reader.ReadByte(); - recordType.Should().Be(RecordType.SystemClassWithMembersAndTypes); - ClassInfo classInfo = ClassInfo.Parse(parser.Reader, out Count memberCount); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(parser.Reader, memberCount); - - recordType = (RecordType)parser.Reader.ReadByte(); - recordType.Should().Be(RecordType.ObjectNull); - } - - public override void NullRecord_ZeroByte() - { - // BinaryFormattedObject blocks before it creates any objects - Action action = base.NullRecord_ZeroByte; - action.Should().Throw(); - } - - public override void NullRecord_ZeroInt() - { - // BinaryFormattedObject blocks before it creates any objects - Action action = base.NullRecord_ZeroInt; - action.Should().Throw(); - } - - public override void NullObjectArray_NotEnoughNulls() - { - Action action = base.NullObjectArray_NotEnoughNulls; - action.Should().Throw(); - } - - public override void Tuple_WithMultipleNullCount() - { - // While it is ok per spec to write out multiple null count records for members, this is never done by the - // BinaryFormatter and it greatly complicates the deserialization logic. BinaryFormattedObject rejects this. - // (But does allow multiple null records for arrays, where they do normally occur.) - Action action = base.Tuple_WithMultipleNullCount; - action.Should().Throw(); - } - - public override void Tuple_WithZeroNullCount() - { - Action action = base.Tuple_WithZeroNullCount; - action.Should().Throw(); - } - - public override void NullRecord_WrongLength_WithTuple(int nullCount) - { - Action action = () => base.NullRecord_WrongLength_WithTuple(nullCount); - action.Should().Throw(); - } - - [Fact] - public void ObjectNullMultiple256_ThrowsOverflowOnWrite() - { - // We read a byte on the way in so there is nothing to check. - - NullRecord.ObjectNullMultiple256 objectNull = new(1000); - - using BinaryWriter writer = new(new MemoryStream()); - Action action = () => objectNull.Write(writer); - action.Should().Throw(); - } - - [Fact] - public void ObjectNullMultiple256_WritesCorrectly() - { - NullRecord.ObjectNullMultiple256 objectNull = new(0xCA); - - byte[] buffer = new byte[2]; - using BinaryWriter writer = new(new MemoryStream(buffer)); - objectNull.Write(writer); - buffer.Should().BeEquivalentTo(new byte[] { (byte)NullRecord.ObjectNullMultiple256.RecordType, 0xCA }); - } -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs index d56dca3b302..3b4bec1712a 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/PrimitiveTypeTests.cs @@ -1,33 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Formats.Nrbf; using System.Runtime.Serialization.Formatters.Binary; -using System.Text; using System.Windows.Forms.BinaryFormat; using FormatTests.Common; -using Record = System.Windows.Forms.BinaryFormat.Record; +using PrimitiveType = System.Windows.Forms.BinaryFormat.PrimitiveType; namespace FormatTests.FormattedObject; public class PrimitiveTypeTests : SerializationTest { - [Theory] - [MemberData(nameof(RoundTrip_Data))] - public void WriteReadPrimitiveValue_RoundTrip(byte type, object value) - { - MemoryStream stream = new(); - using (BinaryWriter writer = new(stream, Encoding.UTF8, leaveOpen: true)) - { - TestRecord.WritePrimitiveValue(writer, (PrimitiveType)type, value); - } - - stream.Position = 0; - - using BinaryReader reader = new(stream); - object result = TestRecord.ReadPrimitiveValue(reader, (PrimitiveType)type); - result.Should().Be(value); - } - public static TheoryData RoundTrip_Data => new() { { (byte)PrimitiveType.Int64, 0L }, @@ -87,9 +70,7 @@ public void WriteReadPrimitiveValue_RoundTrip(byte type, object value) public void PrimitiveTypeMemberName(object value) { BinaryFormattedObject format = new(Serialize(value)); - SystemClassWithMembersAndTypes systemClass = (SystemClassWithMembersAndTypes)format[1]; - systemClass.MemberNames[0].Should().Be("m_value"); - systemClass.MemberValues.Count.Should().Be(1); + VerifyNonGeneric(value, format[format.RootRecord.Id]); } [Theory] @@ -145,17 +126,15 @@ public void BinaryFormattedObject_ReadPrimitive(object value) "Roundabout" }; - internal class TestRecord : Record + private static void VerifyNonGeneric(object value, SerializationRecord record) { - public static void WritePrimitiveValue(BinaryWriter writer, PrimitiveType type, object value) - => WritePrimitiveType(writer, type, value); - - public static object ReadPrimitiveValue(BinaryReader reader, PrimitiveType type) - => ReadPrimitiveType(reader, type); + typeof(PrimitiveTypeTests) + .GetMethod(nameof(VerifyGeneric), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)! + .MakeGenericMethod(value.GetType()).Invoke(null, [value, record]); + } - public override void Write(BinaryWriter writer) - { - throw new NotImplementedException(); - } + private static void VerifyGeneric(T value, SerializationRecord record) where T : unmanaged + { + record.As>().Value.Should().Be(value); } } diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs deleted file mode 100644 index e1cab131f1d..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Windows.Forms; -using System.Windows.Forms.BinaryFormat; - -namespace FormatTests.FormattedObject; - -public class RecordMapTests -{ - private class Record : IRecord - { - public Id Id => 1; - - void IBinaryWriteable.Write(BinaryWriter writer) { } - } - - [Fact] - public void RecordMap_CannotAddSameIndexTwice() - { - RecordMap map = new(); - Action action = () => map.AddRecord(new Record()); - action(); - action.Should().Throw(); - } - - [Fact] - public void RecordMap_GetMissingThrowsKeyNotFound() - { - RecordMap map = new(); - Func func = () => map[(Id)0]; - func.Should().Throw(); - } -} diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/SystemDrawingTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/SystemDrawingTests.cs index 8993960fa6a..038f17786b6 100644 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/SystemDrawingTests.cs +++ b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/SystemDrawingTests.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; -using System.Windows.Forms.BinaryFormat; +using System.Formats.Nrbf; namespace FormatTests.FormattedObject; @@ -11,40 +11,34 @@ public class SystemDrawingTests : Common.SystemDrawingTests() - .Which.LibraryName.Should().Be("System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + classInfo.GetSingle("x").Should().Be(input.X); + classInfo.GetSingle("y").Should().Be(input.Y); } [Fact] public void RectangleF_Parse() { - BinaryFormattedObject format = new(Serialize(new RectangleF())); + RectangleF input = new(x: 123.5f, y: 456.1f, width: 100.25f, height: 200.75f); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(input)); - ClassWithMembersAndTypes classInfo = (ClassWithMembersAndTypes)format.RootRecord; - classInfo.ObjectId.Should().Be(1); - classInfo.Name.Should().Be("System.Drawing.RectangleF"); + ClassRecord classInfo = (ClassRecord)format.RootRecord; + classInfo.RecordType.Should().Be(SerializationRecordType.ClassWithMembersAndTypes); + classInfo.Id.Should().Be(format.RootRecord.Id); + classInfo.TypeName.FullName.Should().Be("System.Drawing.RectangleF"); classInfo.MemberNames.Should().BeEquivalentTo(["x", "y", "width", "height"]); - classInfo.MemberValues.Should().BeEquivalentTo(new object[] { 0.0f, 0.0f, 0.0f, 0.0f }); - classInfo.MemberTypeInfo.Should().BeEquivalentTo(new[] - { - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Single), - (BinaryType.Primitive, PrimitiveType.Single) - }); + classInfo.GetSingle("x").Should().Be(input.X); + classInfo.GetSingle("y").Should().Be(input.Y); + classInfo.GetSingle("width").Should().Be(input.Width); + classInfo.GetSingle("height").Should().Be(input.Height); } public static TheoryData SystemDrawing_TestData => new() diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Formatter/NullRecordTests.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Formatter/NullRecordTests.cs deleted file mode 100644 index 09674b4a8da..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Formatter/NullRecordTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization; - -namespace FormatTests.Formatter; - -public class NullRecordTests : Common.NullRecordTests -{ - public override void NullRecord_FollowingReferenceable() - { - // Not technically valid, the BinaryFormatter gets a null ref that it turns into SerializationException. - Action action = base.NullRecord_FollowingReferenceable; - action.Should().Throw(); - } - - public override void NullRecord_BeforeReferenceable() - { - // Not technically valid, the BinaryFormatter gets a null ref that it turns into SerializationException. - Action action = base.NullRecord_BeforeReferenceable; - action.Should().Throw(); - } - - public override void NullRecord_WrongLength_WithTuple(int nullCount) - { - Action action = () => base.NullRecord_WrongLength_WithTuple(nullCount); - - if (nullCount == 0) - { - action(); - } - else - { - action.Should().Throw(); - } - } -} diff --git a/src/System.Windows.Forms/src/Properties/AssemblyInfo.cs b/src/System.Windows.Forms/src/Properties/AssemblyInfo.cs index 39f4e25bc33..776625f85d3 100644 --- a/src/System.Windows.Forms/src/Properties/AssemblyInfo.cs +++ b/src/System.Windows.Forms/src/Properties/AssemblyInfo.cs @@ -12,6 +12,7 @@ [assembly: InternalsVisibleTo("System.Windows.Forms.UI.IntegrationTests, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("ScratchProjectWithInternals, PublicKey=00000000000000000400000000000000")] [assembly: InternalsVisibleTo("ComDisabled.Tests, PublicKey=00000000000000000400000000000000")] +[assembly: InternalsVisibleTo("BinaryFormatTests, PublicKey=00000000000000000400000000000000")] // This is needed in order to Moq internal interfaces for testing [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs similarity index 82% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs index d53785f6310..fb2bbe3736e 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs @@ -24,13 +24,6 @@ public ArrayInfo(Id objectId, Count length) ObjectId = objectId; } - public static Id Parse(BinaryReader reader, out Count length) - { - Id id = reader.ReadInt32(); - length = reader.ReadInt32(); - return id; - } - public readonly void Write(BinaryWriter writer) { writer.Write(ObjectId); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs similarity index 60% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs index 007dde2c380..1ea2c8315f7 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; -using System.Runtime.Serialization; namespace System.Windows.Forms.BinaryFormat; @@ -34,52 +33,6 @@ internal abstract class ArrayRecord : ObjectRecord, IEnumerable IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private protected abstract IEnumerator GetEnumerator(); - - /// - /// Reads records, expanding null records into individual entries. - /// - private protected static IReadOnlyList ReadObjectArrayValues(BinaryFormattedObject.IParseState state, Count count) - => ReadObjectArrayValues(state, BinaryType.Object, null, count); - - /// - /// Reads a count of object member values of with optional clarifying . - /// - /// was unexpected. - private protected static IReadOnlyList ReadObjectArrayValues( - BinaryFormattedObject.IParseState state, - BinaryType type, - object? typeInfo, - int count) - { - if (count == 0) - { - return []; - } - - ArrayBuilder memberValues = new(count); - for (int i = 0; i < count; i++) - { - object value = ReadValue(state, type, typeInfo); - if (value is not NullRecord nullRecord) - { - memberValues.Add(value); - continue; - } - - i = checked(i + nullRecord.NullCount - 1); - if (i >= count) - { - throw new SerializationException(); - } - - for (int j = 0; j < nullRecord.NullCount; j++) - { - memberValues.Add(null); - } - } - - return memberValues.ToArray(); - } } /// diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs similarity index 76% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs index 0ba7c5aa75d..18b3adc32b4 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs @@ -15,8 +15,7 @@ namespace System.Windows.Forms.BinaryFormat; /// internal sealed class ArraySingleObject : ArrayRecord, - IRecord, - IBinaryFormatParseable + IRecord { public static RecordType RecordType => RecordType.ArraySingleObject; @@ -24,9 +23,6 @@ public ArraySingleObject(Id objectId, IReadOnlyList arrayObjects) : base(new ArrayInfo(objectId, arrayObjects.Count), arrayObjects) { } - static ArraySingleObject IBinaryFormatParseable.Parse(BinaryFormattedObject.IParseState state) => - new(ArrayInfo.Parse(state.Reader, out Count length), ReadObjectArrayValues(state, length)); - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs similarity index 68% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs index 85f9d242467..172be434037 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs @@ -15,9 +15,7 @@ namespace System.Windows.Forms.BinaryFormat; /// internal sealed class ArraySinglePrimitive : ArrayRecord, - IBinaryFormatParseable, - IRecord>, - IPrimitiveTypeRecord + IRecord> where T : unmanaged { public PrimitiveType PrimitiveType { get; } @@ -30,16 +28,6 @@ public ArraySinglePrimitive(Id objectId, IReadOnlyList arrayObjects) PrimitiveType = TypeInfo.GetPrimitiveType(typeof(T)); } - static ArrayRecord IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - Id id = ArrayInfo.Parse(state.Reader, out Count length); - PrimitiveType primitiveType = (PrimitiveType)state.Reader.ReadByte(); - Debug.Assert(typeof(T) == primitiveType.GetPrimitiveTypeType()); - - return new ArraySinglePrimitive(id, state.Reader.ReadPrimitiveArray(length)); - } - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs similarity index 76% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs index f2294af1f42..5ad59c4b278 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class ArraySingleString : ArrayRecord, IRecord, IBinaryFormatParseable +internal sealed class ArraySingleString : ArrayRecord, IRecord { public static RecordType RecordType => RecordType.ArraySingleString; @@ -21,9 +21,6 @@ public ArraySingleString(Id objectId, IReadOnlyList arrayObjects) : base(new ArrayInfo(objectId, arrayObjects.Count), arrayObjects) { } - static ArraySingleString IBinaryFormatParseable.Parse(BinaryFormattedObject.IParseState state) => - new(ArrayInfo.Parse(state.Reader, out Count length), ReadObjectArrayValues(state, length)); - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArrayType.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryArrayType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArrayType.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryArrayType.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriter.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriter.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriter.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/BinaryFormatWriterScope.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriterScope.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/BinaryFormatWriterScope.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriterScope.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs similarity index 77% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs index b8cfeb5841b..4b898f5dd70 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class BinaryLibrary : IRecord, IBinaryFormatParseable +internal sealed class BinaryLibrary : IRecord { public Id LibraryId { get; } public string LibraryName { get; } @@ -27,9 +27,6 @@ public BinaryLibrary(Id libraryId, string libraryName) public static RecordType RecordType => RecordType.BinaryLibrary; - static BinaryLibrary IBinaryFormatParseable.Parse(BinaryFormattedObject.IParseState state) => - new(state.Reader.ReadInt32(), state.Reader.ReadString()); - public void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs similarity index 84% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs index 043bd9caa57..7b6b981fe4f 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class BinaryObjectString : IRecord, IBinaryFormatParseable +internal sealed class BinaryObjectString : IRecord { public Id ObjectId { get; } public string Value { get; } @@ -27,9 +27,6 @@ public BinaryObjectString(Id objectId, string value) Value = value; } - static BinaryObjectString IBinaryFormatParseable.Parse(BinaryFormattedObject.IParseState state) => - new(state.Reader.ReadInt32(), state.Reader.ReadString()); - public void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryType.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryType.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/BinaryType.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs similarity index 69% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs index 730b3355d84..3ae5d6350e6 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs @@ -26,20 +26,6 @@ public ClassInfo(Id objectId, string name, IReadOnlyList memberNames) MemberNames = memberNames; } - public static ClassInfo Parse(BinaryReader reader, out Count memberCount) - { - Id objectId = reader.ReadInt32(); - string name = reader.ReadString(); - memberCount = reader.ReadInt32(); - ArrayBuilder memberNames = new(memberCount); - for (int i = 0; i < memberCount; i++) - { - memberNames.Add(reader.ReadString()); - } - - return new(objectId, name, memberNames.ToArray()); - } - public void Write(BinaryWriter writer) { writer.Write(ObjectId); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs similarity index 75% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs index 078c48b360d..f8fcdcba2db 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.Serialization; - namespace System.Windows.Forms.BinaryFormat; /// @@ -52,40 +50,6 @@ private protected ClassRecord(ClassInfo classInfo, MemberTypeInfo memberTypeInfo MemberTypeInfo = memberTypeInfo; } - /// - /// Reads object member values using . - /// - private protected static IReadOnlyList ReadObjectMemberValues( - BinaryFormattedObject.IParseState state, - MemberTypeInfo memberTypeInfo) - { - int count = memberTypeInfo.Count; - if (count == 0) - { - return []; - } - - ArrayBuilder memberValues = new(count); - foreach ((BinaryType type, object? info) in memberTypeInfo) - { - object value = ReadValue(state, type, info); - if (value is not ObjectNull nullValue) - { - memberValues.Add(value); - continue; - } - - if (nullValue.NullCount != 1) - { - throw new SerializationException("Member values can only have one null assigned."); - } - - memberValues.Add(null); - } - - return memberValues.ToArray(); - } - /// /// Writes as specified by the /// diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs similarity index 87% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs index b2edcb0f7b7..cfd5bf67440 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs @@ -24,10 +24,6 @@ public ClassTypeInfo(string typeName, Id libraryId) LibraryId = libraryId; } - public static ClassTypeInfo Parse(BinaryReader reader) => new( - reader.ReadString(), - reader.ReadInt32()); - public void Write(BinaryWriter writer) { writer.Write(TypeName); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs similarity index 74% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs index 07142616860..7db9a50f373 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.Serialization; - namespace System.Windows.Forms.BinaryFormat; /// @@ -15,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class ClassWithId : ClassRecord, IRecord, IBinaryFormatParseable +internal sealed class ClassWithId : ClassRecord, IRecord { private readonly ClassRecord _metadataClass; @@ -44,23 +42,6 @@ public ClassWithId(Id id, ClassRecord metadataClass, params object?[] memberValu public static RecordType RecordType => RecordType.ClassWithId; - static ClassWithId IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - Id objectId = state.Reader.ReadInt32(); - Id metadataId = state.Reader.ReadInt32(); - - if (state.RecordMap[metadataId] is not ClassRecord referencedRecord) - { - throw new SerializationException("Invalid referenced record type."); - } - - return new( - objectId, - referencedRecord, - ReadObjectMemberValues(state, referencedRecord.MemberTypeInfo)); - } - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); @@ -75,11 +56,10 @@ public override void Write(BinaryWriter writer) case SystemClassWithMembersAndTypes systemClassWithMembersAndTypes: WriteValuesFromMemberTypeInfo(writer, systemClassWithMembersAndTypes.MemberTypeInfo, MemberValues); break; - case ClassWithMembers or SystemClassWithMembers: - WriteRecords(writer, MemberValues, coalesceNulls: false); - break; default: - throw new SerializationException(); + // Really shouldn't be writing this record type. It isn't as safe as the typed variant + // and saves very little space. + throw new NotSupportedException(); } } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs similarity index 71% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs index fd19c7821a1..21527b738bc 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs @@ -15,8 +15,7 @@ namespace System.Windows.Forms.BinaryFormat; /// internal sealed class ClassWithMembersAndTypes : ClassRecord, - IRecord, - IBinaryFormatParseable + IRecord { public override Id LibraryId { get; } @@ -41,19 +40,6 @@ public ClassWithMembersAndTypes( public static RecordType RecordType => RecordType.ClassWithMembersAndTypes; - static ClassWithMembersAndTypes IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - ClassInfo classInfo = ClassInfo.Parse(state.Reader, out Count memberCount); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(state.Reader, memberCount); - - return new( - classInfo, - state.Reader.ReadInt32(), - memberTypeInfo, - ReadObjectMemberValues(state, memberTypeInfo)); - } - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryArray.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/IBinaryArray.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryArray.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/IBinaryArray.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryWriteable.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/IBinaryWriteable.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryWriteable.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/IBinaryWriteable.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IRecord.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/IRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IRecord.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/IRecord.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/ListConverter.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ListConverter.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/ListConverter.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ListConverter.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs similarity index 77% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs index 39beb69d729..86854b03aff 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs @@ -15,9 +15,7 @@ namespace System.Windows.Forms.BinaryFormat; /// internal sealed class MemberPrimitiveTyped : Record, - IRecord, - IPrimitiveTypeRecord, - IBinaryFormatParseable + IRecord { public PrimitiveType PrimitiveType { get; } public object Value { get; } @@ -43,15 +41,6 @@ internal MemberPrimitiveTyped(object value) public static RecordType RecordType => RecordType.MemberPrimitiveTyped; - static MemberPrimitiveTyped IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - PrimitiveType primitiveType = (PrimitiveType)state.Reader.ReadByte(); - return new( - primitiveType, - ReadPrimitiveType(state.Reader, primitiveType)); - } - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberReference.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberReference.cs similarity index 84% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberReference.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberReference.cs index 9c560576665..3b5ed0e059f 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberReference.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberReference.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class MemberReference : Record, IRecord, IBinaryFormatParseable +internal sealed class MemberReference : Record, IRecord { public Id IdRef { get; } @@ -21,9 +21,6 @@ internal sealed class MemberReference : Record, IRecord, IBinar public static RecordType RecordType => RecordType.MemberReference; - static MemberReference IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) => new(state.Reader.ReadInt32()); - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs new file mode 100644 index 00000000000..b4cf7c07515 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs @@ -0,0 +1,67 @@ +// 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; +using System.Runtime.Serialization; + +namespace System.Windows.Forms.BinaryFormat; + +/// +/// Member type info. +/// +/// +/// +/// +/// [MS-NRBF] 2.3.1.2 +/// +/// +/// +internal class MemberTypeInfo : IBinaryWriteable, IEnumerable<(BinaryType Type, object? Info)> +{ + private readonly IList<(BinaryType Type, object? Info)> _info; + + public MemberTypeInfo(IList<(BinaryType Type, object? Info)> info) => _info = info; + + public MemberTypeInfo(params (BinaryType Type, object? Info)[] info) => _info = info; + + public (BinaryType Type, object? Info) this[int index] => _info[index]; + public int Count => _info.Count; + + public void Write(BinaryWriter writer) + { + foreach ((BinaryType type, _) in this) + { + writer.Write((byte)type); + } + + foreach ((BinaryType type, object? info) in this) + { + switch (type) + { + case BinaryType.Primitive: + case BinaryType.PrimitiveArray: + writer.Write((byte)info!); + break; + case BinaryType.SystemClass: + writer.Write((string)info!); + break; + case BinaryType.Class: + ((ClassTypeInfo)info!).Write(writer); + break; + case BinaryType.String: + case BinaryType.ObjectArray: + case BinaryType.StringArray: + case BinaryType.Object: + // Other types have no additional data. + break; + default: + throw new SerializationException("Unexpected binary type."); + } + } + } + + IEnumerator<(BinaryType Type, object? Info)> IEnumerable<(BinaryType Type, object? Info)>.GetEnumerator() + => _info.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _info.GetEnumerator(); +} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs similarity index 70% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs index 846500125e3..29c0cba7dfe 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs @@ -6,7 +6,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// Record that marks the end of the binary format stream. /// -internal sealed class MessageEnd : IRecord, IBinaryFormatParseable +internal sealed class MessageEnd : IRecord { public static MessageEnd Instance { get; } = new(); @@ -14,8 +14,5 @@ private MessageEnd() { } public static RecordType RecordType => RecordType.MessageEnd; - static MessageEnd IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) => Instance; - public void Write(BinaryWriter writer) => writer.Write((byte)RecordType); } diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs similarity index 77% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs index cf14e2926c1..ae1a08172f8 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs @@ -17,17 +17,12 @@ internal abstract partial class NullRecord /// internal sealed class ObjectNullMultiple : NullRecord, - IRecord, - IBinaryFormatParseable + IRecord { public static RecordType RecordType => RecordType.ObjectNullMultiple; public ObjectNullMultiple(Count count) => NullCount = count; - static ObjectNullMultiple IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - => new(state.Reader.ReadInt32()); - public void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs similarity index 78% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs index 22563ed6b77..9edf41601c3 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs @@ -17,16 +17,12 @@ internal abstract partial class NullRecord /// internal sealed class ObjectNullMultiple256 : NullRecord, - IRecord, - IBinaryFormatParseable + IRecord { public static RecordType RecordType => RecordType.ObjectNullMultiple256; public ObjectNullMultiple256(Count count) => NullCount = count; - static ObjectNullMultiple256 IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) => new(state.Reader.ReadByte()); - public void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/NullRecord.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/NullRecord.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs similarity index 84% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs index 40369c3a252..da72e97c491 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class ObjectNull : NullRecord, IRecord, IBinaryFormatParseable +internal sealed class ObjectNull : NullRecord, IRecord { public static ObjectNull Instance { get; } = new(); @@ -23,9 +23,6 @@ private ObjectNull() { } public static RecordType RecordType => RecordType.ObjectNull; - static ObjectNull IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) => Instance; - public void Write(BinaryWriter writer) => writer.Write((byte)RecordType); public override bool Equals(object? obj) => obj is ObjectNull; diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ObjectRecord.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ObjectRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ObjectRecord.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/ObjectRecord.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/PrimitiveType.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/PrimitiveType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/PrimitiveType.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/PrimitiveType.cs diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/Record.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/Record.cs new file mode 100644 index 00000000000..2e4afeb3454 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/Record.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Globalization; + +namespace System.Windows.Forms.BinaryFormat; + +/// +/// Base record class. +/// +internal abstract class Record : IRecord +{ + Id IRecord.Id => Id; + + private protected virtual Id Id => Id.Null; + + /// + /// Writes as to the given . + /// + private protected static unsafe void WritePrimitiveType(BinaryWriter writer, PrimitiveType primitiveType, object value) + { + switch (primitiveType) + { + case PrimitiveType.Boolean: + writer.Write((bool)value); + break; + case PrimitiveType.Byte: + writer.Write((byte)value); + break; + case PrimitiveType.Char: + writer.Write((char)value); + break; + case PrimitiveType.Decimal: + writer.Write(((decimal)value).ToString(CultureInfo.InvariantCulture)); + break; + case PrimitiveType.Double: + writer.Write((double)value); + break; + case PrimitiveType.Int16: + writer.Write((short)value); + break; + case PrimitiveType.Int32: + writer.Write((int)value); + break; + case PrimitiveType.Int64: + writer.Write((long)value); + break; + case PrimitiveType.SByte: + writer.Write((sbyte)value); + break; + case PrimitiveType.Single: + writer.Write((float)value); + break; + case PrimitiveType.TimeSpan: + writer.Write(((TimeSpan)value).Ticks); + break; + case PrimitiveType.DateTime: + writer.Write((DateTime)value); + break; + case PrimitiveType.UInt16: + writer.Write((ushort)value); + break; + case PrimitiveType.UInt32: + writer.Write((uint)value); + break; + case PrimitiveType.UInt64: + writer.Write((ulong)value); + break; + // String is handled with a record, never on it's own + case PrimitiveType.Null: + case PrimitiveType.String: + default: + throw new ArgumentException("Invalid primitive type.", nameof(primitiveType)); + } + } + + /// + /// Writes records, coalescing null records into single entries. + /// + /// + /// contained an object that isn't a record. + /// + private protected static void WriteRecords(BinaryWriter writer, IReadOnlyList objects, bool coalesceNulls) + { + int nullCount = 0; + + // Indexing for performance and avoiding writing an enumerator of ListConverter. + for (int i = 0; i < objects.Count; i++) + { + object? @object = objects[i]; + + // Aggregate consecutive null records. + if (@object is null) + { + nullCount++; + if (coalesceNulls) + { + continue; + } + } + + if (nullCount > 0) + { + NullRecord.Write(writer, nullCount); + nullCount = 0; + } + + if (@object is not IRecord record || record is NullRecord) + { + throw new ArgumentException("Invalid record.", nameof(objects)); + } + + record.Write(writer); + } + + if (nullCount > 0) + { + NullRecord.Write(writer, nullCount); + } + } + + public abstract void Write(BinaryWriter writer); +} diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/RecordType.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/RecordType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/RecordType.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/RecordType.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs similarity index 76% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs index 886f1fee63b..b498d34370f 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs @@ -13,7 +13,7 @@ namespace System.Windows.Forms.BinaryFormat; /// /// /// -internal sealed class SerializationHeader : IRecord, IBinaryFormatParseable +internal sealed class SerializationHeader : IRecord { /// /// The id of the root object record. @@ -44,15 +44,6 @@ internal sealed class SerializationHeader : IRecord, IBinar HeaderId = -1, }; - static SerializationHeader IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) => new() - { - RootId = state.Reader.ReadInt32(), - HeaderId = state.Reader.ReadInt32(), - MajorVersion = state.Reader.ReadInt32(), - MinorVersion = state.Reader.ReadInt32() - }; - public void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/StringRecordsCollection.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/StringRecordsCollection.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/StringRecordsCollection.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/StringRecordsCollection.cs diff --git a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs similarity index 70% rename from src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs rename to src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs index 12521fb31dc..7d05da1cf6d 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs @@ -15,8 +15,7 @@ namespace System.Windows.Forms.BinaryFormat; /// internal sealed class SystemClassWithMembersAndTypes : ClassRecord, - IRecord, - IBinaryFormatParseable + IRecord { public SystemClassWithMembersAndTypes( ClassInfo classInfo, @@ -36,18 +35,6 @@ public SystemClassWithMembersAndTypes( public static RecordType RecordType => RecordType.SystemClassWithMembersAndTypes; - static SystemClassWithMembersAndTypes IBinaryFormatParseable.Parse( - BinaryFormattedObject.IParseState state) - { - ClassInfo classInfo = ClassInfo.Parse(state.Reader, out Count memberCount); - MemberTypeInfo memberTypeInfo = MemberTypeInfo.Parse(state.Reader, memberCount); - - return new( - classInfo, - memberTypeInfo, - ReadObjectMemberValues(state, memberTypeInfo)); - } - public override void Write(BinaryWriter writer) { writer.Write((byte)RecordType); diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/TypeInfo.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/TypeInfo.cs new file mode 100644 index 00000000000..3ff856ec9a7 --- /dev/null +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/TypeInfo.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection; + +namespace System.Windows.Forms.BinaryFormat; + +internal static class TypeInfo +{ + public const string BooleanType = "System.Boolean"; + public const string CharType = "System.Char"; + public const string StringType = "System.String"; + public const string SByteType = "System.SByte"; + public const string ByteType = "System.Byte"; + public const string Int16Type = "System.Int16"; + public const string UInt16Type = "System.UInt16"; + public const string Int32Type = "System.Int32"; + public const string UInt32Type = "System.UInt32"; + public const string Int64Type = "System.Int64"; + public const string DecimalType = "System.Decimal"; + public const string UInt64Type = "System.UInt64"; + public const string SingleType = "System.Single"; + public const string DoubleType = "System.Double"; + public const string TimeSpanType = "System.TimeSpan"; + public const string DateTimeType = "System.DateTime"; + public const string IntPtrType = "System.IntPtr"; + public const string UIntPtrType = "System.UIntPtr"; + + public const string HashtableType = "System.Collections.Hashtable"; + public const string IDictionaryType = "System.Collections.IDictionary"; + public const string ExceptionType = "System.Exception"; + public const string NotSupportedExceptionType = "System.NotSupportedException"; + + public const string MscorlibAssemblyName + = "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; + public const string SystemDrawingAssemblyName + = "System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"; + + private static Assembly? s_mscorlibFacadeAssembly; + + internal static Assembly MscorlibAssembly => s_mscorlibFacadeAssembly + ??= Assembly.Load("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); + + internal static Assembly CorelibAssembly { get; } = typeof(string).Assembly; + internal static string CorelibAssemblyString { get; } = CorelibAssembly.FullName!; + + /// + /// Returns the for the given . + /// + /// or if not a . + internal static PrimitiveType GetPrimitiveType(Type type) => type.IsEnum ? default : Type.GetTypeCode(type) switch + { + TypeCode.Boolean => PrimitiveType.Boolean, + TypeCode.Char => PrimitiveType.Char, + TypeCode.SByte => PrimitiveType.SByte, + TypeCode.Byte => PrimitiveType.Byte, + TypeCode.Int16 => PrimitiveType.Int16, + TypeCode.UInt16 => PrimitiveType.UInt16, + TypeCode.Int32 => PrimitiveType.Int32, + TypeCode.UInt32 => PrimitiveType.UInt32, + TypeCode.Int64 => PrimitiveType.Int64, + TypeCode.UInt64 => PrimitiveType.UInt64, + TypeCode.Single => PrimitiveType.Single, + TypeCode.Double => PrimitiveType.Double, + TypeCode.Decimal => PrimitiveType.Decimal, + TypeCode.DateTime => PrimitiveType.DateTime, + TypeCode.String => PrimitiveType.String, + // TypeCode.Empty => 0, + // TypeCode.Object => 0, + // TypeCode.DBNull => 0, + _ => type == typeof(TimeSpan) ? PrimitiveType.TimeSpan : default, + }; +} diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectExtensions.cs b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectExtensions.cs index 6b8ee8f4d54..36ccfe743f2 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectExtensions.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectExtensions.cs @@ -20,15 +20,15 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? im { imageListStreamer = null; - if (format.RootRecord is not ClassWithMembersAndTypes types - || types.ClassInfo.Name != typeof(ImageListStreamer).FullName - || format[3] is not ArraySinglePrimitive data) + if (format.RootRecord is not System.Formats.Nrbf.ClassRecord types + || !types.TypeNameMatches(typeof(ImageListStreamer)) + || !types.HasMember("Data") + || types.GetRawValue("Data") is not System.Formats.Nrbf.SZArrayRecord data) { return false; } - Debug.Assert(data.ArrayObjects is byte[]); - imageListStreamer = new ImageListStreamer((byte[])data.ArrayObjects); + imageListStreamer = new ImageListStreamer(data.GetArray()); return true; } } @@ -40,15 +40,15 @@ public static bool TryGetBitmap(this BinaryFormattedObject format, out object? b { bitmap = null; - if (format.RootRecord is not ClassWithMembersAndTypes types - || types.ClassInfo.Name != typeof(Bitmap).FullName - || format[3] is not ArraySinglePrimitive data) + if (format.RootRecord is not System.Formats.Nrbf.ClassRecord types + || !types.TypeNameMatches(typeof(Bitmap)) + || !types.HasMember("Data") + || types.GetRawValue("Data") is not System.Formats.Nrbf.SZArrayRecord data) { return false; } - Debug.Assert(data.ArrayObjects is byte[]); - bitmap = new Bitmap(new MemoryStream((byte[])data.ArrayObjects)); + bitmap = new Bitmap(new MemoryStream(data.GetArray())); return true; } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs index 3b0f623abeb..48c726ecb6b 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/BinaryFormat/WinFormsBinaryFormattedObjectTests.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Drawing; +using System.Formats.Nrbf; using System.Runtime.Serialization.Formatters.Binary; namespace System.Windows.Forms.BinaryFormat.Tests; @@ -18,12 +19,12 @@ public void BinaryFormattedObject_Bitmap_FromBinaryFormatter() { using Bitmap bitmap = new(10, 10); BinaryFormattedObject format = bitmap.SerializeAndParse(); - ClassWithMembersAndTypes root = format.RootRecord.Should().BeOfType().Subject; - root.Name.Should().Be(typeof(Bitmap).FullName); - format[root.LibraryId].Should().BeOfType().Which - .LibraryName.Should().Be(AssemblyRef.SystemDrawing); - MemberReference reference = root["Data"].Should().BeOfType().Subject; - format[reference].Should().BeOfType>(); + Formats.Nrbf.ClassRecord root = format.RootRecord.Should().BeAssignableTo().Subject; + root.TypeNameMatches(typeof(Bitmap)).Should().BeTrue(); + root.TypeName.FullName.Should().Be(typeof(Bitmap).FullName); + root.TypeName.AssemblyName!.FullName.Should().Be(AssemblyRef.SystemDrawing); + Formats.Nrbf.ArrayRecord arrayRecord = root.GetArrayRecord("Data")!; + arrayRecord.Should().BeOfType>(); format.TryGetBitmap(out object? result).Should().BeTrue(); using Bitmap deserialized = result.Should().BeOfType().Which; deserialized.Size.Should().Be(bitmap.Size); @@ -73,12 +74,10 @@ public void BinaryFormattedObject_ImageListStreamer_FromBinaryFormatter() using ImageListStreamer stream = sourceList.ImageStream!; BinaryFormattedObject format = stream.SerializeAndParse(); - ClassWithMembersAndTypes root = format.RootRecord.Should().BeOfType().Subject; - root.Name.Should().Be(typeof(ImageListStreamer).FullName); - format[root.LibraryId].Should().BeOfType().Which - .LibraryName.Should().Be(typeof(WinFormsBinaryFormatWriter).Assembly.FullName); - MemberReference reference = root["Data"].Should().BeOfType().Subject; - format[reference].Should().BeOfType>(); + Formats.Nrbf.ClassRecord root = format.RootRecord.Should().BeAssignableTo().Subject; + root.TypeName.FullName.Should().Be(typeof(ImageListStreamer).FullName); + root.TypeName.AssemblyName!.FullName.Should().Be(typeof(WinFormsBinaryFormatWriter).Assembly.FullName); + root.GetArrayRecord("Data")!.Should().BeOfType>(); format.TryGetImageListStreamer(out object? result).Should().BeTrue(); using ImageListStreamer deserialized = result.Should().BeOfType().Which;