From b59abfdda9fb869095024e9f69511dd37f584a40 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Thu, 9 May 2024 18:02:28 +0200 Subject: [PATCH 01/14] extend PayloadReader public API surface: - RecordMap needs to remain internal, but implement IReadOnlyDictionary so it can be returned from a public method without exposing the type itself - introduce new PayloadReader.Read overload that can return RecordMap via out parameter - make SerializationRecord.ObjectId public - make MemberReferenceRecord public, expose Reference property --- .../PayloadReader.cs | 10 ++++++-- .../Records/ArrayRecord.cs | 2 +- .../Records/BinaryLibraryRecord.cs | 2 +- .../Records/BinaryObjectStringRecord.cs | 2 +- .../Records/ClassRecord.cs | 2 +- .../Records/ClassWithIdRecord.cs | 2 +- .../Records/MemberReferenceRecord.cs | 4 ++-- .../Records/SerializationRecord.cs | 2 +- .../Utils/RecordMap.cs | 24 ++++++++++++++----- 9 files changed, 34 insertions(+), 16 deletions(-) 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 index 11ba19fe01f..1bbf4313a11 100644 --- 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 @@ -76,11 +76,16 @@ public static bool ContainsBinaryFormatterPayload(Stream stream) /// When reading input from /// encounters invalid sequence of UTF8 characters. public static SerializationRecord Read(Stream payload, PayloadOptions? options = default, bool leaveOpen = false) + => Read(payload, out _, options); + + /// Record map + /// + public static SerializationRecord Read(Stream payload, out IReadOnlyDictionary recordMap, PayloadOptions? options = default, bool leaveOpen = false) { ArgumentNullException.ThrowIfNull(payload); using BinaryReader reader = new(payload, ThrowOnInvalidUtf8Encoding, leaveOpen: leaveOpen); - return Read(reader, options ?? new()); + return Read(reader, options ?? new(), out recordMap); } /// @@ -91,7 +96,7 @@ public static SerializationRecord Read(Stream payload, PayloadOptions? options = 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) + private static SerializationRecord Read(BinaryReader reader, PayloadOptions options, out IReadOnlyDictionary readOnlyRecordMap) { Stack readStack = new(); RecordMap recordMap = new(); @@ -144,6 +149,7 @@ private static SerializationRecord Read(BinaryReader reader, PayloadOptions opti } while (recordType != RecordType.MessageEnd); + readOnlyRecordMap = recordMap; SerializationRecord rootRecord = recordMap[header.RootId]; return rootRecord is SystemClassWithMembersAndTypesRecord systemClass ? systemClass.TryToMapToUserFriendly() 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 index 6a692876067..cfb704b2663 100644 --- 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 @@ -28,7 +28,7 @@ private protected ArrayRecord(ArrayInfo arrayInfo) /// public ArrayType ArrayType => ArrayInfo.ArrayType; - internal override int ObjectId => ArrayInfo.ObjectId; + public override int ObjectId => ArrayInfo.ObjectId; internal long ValuesToRead { get; private protected set; } 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 index 31f069c5cfa..34a5156ae31 100644 --- 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 @@ -27,7 +27,7 @@ private BinaryLibraryRecord(int libraryId, AssemblyNameInfo libraryName) internal AssemblyNameInfo LibraryName { get; } - internal override int ObjectId { get; } + public 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 index 90aab196fde..ae76b879302 100644 --- 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 @@ -23,7 +23,7 @@ private BinaryObjectStringRecord(int objectId, string value) : base(value) public override RecordType RecordType => RecordType.BinaryObjectString; - internal override int ObjectId { get; } + public 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 index 298db43295d..e752ba5719c 100644 --- 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 @@ -33,7 +33,7 @@ private protected ClassRecord(ClassInfo classInfo) // 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; + public override int ObjectId => ClassInfo.ObjectId; internal abstract int ExpectedValuesCount { get; } 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 index a7388255d83..b1f0c86065b 100644 --- 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 @@ -27,7 +27,7 @@ private ClassWithIdRecord(int objectId, ClassRecord metadataClass) : base(metada public override AssemblyNameInfo LibraryName => MetadataClass.LibraryName; - internal override int ObjectId { get; } + public override int ObjectId { get; } internal ClassRecord MetadataClass { 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 index 29a5db28c55..bf7f9444431 100644 --- 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 @@ -13,7 +13,7 @@ namespace System.Runtime.Serialization.BinaryFormat; /// /// /// -internal sealed class MemberReferenceRecord : SerializationRecord +public 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. @@ -25,7 +25,7 @@ private MemberReferenceRecord(int reference, RecordMap recordMap) public override RecordType RecordType => RecordType.MemberReference; - internal int Reference { get; } + public int Reference { get; } private RecordMap RecordMap { get; } 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 index 3157e90c086..de11f544c63 100644 --- 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 @@ -23,7 +23,7 @@ public abstract class SerializationRecord public abstract RecordType RecordType { get; } - internal virtual int ObjectId => NoId; + public virtual int ObjectId => NoId; /// /// Compares the type and assembly name read from the payload against the specified type. 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 index 7478ce7c998..86983a55473 100644 --- 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 @@ -1,20 +1,34 @@ // 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.IO.Hashing; using System.Runtime.InteropServices; namespace System.Runtime.Serialization.BinaryFormat; -internal sealed class RecordMap +internal sealed class RecordMap : IReadOnlyDictionary { - private readonly List _records = []; // TODO: verify whether we actually need that private readonly Dictionary _map = new(CollisionResistantInt32Comparer.Instance); + public IEnumerable Keys => _map.Keys; + + public IEnumerable Values => _map.Values; + + public int Count => _map.Count; + + public SerializationRecord this[int objectId] => _map[objectId]; + + public bool ContainsKey(int key) => _map.ContainsKey(key); + + public bool TryGetValue(int key, [MaybeNullWhen(false)] out SerializationRecord value) => _map.TryGetValue(key, out value); + + public IEnumerator> GetEnumerator() => _map.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _map.GetEnumerator(); + 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." @@ -25,8 +39,6 @@ internal void Add(SerializationRecord 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 { From 297381cb542aae7b4bca2dcd8a6db381e40bbc7a Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 10 May 2024 18:02:10 +0200 Subject: [PATCH 02/14] extend PayloadReader public API surface: - extend ArrayRecord with ElementTypeName and ElementTypeLibraryName public properties --- .../Infos/MemberTypeInfo.cs | 66 +++++++++++++++++-- .../PayloadReader.cs | 9 ++- .../Records/ArrayOfClassesRecord.cs | 9 +++ .../Records/ArrayRecord.cs | 6 ++ .../Records/ArraySingleObjectRecord.cs | 8 +++ .../Records/ArraySinglePrimitiveRecord.cs | 8 +++ .../Records/ArraySingleStringRecord.cs | 8 +++ .../Records/BinaryArrayRecord.cs | 9 +++ .../RectangularOrCustomOffsetArrayRecord.cs | 8 +++ .../Utils/RecordMap.cs | 6 +- 10 files changed, 131 insertions(+), 6 deletions(-) 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 index f1827932e73..42391837ed7 100644 --- 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 @@ -182,16 +182,74 @@ internal bool ShouldBeRepresentedAsArrayOfClassRecords() { (BinaryType binaryType, object? additionalInfo) = Infos[0]; + return binaryType switch + { + BinaryType.SystemClass => !((TypeName)additionalInfo!).IsSZArray, + BinaryType.Class => !((ClassTypeInfo)additionalInfo!).TypeName.IsSZArray, + _ => false + }; + } + + internal TypeName GetElementTypeName() + { + (BinaryType binaryType, object? additionalInfo) = Infos[0]; + switch (binaryType) { + case BinaryType.String: + return TypeName.Parse(typeof(string).FullName.AsSpan()); + case BinaryType.StringArray: + return TypeName.Parse(typeof(string[]).FullName.AsSpan()); + case BinaryType.Object: + return TypeName.Parse(typeof(object).FullName.AsSpan()); + case BinaryType.ObjectArray: + return TypeName.Parse(typeof(object[]).FullName.AsSpan()); + case BinaryType.Primitive: + case BinaryType.PrimitiveArray: + string? name = ((PrimitiveType)additionalInfo!) switch + { + PrimitiveType.Boolean => typeof(bool).FullName, + PrimitiveType.Byte => typeof(byte).FullName, + PrimitiveType.Char => typeof(char).FullName, + PrimitiveType.Decimal => typeof(decimal).FullName, + PrimitiveType.Double => typeof(double).FullName, + PrimitiveType.Int16 => typeof(short).FullName, + PrimitiveType.Int32 => typeof(int).FullName, + PrimitiveType.Int64 => typeof(long).FullName, + PrimitiveType.SByte => typeof(sbyte).FullName, + PrimitiveType.Single => typeof(float).FullName, + PrimitiveType.TimeSpan => typeof(TimeSpan).FullName, + PrimitiveType.DateTime => typeof(DateTime).FullName, + PrimitiveType.UInt16 => typeof(ushort).FullName, + PrimitiveType.UInt32 => typeof(uint).FullName, + PrimitiveType.UInt64 => typeof(ulong).FullName, + _ => throw new NotSupportedException() + }; + + return binaryType is BinaryType.PrimitiveArray + ? TypeName.Parse($"{name}[]".AsSpan()) + : TypeName.Parse(name.AsSpan()); + case BinaryType.SystemClass: - TypeName typeName = (TypeName)additionalInfo!; - return !typeName.IsSZArray; + return (TypeName)additionalInfo!; case BinaryType.Class: ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!; - return !typeInfo.TypeName.IsSZArray; + return typeInfo.TypeName; default: - return false; + throw new NotSupportedException(); + } + } + + internal AssemblyNameInfo GetElementLibraryName(RecordMap recordMap) + { + (BinaryType binaryType, object? additionalInfo) = Infos[0]; + + if (binaryType is BinaryType.Class) + { + ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!; + return ((BinaryLibraryRecord)recordMap[typeInfo.LibraryId]).LibraryName; } + + return FormatterServices.CoreLibAssemblyName; } } 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 index 1bbf4313a11..a4b2de62058 100644 --- 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 @@ -31,8 +31,11 @@ public static bool ContainsBinaryFormatterPayload(ReadOnlySpan bytes) /// It does not modify the position of the stream. public static bool ContainsBinaryFormatterPayload(Stream stream) { +#if NETCOREAPP ArgumentNullException.ThrowIfNull(stream); - +#else + if (stream is null) throw new ArgumentNullException(nameof(stream)); +#endif // TODO: discuss an alternative approach, where we would parse SerializedStreamHeaderRecord // here and return false on failure @@ -82,7 +85,11 @@ public static SerializationRecord Read(Stream payload, PayloadOptions? options = /// public static SerializationRecord Read(Stream payload, out IReadOnlyDictionary recordMap, PayloadOptions? options = default, bool leaveOpen = false) { +#if NETCOREAPP ArgumentNullException.ThrowIfNull(payload); +#else + if (payload is null) throw new ArgumentNullException(nameof(payload)); +#endif using BinaryReader reader = new(payload, ThrowOnInvalidUtf8Encoding, leaveOpen: leaveOpen); return Read(reader, options ?? new(), out recordMap); 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 index a6856e2b4ee..96c3bb2f8c8 100644 --- 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 @@ -1,15 +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.Reflection.Metadata; + namespace System.Runtime.Serialization.BinaryFormat; internal sealed class ArrayOfClassesRecord : ArrayRecord { + private AssemblyNameInfo? _elementTypeLibraryName; + internal ArrayOfClassesRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, RecordMap recordMap) : base(arrayInfo) { MemberTypeInfo = memberTypeInfo; RecordMap = recordMap; + ElementTypeName = MemberTypeInfo.GetElementTypeName(); Records = []; } @@ -21,6 +26,10 @@ internal ArrayOfClassesRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo private RecordMap RecordMap { get; } + public override TypeName ElementTypeName { get; } + + public override AssemblyNameInfo ElementTypeLibraryName => _elementTypeLibraryName ??= MemberTypeInfo.GetElementLibraryName(RecordMap); + protected override ClassRecord?[] ToArrayOfT(bool allowNulls) { ClassRecord?[] result = new ClassRecord?[Length]; 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 index cfb704b2663..90fdb353fc3 100644 --- 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 @@ -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.Runtime.Serialization.BinaryFormat; public abstract class ArrayRecord : SerializationRecord @@ -28,6 +30,10 @@ private protected ArrayRecord(ArrayInfo arrayInfo) /// public ArrayType ArrayType => ArrayInfo.ArrayType; + public abstract TypeName ElementTypeName { get; } + + public abstract AssemblyNameInfo ElementTypeLibraryName { get; } + public override int ObjectId => ArrayInfo.ObjectId; internal long ValuesToRead { get; private protected set; } 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 index 98dba85559d..37d139ec077 100644 --- 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 @@ -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.Runtime.Serialization.BinaryFormat; /// @@ -15,10 +17,16 @@ namespace System.Runtime.Serialization.BinaryFormat; /// internal sealed class ArraySingleObjectRecord : ArrayRecord { + private static TypeName? s_elementTypeName; + private ArraySingleObjectRecord(ArrayInfo arrayInfo) : base(arrayInfo) => Records = []; public override RecordType RecordType => RecordType.ArraySingleObject; + public override TypeName ElementTypeName => s_elementTypeName ??= TypeName.Parse(typeof(object).FullName.AsSpan()); + + public override AssemblyNameInfo ElementTypeLibraryName => FormatterServices.CoreLibAssemblyName; + private List Records { get; } public override bool IsTypeNameMatching(Type type) => type == typeof(object[]); 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 index cccd839d2dd..6d3d2f0afc9 100644 --- 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 @@ -3,6 +3,7 @@ using System.Buffers; using System.Globalization; +using System.Reflection.Metadata; namespace System.Runtime.Serialization.BinaryFormat; @@ -19,6 +20,9 @@ namespace System.Runtime.Serialization.BinaryFormat; internal class ArraySinglePrimitiveRecord : ArrayRecord where T : unmanaged { + private static TypeName? s_elementTypeName; + private static AssemblyNameInfo? s_elementTypeLibraryName; + internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values) : base(arrayInfo) { Values = values; @@ -27,6 +31,10 @@ internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values public override RecordType RecordType => RecordType.ArraySinglePrimitive; + public override TypeName ElementTypeName => s_elementTypeName ??= TypeName.Parse(typeof(T).FullName.AsSpan()); + + public override AssemblyNameInfo ElementTypeLibraryName => s_elementTypeLibraryName ??= AssemblyNameInfo.Parse(FormatterServices.GetAssemblyNameIncludingTypeForwards(typeof(T)).AsSpan()); + internal IReadOnlyList Values { get; } public override bool IsTypeNameMatching(Type type) => typeof(T[]) == type; 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 index 400eef1383b..48dca5977ab 100644 --- 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 @@ -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.Runtime.Serialization.BinaryFormat; /// @@ -15,10 +17,16 @@ namespace System.Runtime.Serialization.BinaryFormat; /// internal sealed class ArraySingleStringRecord : ArrayRecord { + private static TypeName? s_elementTypeName; + private ArraySingleStringRecord(ArrayInfo arrayInfo) : base(arrayInfo) => Records = []; public override RecordType RecordType => RecordType.ArraySingleString; + public override TypeName ElementTypeName => s_elementTypeName ??= TypeName.Parse(typeof(string).FullName.AsSpan()); + + public override AssemblyNameInfo ElementTypeLibraryName => FormatterServices.CoreLibAssemblyName; + private List Records { get; } public override bool IsTypeNameMatching(Type type) => type == typeof(string[]); 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 index abdb497955c..e1ad8f4d503 100644 --- 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 @@ -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.Runtime.Serialization.BinaryFormat; internal sealed class BinaryArrayRecord : ArrayRecord @@ -14,16 +16,23 @@ internal sealed class BinaryArrayRecord : ArrayRecord typeof(TimeSpan), typeof(string), typeof(object) ]; + private AssemblyNameInfo? _elementTypeLibraryName; + private BinaryArrayRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, RecordMap recordMap) : base(arrayInfo) { MemberTypeInfo = memberTypeInfo; RecordMap = recordMap; + ElementTypeName = memberTypeInfo.GetElementTypeName(); Values = []; } public override RecordType RecordType => RecordType.BinaryArray; + public override TypeName ElementTypeName { get; } + + public override AssemblyNameInfo ElementTypeLibraryName => _elementTypeLibraryName ??= MemberTypeInfo.GetElementLibraryName(RecordMap); + private MemberTypeInfo MemberTypeInfo { get; } private RecordMap RecordMap { get; } 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 index 60767f24a6e..8d7fbd527f4 100644 --- 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 @@ -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.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -8,6 +9,8 @@ namespace System.Runtime.Serialization.BinaryFormat; internal sealed class RectangularOrCustomOffsetArrayRecord : ArrayRecord { + private AssemblyNameInfo? _elementTypeLibraryName; + private RectangularOrCustomOffsetArrayRecord(Type elementType, ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, int[] lengths, int[] offsets, RecordMap recordMap) : base(arrayInfo) { @@ -16,11 +19,16 @@ internal sealed class RectangularOrCustomOffsetArrayRecord : ArrayRecord Lengths = lengths; Offsets = offsets; RecordMap = recordMap; + ElementTypeName = memberTypeInfo.GetElementTypeName(); Values = new(); } public override RecordType RecordType => RecordType.BinaryArray; + public override TypeName ElementTypeName { get; } + + public override AssemblyNameInfo ElementTypeLibraryName => _elementTypeLibraryName ??= MemberTypeInfo.GetElementLibraryName(RecordMap); + private Type ElementType { get; } private MemberTypeInfo MemberTypeInfo { get; } 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 index 86983a55473..bac3b1306fb 100644 --- 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 @@ -21,7 +21,11 @@ internal sealed class RecordMap : IReadOnlyDictionary public bool ContainsKey(int key) => _map.ContainsKey(key); - public bool TryGetValue(int key, [MaybeNullWhen(false)] out SerializationRecord value) => _map.TryGetValue(key, out value); + public bool TryGetValue(int key, +#if NETCOREAPP + [MaybeNullWhen(false)] +#endif + out SerializationRecord value) => _map.TryGetValue(key, out value); public IEnumerator> GetEnumerator() => _map.GetEnumerator(); From 25e123efdb5b296830e719e0f17c667a95135fcb Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 10 May 2024 19:03:30 +0200 Subject: [PATCH 03/14] switch to the new API (System.Private.Windows.Core compiles) --- .../PayloadReader.cs | 2 +- .../BinaryFormattedObject.IParseState.cs | 4 +- .../BinaryFormattedObject.ITypeResolver.cs | 6 +- .../BinaryFormattedObject.ParseState.cs | 4 +- .../BinaryFormattedObject.TypeResolver.cs | 63 +-- .../BinaryFormat/BinaryFormattedObject.cs | 38 +- .../BinaryFormattedObjectExtensions.cs | 386 ++++++------------ .../Deserializer/ArrayRecordDeserializer.cs | 91 +++-- .../Deserializer/ClassRecordDeserializer.cs | 3 +- .../ClassRecordFieldInfoDeserializer.cs | 6 +- ...lassRecordSerializationInfoDeserializer.cs | 9 +- .../BinaryFormat/Deserializer/Deserializer.cs | 45 +- .../Deserializer/ObjectRecordDeserializer.cs | 41 +- .../Support/BinaryFormatWriterScope.cs | 10 +- .../FormatTests/FormattedObject/ArrayTests.cs | 47 +-- 15 files changed, 282 insertions(+), 473 deletions(-) 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 index a4b2de62058..5f1621d8165 100644 --- 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 @@ -106,7 +106,7 @@ public static ClassRecord ReadClassRecord(Stream payload, PayloadOptions? option private static SerializationRecord Read(BinaryReader reader, PayloadOptions options, out IReadOnlyDictionary readOnlyRecordMap) { Stack readStack = new(); - RecordMap recordMap = new(); + RecordMap recordMap = []; // Everything has to start with a header var header = (SerializedStreamHeaderRecord)ReadNext(reader, recordMap, AllowedRecordTypes.SerializedStreamHeader, options, out _); 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..12bac3ce837 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.Runtime.Serialization.BinaryFormat; + 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..867f872bceb 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,9 @@ internal interface ITypeResolver /// /// Resolves the given type name against the specified library. /// - /// The library id, or for the "system" assembly. + /// 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, AssemblyNameInfo libraryName); } } 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..02621487703 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.Runtime.Serialization.BinaryFormat; + 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..32724acb704 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,30 @@ 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<(string TypeName, string LibraryId), Type> _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. + /// 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, AssemblyNameInfo libraryName) { - if (libraryId == _lastLibraryId && string.Equals(typeName, _lastTypeName)) + if (_types.TryGetValue((typeName.FullName, libraryName.FullName), out Type? cachedType)) { - Debug.Assert(_lastType is not null); - return _lastType; - } - - _lastLibraryId = libraryId; - _lastTypeName = typeName; - - if (_types.TryGetValue((typeName, libraryId), 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(libraryName.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 +47,15 @@ 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.FullName, libraryName.FullName)] = binderType; return binderType; } - if (!_assemblies.TryGetValue(libraryId, out Assembly? assembly)) + if (!_assemblies.TryGetValue(libraryName.FullName, out Assembly? assembly)) { - Debug.Assert(!libraryId.IsNull); + Debug.Assert(libraryName.FullName != typeof(object).Assembly.FullName); - AssemblyName assemblyName = new(libraryName); + AssemblyName assemblyName = libraryName.ToAssemblyName(); try { assembly = Assembly.Load(assemblyName); @@ -94,20 +70,19 @@ Type ITypeResolver.GetType(string typeName, Id libraryId) assembly = Assembly.Load(assemblyName.Name!); } - _assemblies.Add(libraryId, assembly); + _assemblies.Add(libraryName.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.FullName, libraryName.FullName)] = 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 +90,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..50277d75194 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.Runtime.Serialization.BinaryFormat; 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 = PayloadReader.Read(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.ObjectId, 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[Id 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..5d23f4ef4d0 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.Runtime.Serialization.BinaryFormat; 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.IsTypeNameMatching(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.IsTypeNameMatching(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,61 +92,37 @@ 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) - { - value = binaryString.Value; - return true; - } - - if (format.RootRecord is not SystemClassWithMembersAndTypes systemClass) + if (format.RootRecord.RecordType is not (RecordType.BinaryObjectString or RecordType.MemberPrimitiveTyped or RecordType.SystemClassWithMembersAndTypes)) { + value = null; return false; } - if (IsPrimitiveTypeClassName(systemClass.Name) && systemClass.MemberTypeInfo[0].Type == BinaryType.Primitive) - { - value = systemClass.MemberValues[0]!; - return true; - } - - if (systemClass.Name == typeof(TimeSpan).FullName) + value = format.RootRecord switch { - value = new TimeSpan((long)systemClass.MemberValues[0]!); - return true; - } + 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 primitive => primitive.Value, + _ => null + }; - 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; - } + return value is not null + || format.RootRecord is PrimitiveTypeRecord; // null string value } } @@ -182,78 +137,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.GetObject("_size") is not int size + || !classInfo.TypeName.IsConstructedGenericType + || classInfo.TypeName.GetGenericTypeDefinition().Name != typeof(List<>).Name + || classInfo.TypeName.GetGenericArguments().Length != 1 + || classInfo.GetObject("_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), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength).CreateTrimmedList(size), _ => throw new InvalidOperationException() }; @@ -272,36 +187,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.IsTypeNameMatching(typeof(ArrayList)) + || !classInfo.HasMember("_items") + || !classInfo.HasMember("_size") + || classInfo.GetObject("_size") is not int size + || classInfo.GetObject("_items") is not ArrayRecord arrayRecord + || size > arrayRecord.Length) { return false; } ArrayList arrayList = new(size); + object?[] array = arrayRecord.ToArray(maxLength: size); 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 +224,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 = stringArray.GetStringValues(format.RecordMap).ToArray(); - return true; - } - - if (array is not IPrimitiveTypeRecord primitiveArray) + value = format.RootRecord switch { - 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 + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + ArrayRecord ar => ar.ToArray(maxLength: Array.MaxLength), + _ => throw new InvalidOperationException() }; return value is not null; @@ -382,20 +278,26 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? ha // 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 is not SystemClassWithMembersAndTypesRecord classInfo + || !classInfo.IsTypeNameMatching(typeof(Hashtable)) + || !classInfo.HasMember("Keys") + || !classInfo.HasMember("Values") + || classInfo.GetObject("Keys") is not ArrayRecord keysRecord + || classInfo.GetObject("Values") is not ArrayRecord valuesRecord + || keysRecord.Length != valuesRecord.Length) { return false; } - Hashtable temp = new(keys.Length); + Hashtable temp = new((int)keysRecord.Length); + object?[] keys = keysRecord.ToArray(maxLength: Array.MaxLength); + object?[] values = valuesRecord.ToArray(maxLength: Array.MaxLength); 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 +310,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 +323,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.IsTypeNameMatching(typeof(NotSupportedException))) { return false; } - exception = new NotSupportedException(classInfo["Message"]!.ToString()); + exception = new NotSupportedException(classInfo.GetString("Message")); return true; } } @@ -484,23 +349,6 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? ex || 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 RecordType.ArraySingleString or RecordType.ArraySinglePrimitive; } 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..18b6224183b 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,79 @@ // 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.Runtime.Serialization.BinaryFormat; 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? _result; 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, AssemblyNameInfo)")] + internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer) : base(arrayRecord, deserializer) { - _arrayRecord = arrayRecord; - - if (arrayRecord is not IBinaryArray binaryArray) + if (arrayRecord.ArrayType is not (ArrayType.Single or ArrayType.Jagged or ArrayType.Rectangular)) { - // 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); + throw new NotSupportedException("Only arrays with zero offsets are supported."); } - else + + // Other array types are handled directly (ArraySinglePrimitive and ArraySingleString). + Debug.Assert(arrayRecord.RecordType is not (RecordType.ArraySingleString or RecordType.ArraySinglePrimitive)); + + _arrayRecord = arrayRecord; + _elementType = deserializer.TypeResolver.GetType(arrayRecord.ElementTypeName, arrayRecord.ElementTypeLibraryName); + Type expectedArrayType = arrayRecord.ArrayType switch + { + ArrayType.Rectangular => _elementType.MakeArrayType(arrayRecord.Rank), + _ => _elementType.MakeArrayType() + }; + // 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) + _array = arrayRecord.ToArray(expectedArrayType, maxLength: Array.MaxLength); + + Type elementType = _array.GetType(); + while (elementType.IsArray) { - (BinaryType binaryType, object? info) = binaryArray.TypeInfo[0]; + elementType = elementType.GetElementType()!; + } - _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)) + // If following is false, then it's an array or primitive types that has already been materialized properly + if (elementType == typeof(object) || elementType == typeof(ClassRecord)) + { + int[] lengths = new int[arrayRecord.Rank]; + for (int dimension = 0; dimension < lengths.Length; dimension++) { - throw new NotSupportedException("Only arrays with zero offsets are supported."); + lengths[dimension] = _array.GetLength(dimension); } - _array = _arrayType is BinaryArrayType.Rectangular - ? Array.CreateInstance(_elementType, binaryArray.Lengths.ToArray()) - : Array.CreateInstance(_elementType, arrayRecord.Length); + Object = _result = Array.CreateInstance(_elementType, lengths); + } + else + { + Object = _array; } - - Object = _array; } + // adsitnik: it may compile, but most likely won't work without any changes internal override Id Continue() { + if (_result is null) + { + return Id.Null; + } + while (_index < _arrayRecord.Length) { - (object? memberValue, Id reference) = UnwrapMemberValue(_arrayRecord.ArrayObjects[_index]); + // TODO: adsitnik: handle multi-dimensional arrays + object? memberValue = _array.GetValue(_index); + _result.SetValue(memberValue, _index); + Id reference = memberValue is ClassRecord classRecord ? classRecord.ObjectId : Id.Null; if (s_missingValueSentinel == memberValue) { @@ -90,11 +97,11 @@ internal override Id Continue() if (_elementType.IsValueType) { - _array.SetArrayValueByFlattenedIndex(memberValue, _index); + _result.SetArrayValueByFlattenedIndex(memberValue, _index); } else { - Span flatSpan = _array.GetArrayData(); + Span flatSpan = _result.GetArrayData(); flatSpan[_index] = memberValue; } 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 994a38af7b1..0d495c4aa39 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.Runtime.Serialization.BinaryFormat; namespace System.Windows.Forms.BinaryFormat.Deserializer; @@ -28,7 +29,7 @@ private protected ClassRecordDeserializer(ClassRecord classRecord, object @objec [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(String, Id)")] internal static ObjectRecordDeserializer Create(ClassRecord classRecord, IDeserializer deserializer) { - Type type = deserializer.TypeResolver.GetType(classRecord.Name, classRecord.LibraryId); + Type type = deserializer.TypeResolver.GetType(classRecord.TypeName, classRecord.LibraryName); Id id = classRecord.ObjectId; 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 a5aa7150b00..776e69c7af6 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 @@ -14,14 +14,14 @@ namespace System.Windows.Forms.BinaryFormat.Deserializer; /// internal sealed class ClassRecordFieldInfoDeserializer : ClassRecordDeserializer { - private readonly ClassRecord _classRecord; + private readonly Runtime.Serialization.BinaryFormat.ClassRecord _classRecord; private readonly MemberInfo[] _fieldInfo; private int _currentFieldIndex; private readonly bool _isValueType; private bool _hasFixups; internal ClassRecordFieldInfoDeserializer( - ClassRecord classRecord, + Runtime.Serialization.BinaryFormat.ClassRecord classRecord, object @object, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicFields)] Type type, @@ -53,7 +53,7 @@ internal override Id Continue() object? rawValue; try { - rawValue = _classRecord[field.Name]; + rawValue = _classRecord.GetObject(field.Name); } catch (KeyNotFoundException) { 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 3b395d6395a..62497ed4107 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 @@ -17,13 +17,13 @@ namespace System.Windows.Forms.BinaryFormat.Deserializer; /// internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDeserializer { - private readonly ClassRecord _classRecord; + private readonly Runtime.Serialization.BinaryFormat.ClassRecord _classRecord; private readonly SerializationInfo _serializationInfo; private readonly ISerializationSurrogate? _surrogate; private int _currentMemberIndex; internal ClassRecordSerializationInfoDeserializer( - ClassRecord classRecord, + Runtime.Serialization.BinaryFormat.ClassRecord classRecord, object @object, Type type, ISerializationSurrogate? surrogate, @@ -36,10 +36,9 @@ internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDese internal override Id 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) + // adsitnik: the order is no longer guaranteed to be preserved + foreach (string memberName in _classRecord.MemberNames) { - string memberName = _classRecord.MemberNames[_currentMemberIndex]; (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.MemberValues[_currentMemberIndex]); if (s_missingValueSentinel == memberValue) 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 f9db33e3079..04bc7088e3a 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.Runtime.Serialization.BinaryFormat; 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; @@ -98,7 +99,7 @@ internal sealed partial class Deserializer : IDeserializer private Deserializer( Id rootId, - IReadOnlyRecordMap recordMap, + IReadOnlyDictionary recordMap, BinaryFormattedObject.ITypeResolver typeResolver, BinaryFormattedObject.Options options) { @@ -119,7 +120,7 @@ internal sealed partial class Deserializer : IDeserializer [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.Deserializer.Deserialize()")] internal static object Deserialize( Id rootId, - IReadOnlyRecordMap recordMap, + IReadOnlyDictionary recordMap, BinaryFormattedObject.ITypeResolver typeResolver, BinaryFormattedObject.Options options) { @@ -196,7 +197,7 @@ private void DeserializeRoot(Id rootId) { // 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) { @@ -230,27 +231,39 @@ object DeserializeNew(Id id) // 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) + SerializationRecord record = _recordMap[id]; + if (record is BinaryObjectStringRecord binaryString) { _deserializedObjects.Add(id, binaryString.Value); return binaryString.Value; } - if (record is ArrayRecord arrayRecord) + if (record.RecordType is RecordType.ArraySingleString or RecordType.ArraySinglePrimitive) { - Array? values = arrayRecord switch + Array? values = record switch { - ArraySingleString stringArray => stringArray.GetStringValues(_recordMap).ToArray(), - IPrimitiveTypeRecord primitiveArray => primitiveArray.GetPrimitiveArray(), + ArrayRecord stringArray => stringArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), _ => null }; - if (values is not null) - { - _deserializedObjects.Add(arrayRecord.ObjectId, values); - return values; - } + Debug.Assert(values is not null); + _deserializedObjects.Add(record.ObjectId, values); + return values; } // Not a simple case, need to do a full deserialization of the record. @@ -348,7 +361,7 @@ void IDeserializer.CompleteObject(Id id) // There are no remaining dependencies. Hook any finished events for this object. // Doing at the end of deserialization for simplicity. - Type type = _typeResolver.GetType(classRecord.Name, classRecord.LibraryId); + Type type = _typeResolver.GetType(classRecord.TypeName, classRecord.LibraryName); object @object = _deserializedObjects[completedId]; OnDeserialized += SerializationEvents.GetOnDeserializedForType(type, @object); 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..d6934606def 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.Runtime.Serialization.BinaryFormat; 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; @@ -45,19 +46,31 @@ private protected (object? value, Id id) UnwrapMemberValue(object? memberValue) return memberValue switch { // String - BinaryObjectString binaryString => (binaryString.Value, Id.Null), - // Inline record - ObjectRecord objectRecord => TryGetObject(objectRecord.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + // Class record + ClassRecord classRecord => (classRecord, classRecord.ObjectId), // Record reference - MemberReference memberReference => TryGetObject(memberReference.IdRef), - // Prmitive type record - MemberPrimitiveTyped memberPrimitiveTyped => (memberPrimitiveTyped.Value, Id.Null), + MemberReferenceRecord memberReference => TryGetObject(memberReference.Reference), + // Primitive type record + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), // Null - ObjectNull or null => (null, Id.Null), + 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()}'."), + _ => throw new SerializationException($"Unexpected member type '{memberValue.GetType()}'."), }; (object? value, Id id) TryGetObject(Id id) @@ -89,10 +102,10 @@ private protected (object? value, Id id) UnwrapMemberValue(object? memberValue) || (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(Id 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/Support/BinaryFormatWriterScope.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/BinaryFormatWriterScope.cs index e3a7c2fff17..5e6cd18fcf8 100644 --- a/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/BinaryFormatWriterScope.cs +++ b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/BinaryFormatWriterScope.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.Runtime.Serialization.BinaryFormat; using System.Text; namespace System.Windows.Forms.BinaryFormat; @@ -12,14 +13,19 @@ namespace System.Windows.Forms.BinaryFormat; public BinaryFormatWriterScope(Stream stream) { Writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); - SerializationHeader.Default.Write(Writer); + // SerializationHeader + Writer.Write((byte)RecordType.SerializedStreamHeader); + Writer.Write(1); // root ID + Writer.Write(1); // header ID + Writer.Write(1); // major version + Writer.Write(0); // minor version } public static implicit operator BinaryWriter(in BinaryFormatWriterScope scope) => scope.Writer; public void Dispose() { - MessageEnd.Instance.Write(Writer); + Writer.Write((byte)RecordType.MessageEnd); Writer.Dispose(); } } 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..dd3a47da138 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,10 @@ // 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.Runtime.Serialization.BinaryFormat; namespace FormatTests.FormattedObject; @@ -17,49 +16,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); + var arrayRecord = (ArrayRecord)format.RootRecord; + arrayRecord.ToArray().Should().BeEquivalentTo(strings); } public static TheoryData StringArray_Parse_Data => new() @@ -74,8 +37,8 @@ public void StringArray_Parse(string?[] strings) public void PrimitiveArray_Parse(Array array) { BinaryFormattedObject format = new(Serialize(array)); - ArrayRecord arrayRecord = (ArrayRecord)format.RootRecord; - arrayRecord.Should().BeEquivalentTo((IEnumerable)array); + var arrayRecord = (ArrayRecord)format.RootRecord; + arrayRecord.Should().BeEquivalentTo(arrayRecord.ToArray()); } public static TheoryData PrimitiveArray_Parse_Data => new() From c6f612d90d57783c4d48f00f8d3cf96d1198d6ac Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 10 May 2024 19:23:49 +0200 Subject: [PATCH 04/14] remove code that won't be needed for reading, move code that will be needed for writing to System.Windows.Forms where it's used (System.Windows.Forms compiles) --- .../BinaryFormat/ArrayRecordExtensions.cs | 57 ---- .../BinaryArray.ObjectBinaryArray.cs | 39 --- .../BinaryArray.PrimitiveBinaryArray.cs | 41 --- .../Windows/Forms/BinaryFormat/BinaryArray.cs | 122 -------- .../BinaryFormat/ClassRecordExtensions.cs | 78 ----- .../Forms/BinaryFormat/ClassWithMembers.cs | 47 --- .../BinaryFormat/IBinaryFormatParseable.cs | 15 - .../BinaryFormat/IPrimitiveTypeRecord.cs | 12 - .../Forms/BinaryFormat/IReadOnlyRecordMap.cs | 12 - .../Forms/BinaryFormat/MemberTypeInfo.cs | 155 ---------- .../Windows/Forms/BinaryFormat/Record.cs | 267 ------------------ .../Windows/Forms/BinaryFormat/RecordMap.cs | 33 --- .../Forms/BinaryFormat/Support/TypeInfo.cs | 190 ------------- .../BinaryFormat/SystemClassWithMembers.cs | 40 --- .../Windows/Forms/BinaryFormat/ArrayInfo.cs | 7 - .../Windows/Forms/BinaryFormat/ArrayRecord.cs | 47 --- .../Forms/BinaryFormat/ArraySingleObject.cs | 6 +- .../BinaryFormat/ArraySinglePrimitive.cs | 14 +- .../Forms/BinaryFormat/ArraySingleString.cs | 5 +- .../Forms/BinaryFormat/BinaryArrayType.cs | 0 .../Forms/BinaryFormat/BinaryFormatWriter.cs | 0 .../BinaryFormat}/BinaryFormatWriterScope.cs | 10 +- .../Forms/BinaryFormat/BinaryLibrary.cs | 5 +- .../Forms/BinaryFormat/BinaryObjectString.cs | 5 +- .../Windows/Forms/BinaryFormat/BinaryType.cs | 0 .../Windows/Forms/BinaryFormat/ClassInfo.cs | 14 - .../Windows/Forms/BinaryFormat/ClassRecord.cs | 36 --- .../Forms/BinaryFormat/ClassTypeInfo.cs | 4 - .../Windows/Forms/BinaryFormat/ClassWithId.cs | 28 +- .../BinaryFormat/ClassWithMembersAndTypes.cs | 16 +- .../Forms/BinaryFormat/IBinaryArray.cs | 0 .../Forms/BinaryFormat/IBinaryWriteable.cs | 0 .../Windows/Forms/BinaryFormat/IRecord.cs | 0 .../Forms/BinaryFormat}/ListConverter.cs | 0 .../BinaryFormat/MemberPrimitiveTyped.cs | 13 +- .../Forms/BinaryFormat/MemberReference.cs | 5 +- .../Forms/BinaryFormat/MemberTypeInfo.cs | 67 +++++ .../Windows/Forms/BinaryFormat/MessageEnd.cs | 5 +- .../NullRecord.ObjectNullMultiple.cs | 7 +- .../NullRecord.ObjectNullMultiple256.cs | 6 +- .../Windows/Forms/BinaryFormat/NullRecord.cs | 0 .../Windows/Forms/BinaryFormat/ObjectNull.cs | 5 +- .../Forms/BinaryFormat/ObjectRecord.cs | 0 .../Forms/BinaryFormat/PrimitiveType.cs | 0 .../Windows/Forms/BinaryFormat/Record.cs | 123 ++++++++ .../Windows/Forms/BinaryFormat/RecordType.cs | 0 .../Forms/BinaryFormat/SerializationHeader.cs | 11 +- .../BinaryFormat}/StringRecordsCollection.cs | 0 .../SystemClassWithMembersAndTypes.cs | 15 +- .../Windows/Forms/BinaryFormat/TypeInfo.cs | 73 +++++ ...WinFormsBinaryFormattedObjectExtensions.cs | 20 +- 51 files changed, 293 insertions(+), 1362 deletions(-) delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ArrayRecordExtensions.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.ObjectBinaryArray.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.PrimitiveBinaryArray.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/BinaryArray.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassRecordExtensions.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/ClassWithMembers.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IBinaryFormatParseable.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IPrimitiveTypeRecord.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/IReadOnlyRecordMap.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Record.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/RecordMap.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support/TypeInfo.cs delete mode 100644 src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembers.cs rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ArrayInfo.cs (82%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ArrayRecord.cs (60%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ArraySingleObject.cs (76%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ArraySinglePrimitive.cs (68%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ArraySingleString.cs (76%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/BinaryArrayType.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/BinaryFormatWriter.cs (100%) rename src/{System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support => System.Windows.Forms/src/System/Windows/Forms/BinaryFormat}/BinaryFormatWriterScope.cs (63%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/BinaryLibrary.cs (77%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/BinaryObjectString.cs (84%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/BinaryType.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ClassInfo.cs (69%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ClassRecord.cs (75%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ClassTypeInfo.cs (87%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ClassWithId.cs (74%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ClassWithMembersAndTypes.cs (71%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/IBinaryArray.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/IBinaryWriteable.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/IRecord.cs (100%) rename src/{System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support => System.Windows.Forms/src/System/Windows/Forms/BinaryFormat}/ListConverter.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/MemberPrimitiveTyped.cs (77%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/MemberReference.cs (84%) create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/MemberTypeInfo.cs rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/MessageEnd.cs (70%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple.cs (77%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/NullRecord.ObjectNullMultiple256.cs (78%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/NullRecord.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ObjectNull.cs (84%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/ObjectRecord.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/PrimitiveType.cs (100%) create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/Record.cs rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/RecordType.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/SerializationHeader.cs (76%) rename src/{System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Support => System.Windows.Forms/src/System/Windows/Forms/BinaryFormat}/StringRecordsCollection.cs (100%) rename src/{System.Private.Windows.Core => System.Windows.Forms}/src/System/Windows/Forms/BinaryFormat/SystemClassWithMembersAndTypes.cs (70%) create mode 100644 src/System.Windows.Forms/src/System/Windows/Forms/BinaryFormat/TypeInfo.cs 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/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/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/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 63% 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 index 5e6cd18fcf8..e3a7c2fff17 100644 --- 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 @@ -1,7 +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.BinaryFormat; using System.Text; namespace System.Windows.Forms.BinaryFormat; @@ -13,19 +12,14 @@ namespace System.Windows.Forms.BinaryFormat; public BinaryFormatWriterScope(Stream stream) { Writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true); - // SerializationHeader - Writer.Write((byte)RecordType.SerializedStreamHeader); - Writer.Write(1); // root ID - Writer.Write(1); // header ID - Writer.Write(1); // major version - Writer.Write(0); // minor version + SerializationHeader.Default.Write(Writer); } public static implicit operator BinaryWriter(in BinaryFormatWriterScope scope) => scope.Writer; public void Dispose() { - Writer.Write((byte)RecordType.MessageEnd); + MessageEnd.Instance.Write(Writer); Writer.Dispose(); } } 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 @@ internal sealed class 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 @@ internal sealed class MessageEnd : IRecord, IBinaryFormatParseable 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 @@ internal sealed class ObjectNull : NullRecord, IRecord, IBinaryForma 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 @@ internal sealed class 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..8e02647eb72 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.Runtime.Serialization.BinaryFormat.ClassRecord types + || !types.IsTypeNameMatching(typeof(ImageListStreamer)) + || !types.HasMember("Data") + || types.GetObject("Data") is not System.Runtime.Serialization.BinaryFormat.ArrayRecord data) { return false; } - Debug.Assert(data.ArrayObjects is byte[]); - imageListStreamer = new ImageListStreamer((byte[])data.ArrayObjects); + imageListStreamer = new ImageListStreamer(data.ToArray(maxLength: Array.MaxLength)); 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.Runtime.Serialization.BinaryFormat.ClassRecord types + || !types.IsTypeNameMatching(typeof(Bitmap)) + || !types.HasMember("Data") + || types.GetObject("Data") is not System.Runtime.Serialization.BinaryFormat.ArrayRecord data) { return false; } - Debug.Assert(data.ArrayObjects is byte[]); - bitmap = new Bitmap(new MemoryStream((byte[])data.ArrayObjects)); + bitmap = new Bitmap(new MemoryStream(data.ToArray(maxLength: Array.MaxLength))); return true; } From ce6709624b393654da5356c3b939809385d9982f Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 13 May 2024 11:13:48 +0200 Subject: [PATCH 05/14] - update test project so it compiles - stop using internal APIs - fix the bugs: - update the record map, so mapping system class of common primtive types to PrimitiveTypeRecord is visible also to those who access it via Id - add missing support for mapping to PrimitiveTypeRecord for IntPtr and UIntPtr - use GetSerializationRecord when unwrapping - remove FormatterTypeStyle.TypesWhenNeeded test (not supported) - remove tests for internal APIs tested in PayloadReader project: - RecordMapTests - NullRecordTests - ClassInfoTests - MemberTypeInfo --- .../PayloadReader.cs | 5 +- .../SystemClassWithMembersAndTypesRecord.cs | 58 ++-- .../Utils/RecordMap.cs | 12 + .../ClassRecordFieldInfoDeserializer.cs | 8 +- ...lassRecordSerializationInfoDeserializer.cs | 4 +- .../BinaryFormat/Deserializer/Deserializer.cs | 2 +- .../Deserializer/ObjectRecordDeserializer.cs | 46 +-- .../FormatTests/Common/ManualParser.cs | 44 --- .../FormatTests/Common/NullRecordTests.cs | 256 -------------- .../FormatTests/Common/PrimitiveTests.cs | 10 - .../FormatTests/FormattedObject/ArrayTests.cs | 9 +- .../BinaryFormattedObjectTests.cs | 326 +++++++----------- .../FormattedObject/ClassInfoTests.cs | 81 ----- .../FormattedObject/ExceptionTests.cs | 52 +-- .../FormattedObject/HashTableTests.cs | 37 +- .../FormatTests/FormattedObject/ListTests.cs | 87 +++-- .../FormattedObject/MemberTypeInfoTests.cs | 71 ---- .../FormattedObject/NullRecordTests.cs | 132 ------- .../FormattedObject/PrimitiveTypeTests.cs | 43 +-- .../FormattedObject/RecordMapTests.cs | 34 -- .../FormattedObject/SystemDrawingTests.cs | 42 +-- .../FormatTests/Formatter/NullRecordTests.cs | 37 -- .../src/Properties/AssemblyInfo.cs | 1 + 23 files changed, 316 insertions(+), 1081 deletions(-) delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ManualParser.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/NullRecordTests.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/ClassInfoTests.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/MemberTypeInfoTests.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/NullRecordTests.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/FormattedObject/RecordMapTests.cs delete mode 100644 src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Formatter/NullRecordTests.cs 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 index 5f1621d8165..fbcaee7ff60 100644 --- 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 @@ -157,10 +157,7 @@ private static SerializationRecord Read(BinaryReader reader, PayloadOptions opti while (recordType != RecordType.MessageEnd); readOnlyRecordMap = recordMap; - SerializationRecord rootRecord = recordMap[header.RootId]; - return rootRecord is SystemClassWithMembersAndTypesRecord systemClass - ? systemClass.TryToMapToUserFriendly() - : rootRecord; + return recordMap.GetRootRecord(header); } private static SerializationRecord ReadNext(BinaryReader reader, RecordMap recordMap, 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 index be40332d8f2..6d8717ad6a9 100644 --- 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 @@ -44,30 +44,42 @@ 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")) + if (MemberValues.Count == 1) { - return MemberValues[0] switch + if (HasMember("m_value")) { - // 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)); + return MemberValues[0] switch + { + // there can be a value match, but no TypeName match + bool value when TypeName.FullName == typeof(bool).FullName => Create(value), + byte value when TypeName.FullName == typeof(byte).FullName => Create(value), + sbyte value when TypeName.FullName == typeof(sbyte).FullName => Create(value), + char value when TypeName.FullName == typeof(char).FullName => Create(value), + short value when TypeName.FullName == typeof(short).FullName => Create(value), + ushort value when TypeName.FullName == typeof(ushort).FullName => Create(value), + int value when TypeName.FullName == typeof(int).FullName => Create(value), + uint value when TypeName.FullName == typeof(uint).FullName => Create(value), + long value when TypeName.FullName == typeof(long).FullName => Create(value), + ulong value when TypeName.FullName == typeof(ulong).FullName => Create(value), + float value when TypeName.FullName == typeof(float).FullName => Create(value), + double value when TypeName.FullName == typeof(double).FullName => Create(value), + _ => this + }; + } + else if (HasMember("value")) + { + return MemberValues[0] switch + { + // there can be a value match, but no TypeName match + long value when TypeName.FullName == typeof(IntPtr).FullName => Create(new IntPtr(value)), + ulong value when TypeName.FullName == typeof(UIntPtr).FullName => Create(new UIntPtr(value)), + _ => this + }; + } + else if (HasMember("_ticks") && MemberValues[0] is long ticks && TypeName.FullName == typeof(TimeSpan).FullName) + { + return Create(new TimeSpan(ticks)); + } } else if (MemberValues.Count == 2 && HasMember("ticks") && HasMember("dateData") @@ -76,7 +88,7 @@ internal SerializationRecord TryToMapToUserFriendly() { return Create(BinaryReaderExtensions.CreateDateTimeFromData(value)); } - else if(MemberValues.Count == 4 + 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))) 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 index bac3b1306fb..0557b4fbb9c 100644 --- 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 @@ -43,6 +43,18 @@ internal void Add(SerializationRecord record) } } + internal SerializationRecord GetRootRecord(SerializedStreamHeaderRecord header) + { + SerializationRecord rootRecord = _map[header.RootId]; + if (rootRecord is SystemClassWithMembersAndTypesRecord systemClass) + { + // update the record map, so it's visible also to those who access it via Id + _map[header.RootId] = rootRecord = systemClass.TryToMapToUserFriendly(); + } + + return rootRecord; + } + // keys (32-bit integer ids) are adversary-provided so we need a collision-resistant comparer private sealed class CollisionResistantInt32Comparer : IEqualityComparer { 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 776e69c7af6..ea28f29db81 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 @@ -50,12 +50,7 @@ internal override Id Continue() { // FormatterServices *never* returns anything but fields. FieldInfo field = (FieldInfo)_fieldInfo[_currentFieldIndex]; - object? rawValue; - try - { - rawValue = _classRecord.GetObject(field.Name); - } - catch (KeyNotFoundException) + if (!_classRecord.HasMember(field.Name)) { if (Deserializer.Options.AssemblyMatching == FormatterAssemblyStyle.Simple || field.GetCustomAttribute() is not null) @@ -67,6 +62,7 @@ internal override Id Continue() throw new SerializationException($"Could not find field '{field.Name}' data for type '{field.DeclaringType!.Name}'."); } + var rawValue = _classRecord.GetSerializationRecord(field.Name); (object? memberValue, Id reference) = UnwrapMemberValue(rawValue); if (s_missingValueSentinel == memberValue) { 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 62497ed4107..bc3a868b1e3 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 @@ -20,7 +20,6 @@ internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDese private readonly Runtime.Serialization.BinaryFormat.ClassRecord _classRecord; private readonly SerializationInfo _serializationInfo; private readonly ISerializationSurrogate? _surrogate; - private int _currentMemberIndex; internal ClassRecordSerializationInfoDeserializer( Runtime.Serialization.BinaryFormat.ClassRecord classRecord, @@ -39,7 +38,7 @@ internal override Id Continue() // adsitnik: the order is no longer guaranteed to be preserved foreach (string memberName in _classRecord.MemberNames) { - (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.MemberValues[_currentMemberIndex]); + (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetSerializationRecord(memberName)); if (s_missingValueSentinel == memberValue) { @@ -57,7 +56,6 @@ internal override Id Continue() } _serializationInfo.AddValue(memberName, memberValue); - _currentMemberIndex++; } // We can't complete these in the same way we do with direct field sets as user code can dereference the 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 04bc7088e3a..662377784a4 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 @@ -232,7 +232,7 @@ object DeserializeNew(Id id) // string matches and as such we'll just create the parser object. SerializationRecord record = _recordMap[id]; - if (record is BinaryObjectStringRecord binaryString) + if (record is PrimitiveTypeRecord binaryString) { _deserializedObjects.Add(id, binaryString.Value); return binaryString.Value; 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 d6934606def..11767de62e7 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 @@ -41,34 +41,36 @@ private protected ObjectRecordDeserializer(SerializationRecord objectRecord, IDe /// the object record has not been encountered yet. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected (object? value, Id id) UnwrapMemberValue(object? memberValue) + private protected (object? value, Id id) UnwrapMemberValue(SerializationRecord? memberValue) { + // NullRecord is expressed via the public API by just returning a null + if (memberValue is null) + { + return (null, Id.Null); + } + return memberValue switch { // String - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), // Class record - ClassRecord classRecord => (classRecord, classRecord.ObjectId), - // Record reference - MemberReferenceRecord memberReference => TryGetObject(memberReference.Reference), + ClassRecord classRecord => TryGetObject(classRecord.ObjectId), // Primitive type record - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - PrimitiveTypeRecord primitive => (primitive.Value, primitive.ObjectId), - // Null - null => (null, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), + PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), // At this point should be an inline primitive _ => throw new SerializationException($"Unexpected member type '{memberValue.GetType()}'."), }; diff --git a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ManualParser.cs b/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ManualParser.cs deleted file mode 100644 index 3c11d5e3d10..00000000000 --- a/src/System.Private.Windows.Core/tests/BinaryFormatTests/FormatTests/Common/ManualParser.cs +++ /dev/null @@ -1,44 +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.Formatters.Binary; -using System.Windows.Forms.BinaryFormat; -using Record = System.Windows.Forms.BinaryFormat.Record; - -namespace FormatTests.Common; - -/// -/// 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 dd3a47da138..bc046940f38 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 @@ -3,7 +3,6 @@ using System.Drawing; using System.Runtime.Serialization; -using System.Windows.Forms.BinaryFormat; using System.Runtime.Serialization.BinaryFormat; namespace FormatTests.FormattedObject; @@ -20,7 +19,7 @@ public override void Roundtrip_ArrayContainingArrayAtNonZeroLowerBound() [MemberData(nameof(StringArray_Parse_Data))] public void StringArray_Parse(string?[] strings) { - BinaryFormattedObject format = new(Serialize(strings)); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(strings)); var arrayRecord = (ArrayRecord)format.RootRecord; arrayRecord.ToArray().Should().BeEquivalentTo(strings); } @@ -36,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)); - var arrayRecord = (ArrayRecord)format.RootRecord; - arrayRecord.Should().BeEquivalentTo(arrayRecord.ToArray()); + System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(array)); + var arrayRecord = (ArrayRecord)format.RootRecord; + arrayRecord.ToArray(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..5c2057a345b 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.Runtime.Serialization.BinaryFormat; using FormatTests.Common; namespace FormatTests.FormattedObject; @@ -12,8 +12,8 @@ public class BinaryFormattedObjectTests : SerializationTest stringRecord = (PrimitiveTypeRecord)format[1]; stringRecord.ObjectId.Should().Be(1); stringRecord.Value.Should().Be(testString); } @@ -31,11 +31,24 @@ public void ReadBinaryObjectString(string 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]; + ClassRecord systemClass = (ClassRecord)format[1]; + VerifyHashTable(systemClass, expectedVersion: 0, expectedHashSize: 3); + + ArrayRecord keys = (ArrayRecord)systemClass.GetSerializationRecord("Keys")!; + keys.ObjectId.Should().Be(2); + keys.Length.Should().Be(0); + ArrayRecord values = (ArrayRecord)systemClass.GetSerializationRecord("Values")!; + values.ObjectId.Should().Be(3); + values.Length.Should().Be(0); + } + + private static void VerifyHashTable(ClassRecord systemClass, int expectedVersion, int expectedHashSize) + { + systemClass.RecordType.Should().Be(RecordType.SystemClassWithMembersAndTypes); systemClass.ObjectId.Should().Be(1); - systemClass.Name.Should().Be("System.Collections.Hashtable"); + systemClass.TypeName.FullName.Should().Be("System.Collections.Hashtable"); systemClass.MemberNames.Should().BeEquivalentTo( [ "LoadFactor", @@ -47,300 +60,217 @@ 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.GetObject("Comparer").Should().BeNull(); + systemClass.GetObject("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]; - - 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"); + ClassRecord systemClass = (ClassRecord)format[1]; + VerifyHashTable(systemClass, expectedVersion: 1, expectedHashSize: 3); + + ArrayRecord keys = (ArrayRecord)format[2]; + keys.ObjectId.Should().Be(2); + keys.Length.Should().Be(1); + keys.ToArray().Single().Should().Be("This"); + ArrayRecord values = (ArrayRecord)format[3]; + values.ObjectId.Should().Be(3); + values.Length.Should().Be(1); + values.ToArray().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[1]; + 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. + ArrayRecord array = (ArrayRecord)systemClass.GetSerializationRecord("Keys")!; + array.ObjectId.Should().Be(2); + array.ToArray().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[1]; + 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. + ArrayRecord keys = (ArrayRecord)systemClass.GetSerializationRecord("Keys")!; + keys.ObjectId.Should().Be(2); + keys.ToArray().Should().BeEquivalentTo(new object[] { "Yowza", "Youza", "Meeza" }); + + ArrayRecord values = (ArrayRecord)systemClass.GetSerializationRecord("Values")!; + values.ObjectId.Should().Be(3); + values.ToArray().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[1].RecordType.Should().Be(RecordType.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[1].RecordType.Should().Be(RecordType.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; + ClassRecord @class = (ClassRecord)format.RootRecord; + @class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); @class.ObjectId.Should().Be(1); - @class.Name.Should().Be(typeof(SimpleSerializableObject).FullName); + @class.TypeName.FullName.Should().Be(typeof(SimpleSerializableObject).FullName); + @class.LibraryName.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; + ClassRecord @class = (ClassRecord)format.RootRecord; + @class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); @class.ObjectId.Should().Be(1); - @class.Name.Should().Be(typeof(NestedSerializableObject).FullName); + @class.TypeName.FullName.Should().Be(typeof(NestedSerializableObject).FullName); + @class.LibraryName.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.GetObject("_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; + ClassRecord @class = (ClassRecord)format.RootRecord; + @class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); @class.ObjectId.Should().Be(1); - @class.Name.Should().Be(typeof(TwoIntSerializableObject).FullName); + @class.TypeName.FullName.Should().Be(typeof(TwoIntSerializableObject).FullName); + @class.LibraryName.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.GetObject("_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 firstClass = (ClassRecord)format[3]; + firstClass.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); + ClassRecord classWithId = (ClassRecord)format[4]; + classWithId.RecordType.Should().Be(RecordType.ClassWithId); + classWithId.GetObject("_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 }); + ArrayRecord array = (ArrayRecord)format[1]; + array.Length.Should().Be(4); + array.ToArray().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)); + + ArrayRecord array = (ArrayRecord)format[1]; + array.ObjectId.Should().Be(1); + array.Length.Should().Be(3); + array.ToArray().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)); + + ArrayRecord array = (ArrayRecord)format[1]; + array.ObjectId.Should().Be(1); + array.Length.Should().Be(6); + array.ToArray().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)); + + ArrayRecord array = (ArrayRecord)format[1]; + array.ObjectId.Should().Be(1); + array.Length.Should().Be(3); + array.ToArray().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(RecordType.ClassWithMembersAndTypes); + classRecord.LibraryName.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(RecordType.ClassWithMembersAndTypes); + classRecord.LibraryName.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..347a42c8b07 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.Runtime.Serialization.BinaryFormat; 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.GetObject("Data").Should().BeNull(); + systemClass.GetObject("InnerException").Should().BeNull(); + systemClass.GetObject("HelpURL").Should().BeNull(); + systemClass.GetObject("StackTraceString").Should().BeNull(); + systemClass.GetObject("RemoteStackTraceString").Should().BeNull(); + systemClass.GetInt32("RemoteStackIndex").Should().Be(0); + systemClass.GetObject("ExceptionMethod").Should().BeNull(); + systemClass.GetInt32("HResult").Should().Be(unchecked((int)0x80131515)); + systemClass.GetObject("Source").Should().BeNull(); + systemClass.GetObject("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..5e8b69d7980 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.Runtime.Serialization.BinaryFormat; 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(RecordType.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..71511a9f760 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.Runtime.Serialization.BinaryFormat; 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[1]); - format[2].Should().BeOfType(); + format[2].Should().BeAssignableTo>(); + } + + private static void VerifyArrayList(ClassRecord systemClass) + { + systemClass.RecordType.Should().Be(RecordType.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[1]; + VerifyArrayList(listRecord); - systemClass.Name.Should().Be(typeof(ArrayList).FullName); - systemClass.MemberNames.Should().BeEquivalentTo(["_items", "_size", "_version"]); - systemClass.MemberTypeInfo[0].Should().Be((BinaryType.ObjectArray, null)); + ArrayRecord array = (ArrayRecord)format[2]; - ArraySingleObject array = (ArraySingleObject)format[2]; - MemberPrimitiveTyped primitve = (MemberPrimitiveTyped)array[0]!; - primitve.Value.Should().Be(value); + array.ToArray().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[1]; + VerifyArrayList(listRecord); - systemClass.Name.Should().Be(typeof(ArrayList).FullName); - systemClass.MemberNames.Should().BeEquivalentTo(["_items", "_size", "_version"]); - systemClass.MemberTypeInfo[0].Should().Be((BinaryType.ObjectArray, null)); + ArrayRecord array = (ArrayRecord)format[2]; - ArraySingleObject array = (ArraySingleObject)format[2]; - BinaryObjectString binaryString = (BinaryObjectString)array[0]!; - binaryString.Value.Should().Be("JarJar"); + array.ToArray().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[1]; + classInfo.RecordType.Should().Be(RecordType.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]; + ArrayRecord array = (ArrayRecord)format[2]; 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[1]; + classInfo.RecordType.Should().Be(RecordType.SystemClassWithMembersAndTypes); + classInfo.TypeName.FullName.Should().StartWith("System.Collections.Generic.List`1[[System.String,"); + classInfo.GetSerializationRecord("_items").Should().BeAssignableTo>(); - ArraySingleString array = (ArraySingleString)format[2]; + ArrayRecord array = (ArrayRecord)format[2]; 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..8d30ca6579c 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.Runtime.Serialization.BinaryFormat; 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[1]); } [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..ce9daa17615 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.Runtime.Serialization.BinaryFormat; 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; + ClassRecord classInfo = (ClassRecord)format.RootRecord; + classInfo.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); classInfo.ObjectId.Should().Be(1); - classInfo.Name.Should().Be("System.Drawing.RectangleF"); + 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")] From 045baedf45feda26c65a56e78711c41a3228372d Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 13:27:51 +0200 Subject: [PATCH 06/14] fix the bugs: - check records by Ids to detect custom comparers for Hashtable - ObjectRecordDeserializer needs to handle ArrayRecords and raw primitive values - add support for jagged arrays with custom offsets (just to rejct them later) - materialize arrays of primitive types using the provided API - when mapping System Classes to PrimitiveTypeRecord the Ids need to be preserved - don't iterate over the same member twice - support arrays of nullable primitives (don't represent them as ArrayRecord because they consists of MemberPrimitiveTypedRecord and NullsRecords) --- .../Infos/MemberTypeInfo.cs | 28 +++++- .../Records/BinaryArrayRecord.cs | 2 +- .../Records/MemberPrimitiveTypedRecord.cs | 9 +- .../RectangularOrCustomOffsetArrayRecord.cs | 15 +-- .../SystemClassWithMembersAndTypesRecord.cs | 4 +- .../BinaryFormattedObjectExtensions.cs | 70 ++++++++------ .../Deserializer/ArrayRecordDeserializer.cs | 94 +++++++++++++------ .../ClassRecordFieldInfoDeserializer.cs | 3 +- ...lassRecordSerializationInfoDeserializer.cs | 41 +++++--- .../BinaryFormat/Deserializer/Deserializer.cs | 43 +++------ .../Deserializer/ObjectRecordDeserializer.cs | 47 ++++------ 11 files changed, 205 insertions(+), 151 deletions(-) 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 index 42391837ed7..96e090dd6a8 100644 --- 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 @@ -182,12 +182,30 @@ internal bool ShouldBeRepresentedAsArrayOfClassRecords() { (BinaryType binaryType, object? additionalInfo) = Infos[0]; - return binaryType switch + if (binaryType is BinaryType.Class) { - BinaryType.SystemClass => !((TypeName)additionalInfo!).IsSZArray, - BinaryType.Class => !((ClassTypeInfo)additionalInfo!).TypeName.IsSZArray, - _ => false - }; + return !((ClassTypeInfo)additionalInfo!).TypeName.IsSZArray; + } + else if (binaryType is BinaryType.SystemClass) + { + TypeName typeName = (TypeName)additionalInfo!; + + if (typeName.IsSZArray) + { + return false; + } + + if (!typeName.IsConstructedGenericType) + { + return true; + } + + // Can't use ArrayRecord for Nullable[] + // as it consists of MemberPrimitiveTypedRecord and NullsRecord + return typeName.GetGenericTypeDefinition().FullName != typeof(Nullable<>).FullName; + } + + return false; } internal TypeName GetElementTypeName() 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 index e1ad8f4d503..fc029c0d217 100644 --- 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 @@ -209,7 +209,7 @@ private static Type MapElementType(Type arrayType) arrayNestingDepth++; } - if (PrimitiveTypes.Contains(elementType)) + if (PrimitiveTypes.Contains(elementType) || (Nullable.GetUnderlyingType(elementType) is Type nullable && PrimitiveTypes.Contains(nullable))) { return arrayNestingDepth == 1 ? elementType : arrayType.GetElementType()!; } 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 index d4853c4288d..d39ed407b98 100644 --- 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 @@ -16,10 +16,9 @@ namespace System.Runtime.Serialization.BinaryFormat; internal sealed class MemberPrimitiveTypedRecord : PrimitiveTypeRecord where T : unmanaged { - internal MemberPrimitiveTypedRecord(T value, bool pretend = false) : base(value) - { - RecordType = pretend ? RecordType.MemberPrimitiveTyped : RecordType.SystemClassWithMembersAndTypes; - } + internal MemberPrimitiveTypedRecord(T value, int objectId = 0) : base(value) => ObjectId = objectId; - public override RecordType RecordType { get; } + public override RecordType RecordType => RecordType.MemberPrimitiveTyped; + + public override int ObjectId { get; } } 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 index 8d7fbd527f4..d370512b6b2 100644 --- 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 @@ -152,14 +152,17 @@ internal override (AllowedRecordTypes allowed, PrimitiveType primitiveType) GetA internal static RectangularOrCustomOffsetArrayRecord Create(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, int[] lengths, int[] offsets, RecordMap recordMap) { - return memberTypeInfo.Infos[0].BinaryType switch + Type elementType = 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), + BinaryType.Primitive => MapPrimitive((PrimitiveType)memberTypeInfo.Infos[0].AdditionalInfo!), + BinaryType.PrimitiveArray => MapPrimitive((PrimitiveType)memberTypeInfo.Infos[0].AdditionalInfo!).MakeArrayType(), + BinaryType.String => typeof(string), + BinaryType.Object => typeof(object), + BinaryType.SystemClass or BinaryType.Class => typeof(ClassRecord), + _ => throw ThrowHelper.InvalidBinaryType(memberTypeInfo.Infos[0].BinaryType) }; + + return new(elementType, arrayInfo, memberTypeInfo, lengths, offsets, recordMap); } private static Type MapPrimitive(PrimitiveType primitiveType) 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 index 6d8717ad6a9..59553e1f298 100644 --- 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 @@ -106,7 +106,7 @@ internal SerializationRecord TryToMapToUserFriendly() return this; - static SerializationRecord Create(T value) where T : unmanaged - => new MemberPrimitiveTypedRecord(value, pretend: true); + SerializationRecord Create(T value) where T : unmanaged + => new MemberPrimitiveTypedRecord(value, ObjectId); } } 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 5d23f4ef4d0..b9d7f104842 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 @@ -92,40 +92,49 @@ public static bool TryGetPrimitiveType(this BinaryFormattedObject format, [NotNu static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? value) { - if (format.RootRecord.RecordType is not (RecordType.BinaryObjectString or RecordType.MemberPrimitiveTyped or RecordType.SystemClassWithMembersAndTypes)) + if (format.RootRecord.RecordType is RecordType.BinaryObjectString) { - value = null; - return false; + value = ((PrimitiveTypeRecord)format.RootRecord).Value; + return true; } - - value = format.RootRecord switch + else if (format.RootRecord.RecordType is RecordType.MemberPrimitiveTyped) { - 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 primitive => primitive.Value, - _ => null - }; + value = GetMemberPrimitiveTypedValue(format.RootRecord); + return true; + } - return value is not null - || format.RootRecord is PrimitiveTypeRecord; // null string value + value = null; + return false; } } + internal static object GetMemberPrimitiveTypedValue(this SerializationRecord record) + { + Debug.Assert(record.RecordType is RecordType.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 . /// @@ -278,12 +287,13 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? ha // 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 SystemClassWithMembersAndTypesRecord classInfo + if (format.RootRecord.RecordType != RecordType.SystemClassWithMembersAndTypes + || format.RootRecord is not ClassRecord classInfo || !classInfo.IsTypeNameMatching(typeof(Hashtable)) || !classInfo.HasMember("Keys") || !classInfo.HasMember("Values") - || classInfo.GetObject("Keys") is not ArrayRecord keysRecord - || classInfo.GetObject("Values") is not ArrayRecord valuesRecord + || format[2] is not ArrayRecord keysRecord + || format[3] is not ArrayRecord valuesRecord || keysRecord.Length != valuesRecord.Length) { return false; 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 18b6224183b..373239fe983 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 @@ -10,7 +10,7 @@ internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer private readonly ArrayRecord _arrayRecord; private readonly Type _elementType; private readonly Array _array; - private readonly Array? _result; + private readonly Array _result; private int _index; private bool _hasFixups; @@ -18,13 +18,9 @@ internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer) : base(arrayRecord, deserializer) { - if (arrayRecord.ArrayType is not (ArrayType.Single or ArrayType.Jagged or ArrayType.Rectangular)) - { - throw new NotSupportedException("Only arrays with zero offsets are supported."); - } - // Other array types are handled directly (ArraySinglePrimitive and ArraySingleString). Debug.Assert(arrayRecord.RecordType is not (RecordType.ArraySingleString or RecordType.ArraySinglePrimitive)); + Debug.Assert(arrayRecord.ArrayType is (ArrayType.Single or ArrayType.Jagged or ArrayType.Rectangular)); _arrayRecord = arrayRecord; _elementType = deserializer.TypeResolver.GetType(arrayRecord.ElementTypeName, arrayRecord.ElementTypeLibraryName); @@ -43,37 +39,22 @@ internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserial elementType = elementType.GetElementType()!; } - // If following is false, then it's an array or primitive types that has already been materialized properly - if (elementType == typeof(object) || elementType == typeof(ClassRecord)) - { - int[] lengths = new int[arrayRecord.Rank]; - for (int dimension = 0; dimension < lengths.Length; dimension++) - { - lengths[dimension] = _array.GetLength(dimension); - } - - Object = _result = Array.CreateInstance(_elementType, lengths); - } - else + int[] lengths = new int[arrayRecord.Rank]; + for (int dimension = 0; dimension < lengths.Length; dimension++) { - Object = _array; + lengths[dimension] = _array.GetLength(dimension); } + + Object = _result = Array.CreateInstance(_elementType, lengths); } // adsitnik: it may compile, but most likely won't work without any changes internal override Id Continue() { - if (_result is null) - { - return Id.Null; - } - while (_index < _arrayRecord.Length) { // TODO: adsitnik: handle multi-dimensional arrays - object? memberValue = _array.GetValue(_index); - _result.SetValue(memberValue, _index); - Id reference = memberValue is ClassRecord classRecord ? classRecord.ObjectId : Id.Null; + (object? memberValue, Id reference) = UnwrapMemberValue(_array.GetArrayData()[_index]); if (s_missingValueSentinel == memberValue) { @@ -117,4 +98,63 @@ internal override Id Continue() return Id.Null; } + + internal static Array GetArraySinglePrimitive(SerializationRecord record) => record switch + { + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), + _ => throw new NotSupportedException(), + }; + + [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName, AssemblyNameInfo)")] + internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, BinaryFormattedObject.ITypeResolver typeResolver) + { + if (arrayRecord.ArrayType is not (ArrayType.Single or ArrayType.Jagged or ArrayType.Rectangular)) + { + throw new NotSupportedException("Only arrays with zero offsets are supported."); + } + + Type arrayRecordElementType = typeResolver.GetType(arrayRecord.ElementTypeName, arrayRecord.ElementTypeLibraryName); + Type elementType = arrayRecordElementType; + while (elementType.IsArray) + { + elementType = elementType.GetElementType()!; + } + + if (!(HasBuiltInSupport(elementType) + || (Nullable.GetUnderlyingType(elementType) is Type nullable && HasBuiltInSupport(nullable)))) + { + return null; + } + + Type expectedArrayType = arrayRecord.ArrayType switch + { + ArrayType.Rectangular => arrayRecordElementType.MakeArrayType(arrayRecord.Rank), + _ => arrayRecordElementType.MakeArrayType() + }; + + return arrayRecord.ToArray(expectedArrayType, maxLength: Array.MaxLength); + + 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/ClassRecordFieldInfoDeserializer.cs b/src/System.Private.Windows.Core/src/System/Windows/Forms/BinaryFormat/Deserializer/ClassRecordFieldInfoDeserializer.cs index ea28f29db81..ff53e04d14b 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 @@ -62,8 +62,7 @@ internal override Id Continue() throw new SerializationException($"Could not find field '{field.Name}' data for type '{field.DeclaringType!.Name}'."); } - var rawValue = _classRecord.GetSerializationRecord(field.Name); - (object? memberValue, Id reference) = UnwrapMemberValue(rawValue); + (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetObject(field.Name)); if (s_missingValueSentinel == memberValue) { // Record has not been encountered yet, need to pend iteration. 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 bc3a868b1e3..9e10097f1ef 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 @@ -20,6 +20,8 @@ internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDese private readonly Runtime.Serialization.BinaryFormat.ClassRecord _classRecord; private readonly SerializationInfo _serializationInfo; private readonly ISerializationSurrogate? _surrogate; + private readonly IEnumerator _memberNamesIterator; + private bool _canIterate; internal ClassRecordSerializationInfoDeserializer( Runtime.Serialization.BinaryFormat.ClassRecord classRecord, @@ -31,31 +33,40 @@ internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDese _classRecord = classRecord; _surrogate = surrogate; _serializationInfo = new(type, BinaryFormattedObject.DefaultConverter); + _memberNamesIterator = _classRecord.MemberNames.GetEnumerator(); + _canIterate = _memberNamesIterator.MoveNext(); // start the iterator } internal override Id Continue() { // adsitnik: the order is no longer guaranteed to be preserved - foreach (string memberName in _classRecord.MemberNames) + if (_canIterate) { - (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetSerializationRecord(memberName)); - - if (s_missingValueSentinel == memberValue) + do { - // Record has not been encountered yet, need to pend iteration. - return reference; - } + string memberName = _memberNamesIterator.Current; + (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetObject(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.ObjectId, + reference, + _serializationInfo, + memberName)); + } + + _serializationInfo.AddValue(memberName, memberValue); } + while (_memberNamesIterator.MoveNext()); - _serializationInfo.AddValue(memberName, memberValue); + _canIterate = false; } // We can't complete these in the same way we do with direct field sets as user code can dereference the 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 662377784a4..e13007cc24c 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 @@ -232,38 +232,21 @@ object DeserializeNew(Id id) // string matches and as such we'll just create the parser object. SerializationRecord record = _recordMap[id]; - if (record is PrimitiveTypeRecord binaryString) - { - _deserializedObjects.Add(id, binaryString.Value); - return binaryString.Value; - } - if (record.RecordType is RecordType.ArraySingleString or RecordType.ArraySinglePrimitive) + object? value = record.RecordType switch { - Array? values = record switch - { - ArrayRecord stringArray => stringArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - ArrayRecord primitiveArray => primitiveArray.ToArray(maxLength: Array.MaxLength), - _ => null - }; - - Debug.Assert(values is not null); - _deserializedObjects.Add(record.ObjectId, values); - return values; + RecordType.BinaryObjectString => ((PrimitiveTypeRecord)record).Value, + RecordType.MemberPrimitiveTyped => record.GetMemberPrimitiveTypedValue(), + RecordType.ArraySingleString => ((ArrayRecord)record).ToArray(maxLength: Array.MaxLength), + RecordType.ArraySinglePrimitive => ArrayRecordDeserializer.GetArraySinglePrimitive(record), + RecordType.BinaryArray => ArrayRecordDeserializer.GetSimpleBinaryArray((ArrayRecord)record, _typeResolver), + _ => null + }; + + if (value is not null) + { + _deserializedObjects.Add(record.ObjectId, value); + return value; } // Not a simple case, need to do a full deserialization of the record. 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 11767de62e7..9c7e7ad0c78 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 @@ -41,39 +41,30 @@ private protected ObjectRecordDeserializer(SerializationRecord objectRecord, IDe /// the object record has not been encountered yet. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected (object? value, Id id) UnwrapMemberValue(SerializationRecord? memberValue) + private protected (object? value, Id id) UnwrapMemberValue(object? memberValue) { - // NullRecord is expressed via the public API by just returning a null - if (memberValue is null) + if (memberValue is null) // PayloadReader does not return NullRecord, just null { return (null, Id.Null); } - - return memberValue switch + else if (memberValue is not SerializationRecord serializationRecord) // a primitive value + { + return (memberValue, Id.Null); + } + else if (serializationRecord.RecordType is RecordType.BinaryObjectString) + { + PrimitiveTypeRecord stringRecord = (PrimitiveTypeRecord)serializationRecord; + return (stringRecord.Value, stringRecord.ObjectId); + } + else if (serializationRecord.RecordType is RecordType.MemberPrimitiveTyped) + { + return (serializationRecord.GetMemberPrimitiveTypedValue(), Id.Null); + } + else { - // String - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - // Class record - ClassRecord classRecord => TryGetObject(classRecord.ObjectId), - // Primitive type record - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - PrimitiveTypeRecord primitive => (primitive.Value, Id.Null), - // At this point should be an inline primitive - _ => throw new SerializationException($"Unexpected member type '{memberValue.GetType()}'."), - }; + // ClassRecords & ArrayRecords + return TryGetObject(serializationRecord.ObjectId); + } (object? value, Id id) TryGetObject(Id id) { From 7f409aeea30626a1299e0f1f2c409c161e0b7bc7 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 19:15:07 +0200 Subject: [PATCH 07/14] disable some of the FormatterTypeStyle.TypesWhenNeeded tests (this needs a discussion whether we want to support that or not) --- .../BinaryFormatTests/FormatTests/Common/BasicObjectTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..a83426d48b6 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 void DeserializeStoredObjects(object value, TypeSerializableValue[] seria // Explicitly not supporting offset arrays from value in BinaryFormatterTests.RawSerializableObjects() from FormatterAssemblyStyle assemblyFormat in new[] { FormatterAssemblyStyle.Full, FormatterAssemblyStyle.Simple } - from FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString } + from FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways/*, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString*/ } where value.Item1 is not Array array || array.GetLowerBound(0) == 0 select (value.Item1, assemblyFormat, typeFormat)).ToArray()); } From 85c01ff3b86c22fbb010cef125836cae3e53d6c4 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 19:31:29 +0200 Subject: [PATCH 08/14] move the payload reader code to separate project to ensure we are not using any of the internal APIs --- Winforms.sln | 19 +++++++++++++++ .../src/System.Private.Windows.Core.csproj | 7 +++--- .../Directory.Build.props | 7 ++++++ .../Enums/AllowedRecordType.cs | 0 .../Enums/ArrayType.cs | 0 .../Enums/BinaryType.cs | 0 .../Enums/PrimitiveType.cs | 0 .../Enums/RecordType.cs | 0 .../GlobalUsings.cs | 5 ++++ .../Infos/ArrayInfo.cs | 0 .../Infos/ClassInfo.cs | 0 .../Infos/ClassTypeInfo.cs | 0 .../Infos/MemberTypeInfo.cs | 0 .../Infos/NextInfo.cs | 0 .../PayloadOptions.cs | 0 .../PayloadReader.cs | 0 .../Records/ArrayOfClassesRecord.cs | 0 .../Records/ArrayRecord.cs | 0 .../Records/ArraySingleObjectRecord.cs | 0 .../Records/ArraySinglePrimitiveRecord.cs | 0 .../Records/ArraySingleStringRecord.cs | 0 .../Records/BinaryArrayRecord.cs | 0 .../Records/BinaryLibraryRecord.cs | 0 .../Records/BinaryObjectStringRecord.cs | 0 .../Records/ClassRecord.cs | 0 .../Records/ClassWithIdRecord.cs | 0 .../Records/ClassWithMembersAndTypesRecord.cs | 0 .../Records/ClassWithMembersRecord.cs | 0 .../Records/MemberPrimitiveTypedRecord.cs | 0 .../Records/MemberReferenceRecord.cs | 0 .../Records/MessageEndRecord.cs | 0 .../Records/NullsRecord.cs | 0 .../Records/ObjectNullMultiple256Record.cs | 0 .../Records/ObjectNullMultipleRecord.cs | 0 .../Records/ObjectNullRecord.cs | 0 .../Records/PrimitiveTypeRecord.cs | 0 .../RectangularOrCustomOffsetArrayRecord.cs | 0 .../Records/SerializationRecord.cs | 0 .../Records/SerializedStreamHeaderRecord.cs | 0 .../SystemClassWithMembersAndTypesRecord.cs | 0 .../Records/SystemClassWithMembersRecord.cs | 0 ....Runtime.Serialization.BinaryFormat.csproj | 23 +++++++++++++++++++ .../Utils/BinaryReaderExtensions.cs | 0 .../Utils/FormatterServices.cs | 0 .../Utils/RecordMap.cs | 0 .../Utils/ThrowHelper.cs | 0 46 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/System.Runtime.Serialization.BinaryFormat/Directory.Build.props rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs (100%) create mode 100644 src/System.Runtime.Serialization.BinaryFormat/GlobalUsings.cs rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs (100%) create mode 100644 src/System.Runtime.Serialization.BinaryFormat/System.Runtime.Serialization.BinaryFormat.csproj rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs (100%) rename src/{System.Private.Windows.Core/src => }/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs (100%) diff --git a/Winforms.sln b/Winforms.sln index 0ac5ab2ac2f..35553332cc5 100644 --- a/Winforms.sln +++ b/Winforms.sln @@ -177,6 +177,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TrimTestBinaryDeserializati EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BinaryFormatTests", "src\System.Private.Windows.Core\tests\BinaryFormatTests\BinaryFormatTests.csproj", "{57EC5288-9513-46CF-8FB7-626199690D90}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Runtime.Serialization.BinaryFormat", "src\System.Runtime.Serialization.BinaryFormat\System.Runtime.Serialization.BinaryFormat.csproj", "{DC3DD357-7FB4-4384-92DC-5D34864D010B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -981,6 +983,22 @@ Global {57EC5288-9513-46CF-8FB7-626199690D90}.Release|x64.Build.0 = Release|Any CPU {57EC5288-9513-46CF-8FB7-626199690D90}.Release|x86.ActiveCfg = Release|Any CPU {57EC5288-9513-46CF-8FB7-626199690D90}.Release|x86.Build.0 = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|arm64.ActiveCfg = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|arm64.Build.0 = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|x64.Build.0 = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Debug|x86.Build.0 = Debug|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|Any CPU.Build.0 = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|arm64.ActiveCfg = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|arm64.Build.0 = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|x64.ActiveCfg = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|x64.Build.0 = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|x86.ActiveCfg = Release|Any CPU + {DC3DD357-7FB4-4384-92DC-5D34864D010B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1044,6 +1062,7 @@ Global {55F3174F-C1FE-4C8F-AF71-2512630088F8} = {583F1292-AE8D-4511-B8D8-A81FE4642DDC} {CFBCC732-C988-4FE7-A21B-98A142365374} = {680FB14C-7B0C-4D63-9F1A-18ACCDB0F52A} {57EC5288-9513-46CF-8FB7-626199690D90} = {583F1292-AE8D-4511-B8D8-A81FE4642DDC} + {DC3DD357-7FB4-4384-92DC-5D34864D010B} = {77FEDB47-F7F6-490D-AF7C-ABB4A9E0B9D7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7B1B0433-F612-4E5A-BE7E-FCF5B9F6E136} 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..b27c2d17d01 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,10 @@ - - - + + + + diff --git a/src/System.Runtime.Serialization.BinaryFormat/Directory.Build.props b/src/System.Runtime.Serialization.BinaryFormat/Directory.Build.props new file mode 100644 index 00000000000..8b145257385 --- /dev/null +++ b/src/System.Runtime.Serialization.BinaryFormat/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + ECMA + + diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs rename to src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs rename to src/System.Runtime.Serialization.BinaryFormat/Enums/ArrayType.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs rename to src/System.Runtime.Serialization.BinaryFormat/Enums/BinaryType.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs rename to src/System.Runtime.Serialization.BinaryFormat/Enums/PrimitiveType.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs rename to src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs diff --git a/src/System.Runtime.Serialization.BinaryFormat/GlobalUsings.cs b/src/System.Runtime.Serialization.BinaryFormat/GlobalUsings.cs new file mode 100644 index 00000000000..a1a1ea28d85 --- /dev/null +++ b/src/System.Runtime.Serialization.BinaryFormat/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs rename to src/System.Runtime.Serialization.BinaryFormat/Infos/ArrayInfo.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs rename to src/System.Runtime.Serialization.BinaryFormat/Infos/ClassInfo.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs rename to src/System.Runtime.Serialization.BinaryFormat/Infos/ClassTypeInfo.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs rename to src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs rename to src/System.Runtime.Serialization.BinaryFormat/Infos/NextInfo.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs b/src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs rename to src/System.Runtime.Serialization.BinaryFormat/PayloadOptions.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs b/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs rename to src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/BinaryLibraryRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/BinaryObjectStringRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/MemberPrimitiveTypedRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/MessageEndRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/NullsRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultiple256Record.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullMultipleRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/ObjectNullRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/PrimitiveTypeRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/SerializationRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/SerializedStreamHeaderRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs rename to src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs diff --git a/src/System.Runtime.Serialization.BinaryFormat/System.Runtime.Serialization.BinaryFormat.csproj b/src/System.Runtime.Serialization.BinaryFormat/System.Runtime.Serialization.BinaryFormat.csproj new file mode 100644 index 00000000000..7be179b6775 --- /dev/null +++ b/src/System.Runtime.Serialization.BinaryFormat/System.Runtime.Serialization.BinaryFormat.csproj @@ -0,0 +1,23 @@ + + + + System.Runtime.Serialization.BinaryFormat + $(NetCurrent);$(NetPrevious);$(NetMinimum) + + + enable + true + true + false + + true + false + + + + + + + + + diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs rename to src/System.Runtime.Serialization.BinaryFormat/Utils/BinaryReaderExtensions.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs rename to src/System.Runtime.Serialization.BinaryFormat/Utils/FormatterServices.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs rename to src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs diff --git a/src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs similarity index 100% rename from src/System.Private.Windows.Core/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs rename to src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs From 7b7d4850981b66d5a269b60a56def0874647dc98 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 19:50:04 +0200 Subject: [PATCH 09/14] silence the compiler warnings --- src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs | 2 +- .../Records/ArraySinglePrimitiveRecord.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs b/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs index fbcaee7ff60..942f9d2eac6 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs @@ -106,7 +106,7 @@ public static ClassRecord ReadClassRecord(Stream payload, PayloadOptions? option private static SerializationRecord Read(BinaryReader reader, PayloadOptions options, out IReadOnlyDictionary readOnlyRecordMap) { Stack readStack = new(); - RecordMap recordMap = []; + var recordMap = new RecordMap(); // Everything has to start with a header var header = (SerializedStreamHeaderRecord)ReadNext(reader, recordMap, AllowedRecordTypes.SerializedStreamHeader, options, out _); diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs index 6d3d2f0afc9..4a609335d14 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs @@ -17,7 +17,7 @@ namespace System.Runtime.Serialization.BinaryFormat; /// /// /// -internal class ArraySinglePrimitiveRecord : ArrayRecord +internal sealed class ArraySinglePrimitiveRecord : ArrayRecord where T : unmanaged { private static TypeName? s_elementTypeName; From c88443f334adf91cf748d81ff25631418fc6d263 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 19:58:16 +0200 Subject: [PATCH 10/14] MemberReferenceRecord does not need to be public --- .../Records/MemberReferenceRecord.cs | 4 ++-- .../Records/SystemClassWithMembersAndTypesRecord.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs index bf7f9444431..29a5db28c55 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/MemberReferenceRecord.cs @@ -13,7 +13,7 @@ namespace System.Runtime.Serialization.BinaryFormat; /// /// /// -public sealed class MemberReferenceRecord : SerializationRecord +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. @@ -25,7 +25,7 @@ private MemberReferenceRecord(int reference, RecordMap recordMap) public override RecordType RecordType => RecordType.MemberReference; - public int Reference { get; } + internal int Reference { get; } private RecordMap RecordMap { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs index 59553e1f298..d934a2e09bd 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs @@ -17,7 +17,7 @@ private SystemClassWithMembersAndTypesRecord(ClassInfo classInfo, MemberTypeInfo public override AssemblyNameInfo LibraryName => FormatterServices.CoreLibAssemblyName; - public MemberTypeInfo MemberTypeInfo { get; } + internal MemberTypeInfo MemberTypeInfo { get; } internal override int ExpectedValuesCount => MemberTypeInfo.Infos.Count; From c63a18c03e8f5231825931d2588b12a5f125e39e Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 20:02:08 +0200 Subject: [PATCH 11/14] rename ClassRecord.GetObject to GetRawValue --- .../BinaryFormattedObjectExtensions.cs | 8 ++++---- .../ClassRecordFieldInfoDeserializer.cs | 2 +- .../ClassRecordSerializationInfoDeserializer.cs | 2 +- .../BinaryFormattedObjectTests.cs | 10 +++++----- .../FormattedObject/ExceptionTests.cs | 16 ++++++++-------- .../Records/ClassRecord.cs | 2 +- .../WinFormsBinaryFormattedObjectExtensions.cs | 4 ++-- 7 files changed, 22 insertions(+), 22 deletions(-) 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 b9d7f104842..7ace11d8cf9 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 @@ -149,11 +149,11 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? li if (format.RootRecord is not ClassRecord classInfo || !classInfo.HasMember("_items") || !classInfo.HasMember("_size") - || classInfo.GetObject("_size") is not int size + || classInfo.GetRawValue("_size") is not int size || !classInfo.TypeName.IsConstructedGenericType || classInfo.TypeName.GetGenericTypeDefinition().Name != typeof(List<>).Name || classInfo.TypeName.GetGenericArguments().Length != 1 - || classInfo.GetObject("_items") is not ArrayRecord arrayRecord + || classInfo.GetRawValue("_items") is not ArrayRecord arrayRecord || !IsPrimitiveArrayRecord(arrayRecord)) { return false; @@ -200,8 +200,8 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? va || !classInfo.IsTypeNameMatching(typeof(ArrayList)) || !classInfo.HasMember("_items") || !classInfo.HasMember("_size") - || classInfo.GetObject("_size") is not int size - || classInfo.GetObject("_items") is not ArrayRecord arrayRecord + || classInfo.GetRawValue("_size") is not int size + || classInfo.GetRawValue("_items") is not ArrayRecord arrayRecord || size > arrayRecord.Length) { return false; 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 ff53e04d14b..2455a25789e 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 @@ -62,7 +62,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(_classRecord.GetObject(field.Name)); + (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetRawValue(field.Name)); if (s_missingValueSentinel == memberValue) { // Record has not been encountered yet, need to pend iteration. 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 9e10097f1ef..6c85a02957b 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 @@ -45,7 +45,7 @@ internal override Id Continue() do { string memberName = _memberNamesIterator.Current; - (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetObject(memberName)); + (object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetRawValue(memberName)); if (s_missingValueSentinel == memberValue) { 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 5c2057a345b..16f46475750 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 @@ -62,8 +62,8 @@ private static void VerifyHashTable(ClassRecord systemClass, int expectedVersion systemClass.GetSingle("LoadFactor").Should().Be(0.72f); systemClass.GetInt32("Version").Should().Be(expectedVersion); - systemClass.GetObject("Comparer").Should().BeNull(); - systemClass.GetObject("HashCodeProvider").Should().BeNull(); + systemClass.GetRawValue("Comparer").Should().BeNull(); + systemClass.GetRawValue("HashCodeProvider").Should().BeNull(); systemClass.GetInt32("HashSize").Should().Be(expectedHashSize); } @@ -171,7 +171,7 @@ public void ReadNestedSerializableObject() @class.TypeName.FullName.Should().Be(typeof(NestedSerializableObject).FullName); @class.LibraryName.FullName.Should().Be(typeof(NestedSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEquivalentTo(["_object", "_meaning"]); - @class.GetObject("_object").Should().NotBeNull(); + @class.GetRawValue("_object").Should().NotBeNull(); @class.GetInt32("_meaning").Should().Be(42); } @@ -186,7 +186,7 @@ public void ReadTwoIntObject() @class.TypeName.FullName.Should().Be(typeof(TwoIntSerializableObject).FullName); @class.LibraryName.FullName.Should().Be(typeof(TwoIntSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEquivalentTo(["_value", "_meaning"]); - @class.GetObject("_value").Should().Be(1970); + @class.GetRawValue("_value").Should().Be(1970); @class.GetInt32("_meaning").Should().Be(42); } @@ -198,7 +198,7 @@ public void ReadRepeatedNestedObject() firstClass.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); ClassRecord classWithId = (ClassRecord)format[4]; classWithId.RecordType.Should().Be(RecordType.ClassWithId); - classWithId.GetObject("_value").Should().Be(1970); + classWithId.GetRawValue("_value").Should().Be(1970); classWithId.GetInt32("_meaning").Should().Be(42); } 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 347a42c8b07..741008abc90 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 @@ -32,15 +32,15 @@ public void NotSupportedException_Parse() systemClass.GetString("ClassName").Should().Be("System.NotSupportedException"); systemClass.GetString("Message").Should().Be("Specified method is not supported."); - systemClass.GetObject("Data").Should().BeNull(); - systemClass.GetObject("InnerException").Should().BeNull(); - systemClass.GetObject("HelpURL").Should().BeNull(); - systemClass.GetObject("StackTraceString").Should().BeNull(); - systemClass.GetObject("RemoteStackTraceString").Should().BeNull(); + 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.GetObject("ExceptionMethod").Should().BeNull(); + systemClass.GetRawValue("ExceptionMethod").Should().BeNull(); systemClass.GetInt32("HResult").Should().Be(unchecked((int)0x80131515)); - systemClass.GetObject("Source").Should().BeNull(); - systemClass.GetObject("WatsonBuckets").Should().BeNull(); + systemClass.GetRawValue("Source").Should().BeNull(); + systemClass.GetRawValue("WatsonBuckets").Should().BeNull(); } } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs index e752ba5719c..6d9e38cb815 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs @@ -104,7 +104,7 @@ private protected ClassRecord(ClassInfo classInfo) /// For jagged and multi-dimensional arrays, returns an instance of . /// /// - public object? GetObject(string memberName) => GetMember(memberName); + public object? GetRawValue(string memberName) => GetMember(memberName); /// /// Retrieves an array for the provided . 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 8e02647eb72..75196a9df21 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 @@ -23,7 +23,7 @@ static bool Get(BinaryFormattedObject format, [NotNullWhen(true)] out object? im if (format.RootRecord is not System.Runtime.Serialization.BinaryFormat.ClassRecord types || !types.IsTypeNameMatching(typeof(ImageListStreamer)) || !types.HasMember("Data") - || types.GetObject("Data") is not System.Runtime.Serialization.BinaryFormat.ArrayRecord data) + || types.GetRawValue("Data") is not System.Runtime.Serialization.BinaryFormat.ArrayRecord data) { return false; } @@ -43,7 +43,7 @@ public static bool TryGetBitmap(this BinaryFormattedObject format, out object? b if (format.RootRecord is not System.Runtime.Serialization.BinaryFormat.ClassRecord types || !types.IsTypeNameMatching(typeof(Bitmap)) || !types.HasMember("Data") - || types.GetObject("Data") is not System.Runtime.Serialization.BinaryFormat.ArrayRecord data) + || types.GetRawValue("Data") is not System.Runtime.Serialization.BinaryFormat.ArrayRecord data) { return false; } From a8af796c13e7f75ec44f5a36fd4ab1cf5d6f1649 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 14 May 2024 20:14:07 +0200 Subject: [PATCH 12/14] polishing after reading the code again --- .../Deserializer/ArrayRecordDeserializer.cs | 22 +++++++++---------- ...lassRecordSerializationInfoDeserializer.cs | 1 - 2 files changed, 10 insertions(+), 13 deletions(-) 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 373239fe983..a3ff65d45be 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 @@ -9,8 +9,8 @@ internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer { private readonly ArrayRecord _arrayRecord; private readonly Type _elementType; - private readonly Array _array; - private readonly Array _result; + private readonly Array _arrayOfClassRecords; + private readonly Array _arrayOfT; private int _index; private bool _hasFixups; @@ -31,9 +31,9 @@ internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserial }; // 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) - _array = arrayRecord.ToArray(expectedArrayType, maxLength: Array.MaxLength); - - Type elementType = _array.GetType(); + _arrayOfClassRecords = arrayRecord.ToArray(expectedArrayType, maxLength: Array.MaxLength); + // Now we need to create an array of the same length, but of a different, exact type + Type elementType = _arrayOfClassRecords.GetType(); while (elementType.IsArray) { elementType = elementType.GetElementType()!; @@ -42,19 +42,17 @@ internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserial int[] lengths = new int[arrayRecord.Rank]; for (int dimension = 0; dimension < lengths.Length; dimension++) { - lengths[dimension] = _array.GetLength(dimension); + lengths[dimension] = _arrayOfClassRecords.GetLength(dimension); } - Object = _result = Array.CreateInstance(_elementType, lengths); + Object = _arrayOfT = Array.CreateInstance(_elementType, lengths); } - // adsitnik: it may compile, but most likely won't work without any changes internal override Id Continue() { while (_index < _arrayRecord.Length) { - // TODO: adsitnik: handle multi-dimensional arrays - (object? memberValue, Id reference) = UnwrapMemberValue(_array.GetArrayData()[_index]); + (object? memberValue, Id reference) = UnwrapMemberValue(_arrayOfClassRecords.GetArrayData()[_index]); if (s_missingValueSentinel == memberValue) { @@ -78,11 +76,11 @@ internal override Id Continue() if (_elementType.IsValueType) { - _result.SetArrayValueByFlattenedIndex(memberValue, _index); + _arrayOfT.SetArrayValueByFlattenedIndex(memberValue, _index); } else { - Span flatSpan = _result.GetArrayData(); + Span flatSpan = _arrayOfT.GetArrayData(); flatSpan[_index] = memberValue; } 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 6c85a02957b..49a5abb0c1d 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 @@ -39,7 +39,6 @@ internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDese internal override Id Continue() { - // adsitnik: the order is no longer guaranteed to be preserved if (_canIterate) { do From f9e1aeeab18deb2ebe73a843415675dbc387af05 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 15 May 2024 14:52:37 +0200 Subject: [PATCH 13/14] address code review feedback: - FormatterTypeStyle.TypeAlways is a must have, remove unsupported RecordTypes from the public enum and throw NotSupportedException when they are provided - add support for Formatters.FormatterTypeStyle.XsdString - enable tests for FormatterTypeStyle.TypesAlways | FormatterTypeStyle.XsdStringr --- .../FormatTests/Common/BasicObjectTests.cs | 2 +- .../Enums/AllowedRecordType.cs | 4 +- .../Enums/RecordType.cs | 35 +++++++------ .../Infos/MemberTypeInfo.cs | 6 +-- .../PayloadReader.cs | 17 +------ .../Records/ClassWithMembersRecord.cs | 49 ------------------- .../Records/SystemClassWithMembersRecord.cs | 43 ---------------- .../Utils/RecordMap.cs | 13 ++++- .../Utils/ThrowHelper.cs | 25 ++++++++++ 9 files changed, 58 insertions(+), 136 deletions(-) delete mode 100644 src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs delete mode 100644 src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs 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 a83426d48b6..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 void DeserializeStoredObjects(object value, TypeSerializableValue[] seria // Explicitly not supporting offset arrays from value in BinaryFormatterTests.RawSerializableObjects() from FormatterAssemblyStyle assemblyFormat in new[] { FormatterAssemblyStyle.Full, FormatterAssemblyStyle.Simple } - from FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways/*, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString*/ } + from FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways, FormatterTypeStyle.TypesAlways | FormatterTypeStyle.XsdString } where value.Item1 is not Array array || array.GetLowerBound(0) == 0 select (value.Item1, assemblyFormat, typeFormat)).ToArray()); } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs index 92f0c4ba20a..df80ce32ee9 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Enums/AllowedRecordType.cs @@ -9,8 +9,6 @@ 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, @@ -33,7 +31,7 @@ internal enum AllowedRecordTypes : uint /// AnyObject = MemberPrimitiveTyped | ArraySingleObject | ArraySinglePrimitive | ArraySingleString | BinaryArray - | ClassWithId | ClassWithMembers | ClassWithMembersAndTypes | SystemClassWithMembers | SystemClassWithMembersAndTypes + | ClassWithId | ClassWithMembersAndTypes | SystemClassWithMembersAndTypes | BinaryObjectString | MemberReference | ObjectNull, diff --git a/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs b/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs index 213587af06e..7c9314e3f8f 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Enums/RecordType.cs @@ -14,22 +14,21 @@ namespace System.Runtime.Serialization.BinaryFormat; /// public enum RecordType : byte { - SerializedStreamHeader, - ClassWithId, - SystemClassWithMembers, - ClassWithMembers, - SystemClassWithMembersAndTypes, - ClassWithMembersAndTypes, - BinaryObjectString, - BinaryArray, - MemberPrimitiveTyped, - MemberReference, - ObjectNull, - MessageEnd, - BinaryLibrary, - ObjectNullMultiple256, - ObjectNullMultiple, - ArraySinglePrimitive, - ArraySingleObject, - ArraySingleString + SerializedStreamHeader = 0, + ClassWithId = 1, + // SystemClassWithMembers and ClassWithMembers are not supported by design (require type loading) + SystemClassWithMembersAndTypes = 4, + ClassWithMembersAndTypes = 5, + BinaryObjectString = 6, + BinaryArray = 7, + MemberPrimitiveTyped = 8, + MemberReference = 9, + ObjectNull = 10, + MessageEnd = 11, + BinaryLibrary = 12, + ObjectNullMultiple256 = 13, + ObjectNullMultiple = 14, + ArraySinglePrimitive = 15, + ArraySingleObject = 16, + ArraySingleString = 17 } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs index 96e090dd6a8..2123295bdaf 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs @@ -89,10 +89,8 @@ internal static MemberTypeInfo Parse(BinaryReader reader, int count, PayloadOpti | 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; + const AllowedRecordTypes systemClass = classes | AllowedRecordTypes.SystemClassWithMembersAndTypes; + const AllowedRecordTypes nonSystemClass = classes | AllowedRecordTypes.ClassWithMembersAndTypes; return binaryType switch { diff --git a/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs b/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs index 942f9d2eac6..e196f8bf309 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/PayloadReader.cs @@ -167,18 +167,9 @@ private static SerializationRecord Read(BinaryReader reader, PayloadOptions opti if (((uint)allowed & (1u << (int)recordType)) == 0) { - throw new SerializationException($"Unexpected type seen: {recordType}."); + ThrowHelper.ThrowForUnexpectedRecordType(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), @@ -188,7 +179,6 @@ private static SerializationRecord Read(BinaryReader reader, PayloadOptions opti 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), @@ -197,12 +187,7 @@ private static SerializationRecord Read(BinaryReader reader, PayloadOptions opti 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}") }; diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersRecord.cs deleted file mode 100644 index 308979a36c4..00000000000 --- a/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.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersRecord.cs deleted file mode 100644 index 2c231d78cce..00000000000 --- a/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.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs index 0557b4fbb9c..42d16411580 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Utils/RecordMap.cs @@ -38,8 +38,17 @@ internal void Add(SerializationRecord record) // 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); + if (record.ObjectId < 0) + { + // Negative record Ids should never be referenced. Duplicate negative ids can be + // exported by the writer. The root object Id can be negative. + _map[record.ObjectId] = record; + } + else + { + // use Add on purpose, so in case of duplicate Ids we get an exception + _map.Add(record.ObjectId, record); + } } } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs index 365ae58745c..d9b94f8d72b 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Utils/ThrowHelper.cs @@ -26,4 +26,29 @@ internal static void ThrowTypeMismatch(Type expected) internal static void ThrowEndOfStreamException() => throw new EndOfStreamException(); + + internal static void ThrowForUnexpectedRecordType(RecordType recordType) + { + // The enum values are not part of the public API surface, as they are not supported + // and users don't need to handle these values. + +#pragma warning disable IDE0066 // Convert switch statement to expression + switch ((int)recordType) + { + case 2: // SystemClassWithMembers + case 3: // ClassWithMembers + throw new NotSupportedException("FormatterTypeStyle.TypesAlways is a must have."); + // 18~20 are from the reference source but aren't in the OpenSpecs doc + case 18: // CrossAppDomainMap + case 19: // CrossAppDomainString + case 20: // CrossAppDomainAssembly + throw new NotSupportedException("Cross domain is not supported by design"); + case 21: // MethodCall + case 22: // MethodReturn + throw new NotSupportedException("Remote invocation is not supported by design"); + default: + throw new SerializationException($"Unexpected type seen: {recordType}."); + } +#pragma warning restore IDE0066 // Convert switch statement to expression + } } From a3043a2786c7415452121e41ee60425d2cca1f49 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Wed, 15 May 2024 18:09:01 +0200 Subject: [PATCH 14/14] address API review feedback: expose LibraryName via TypeName.AssemblyName --- .../BinaryFormattedObject.ITypeResolver.cs | 3 +- .../BinaryFormattedObject.TypeResolver.cs | 23 +++++++------ .../Deserializer/ArrayRecordDeserializer.cs | 8 ++--- .../Deserializer/ClassRecordDeserializer.cs | 4 +-- .../BinaryFormat/Deserializer/Deserializer.cs | 2 +- .../BinaryFormattedObjectTests.cs | 10 +++--- .../FormattedObject/SystemDrawingTests.cs | 2 +- .../Infos/MemberTypeInfo.cs | 32 ++++++------------- .../Records/ArrayOfClassesRecord.cs | 7 ++-- .../Records/ArrayRecord.cs | 2 -- .../Records/ArraySingleObjectRecord.cs | 5 ++- .../Records/ArraySinglePrimitiveRecord.cs | 6 ++-- .../Records/ArraySingleStringRecord.cs | 5 ++- .../Records/BinaryArrayRecord.cs | 8 ++--- .../Records/ClassRecord.cs | 6 ++-- .../Records/ClassWithIdRecord.cs | 2 +- .../Records/ClassWithMembersAndTypesRecord.cs | 2 +- .../RectangularOrCustomOffsetArrayRecord.cs | 8 ++--- .../SystemClassWithMembersAndTypesRecord.cs | 4 +-- .../Utils/TypeNameExtensions.cs | 32 +++++++++++++++++++ 20 files changed, 89 insertions(+), 82 deletions(-) create mode 100644 src/System.Runtime.Serialization.BinaryFormat/Utils/TypeNameExtensions.cs 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 867f872bceb..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 @@ -15,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(TypeName typeName, AssemblyNameInfo libraryName); + Type GetType(TypeName typeName); } } 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 32724acb704..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 @@ -18,7 +18,7 @@ internal sealed class DefaultTypeResolver : ITypeResolver private readonly SerializationBinder? _binder; private readonly Dictionary _assemblies = []; - private readonly Dictionary<(string TypeName, string LibraryId), Type> _types = []; + private readonly Dictionary _types = []; internal DefaultTypeResolver(Options options) { @@ -29,17 +29,18 @@ internal DefaultTypeResolver(Options options) /// /// 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(TypeName typeName, AssemblyNameInfo libraryName) + Type ITypeResolver.GetType(TypeName typeName) { - if (_types.TryGetValue((typeName.FullName, libraryName.FullName), out Type? cachedType)) + Debug.Assert(typeName.AssemblyName is not null); + + if (_types.TryGetValue(typeName.AssemblyQualifiedName, out Type? cachedType)) { return cachedType; } - if (_binder?.BindToType(libraryName.FullName, typeName.FullName) 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 @@ -47,15 +48,13 @@ Type ITypeResolver.GetType(TypeName typeName, AssemblyNameInfo libraryName) // always return the same result for a given set of strings. Choosing to always cache // for performance. - _types[(typeName.FullName, libraryName.FullName)] = binderType; + _types[typeName.AssemblyQualifiedName] = binderType; return binderType; } - if (!_assemblies.TryGetValue(libraryName.FullName, out Assembly? assembly)) + if (!_assemblies.TryGetValue(typeName.AssemblyName.FullName, out Assembly? assembly)) { - Debug.Assert(libraryName.FullName != typeof(object).Assembly.FullName); - - AssemblyName assemblyName = libraryName.ToAssemblyName(); + AssemblyName assemblyName = typeName.AssemblyName.ToAssemblyName(); try { assembly = Assembly.Load(assemblyName); @@ -70,14 +69,14 @@ Type ITypeResolver.GetType(TypeName typeName, AssemblyNameInfo libraryName) assembly = Assembly.Load(assemblyName.Name!); } - _assemblies.Add(libraryName.FullName, assembly); + _assemblies.Add(typeName.AssemblyName.FullName, assembly); } Type? type = _assemblyMatching != FormatterAssemblyStyle.Simple ? assembly.GetType(typeName.FullName) : GetSimplyNamedTypeFromAssembly(assembly, typeName); - _types[(typeName.FullName, libraryName.FullName)] = type ?? throw new SerializationException($"Could not find type '{typeName}'."); + _types[typeName.AssemblyQualifiedName] = type ?? throw new SerializationException($"Could not find type '{typeName}'."); return type; } 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 a3ff65d45be..38dd68309d0 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 @@ -14,7 +14,7 @@ internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer private int _index; private bool _hasFixups; - [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName, AssemblyNameInfo)")] + [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer) : base(arrayRecord, deserializer) { @@ -23,7 +23,7 @@ internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserial Debug.Assert(arrayRecord.ArrayType is (ArrayType.Single or ArrayType.Jagged or ArrayType.Rectangular)); _arrayRecord = arrayRecord; - _elementType = deserializer.TypeResolver.GetType(arrayRecord.ElementTypeName, arrayRecord.ElementTypeLibraryName); + _elementType = deserializer.TypeResolver.GetType(arrayRecord.ElementTypeName); Type expectedArrayType = arrayRecord.ArrayType switch { ArrayType.Rectangular => _elementType.MakeArrayType(arrayRecord.Rank), @@ -117,7 +117,7 @@ internal override Id Continue() _ => throw new NotSupportedException(), }; - [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName, AssemblyNameInfo)")] + [RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")] internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, BinaryFormattedObject.ITypeResolver typeResolver) { if (arrayRecord.ArrayType is not (ArrayType.Single or ArrayType.Jagged or ArrayType.Rectangular)) @@ -125,7 +125,7 @@ internal override Id Continue() throw new NotSupportedException("Only arrays with zero offsets are supported."); } - Type arrayRecordElementType = typeResolver.GetType(arrayRecord.ElementTypeName, arrayRecord.ElementTypeLibraryName); + Type arrayRecordElementType = typeResolver.GetType(arrayRecord.ElementTypeName); Type elementType = arrayRecordElementType; while (elementType.IsArray) { 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 0d495c4aa39..19c39402235 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 @@ -26,10 +26,10 @@ private protected ClassRecordDeserializer(ClassRecord classRecord, object @objec _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.TypeName, classRecord.LibraryName); + Type type = deserializer.TypeResolver.GetType(classRecord.TypeName); Id id = classRecord.ObjectId; ISerializationSurrogate? surrogate = deserializer.GetSurrogate(type); 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 e13007cc24c..1cd3dbb5a3d 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 @@ -344,7 +344,7 @@ void IDeserializer.CompleteObject(Id id) // There are no remaining dependencies. Hook any finished events for this object. // Doing at the end of deserialization for simplicity. - Type type = _typeResolver.GetType(classRecord.TypeName, classRecord.LibraryName); + Type type = _typeResolver.GetType(classRecord.TypeName); object @object = _deserializedObjects[completedId]; OnDeserialized += SerializationEvents.GetOnDeserializedForType(type, @object); 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 16f46475750..18d777192e5 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 @@ -156,7 +156,7 @@ public void ReadSimpleSerializableObject() @class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); @class.ObjectId.Should().Be(1); @class.TypeName.FullName.Should().Be(typeof(SimpleSerializableObject).FullName); - @class.LibraryName.FullName.Should().Be(typeof(SimpleSerializableObject).Assembly.FullName); + @class.TypeName.AssemblyName!.FullName.Should().Be(typeof(SimpleSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEmpty(); } @@ -169,7 +169,7 @@ public void ReadNestedSerializableObject() @class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); @class.ObjectId.Should().Be(1); @class.TypeName.FullName.Should().Be(typeof(NestedSerializableObject).FullName); - @class.LibraryName.FullName.Should().Be(typeof(NestedSerializableObject).Assembly.FullName); + @class.TypeName.AssemblyName!.FullName.Should().Be(typeof(NestedSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEquivalentTo(["_object", "_meaning"]); @class.GetRawValue("_object").Should().NotBeNull(); @class.GetInt32("_meaning").Should().Be(42); @@ -184,7 +184,7 @@ public void ReadTwoIntObject() @class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); @class.ObjectId.Should().Be(1); @class.TypeName.FullName.Should().Be(typeof(TwoIntSerializableObject).FullName); - @class.LibraryName.FullName.Should().Be(typeof(TwoIntSerializableObject).Assembly.FullName); + @class.TypeName.AssemblyName!.FullName.Should().Be(typeof(TwoIntSerializableObject).Assembly.FullName); @class.MemberNames.Should().BeEquivalentTo(["_value", "_meaning"]); @class.GetRawValue("_value").Should().Be(1970); @class.GetInt32("_meaning").Should().Be(42); @@ -261,7 +261,7 @@ public void ReadObjectWithNullableObjects() System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new ObjectWithNullableObjects())); ClassRecord classRecord = (ClassRecord)format.RootRecord; classRecord.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); - classRecord.LibraryName.FullName.Should().Be(typeof(ObjectWithNullableObjects).Assembly.FullName); + classRecord.TypeName.AssemblyName!.FullName.Should().Be(typeof(ObjectWithNullableObjects).Assembly.FullName); } [Fact] @@ -270,7 +270,7 @@ public void ReadNestedObjectWithNullableObjects() System.Windows.Forms.BinaryFormat.BinaryFormattedObject format = new(Serialize(new NestedObjectWithNullableObjects())); ClassRecord classRecord = (ClassRecord)format.RootRecord; classRecord.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); - classRecord.LibraryName.FullName.Should().Be(typeof(NestedObjectWithNullableObjects).Assembly.FullName); + classRecord.TypeName.AssemblyName!.FullName.Should().Be(typeof(NestedObjectWithNullableObjects).Assembly.FullName); } [Serializable] 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 ce9daa17615..307b7e6639e 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 @@ -18,7 +18,7 @@ public void PointF_Parse() classInfo.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes); classInfo.ObjectId.Should().Be(1); classInfo.TypeName.FullName.Should().Be("System.Drawing.PointF"); - classInfo.LibraryName.FullName.Should().Be("System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); + classInfo.TypeName.AssemblyName!.FullName.Should().Be("System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"); classInfo.MemberNames.Should().BeEquivalentTo(["x", "y"]); classInfo.GetSingle("x").Should().Be(input.X); classInfo.GetSingle("y").Should().Be(input.Y); diff --git a/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs b/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs index 2123295bdaf..7c55fe4471e 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Infos/MemberTypeInfo.cs @@ -206,20 +206,20 @@ internal bool ShouldBeRepresentedAsArrayOfClassRecords() return false; } - internal TypeName GetElementTypeName() + internal TypeName GetElementTypeName(RecordMap recordMap) { (BinaryType binaryType, object? additionalInfo) = Infos[0]; switch (binaryType) { case BinaryType.String: - return TypeName.Parse(typeof(string).FullName.AsSpan()); + return TypeName.Parse(typeof(string).FullName.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); case BinaryType.StringArray: - return TypeName.Parse(typeof(string[]).FullName.AsSpan()); + return TypeName.Parse(typeof(string[]).FullName.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); ; case BinaryType.Object: - return TypeName.Parse(typeof(object).FullName.AsSpan()); + return TypeName.Parse(typeof(object).FullName.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); ; case BinaryType.ObjectArray: - return TypeName.Parse(typeof(object[]).FullName.AsSpan()); + return TypeName.Parse(typeof(object[]).FullName.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); ; case BinaryType.Primitive: case BinaryType.PrimitiveArray: string? name = ((PrimitiveType)additionalInfo!) switch @@ -243,29 +243,17 @@ internal TypeName GetElementTypeName() }; return binaryType is BinaryType.PrimitiveArray - ? TypeName.Parse($"{name}[]".AsSpan()) - : TypeName.Parse(name.AsSpan()); + ? TypeName.Parse($"{name}[], {FormatterServices.CoreLibAssemblyName.FullName}".AsSpan()) + : TypeName.Parse(name.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); case BinaryType.SystemClass: - return (TypeName)additionalInfo!; + return ((TypeName)additionalInfo!).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); case BinaryType.Class: ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!; - return typeInfo.TypeName; + AssemblyNameInfo libraryName = ((BinaryLibraryRecord)recordMap[typeInfo.LibraryId]).LibraryName; + return typeInfo.TypeName.WithAssemblyName(libraryName.FullName); default: throw new NotSupportedException(); } } - - internal AssemblyNameInfo GetElementLibraryName(RecordMap recordMap) - { - (BinaryType binaryType, object? additionalInfo) = Infos[0]; - - if (binaryType is BinaryType.Class) - { - ClassTypeInfo typeInfo = (ClassTypeInfo)additionalInfo!; - return ((BinaryLibraryRecord)recordMap[typeInfo.LibraryId]).LibraryName; - } - - return FormatterServices.CoreLibAssemblyName; - } } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs index 96c3bb2f8c8..af8f7132cd5 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayOfClassesRecord.cs @@ -7,14 +7,13 @@ namespace System.Runtime.Serialization.BinaryFormat; internal sealed class ArrayOfClassesRecord : ArrayRecord { - private AssemblyNameInfo? _elementTypeLibraryName; + private TypeName? _elementTypeName; internal ArrayOfClassesRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, RecordMap recordMap) : base(arrayInfo) { MemberTypeInfo = memberTypeInfo; RecordMap = recordMap; - ElementTypeName = MemberTypeInfo.GetElementTypeName(); Records = []; } @@ -26,9 +25,7 @@ internal ArrayOfClassesRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo private RecordMap RecordMap { get; } - public override TypeName ElementTypeName { get; } - - public override AssemblyNameInfo ElementTypeLibraryName => _elementTypeLibraryName ??= MemberTypeInfo.GetElementLibraryName(RecordMap); + public override TypeName ElementTypeName => _elementTypeName ??= MemberTypeInfo.GetElementTypeName(RecordMap); protected override ClassRecord?[] ToArrayOfT(bool allowNulls) { diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs index 90fdb353fc3..754d6d8bcc7 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ArrayRecord.cs @@ -32,8 +32,6 @@ private protected ArrayRecord(ArrayInfo arrayInfo) public abstract TypeName ElementTypeName { get; } - public abstract AssemblyNameInfo ElementTypeLibraryName { get; } - public override int ObjectId => ArrayInfo.ObjectId; internal long ValuesToRead { get; private protected set; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs index 37d139ec077..5a03ef3f29a 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleObjectRecord.cs @@ -23,9 +23,8 @@ internal sealed class ArraySingleObjectRecord : ArrayRecord public override RecordType RecordType => RecordType.ArraySingleObject; - public override TypeName ElementTypeName => s_elementTypeName ??= TypeName.Parse(typeof(object).FullName.AsSpan()); - - public override AssemblyNameInfo ElementTypeLibraryName => FormatterServices.CoreLibAssemblyName; + public override TypeName ElementTypeName + => s_elementTypeName ??= TypeName.Parse(typeof(object).FullName.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); private List Records { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs index 4a609335d14..1d98de1b097 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySinglePrimitiveRecord.cs @@ -21,7 +21,6 @@ internal sealed class ArraySinglePrimitiveRecord : ArrayRecord where T : unmanaged { private static TypeName? s_elementTypeName; - private static AssemblyNameInfo? s_elementTypeLibraryName; internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values) : base(arrayInfo) { @@ -31,9 +30,8 @@ internal ArraySinglePrimitiveRecord(ArrayInfo arrayInfo, IReadOnlyList values public override RecordType RecordType => RecordType.ArraySinglePrimitive; - public override TypeName ElementTypeName => s_elementTypeName ??= TypeName.Parse(typeof(T).FullName.AsSpan()); - - public override AssemblyNameInfo ElementTypeLibraryName => s_elementTypeLibraryName ??= AssemblyNameInfo.Parse(FormatterServices.GetAssemblyNameIncludingTypeForwards(typeof(T)).AsSpan()); + public override TypeName ElementTypeName + => s_elementTypeName ??= TypeName.Parse(typeof(T).FullName.AsSpan()).WithAssemblyName(FormatterServices.GetAssemblyNameIncludingTypeForwards(typeof(T))); internal IReadOnlyList Values { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs index 48dca5977ab..1bfcbcc248b 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ArraySingleStringRecord.cs @@ -23,9 +23,8 @@ internal sealed class ArraySingleStringRecord : ArrayRecord public override RecordType RecordType => RecordType.ArraySingleString; - public override TypeName ElementTypeName => s_elementTypeName ??= TypeName.Parse(typeof(string).FullName.AsSpan()); - - public override AssemblyNameInfo ElementTypeLibraryName => FormatterServices.CoreLibAssemblyName; + public override TypeName ElementTypeName + => s_elementTypeName ??= TypeName.Parse(typeof(string).FullName.AsSpan()).WithAssemblyName(FormatterServices.CoreLibAssemblyName.FullName); private List Records { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs index fc029c0d217..060b9b9315a 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/BinaryArrayRecord.cs @@ -16,22 +16,20 @@ internal sealed class BinaryArrayRecord : ArrayRecord typeof(TimeSpan), typeof(string), typeof(object) ]; - private AssemblyNameInfo? _elementTypeLibraryName; + private TypeName? _elementTypeName; private BinaryArrayRecord(ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, RecordMap recordMap) : base(arrayInfo) { MemberTypeInfo = memberTypeInfo; RecordMap = recordMap; - ElementTypeName = memberTypeInfo.GetElementTypeName(); Values = []; } public override RecordType RecordType => RecordType.BinaryArray; - public override TypeName ElementTypeName { get; } - - public override AssemblyNameInfo ElementTypeLibraryName => _elementTypeLibraryName ??= MemberTypeInfo.GetElementLibraryName(RecordMap); + public override TypeName ElementTypeName + => _elementTypeName ??= MemberTypeInfo.GetElementTypeName(RecordMap); private MemberTypeInfo MemberTypeInfo { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs index 6d9e38cb815..0ed65025657 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassRecord.cs @@ -20,15 +20,17 @@ public abstract class ClassRecord : SerializationRecord { private const int MaxLength = ArrayRecord.DefaultMaxArrayLength; + private TypeName? _typeName; + private protected ClassRecord(ClassInfo classInfo) { ClassInfo = classInfo; MemberValues = []; } - public TypeName TypeName => ClassInfo.Name; + public TypeName TypeName => _typeName ??= ClassInfo.Name.WithAssemblyName(LibraryName.FullName); - public abstract AssemblyNameInfo LibraryName { get; } + internal 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; diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs index b1f0c86065b..b2cc3df02e8 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithIdRecord.cs @@ -25,7 +25,7 @@ private ClassWithIdRecord(int objectId, ClassRecord metadataClass) : base(metada public override RecordType RecordType => RecordType.ClassWithId; - public override AssemblyNameInfo LibraryName => MetadataClass.LibraryName; + internal override AssemblyNameInfo LibraryName => MetadataClass.LibraryName; public override int ObjectId { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs index 0a6e0863b6f..644db00ca40 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/ClassWithMembersAndTypesRecord.cs @@ -26,7 +26,7 @@ private ClassWithMembersAndTypesRecord(ClassInfo classInfo, BinaryLibraryRecord public override RecordType RecordType => RecordType.ClassWithMembersAndTypes; - public override AssemblyNameInfo LibraryName => Library.LibraryName; + internal override AssemblyNameInfo LibraryName => Library.LibraryName; internal BinaryLibraryRecord Library { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs index d370512b6b2..f271838dbd9 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/RectangularOrCustomOffsetArrayRecord.cs @@ -9,7 +9,7 @@ namespace System.Runtime.Serialization.BinaryFormat; internal sealed class RectangularOrCustomOffsetArrayRecord : ArrayRecord { - private AssemblyNameInfo? _elementTypeLibraryName; + private TypeName? _elementTypeName; private RectangularOrCustomOffsetArrayRecord(Type elementType, ArrayInfo arrayInfo, MemberTypeInfo memberTypeInfo, int[] lengths, int[] offsets, RecordMap recordMap) : base(arrayInfo) @@ -19,15 +19,13 @@ internal sealed class RectangularOrCustomOffsetArrayRecord : ArrayRecord Lengths = lengths; Offsets = offsets; RecordMap = recordMap; - ElementTypeName = memberTypeInfo.GetElementTypeName(); Values = new(); } public override RecordType RecordType => RecordType.BinaryArray; - public override TypeName ElementTypeName { get; } - - public override AssemblyNameInfo ElementTypeLibraryName => _elementTypeLibraryName ??= MemberTypeInfo.GetElementLibraryName(RecordMap); + public override TypeName ElementTypeName + => _elementTypeName ??= MemberTypeInfo.GetElementTypeName(RecordMap); private Type ElementType { get; } diff --git a/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs index d934a2e09bd..163595e00db 100644 --- a/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs +++ b/src/System.Runtime.Serialization.BinaryFormat/Records/SystemClassWithMembersAndTypesRecord.cs @@ -15,7 +15,7 @@ private SystemClassWithMembersAndTypesRecord(ClassInfo classInfo, MemberTypeInfo public override RecordType RecordType => RecordType.SystemClassWithMembersAndTypes; - public override AssemblyNameInfo LibraryName => FormatterServices.CoreLibAssemblyName; + internal override AssemblyNameInfo LibraryName => FormatterServices.CoreLibAssemblyName; internal MemberTypeInfo MemberTypeInfo { get; } @@ -88,7 +88,7 @@ internal SerializationRecord TryToMapToUserFriendly() { return Create(BinaryReaderExtensions.CreateDateTimeFromData(value)); } - else if (MemberValues.Count == 4 + 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))) diff --git a/src/System.Runtime.Serialization.BinaryFormat/Utils/TypeNameExtensions.cs b/src/System.Runtime.Serialization.BinaryFormat/Utils/TypeNameExtensions.cs new file mode 100644 index 00000000000..6e674e53956 --- /dev/null +++ b/src/System.Runtime.Serialization.BinaryFormat/Utils/TypeNameExtensions.cs @@ -0,0 +1,32 @@ +using System.Buffers; +using System.Reflection.Metadata; + +namespace System.Runtime.Serialization.BinaryFormat; + +internal static class TypeNameExtensions +{ + internal static TypeName WithAssemblyName(this TypeName typeName, string assemblyName) + { + // For ClassWithMembersAndTypesRecord, the TypeName and LibraryName and provided separately, + // and the LibraryName may not be known when parsing TypeName. + // For SystemClassWithMembersAndTypesRecord, the LibraryName is not provided, it's always mscorlib. + // Ideally, we would just create TypeName with new AssemblyNameInfo. + // This will be possible once https://github.com/dotnet/runtime/issues/102263 is done. + + int length = typeName.FullName.Length + 1 + assemblyName.Length; + char[] rented = ArrayPool.Shared.Rent(length); + + try + { + typeName.FullName.AsSpan().CopyTo(rented); + rented[typeName.FullName.Length] = ','; + assemblyName.AsSpan().CopyTo(rented.AsSpan(typeName.FullName.Length + 1)); + + return TypeName.Parse(rented.AsSpan(0, length)); + } + finally + { + ArrayPool.Shared.Return(rented); + } + } +}