1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-08 03:27:04 +09:00

Use PayloadReader in System.Resources.Extensions (#102379)

---------

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
This commit is contained in:
Adam Sitnik 2024-06-10 13:07:59 +02:00 committed by GitHub
parent 3073a0326a
commit 82aba8203f
Signed by: github
GPG key ID: B5690EEEBB952194
160 changed files with 16615 additions and 13 deletions

View file

@ -55,6 +55,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{DB5983
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{7FF9B3DF-E383-4487-9ADB-E3BF59CFCE83}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Resources.Extensions.Compat.Tests", "tests\CompatTests\System.Resources.Extensions.Compat.Tests.csproj", "{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Resources.Extensions.BinaryFormat.Tests", "tests\BinaryFormatTests\System.Resources.Extensions.BinaryFormat.Tests.csproj", "{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -503,6 +507,66 @@ Global
{7E29F73C-D2DE-4DA4-B399-F6A2475A11EC}.Checked|arm64.ActiveCfg = Debug|Any CPU
{7E29F73C-D2DE-4DA4-B399-F6A2475A11EC}.Checked|x64.ActiveCfg = Debug|Any CPU
{7E29F73C-D2DE-4DA4-B399-F6A2475A11EC}.Checked|x86.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|Any CPU.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|arm.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|arm.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|arm64.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|arm64.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|x64.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|x64.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|x86.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Checked|x86.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|arm.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|arm.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|arm64.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|arm64.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|x64.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|x64.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|x86.ActiveCfg = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Debug|x86.Build.0 = Debug|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|Any CPU.Build.0 = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|arm.ActiveCfg = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|arm.Build.0 = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|arm64.ActiveCfg = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|arm64.Build.0 = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|x64.ActiveCfg = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|x64.Build.0 = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|x86.ActiveCfg = Release|Any CPU
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1}.Release|x86.Build.0 = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|Any CPU.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|Any CPU.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|arm.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|arm.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|arm64.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|arm64.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|x64.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|x64.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|x86.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Checked|x86.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|arm.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|arm.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|arm64.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|arm64.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|x64.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|x64.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|x86.ActiveCfg = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Debug|x86.Build.0 = Debug|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|Any CPU.Build.0 = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|arm.ActiveCfg = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|arm.Build.0 = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|arm64.ActiveCfg = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|arm64.Build.0 = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|x64.ActiveCfg = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|x64.Build.0 = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|x86.ActiveCfg = Release|Any CPU
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -531,6 +595,8 @@ Global
{BE197124-901B-4517-879B-93D0E6684A2C} = {7FF9B3DF-E383-4487-9ADB-E3BF59CFCE83}
{7E29F73C-D2DE-4DA4-B399-F6A2475A11EC} = {DB598308-6179-48EA-A670-0DA6CE5D8340}
{DB598308-6179-48EA-A670-0DA6CE5D8340} = {7FF9B3DF-E383-4487-9ADB-E3BF59CFCE83}
{BD76A85A-8E6F-4D4C-A628-661EFE6999F1} = {2195CD2B-EF9E-46A7-B4AA-A2CD31625957}
{4D72DCD5-BB24-47ED-9B98-8E0B49A9F314} = {2195CD2B-EF9E-46A7-B4AA-A2CD31625957}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E201426B-999F-48A5-BCFD-3E757AA0E182}

View file

@ -207,4 +207,70 @@
<data name="TypeLoadException_CannotLoadConverter" xml:space="preserve">
<value>Could not load a converter for type {0}.</value>
</data>
<data name="Serialization_ArrayContainedNulls" xml:space="preserve">
<value>The array contained null(s).</value>
</data>
<data name="Serialization_InvalidValue" xml:space="preserve">
<value>Invalid value: `{0}`.</value>
</data>
<data name="Serialization_UnexpectedNullRecordCount" xml:space="preserve">
<value>Unexpected Null Record count.</value>
</data>
<data name="Serialization_MaxArrayLength" xml:space="preserve">
<value>The serialized array length ({0}) was larger than the configured limit {1}.</value>
</data>
<data name="NotSupported_RecordType" xml:space="preserve">
<value>{0} Record Type is not supported by design.</value>
</data>
<data name="Serialization_InvalidReference" xml:space="preserve">
<value>Member reference was pointing to a record of unexpected type.</value>
</data>
<data name="Serialization_InvalidTypeName" xml:space="preserve">
<value>Invalid type name: `{0}`.</value>
</data>
<data name="Serialization_TypeMismatch" xml:space="preserve">
<value>Expected the array to be of type {0}, but its element type was {1}.</value>
</data>
<data name="NotSupported_NonZeroOffsets" xml:space="preserve">
<value>Only arrays with zero offsets are supported.</value>
</data>
<data name="Serialization_Cycle" xml:space="preserve">
<value>Unexpected parser cycle.</value>
</data>
<data name="Serialization_Incomplete" xml:space="preserve">
<value>Objects could not be deserialized completely.</value>
</data>
<data name="Serialization_IObjectReferenceOnlyPrimivite" xml:space="preserve">
<value>IObjectReference type '{0}' can only have primitive member data.</value>
</data>
<data name="Serialization_MissingCtor" xml:space="preserve">
<value>The constructor to deserialize an object of type '{0}' was not found.</value>
</data>
<data name="Serialization_MissingField" xml:space="preserve">
<value>Could not find field '{0}' data for type '{1}'.</value>
</data>
<data name="Serialization_MissingType" xml:space="preserve">
<value>Could not find type '{0}'.</value>
</data>
<data name="Serialization_Surrogates" xml:space="preserve">
<value>Surrogate must return the same object that was provided in the 'obj' parameter.</value>
</data>
<data name="Serialization_TypeNotSerializable" xml:space="preserve">
<value>Type '{0}' is not marked as serializable.</value>
</data>
<data name="Serialization_InvalidTypeOrAssemblyName" xml:space="preserve">
<value>Invalid type or assembly name: `{0},{1}`.</value>
</data>
<data name="Argument_NonSeekableStream" xml:space="preserve">
<value>Stream does not support seeking.</value>
</data>
<data name="Serialization_DuplicateMemberName" xml:space="preserve">
<value>Duplicate member name: `{0}`.</value>
</data>
<data name="Serialization_DuplicateSerializationRecordId" xml:space="preserve">
<value>Duplicate Serialization Record Id: `{0}`.</value>
</data>
<data name="Serialization_MemberTypeMismatchException" xml:space="preserve">
<value>Specified member '{0}' was not of the expected type.</value>
</data>
</root>

View file

@ -38,15 +38,54 @@ System.Resources.Extensions.PreserializedResourceWriter</PackageDescription>
<Compile Include="System\Resources\Extensions\PreserializedResourceWriter.cs" />
<Compile Include="System\Resources\Extensions\SerializationFormat.cs" />
<Compile Include="System\Resources\Extensions\TypeNameComparer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObject.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObject.IParseState.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObject.ITypeResolver.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObject.Options.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObject.ParseState.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObject.TypeResolver.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\BinaryFormattedObjectExtensions.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Id.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\SerializationEvents.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\SerializationExtensions.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\SerializationInfoExtensions.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ArrayRecordDeserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ArrayUpdater.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ClassRecordDeserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ClassRecordFieldInfoDeserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ClassRecordSerializationInfoDeserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\Deserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\FieldValueUpdater.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\IDeserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ObjectRecordDeserializer.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\PendingSerializationInfo.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\SerializationInfoValueUpdater.cs" />
<Compile Include="System\Resources\Extensions\BinaryFormat\Deserializer\ValueUpdater.cs" />
<Compile Include="$(CoreLibSharedDir)System\Numerics\Hashing\HashHelpers.cs"
Link="System\Numerics\Hashing\HashHelpers.cs" />
</ItemGroup>
<!-- when System.Runtime.Serialization.BinaryFormat gets approved, this whole block will be repalced with a reference to package -->
<ItemGroup>
<Compile Include="$(LibrariesProjectRoot)\System.Runtime.Serialization.BinaryFormat\src\**\*.cs" />
<ProjectReference Include="$(LibrariesProjectRoot)\System.IO.Hashing\src\System.IO.Hashing.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj" />
<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs"
Link="System\Diagnostics\CodeAnalysis\RequiresUnreferencedCodeAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs"
Link="System\Diagnostics\CodeAnalysis\UnconditionalSuppressMessageAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicDependencyAttribute.cs"
Link="System\Diagnostics\CodeAnalysis\DynamicDependencyAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs"
Link="System\Diagnostics\CodeAnalysis\DynamicallyAccessedMembersAttribute.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs"
Link="System\Diagnostics\CodeAnalysis\DynamicallyAccessedMemberTypes.cs" />
<Compile Include="$(CoreLibSharedDir)System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs"
Link="System\Diagnostics\CodeAnalysis\RequiresDynamicCodeAttribute.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">

View file

@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat;
internal sealed partial class BinaryFormattedObject
{
/// <summary>
/// Parsing state.
/// </summary>
internal interface IParseState
{
BinaryReader Reader { get; }
IReadOnlyDictionary<int, SerializationRecord> RecordMap { get; }
Options Options { get; }
ITypeResolver TypeResolver { get; }
}
}

View file

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
namespace System.Resources.Extensions.BinaryFormat;
internal sealed partial class BinaryFormattedObject
{
/// <summary>
/// Resolver for types.
/// </summary>
internal interface ITypeResolver
{
/// <summary>
/// Resolves the given type name against the specified library.
/// </summary>
[RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")]
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
Type GetType(TypeName typeName);
}
}

View file

@ -0,0 +1,37 @@
// 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.Formatters;
namespace System.Resources.Extensions.BinaryFormat;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
internal sealed partial class BinaryFormattedObject
{
internal sealed class Options
{
/// <summary>
/// How exactly assembly names need to match for deserialization.
/// </summary>
public FormatterAssemblyStyle AssemblyMatching { get; set; } = FormatterAssemblyStyle.Simple;
/// <summary>
/// Type name binder.
/// </summary>
public SerializationBinder? Binder { get; set; }
/// <summary>
/// Optional type <see cref="ISerializationSurrogate"/> provider.
/// </summary>
public ISurrogateSelector? SurrogateSelector { get; set; }
/// <summary>
/// Streaming context.
/// </summary>
public StreamingContext StreamingContext { get; set; } = new(StreamingContextStates.All);
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat;
internal sealed partial class BinaryFormattedObject
{
/// <summary>
/// Parsing state for <see cref="BinaryFormattedObject"/>.
/// </summary>
internal sealed class ParseState : IParseState
{
private readonly BinaryFormattedObject _format;
public ParseState(BinaryReader reader, BinaryFormattedObject format)
{
Reader = reader;
_format = format;
}
public BinaryReader Reader { get; }
public IReadOnlyDictionary<int, SerializationRecord> RecordMap => _format.RecordMap;
public Options Options => _format._options;
public ITypeResolver TypeResolver => _format.TypeResolver;
}
}

View file

@ -0,0 +1,139 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
namespace System.Resources.Extensions.BinaryFormat;
internal sealed partial class BinaryFormattedObject
{
internal sealed class DefaultTypeResolver : ITypeResolver
{
private readonly FormatterAssemblyStyle _assemblyMatching;
private readonly SerializationBinder? _binder;
private readonly Dictionary<string, Assembly> _assemblies = [];
private readonly Dictionary<string, Type> _types = [];
internal DefaultTypeResolver(Options options)
{
_assemblyMatching = options.AssemblyMatching;
_binder = options.Binder;
}
/// <summary>
/// Resolves the given type name against the specified library.
/// </summary>
[RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String)")]
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
Type ITypeResolver.GetType(TypeName typeName)
{
Debug.Assert(typeName.AssemblyName is not null);
if (_types.TryGetValue(typeName.AssemblyQualifiedName, out Type? cachedType))
{
return cachedType;
}
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
// in the type cache. This could lead to inconsistent results if the binder didn't
// always return the same result for a given set of strings. Choosing to always cache
// for performance.
_types[typeName.AssemblyQualifiedName] = binderType;
return binderType;
}
if (!_assemblies.TryGetValue(typeName.AssemblyName.FullName, out Assembly? assembly))
{
AssemblyName assemblyName = typeName.AssemblyName.ToAssemblyName();
try
{
assembly = Assembly.Load(assemblyName);
}
catch
{
if (_assemblyMatching != FormatterAssemblyStyle.Simple)
{
throw;
}
assembly = Assembly.Load(assemblyName.Name!);
}
_assemblies.Add(typeName.AssemblyName.FullName, assembly);
}
Type? type = _assemblyMatching != FormatterAssemblyStyle.Simple
? assembly.GetType(typeName.FullName)
: GetSimplyNamedTypeFromAssembly(assembly, typeName);
_types[typeName.AssemblyQualifiedName] = type ?? throw new SerializationException(SR.Format(SR.Serialization_MissingType, typeName.AssemblyQualifiedName));
return type;
}
[RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String, Boolean, Boolean)")]
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
// with a version of the assembly that predates the one available.
try
{
return assembly.GetType(typeName.FullName, throwOnError: false, ignoreCase: false);
}
catch (TypeLoadException) { }
catch (FileNotFoundException) { }
catch (FileLoadException) { }
catch (BadImageFormatException) { }
return Type.GetType(typeName.FullName, ResolveSimpleAssemblyName, new TopLevelAssemblyTypeResolver(assembly).ResolveType, throwOnError: false);
static Assembly? ResolveSimpleAssemblyName(AssemblyName assemblyName)
{
try
{
return Assembly.Load(assemblyName);
}
catch { }
try
{
return Assembly.Load(assemblyName.Name!);
}
catch { }
return null;
}
}
private sealed class TopLevelAssemblyTypeResolver
{
private readonly Assembly _topLevelAssembly;
public TopLevelAssemblyTypeResolver(Assembly topLevelAssembly) => _topLevelAssembly = topLevelAssembly;
[RequiresUnreferencedCode("Calls System.Reflection.Assembly.GetType(String, Boolean, Boolean)")]
public Type? ResolveType(Assembly? assembly, string simpleTypeName, bool ignoreCase)
{
assembly ??= _topLevelAssembly;
return assembly.GetType(simpleTypeName, throwOnError: false, ignoreCase);
}
}
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,95 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat;
/// <summary>
/// Object model for the binary format put out by BinaryFormatter. It parses and creates a model but does not
/// instantiate any reference types outside of string.
/// </summary>
/// <remarks>
/// <para>
/// This is useful for explicitly controlling the rehydration of binary formatted data.
/// </para>
/// </remarks>
internal sealed partial class BinaryFormattedObject
{
#pragma warning disable SYSLIB0050 // Type or member is obsolete
internal static FormatterConverter DefaultConverter { get; } = new();
#pragma warning restore SYSLIB0050
private static readonly Options s_defaultOptions = new();
private static readonly PayloadOptions s_payloadOptions = new()
{
UndoTruncatedTypeNames = true // Required for backward compat
};
private readonly Options _options;
private ITypeResolver? _typeResolver;
private ITypeResolver TypeResolver => _typeResolver ??= new DefaultTypeResolver(_options);
/// <summary>
/// Creates <see cref="BinaryFormattedObject"/> by parsing <paramref name="stream"/>.
/// </summary>
public BinaryFormattedObject(Stream stream, Options? options = null)
{
_options = options ?? s_defaultOptions;
try
{
RootRecord = PayloadReader.Read(stream, out var readonlyRecordMap, options: s_payloadOptions, leaveOpen: true);
RecordMap = readonlyRecordMap;
}
catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException)
{
// Make the exception easier to catch, but retain the original stack trace.
throw ex.ConvertToSerializationException();
}
catch (TargetInvocationException ex)
{
throw ExceptionDispatchInfo.Capture(ex.InnerException!).SourceException.ConvertToSerializationException();
}
}
/// <summary>
/// Deserializes the <see cref="BinaryFormattedObject"/> back to an object.
/// </summary>
[RequiresUnreferencedCode("Ultimately calls Assembly.GetType for type names in the data.")]
public object Deserialize()
{
try
{
return Deserializer.Deserializer.Deserialize(RootRecord.ObjectId, RecordMap, TypeResolver, _options);
}
catch (Exception ex) when (ex is ArgumentException or InvalidCastException or ArithmeticException or IOException)
{
// Make the exception easier to catch, but retain the original stack trace.
throw ex.ConvertToSerializationException();
}
catch (TargetInvocationException ex)
{
throw ExceptionDispatchInfo.Capture(ex.InnerException!).SourceException.ConvertToSerializationException();
}
}
/// <summary>
/// The Id of the root record of the object graph.
/// </summary>
public SerializationRecord RootRecord { get; }
/// <summary>
/// Gets a record by it's identifier. Not all records have identifiers, only ones that
/// can be referenced by other records.
/// </summary>
public SerializationRecord this[Id id] => RecordMap[id];
public IReadOnlyDictionary<int, SerializationRecord> RecordMap { get; }
}

View file

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat;
internal static class BinaryFormattedObjectExtensions
{
internal static object GetMemberPrimitiveTypedValue(this SerializationRecord record)
{
Debug.Assert(record.RecordType is RecordType.MemberPrimitiveTyped or RecordType.BinaryObjectString);
return record switch
{
PrimitiveTypeRecord<string> primitive => primitive.Value,
PrimitiveTypeRecord<bool> primitive => primitive.Value,
PrimitiveTypeRecord<byte> primitive => primitive.Value,
PrimitiveTypeRecord<sbyte> primitive => primitive.Value,
PrimitiveTypeRecord<char> primitive => primitive.Value,
PrimitiveTypeRecord<short> primitive => primitive.Value,
PrimitiveTypeRecord<ushort> primitive => primitive.Value,
PrimitiveTypeRecord<int> primitive => primitive.Value,
PrimitiveTypeRecord<uint> primitive => primitive.Value,
PrimitiveTypeRecord<long> primitive => primitive.Value,
PrimitiveTypeRecord<ulong> primitive => primitive.Value,
PrimitiveTypeRecord<float> primitive => primitive.Value,
PrimitiveTypeRecord<double> primitive => primitive.Value,
PrimitiveTypeRecord<decimal> primitive => primitive.Value,
PrimitiveTypeRecord<TimeSpan> primitive => primitive.Value,
PrimitiveTypeRecord<DateTime> primitive => primitive.Value,
PrimitiveTypeRecord<IntPtr> primitive => primitive.Value,
_ => ((PrimitiveTypeRecord<UIntPtr>)record).Value
};
}
}

View file

@ -0,0 +1,162 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
internal sealed class ArrayRecordDeserializer : ObjectRecordDeserializer
{
private readonly ArrayRecord _arrayRecord;
private readonly Type _elementType;
private readonly Array _arrayOfClassRecords;
private readonly Array _arrayOfT;
private readonly int[] _lengths, _indices;
private bool _hasFixups, _canIterate;
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
internal ArrayRecordDeserializer(ArrayRecord arrayRecord, IDeserializer deserializer)
: base(arrayRecord, deserializer)
{
// 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 (BinaryArrayType.Single or BinaryArrayType.Jagged or BinaryArrayType.Rectangular));
_arrayRecord = arrayRecord;
_elementType = deserializer.TypeResolver.GetType(arrayRecord.ElementTypeName);
Type expectedArrayType = arrayRecord.ArrayType switch
{
BinaryArrayType.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)
_arrayOfClassRecords = arrayRecord.GetArray(expectedArrayType);
// Now we need to create an array of the same length, but of a different, exact type
Type elementType = _arrayOfClassRecords.GetType();
while (elementType.IsArray)
{
elementType = elementType.GetElementType()!;
}
_lengths = arrayRecord.Lengths.ToArray();
Object = _arrayOfT = Array.CreateInstance(_elementType, _lengths);
_indices = new int[_lengths.Length];
_canIterate = _arrayOfT.Length > 0;
}
internal override Id Continue()
{
int[] indices = _indices;
int[] lengths = _lengths;
while (_canIterate)
{
(object? memberValue, Id reference) = UnwrapMemberValue(_arrayOfClassRecords.GetValue(indices));
if (s_missingValueSentinel == memberValue)
{
// Record has not been encountered yet, need to pend iteration.
return reference;
}
if (memberValue is not null && DoesValueNeedUpdated(memberValue, reference))
{
// Need to track a fixup for this index.
_hasFixups = true;
Deserializer.PendValueUpdater(new ArrayUpdater(_arrayRecord.ObjectId, reference, indices.ToArray()));
}
_arrayOfT.SetValue(memberValue, indices);
int dimension = indices.Length - 1;
while (dimension >= 0)
{
indices[dimension]++;
if (indices[dimension] < lengths[dimension])
{
break;
}
indices[dimension] = 0;
dimension--;
}
if (dimension < 0)
{
_canIterate = false;
}
}
// No more missing member refs.
if (!_hasFixups)
{
Deserializer.CompleteObject(_arrayRecord.ObjectId);
}
return Id.Null;
}
internal static Array GetArraySinglePrimitive(SerializationRecord record) => record switch
{
ArrayRecord<bool> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<byte> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<sbyte> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<char> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<short> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<ushort> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<int> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<uint> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<long> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<ulong> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<float> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<double> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<decimal> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<DateTime> primitiveArray => primitiveArray.GetArray(),
ArrayRecord<TimeSpan> primitiveArray => primitiveArray.GetArray(),
_ => throw new NotSupportedException(),
};
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.BinaryFormattedObject.TypeResolver.GetType(TypeName)")]
internal static Array? GetSimpleBinaryArray(ArrayRecord arrayRecord, BinaryFormattedObject.ITypeResolver typeResolver)
{
if (arrayRecord.ArrayType is not (BinaryArrayType.Single or BinaryArrayType.Jagged or BinaryArrayType.Rectangular))
{
throw new NotSupportedException(SR.NotSupported_NonZeroOffsets);
}
Type arrayRecordElementType = typeResolver.GetType(arrayRecord.ElementTypeName);
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
{
BinaryArrayType.Rectangular => arrayRecordElementType.MakeArrayType(arrayRecord.Rank),
_ => arrayRecordElementType.MakeArrayType()
};
return arrayRecord.GetArray(expectedArrayType);
static bool HasBuiltInSupport(Type elementType)
=> elementType == typeof(string)
|| elementType == typeof(bool) || elementType == typeof(byte) || elementType == typeof(sbyte)
|| elementType == typeof(char) || elementType == typeof(short) || elementType == typeof(ushort)
|| elementType == typeof(int) || elementType == typeof(uint)
|| elementType == typeof(long) || elementType == typeof(ulong)
|| elementType == typeof(float) || elementType == typeof(double) || elementType == typeof(decimal)
|| elementType == typeof(DateTime) || elementType == typeof(TimeSpan);
}
}

View file

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
internal sealed class ArrayUpdater : ValueUpdater
{
private readonly int[] _indices;
internal ArrayUpdater(int objectId, int valueId, int[] indices) : base(objectId, valueId)
{
_indices = indices;
}
internal override void UpdateValue(IDictionary<int, object> objects)
{
object value = objects[ValueId];
Array array = (Array)objects[ObjectId];
array.SetValue(value, _indices);
}
}

View file

@ -0,0 +1,101 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
/// <summary>
/// Base class for deserializing <see cref="ClassRecord"/>s.
/// </summary>
internal abstract class ClassRecordDeserializer : ObjectRecordDeserializer
{
private readonly bool _onlyAllowPrimitives;
private protected ClassRecordDeserializer(ClassRecord classRecord, object @object, IDeserializer deserializer)
: base(classRecord, deserializer)
{
Object = @object;
// We want to be able to complete IObjectReference without having to evaluate their dependencies
// for circular references. See ValidateNewMemberObjectValue below for more.
_onlyAllowPrimitives = @object is IObjectReference;
}
[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);
Id id = classRecord.ObjectId;
ISerializationSurrogate? surrogate = deserializer.GetSurrogate(type);
if (!type.IsSerializable && surrogate is null)
{
// SurrogateSelectors allow populating types that are not marked as serializable.
throw new SerializationException(SR.Format(SR.Serialization_TypeNotSerializable, type));
}
object @object =
#if NETCOREAPP
RuntimeHelpers.GetUninitializedObject(type);
#else
Runtime.Serialization.FormatterServices.GetUninitializedObject(type);
#endif
// Invoke any OnDeserializing methods.
SerializationEvents.GetOnDeserializingForType(type, @object)?.Invoke(deserializer.Options.StreamingContext);
ObjectRecordDeserializer? recordDeserializer;
if (surrogate is not null || typeof(ISerializable).IsAssignableFrom(type))
{
recordDeserializer = new ClassRecordSerializationInfoDeserializer(classRecord, @object, type, surrogate, deserializer);
}
else
{
// Directly set fields for non-ISerializable types.
recordDeserializer = new ClassRecordFieldInfoDeserializer(classRecord, @object, type, deserializer);
}
return recordDeserializer;
}
private protected override void ValidateNewMemberObjectValue(object value)
{
if (!_onlyAllowPrimitives)
{
return;
}
// The goal with this restriction is to know definitively that we can complete the contianing object when we
// finish with it's members, even if it is going to be replaced with another instance (as IObjectReference does).
// If there are no reference types we know that there is no way for references to this object getting it in an
// in an unconverted state or converted with uncompleted state (due to some direct or indirect reference from
// this object).
//
// If we wanted support to be fully open-ended we would have queue completion along with pending SerializationInfo
// objects to rehydrate in the proper order (depth-first) and reject any case where the object is involved in
// a cycle.
Type type = value.GetType();
if (type.IsArray)
{
type = type.GetElementType()!;
}
bool primitive = type.IsPrimitive || type.IsEnum || type == typeof(string);
if (!primitive)
{
throw new SerializationException(SR.Format(SR.Serialization_IObjectReferenceOnlyPrimivite, type));
}
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
using System.Runtime.Serialization.Formatters;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
/// <summary>
/// Deserializer for <see cref="ClassRecord"/>s that directly set fields.
/// </summary>
internal sealed class ClassRecordFieldInfoDeserializer : ClassRecordDeserializer
{
private readonly Runtime.Serialization.BinaryFormat.ClassRecord _classRecord;
private readonly MemberInfo[] _fieldInfo;
private int _currentFieldIndex;
private readonly bool _isValueType;
private bool _hasFixups;
internal ClassRecordFieldInfoDeserializer(
Runtime.Serialization.BinaryFormat.ClassRecord classRecord,
object @object,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
Type type,
IDeserializer deserializer)
: base(classRecord, @object, deserializer)
{
_classRecord = classRecord;
#pragma warning disable IL2067 // GetSerializableMembers is not attributed correctly. Should just be fields.
_fieldInfo = Runtime.Serialization.FormatterServices.GetSerializableMembers(type);
#pragma warning restore IL2067
_isValueType = type.IsValueType;
}
internal override Id Continue()
{
// When directly setting fields we need to populate fields with primitive types before we
// can add the object to the deserialized object list to handle value types. This ensures
// partialially filled boxed value types in the collection are assigned (and unboxed) in
// this path (non-ISerializable) with nothing directly pending other than reference types.
Debug.Assert(_fieldInfo is not null);
// Note that while fields must have member data, fields are not required for all member data.
while (_currentFieldIndex < _fieldInfo.Length)
{
// FormatterServices *never* returns anything but fields.
FieldInfo field = (FieldInfo)_fieldInfo[_currentFieldIndex];
if (!_classRecord.HasMember(field.Name))
{
if (Deserializer.Options.AssemblyMatching == FormatterAssemblyStyle.Simple
|| field.GetCustomAttribute<OptionalFieldAttribute>() is not null)
{
_currentFieldIndex++;
continue;
}
throw new SerializationException(SR.Format(SR.Serialization_MissingField, field.Name, field.DeclaringType!.Name));
}
(object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetRawValue(field.Name));
if (s_missingValueSentinel == memberValue)
{
// Record has not been encountered yet, need to pend iteration.
return reference;
}
field.SetValue(Object, memberValue);
if (memberValue is not null && DoesValueNeedUpdated(memberValue, reference))
{
// Need to track a fixup for this field.
_hasFixups = true;
Deserializer.PendValueUpdater(new FieldValueUpdater(_classRecord.ObjectId, reference, field));
}
_currentFieldIndex++;
}
if (!_hasFixups || !_isValueType)
{
// We can be used for completion even with fixups if we're not a value type as our fixups won't need to be
// copied to propogate them. Note that surrogates cannot replace our created instance for reference types.
Deserializer.CompleteObject(_classRecord.ObjectId);
}
// No more missing member refs.
return Id.Null;
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,92 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
/// <summary>
/// Deserializer for <see cref="ClassRecord"/>s that use <see cref="SerializationInfo"/> to initialize class state.
/// </summary>
/// <remarks>
/// <para>
/// This is used either because the class implements <see cref="ISerializable"/> or because a surrogate was used.
/// </para>
/// </remarks>
internal sealed class ClassRecordSerializationInfoDeserializer : ClassRecordDeserializer
{
private readonly Runtime.Serialization.BinaryFormat.ClassRecord _classRecord;
private readonly SerializationInfo _serializationInfo;
private readonly ISerializationSurrogate? _surrogate;
private readonly IEnumerator<string> _memberNamesIterator;
private bool _canIterate;
internal ClassRecordSerializationInfoDeserializer(
Runtime.Serialization.BinaryFormat.ClassRecord classRecord,
object @object,
Type type,
ISerializationSurrogate? surrogate,
IDeserializer deserializer) : base(classRecord, @object, deserializer)
{
_classRecord = classRecord;
_surrogate = surrogate;
_serializationInfo = new(type, BinaryFormattedObject.DefaultConverter);
_memberNamesIterator = _classRecord.MemberNames.GetEnumerator();
_canIterate = _memberNamesIterator.MoveNext(); // start the iterator
}
internal override Id Continue()
{
if (_canIterate)
{
do
{
string memberName = _memberNamesIterator.Current;
(object? memberValue, Id reference) = UnwrapMemberValue(_classRecord.GetRawValue(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());
_canIterate = false;
}
// We can't complete these in the same way we do with direct field sets as user code can dereference the
// reference type members from the SerializationInfo that aren't fully completed (due to cycles). With direct
// field sets it doesn't matter if the referenced object isn't fully completed. Waiting until the graph is
// fully parsed to allow cycles the best chance to resolve as much as possible without having to walk the
// entire graph from this point to make a determination.
//
// The same issue applies to "complete" events, which is why we pend them as well.
//
// If we were confident that there were no cycles in the graph to this point we could apply directly
// if there were no pending value types (which should also not happen if there are no cycles).
PendingSerializationInfo pending = new(_classRecord.ObjectId, _serializationInfo, _surrogate);
Deserializer.PendSerializationInfo(pending);
// No more missing member refs.
return Id.Null;
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,409 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
/// <summary>
/// General binary format deserializer.
/// </summary>
/// <remarks>
/// <para>
/// This has some constraints over the BinaryFormatter. Notably it does not support all <see cref="IObjectReference"/>
/// usages or surrogates that replace object instances. This greatly simplifies the deserialization. It also does not
/// allow offset arrays (arrays that have lower bounds other than zero) or multidimensional arrays that have more
/// than <see cref="int.MaxValue"/> elements.
/// </para>
/// <para>
/// This deserializer ensures that all value types are assigned to fields or populated in <see cref="SerializationInfo"/>
/// callbacks with their final state, throwing if that is impossible to attain due to graph cycles or data corruption.
/// The value type instance may contain references to uncompleted reference types when there are cycles in the graph.
/// In general it is risky to dereference reference types in <see cref="ISerializable"/> constructors or in
/// <see cref="ISerializationSurrogate"/> call backs if there is any risk of the objects enabling a cycle.
/// </para>
/// <para>
/// If you need to dereference reference types in <see cref="SerializationInfo"/> waiting for final state by
/// implementing <see cref="IDeserializationCallback"/> or <see cref="OnDeserializedAttribute"/> is the safer way to
/// do so. This deserializer does not fire completed events until the entire graph has been deserialized. If a
/// surrogate (<see cref="ISerializationSurrogate"/>) needs to dereference with potential cycles it would require
/// tracking instances by stashing them in a provided <see cref="StreamingContext"/> to handle after invoking the
/// deserializer.
/// </para>
/// </remarks>
/// <devdoc>
/// <see cref="IObjectReference"/> makes deserializing difficult as you don't know the final type until you've finished
/// populating the serialized type. If <see cref="SerializationInfo"/> is involved and you have a cycle you may never
/// be able to complete the deserialization as the reference type values in the <see cref="SerializationInfo"/> can't
/// get the final object.
///
/// <see cref="IObjectReference"/> is really the only practical way to represent singletons. A common pattern is to
/// nest an <see cref="IObjectReference"/> object in an <see cref="ISerializable"/> object. Specifying the nested
/// type when <see cref="ISerializable.GetObjectData(SerializationInfo, StreamingContext)"/> is called by invoking
/// <see cref="SerializationInfo.SetType(Type)"/> will get that type info serialized into the stream.
/// </devdoc>
internal sealed partial class Deserializer : IDeserializer
{
private readonly IReadOnlyDictionary<int, SerializationRecord> _recordMap;
private readonly BinaryFormattedObject.ITypeResolver _typeResolver;
BinaryFormattedObject.ITypeResolver IDeserializer.TypeResolver => _typeResolver;
/// <inheritdoc cref="IDeserializer.Options"/>
private BinaryFormattedObject.Options Options { get; }
BinaryFormattedObject.Options IDeserializer.Options => Options;
/// <inheritdoc cref="IDeserializer.DeserializedObjects"/>
private readonly Dictionary<int, object> _deserializedObjects = [];
IDictionary<int, object> IDeserializer.DeserializedObjects => _deserializedObjects;
// Surrogate cache.
private readonly Dictionary<Type, ISerializationSurrogate?>? _surrogates;
// Queue of SerializationInfo objects that need to be applied. These are in depth first order,
// if there are no cycles in the graph this ensures that all objects are available when the
// SerializationInfo is applied.
//
// We also keep a hashset for quickly checking to make sure we do not complete objects before we
// actually apply the SerializationInfo. While we could mark them in the incomplete dependencies
// dictionary, to do so we'd need to know if any referenced object is going to get to this state
// even if it hasn't finished parsing, which isn't easy to do with cycles involved.
private Queue<PendingSerializationInfo>? _pendingSerializationInfo;
private HashSet<int>? _pendingSerializationInfoIds;
// Keeping a separate stack for ids for fast infinite loop checks.
private readonly Stack<int> _parseStack = [];
private readonly Stack<ObjectRecordDeserializer> _parserStack = [];
/// <inheritdoc cref="IDeserializer.IncompleteObjects"/>
private readonly HashSet<int> _incompleteObjects = [];
public HashSet<int> IncompleteObjects => _incompleteObjects;
// For a given object id, the set of ids that it is waiting on to complete.
private Dictionary<int, HashSet<int>>? _incompleteDependencies;
// The pending value updaters. Scanned each time an object is completed.
private HashSet<ValueUpdater>? _pendingUpdates;
// Kept as a field to avoid allocating a new one every time we complete objects.
private readonly Queue<int> _pendingCompletions = [];
private readonly Id _rootId;
// We group individual object events here to fire them all when we complete the graph.
private event Action<object?>? OnDeserialization;
private event Action<StreamingContext>? OnDeserialized;
private Deserializer(
Id rootId,
IReadOnlyDictionary<int, SerializationRecord> recordMap,
BinaryFormattedObject.ITypeResolver typeResolver,
BinaryFormattedObject.Options options)
{
_rootId = rootId;
_recordMap = recordMap;
_typeResolver = typeResolver;
Options = options;
if (Options.SurrogateSelector is not null)
{
_surrogates = [];
}
}
/// <summary>
/// Deserializes the object graph for the given <paramref name="recordMap"/> and <paramref name="rootId"/>.
/// </summary>
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.Deserializer.Deserialize()")]
internal static object Deserialize(
Id rootId,
IReadOnlyDictionary<int, SerializationRecord> recordMap,
BinaryFormattedObject.ITypeResolver typeResolver,
BinaryFormattedObject.Options options)
{
var deserializer = new Deserializer(rootId, recordMap, typeResolver, options);
return deserializer.Deserialize();
}
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.Deserializer.DeserializeRoot(Id)")]
private object Deserialize()
{
DeserializeRoot(_rootId);
// Complete all pending SerializationInfo objects.
int pendingCount = _pendingSerializationInfo?.Count ?? 0;
while (_pendingSerializationInfo is not null && _pendingSerializationInfo.Count > 0)
{
PendingSerializationInfo? pending = _pendingSerializationInfo.Dequeue();
// Using pendingCount to only requeue on the first pass.
if (--pendingCount >= 0
&& _pendingSerializationInfo.Count != 0
&& _incompleteDependencies is not null
&& _incompleteDependencies.TryGetValue(pending.ObjectId, out HashSet<int>? dependencies))
{
// We can get here with nested ISerializable value types.
// Hopefully another pass will complete this.
if (dependencies.Count > 0)
{
_pendingSerializationInfo.Enqueue(pending);
continue;
}
Debug.Fail("Completed dependencies should have been removed from the dictionary.");
}
// All _pendingSerializationInfo objects are considered incomplete.
pending.Populate(_deserializedObjects, Options.StreamingContext);
_pendingSerializationInfoIds?.Remove(pending.ObjectId);
((IDeserializer)this).CompleteObject(pending.ObjectId);
}
if (_incompleteObjects.Count > 0 || (_pendingUpdates is not null && _pendingUpdates.Count > 0))
{
// This should never happen outside of corrupted data.
throw new SerializationException(SR.Serialization_Incomplete);
}
// Notify [OnDeserialized] instance methods for all relevant deserialized objects,
// then callback IDeserializationCallback on all objects that implement it.
OnDeserialized?.Invoke(Options.StreamingContext);
OnDeserialization?.Invoke(null);
return _deserializedObjects[_rootId];
}
[RequiresUnreferencedCode("Calls DeserializeNew(Id)")]
private void DeserializeRoot(Id rootId)
{
object root = DeserializeNew(rootId);
if (root is not ObjectRecordDeserializer parser)
{
return;
}
_parseStack.Push(rootId);
_parserStack.Push(parser);
while (_parserStack.Count > 0)
{
ObjectRecordDeserializer? currentParser = _parserStack.Pop();
int currentId = _parseStack.Pop();
Debug.Assert(currentId == currentParser.ObjectRecord.ObjectId);
Id requiredId;
while (!(requiredId = currentParser.Continue()).IsNull)
{
// Beside ObjectRecordDeserializer, DeserializeNew can return a raw value like int, string or an array.
if (DeserializeNew(requiredId) is ObjectRecordDeserializer requiredParser)
{
// The required object is not complete.
if (_parseStack.Contains(requiredId))
{
// All objects should be available before they're asked for a second time.
throw new SerializationException(SR.Serialization_Cycle);
}
// Push our current parser.
_parseStack.Push(currentId);
_parserStack.Push(currentParser);
// Push the required parser so we can complete it.
_parseStack.Push(requiredId);
_parserStack.Push(requiredParser);
break;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.ObjectRecordParser.Create(Id, IRecord, IDeserializer)")]
object DeserializeNew(Id id)
{
// Strings, string arrays, and primitive arrays can be completed without creating a
// parser object. Single primitives don't normally show up as records unless they are top
// level or are boxed into an interface reference. Checking for these requires costly
// string matches and as such we'll just create the parser object.
SerializationRecord record = _recordMap[id];
object? value = record.RecordType switch
{
RecordType.BinaryObjectString => ((PrimitiveTypeRecord<string>)record).Value,
RecordType.MemberPrimitiveTyped => record.GetMemberPrimitiveTypedValue(),
RecordType.ArraySingleString => ((ArrayRecord<string>)record).GetArray(),
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.
_incompleteObjects.Add(id);
var deserializer = ObjectRecordDeserializer.Create(record, this);
// Add the object as soon as possible to support circular references.
_deserializedObjects.Add(id, deserializer.Object);
return deserializer;
}
}
ISerializationSurrogate? IDeserializer.GetSurrogate(Type type)
{
// If we decide not to cache, this method could be moved to the callsite.
if (_surrogates is null)
{
return null;
}
Debug.Assert(Options.SurrogateSelector is not null);
if (!_surrogates.TryGetValue(type, out ISerializationSurrogate? surrogate))
{
surrogate = Options.SurrogateSelector.GetSurrogate(type, Options.StreamingContext, out _);
_surrogates[type] = surrogate;
}
return surrogate;
}
void IDeserializer.PendSerializationInfo(PendingSerializationInfo pending)
{
_pendingSerializationInfo ??= new();
_pendingSerializationInfo.Enqueue(pending);
_pendingSerializationInfoIds ??= [];
_pendingSerializationInfoIds.Add(pending.ObjectId);
}
void IDeserializer.PendValueUpdater(ValueUpdater updater)
{
// Add the pending update and update the dependencies list.
_pendingUpdates ??= [];
_pendingUpdates.Add(updater);
_incompleteDependencies ??= [];
if (_incompleteDependencies.TryGetValue(updater.ObjectId, out HashSet<int>? dependencies))
{
dependencies.Add(updater.ValueId);
}
else
{
_incompleteDependencies.Add(updater.ObjectId, [updater.ValueId]);
}
}
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "The type is already in the cache of the TypeResolver, no need to mark this one again.")]
void IDeserializer.CompleteObject(Id id)
{
// Need to use a queue as Completion is recursive.
_pendingCompletions.Enqueue(id);
Id completed = Id.Null;
while (_pendingCompletions.Count > 0)
{
int completedId = _pendingCompletions.Dequeue();
_incompleteObjects.Remove(completedId);
// When we've recursed, we've done so because there are no more dependencies for the current id, so we can
// remove it from the dictionary. We have to pend as we can't remove while we're iterating the dictionary.
if (!completed.IsNull)
{
_incompleteDependencies?.Remove(completed);
if (_pendingSerializationInfoIds is not null && _pendingSerializationInfoIds.Contains(completed))
{
// We came back for an object that has no remaining direct dependencies, but still has
// PendingSerializationInfo. As such it cannot be considered completed yet.
continue;
}
completed = Id.Null;
}
if (_recordMap[completedId] is ClassRecord classRecord
&& (_incompleteDependencies is null || !_incompleteDependencies.ContainsKey(completedId)))
{
// 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);
object @object = _deserializedObjects[completedId];
OnDeserialized += SerializationEvents.GetOnDeserializedForType(type, @object);
if (@object is IDeserializationCallback callback)
{
OnDeserialization += callback.OnDeserialization;
}
if (@object is IObjectReference objectReference)
{
_deserializedObjects[completedId] = objectReference.GetRealObject(Options.StreamingContext);
}
}
if (_incompleteDependencies is null)
{
continue;
}
Debug.Assert(_pendingUpdates is not null);
foreach (KeyValuePair<int, HashSet<int>> pair in _incompleteDependencies)
{
int incompleteId = pair.Key;
HashSet<int> dependencies = pair.Value;
if (!dependencies.Remove(completedId))
{
continue;
}
// Search for fixups that need to be applied for this dependency.
int removals = _pendingUpdates.RemoveWhere((ValueUpdater updater) =>
{
if (updater.ValueId != completedId)
{
return false;
}
updater.UpdateValue(_deserializedObjects);
return true;
});
if (dependencies.Count != 0)
{
continue;
}
// No more dependencies, enqueue for completion
completed = incompleteId;
_pendingCompletions.Enqueue(incompleteId);
}
}
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Reflection;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
internal sealed class FieldValueUpdater : ValueUpdater
{
private readonly FieldInfo _field;
internal FieldValueUpdater(int objectId, int valueId, FieldInfo field) : base(objectId, valueId)
{
_field = field;
}
internal override void UpdateValue(IDictionary<int, object> objects)
{
object newValue = objects[ValueId];
_field.SetValue(objects[ObjectId], newValue);
}
}

View file

@ -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.Collections.Generic;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
/// <summary>
/// Interface for deserialization used to define the coupling between the main <see cref="Deserializer"/>
/// and its <see cref="ObjectRecordDeserializer"/>s.
/// </summary>
internal interface IDeserializer
{
/// <summary>
/// The current deserialization options.
/// </summary>
BinaryFormattedObject.Options Options { get; }
/// <summary>
/// The set of object record ids that are not considered "complete" yet.
/// </summary>
/// <remarks>
/// <para>
/// Objects are considered incomplete if they contain references to value or <see cref="IObjectReference"/> types
/// that need completed or if they have not yet finished evaluating all of their member data. They are also
/// considered incomplete if they implement <see cref="ISerializable"/> or have a surrogate and the
/// <see cref="SerializationInfo"/> has not yet been applied.
/// </para>
/// </remarks>
HashSet<int> IncompleteObjects { get; }
/// <summary>
/// The set of objects that have been deserialized, indexed by record id.
/// </summary>
/// <remarks>
/// <para>
/// Objects may not be fully filled out. If they are not in <see cref="IncompleteObjects"/>, they are
/// guaranteed to have their reference type members created (this is not transitive- their members may not
/// be ready if there are cycles in the object graph).
/// </para>
/// </remarks>
IDictionary<int, object> DeserializedObjects { get; }
/// <summary>
/// Resolver for types.
/// </summary>
BinaryFormattedObject.ITypeResolver TypeResolver { get; }
/// <summary>
/// Pend the given value updater to be run when it's value type dependency is complete.
/// </summary>
void PendValueUpdater(ValueUpdater updater);
/// <summary>
/// Pend a <see cref="SerializationInfo"/> to be applied when the graph is fully parsed.
/// </summary>
void PendSerializationInfo(PendingSerializationInfo pending);
/// <summary>
/// Mark the object id as complete. This will check dependencies and resolve relevant <see cref="ValueUpdater"/>s.
/// </summary>
void CompleteObject(Id id);
/// <summary>
/// Check for a surrogate for the given type. If none exists, returns <see langword="null"/>.
/// </summary>
ISerializationSurrogate? GetSurrogate(Type type);
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,103 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
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 SerializationRecord ObjectRecord { get; }
[AllowNull]
internal object Object { get; private protected set; }
private protected IDeserializer Deserializer { get; }
private protected ObjectRecordDeserializer(SerializationRecord objectRecord, IDeserializer deserializer)
{
Deserializer = deserializer;
ObjectRecord = objectRecord;
}
/// <summary>
/// Continue parsing.
/// </summary>
/// <returns>The id that is necessary to complete parsing or <see cref="Id.Null"/> if complete.</returns>
internal abstract Id Continue();
/// <summary>
/// Gets the actual object for a member value primitive or record. Returns <see cref="s_missingValueSentinel"/> if
/// the object record has not been encountered yet.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private protected (object? value, Id id) UnwrapMemberValue(object? memberValue)
{
if (memberValue is null) // PayloadReader does not return NullRecord, just null
{
return (null, Id.Null);
}
else if (memberValue is not SerializationRecord serializationRecord) // a primitive value
{
return (memberValue, Id.Null);
}
else if (serializationRecord.RecordType is RecordType.BinaryObjectString)
{
PrimitiveTypeRecord<string> stringRecord = (PrimitiveTypeRecord<string>)serializationRecord;
return (stringRecord.Value, stringRecord.ObjectId);
}
else if (serializationRecord.RecordType is RecordType.MemberPrimitiveTyped)
{
return (serializationRecord.GetMemberPrimitiveTypedValue(), Id.Null);
}
else
{
// ClassRecords & ArrayRecords
return TryGetObject(serializationRecord.ObjectId);
}
(object? value, Id id) TryGetObject(Id id)
{
if (!Deserializer.DeserializedObjects.TryGetValue(id, out object? value))
{
return (s_missingValueSentinel, id);
}
ValidateNewMemberObjectValue(value);
return (value, id);
}
}
/// <summary>
/// Called for new non-primitive reference types.
/// </summary>
private protected virtual void ValidateNewMemberObjectValue(object value) { }
/// <summary>
/// Returns <see langword="true"/> if the given record's value needs an updater applied.
/// </summary>
private protected bool DoesValueNeedUpdated(object value, Id valueRecord) =>
// Null Id is a primitive value.
!valueRecord.IsNull
// IObjectReference is going to have its object replaced.
&& (value is IObjectReference
// Value types that aren't "complete" need to be reapplied.
|| (Deserializer.IncompleteObjects.Contains(valueRecord) && value.GetType().IsValueType));
[RequiresUnreferencedCode("Calls System.Windows.Forms.BinaryFormat.Deserializer.ClassRecordParser.Create(ClassRecord, IDeserializer)")]
internal static ObjectRecordDeserializer Create(SerializationRecord record, IDeserializer deserializer) => record switch
{
ClassRecord classRecord => ClassRecordDeserializer.Create(classRecord, deserializer),
_ => new ArrayRecordDeserializer((ArrayRecord)record, deserializer),
};
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,83 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
#pragma warning disable SYSLIB0050 // Type or member is obsolete
internal sealed class PendingSerializationInfo
{
internal int ObjectId { get; }
private readonly ISerializationSurrogate? _surrogate;
private readonly SerializationInfo _info;
internal PendingSerializationInfo(
int objectId,
SerializationInfo info,
ISerializationSurrogate? surrogate)
{
ObjectId = objectId;
_surrogate = surrogate;
_info = info;
}
[RequiresUnreferencedCode("We can't guarantee that the ctor will be present, as the type is not known up-front.")]
internal void Populate(IDictionary<int, object> objects, StreamingContext context)
{
object @object = objects[ObjectId];
Type type = @object.GetType();
if (_surrogate is not null)
{
object populated = _surrogate.SetObjectData(@object, _info, context, selector: null);
if (populated is null)
{
// Sort of odd to allow returning null to ignore setting the returned value back,
// but that is the way this worked in the BinaryFormatter.
return;
}
// Don't use == on reference types as we are dependent on the instance in the objects
// dictionary never changing. Value types can be modified but this is ok as any usages
// of this object will have a pending fixup that will reapply the value.
//
// BinaryFormatter would allow this to change as long as you didn't wrap the surrogate
// in FormaterServices.GetSurrogateForCyclicalReference. We break here on value types
// as they can never be observed in an unfinished state.
if (!type.IsValueType && !ReferenceEquals(populated, @object))
{
throw new SerializationException(SR.Serialization_Surrogates);
}
objects[ObjectId] = populated;
return;
}
ConstructorInfo constructor = GetDeserializationConstructor(type);
constructor.Invoke(@object, [_info, context]);
}
private static ConstructorInfo GetDeserializationConstructor(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type)
{
foreach (ConstructorInfo constructor in type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
ParameterInfo[] parameters = constructor.GetParameters();
if (parameters.Length == 2
&& parameters[0].ParameterType == typeof(SerializationInfo)
&& parameters[1].ParameterType == typeof(StreamingContext))
{
return constructor;
}
}
throw new SerializationException(SR.Format(SR.Serialization_MissingCtor, type.FullName));
}
}
#pragma warning restore SYSLIB0050 // Type or member is obsolete

View file

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
internal sealed class SerializationInfoValueUpdater : ValueUpdater
{
private readonly SerializationInfo _info;
private readonly string _name;
internal SerializationInfoValueUpdater(int objectId, int valueId, SerializationInfo info, string name) : base(objectId, valueId)
{
_info = info;
_name = name;
}
internal override void UpdateValue(IDictionary<int, object> objects)
{
object newValue = objects[ValueId];
_info.UpdateValue(_name, newValue, newValue.GetType());
}
}

View file

@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
namespace System.Resources.Extensions.BinaryFormat.Deserializer;
internal abstract class ValueUpdater
{
/// <summary>
/// The value id that needs to be reapplied.
/// </summary>
internal int ValueId { get; }
/// <summary>
/// The object id that is dependent on <see cref="ValueId"/>.
/// </summary>
internal int ObjectId { get; }
private protected ValueUpdater(int objectId, int valueId)
{
ObjectId = objectId;
ValueId = valueId;
}
internal abstract void UpdateValue(IDictionary<int, object> objects);
}

View file

@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
namespace System.Resources.Extensions;
/// <summary>
/// Identifier struct.
/// </summary>
internal readonly struct Id : IEquatable<Id>
{
private readonly int _id;
private readonly bool _isNull = true;
// It is possible that the id may be negative with value types. See BinaryObjectWriter.InternalGetId.
private Id(int id)
{
_id = id;
_isNull = false;
}
private Id(bool isNull)
{
_id = 0;
_isNull = isNull;
}
public static Id Null => new(isNull: true);
public bool IsNull => _isNull;
public static implicit operator int(Id value) => value._isNull ? throw new InvalidOperationException() : value._id;
public static implicit operator Id(int value) => new(value);
public override bool Equals([NotNullWhen(true)] object? obj)
=> (obj is Id id && Equals(id)) || (obj is int value && value == _id);
public bool Equals(Id other) => _isNull == other._isNull && _id == other._id;
public override readonly int GetHashCode() => _id.GetHashCode();
public override readonly string ToString() => _isNull ? "<null>" : _id.ToString();
public static bool operator ==(Id left, Id right) => left.Equals(right);
public static bool operator !=(Id left, Id right) => !(left == right);
}

View file

@ -0,0 +1,114 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.BinaryFormat;
internal sealed class SerializationEvents
{
private static readonly ConcurrentDictionary<Type, SerializationEvents> s_cache = new();
private static readonly SerializationEvents s_noEvents = new();
private readonly List<MethodInfo>? _onDeserializingMethods;
private readonly List<MethodInfo>? _onDeserializedMethods;
private SerializationEvents() { }
private SerializationEvents(
List<MethodInfo>? onDeserializingMethods,
List<MethodInfo>? onDeserializedMethods)
{
_onDeserializingMethods = onDeserializingMethods;
_onDeserializedMethods = onDeserializedMethods;
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:UnrecognizedReflectionPattern",
Justification = "The Type is annotated correctly, it just can't pass through the lambda method.")]
private static SerializationEvents GetSerializationEventsForType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type t) =>
s_cache.GetOrAdd(t, CreateSerializationEvents);
private static SerializationEvents CreateSerializationEvents([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type)
{
List<MethodInfo>? onDeserializingMethods = GetMethodsWithAttribute(typeof(OnDeserializingAttribute), type);
List<MethodInfo>? onDeserializedMethods = GetMethodsWithAttribute(typeof(OnDeserializedAttribute), type);
return onDeserializingMethods is null && onDeserializedMethods is null
? s_noEvents
: new SerializationEvents(onDeserializingMethods, onDeserializedMethods);
}
private static List<MethodInfo>? GetMethodsWithAttribute(
Type attribute,
// Currently the only way to preserve base, non-public methods is to use All
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? type)
{
List<MethodInfo>? attributedMethods = null;
// Traverse the hierarchy to find all methods with the specified attribute.
Type? baseType = type;
while (baseType is not null && baseType != typeof(object))
{
MethodInfo[] methods = baseType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
foreach (MethodInfo method in methods)
{
if (method.IsDefined(attribute, inherit: false))
{
attributedMethods ??= [];
attributedMethods.Add(method);
}
}
baseType = baseType.BaseType;
}
// We should invoke the methods starting from base.
attributedMethods?.Reverse();
return attributedMethods;
}
internal static Action<StreamingContext>? GetOnDeserializingForType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type,
object obj) =>
GetSerializationEventsForType(type).GetOnDeserializing(obj);
internal static Action<StreamingContext>? GetOnDeserializedForType(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type,
object obj) =>
GetSerializationEventsForType(type).GetOnDeserialized(obj);
private Action<StreamingContext>? GetOnDeserialized(object obj) =>
AddOnDelegate(obj, _onDeserializedMethods);
private Action<StreamingContext>? GetOnDeserializing(object obj) =>
AddOnDelegate(obj, _onDeserializingMethods);
/// <summary>Add all methods to a delegate.</summary>
private static Action<StreamingContext>? AddOnDelegate(object obj, List<MethodInfo>? methods)
{
Action<StreamingContext>? handler = null;
if (methods is not null)
{
foreach (MethodInfo method in methods)
{
Action<StreamingContext> onDeserialized =
#if NETCOREAPP
method.CreateDelegate<Action<StreamingContext>>(obj);
#else
(Action<StreamingContext>)method.CreateDelegate(typeof(Action<StreamingContext>), obj);
#endif
handler += onDeserialized;
}
}
return handler;
}
}

View file

@ -0,0 +1,25 @@
// 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.ExceptionServices;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.BinaryFormat;
internal static class SerializationExtensions
{
/// <summary>
/// Converts the given exception to a <see cref="SerializationException"/> if needed, nesting the original exception
/// and assigning the original stack trace.
/// </summary>
public static SerializationException ConvertToSerializationException(this Exception ex)
=> ex is SerializationException serializationException
? serializationException
#if NETCOREAPP
: (SerializationException)ExceptionDispatchInfo.SetRemoteStackTrace(
new SerializationException(ex.Message, ex),
ex.StackTrace ?? string.Empty);
#else
: new SerializationException(ex.Message, ex);
#endif
}

View file

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.BinaryFormat;
internal static class SerializationInfoExtensions
{
private static readonly Action<SerializationInfo, string, object, Type> s_updateValue =
typeof(SerializationInfo)
.GetMethod("UpdateValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!
#if NETCOREAPP
.CreateDelegate<Action<SerializationInfo, string, object, Type>>();
#else
.CreateDelegate(typeof(Action<SerializationInfo, string, object, Type>)) as Action<SerializationInfo, string, object, Type>;
#endif
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, typeof(SerializationInfo))]
internal static void UpdateValue(this SerializationInfo si, string name, object value, Type type) =>
s_updateValue(si, name, value, type);
}

View file

@ -3,6 +3,7 @@
using System.ComponentModel;
using System.IO;
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
@ -10,12 +11,9 @@ namespace System.Resources.Extensions
{
public partial class DeserializingResourceReader
{
private bool _assumeBinaryFormatter;
private static readonly bool s_useBinaryFormatter = AppContext.TryGetSwitch("System.Resources.Extensions.UseBinaryFormatter", out bool isEnabled) && isEnabled;
// Issue https://github.com/dotnet/runtime/issues/39292 tracks finding an alternative to BinaryFormatter
#pragma warning disable SYSLIB0011
private BinaryFormatter? _formatter;
#pragma warning restore SYSLIB0011
private bool _assumeBinaryFormatter;
private bool ValidateReaderType(string readerType)
{
@ -37,18 +35,25 @@ namespace System.Resources.Extensions
return false;
}
// Issue https://github.com/dotnet/runtime/issues/39292 tracks finding an alternative to BinaryFormatter
#pragma warning disable SYSLIB0011
private object ReadBinaryFormattedObject()
{
_formatter ??= new BinaryFormatter()
if (!s_useBinaryFormatter)
{
Binder = new UndoTruncatedTypeNameSerializationBinder()
};
BinaryFormattedObject binaryFormattedObject = new(_store.BaseStream);
return _formatter.Deserialize(_store.BaseStream);
}
return binaryFormattedObject.Deserialize();
}
else
{
#pragma warning disable SYSLIB0011
BinaryFormatter? formatter = new()
{
Binder = new UndoTruncatedTypeNameSerializationBinder()
};
return formatter.Deserialize(_store.BaseStream);
#pragma warning restore SYSLIB0011
}
}
internal sealed class UndoTruncatedTypeNameSerializationBinder : SerializationBinder
{
@ -227,6 +232,5 @@ namespace System.Resources.Extensions
return value;
}
}
}

View file

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common;
public abstract class ArrayTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public virtual void Roundtrip_ArrayContainingArrayAtNonZeroLowerBound()
{
// Not supported by BinaryFormattedObject
RoundTrip(Array.CreateInstance(typeof(uint[]), [5], [1]));
}
[Fact]
public void ArraySerializableValueType()
{
nint[] nints = [42, 43, 44];
object deserialized = Deserialize(Serialize(nints));
deserialized.Should().BeEquivalentTo(nints);
}
[Fact]
public void SameObjectRepeatedInArray()
{
object o = new();
object[] arr = [o, o, o, o, o];
object[] result = (object[])Deserialize(Serialize(arr));
Assert.Equal(arr.Length, result.Length);
Assert.NotSame(arr, result);
Assert.NotSame(arr[0], result[0]);
for (int i = 1; i < result.Length; i++)
{
Assert.Same(result[0], result[i]);
}
}
}

View file

@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Runtime.Serialization.Formatters;
using BinaryFormatTests;
using BinaryFormatTests.FormatterTests;
namespace System.Resources.Extensions.Tests.Common;
public abstract class BasicObjectTests<T> : SerializationTest<T> where T : ISerializer
{
private protected abstract bool SkipOffsetArrays { get; }
[Theory]
[MemberData(nameof(SerializableObjects))]
public void DeserializeStoredObjects(object value, TypeSerializableValue[] serializedData)
{
_ = value;
int platformIndex = serializedData.GetPlatformIndex();
for (int i = 0; i < serializedData.Length; i++)
{
for (FormatterAssemblyStyle assemblyMatching = 0; assemblyMatching <= FormatterAssemblyStyle.Full; assemblyMatching++)
{
object deserialized = DeserializeFromBase64Chars(serializedData[i].Base64Blob, assemblyMatching: assemblyMatching);
if (deserialized is StringComparer)
{
// StringComparer derived classes are not public and they don't serialize the actual type.
value.Should().BeAssignableTo<StringComparer>();
}
else
{
deserialized.Should().BeOfType(value.GetType());
}
bool isSamePlatform = i == platformIndex;
EqualityExtensions.CheckEquals(value, deserialized, isSamePlatform);
}
}
}
[Theory]
[MemberData(nameof(BasicObjectsRoundtrip_MemberData))]
public void BasicObjectsRoundtrip(
object value,
FormatterAssemblyStyle assemblyMatching,
FormatterTypeStyle typeStyle)
{
object deserialized = RoundTrip(value, typeStyle: typeStyle, assemblyMatching: assemblyMatching);
// string.Empty and DBNull are both singletons
if (!ReferenceEquals(value, string.Empty)
&& value is not DBNull
&& value is Array array
&& array.Length > 0)
{
deserialized.Should().NotBeSameAs(value);
}
EqualityExtensions.CheckEquals(value, deserialized, isSamePlatform: true);
}
public static EnumerableTupleTheoryData<object, TypeSerializableValue[]> SerializableObjects()
{
// Can add a .Skip() to get to the failing scenario easier when debugging.
return new EnumerableTupleTheoryData<object, TypeSerializableValue[]>((
// Explicitly not supporting offset arrays
from value in BinaryFormatterTests.RawSerializableObjects()
where value.Item1 is not Array array || array.GetLowerBound(0) == 0
select value).ToArray());
}
public static EnumerableTupleTheoryData<object, FormatterAssemblyStyle, FormatterTypeStyle> BasicObjectsRoundtrip_MemberData()
{
return new EnumerableTupleTheoryData<object, FormatterAssemblyStyle, FormatterTypeStyle>((
// 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.TypesAlways | FormatterTypeStyle.XsdString }
where value.Item1 is not Array array || array.GetLowerBound(0) == 0
select (value.Item1, assemblyFormat, typeFormat)).ToArray());
}
}

View file

@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using static BinaryFormatTests.FormatterTests.BinaryFormatterTests;
namespace System.Resources.Extensions.Tests.Common;
public abstract class ComparerTests<T> : SerializationTest<T> where T : ISerializer
{
public static TheoryData<string, object> NullableComparersTestData => new()
{
{ "NullableEqualityComparer`1", EqualityComparer<byte?>.Default },
{ "NullableEqualityComparer`1", EqualityComparer<int?>.Default },
{ "NullableEqualityComparer`1", EqualityComparer<float?>.Default },
{ "NullableEqualityComparer`1", EqualityComparer<Guid?>.Default }, // implements IEquatable<>
{ "ObjectEqualityComparer`1", EqualityComparer<MyStruct?>.Default }, // doesn't implement IEquatable<>
{ "ObjectEqualityComparer`1", EqualityComparer<DayOfWeek?>.Default },
{ "NullableComparer`1", Comparer<byte?>.Default },
{ "NullableComparer`1", Comparer<int?>.Default },
{ "NullableComparer`1", Comparer<float?>.Default },
{ "NullableComparer`1", Comparer<Guid?>.Default },
{ "ObjectComparer`1", Comparer<MyStruct?>.Default },
{ "ObjectComparer`1", Comparer<DayOfWeek?>.Default }
};
[Theory]
[MemberData(nameof(NullableComparersTestData))]
public void NullableComparers_Roundtrip(string expectedType, object obj)
{
object roundTrip = RoundTrip(obj);
roundTrip.GetType().Name.Should().Be(expectedType);
}
}

View file

@ -0,0 +1,127 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Resources.Extensions.Tests.Common.TestTypes;
using System.Resources.Extensions.Tests.FormattedObject;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
using System.Text;
namespace System.Resources.Extensions.Tests.Common;
public class CorruptedTests : SerializationTest<FormattedObjectSerializer>
{
private const int ClassId = 1, LibraryId = 2;
[Fact]
public void ValueTypeReferencesSelf()
{
using MemoryStream stream = new();
BinaryWriter writer = new(stream, Encoding.UTF8);
WriteSerializedStreamHeader(writer);
WriteBinaryLibrary(writer, LibraryId, typeof(NodeStruct).Assembly.FullName!);
WriteClassInfo(writer, ClassId, typeof(NodeStruct).FullName!, ["Node"]);
WriteClassFieldInfo(writer, typeof(NodeWithNodeStruct).FullName!, LibraryId);
writer.Write(LibraryId);
WriteMemberReference(writer, ClassId);
WriteMessageEnd(writer);
stream.Position = 0;
// This fails in the SerializationConstructor in BinaryFormattedObject's deserializer because the
// type isn't convertible to NodeWithNodeStruct. In BinaryFormatter it fails with fixups.
Action action = () => Deserialize(stream);
action.Should().Throw<SerializationException>();
}
[Fact]
public void ValueTypeReferencesSelf2()
{
using MemoryStream stream = new();
BinaryWriter writer = new(stream, Encoding.UTF8);
WriteSerializedStreamHeader(writer);
WriteBinaryLibrary(writer, LibraryId, typeof(StructWithObject).Assembly.FullName!);
WriteClassInfo(writer, ClassId, typeof(StructWithObject).FullName!, ["Value"]);
WriteClassFieldInfo(writer, typeof(StructWithObject).FullName!, LibraryId);
writer.Write(LibraryId);
WriteMemberReference(writer, ClassId);
WriteMessageEnd(writer);
stream.Position = 0;
Action action = () => Deserialize(stream);
action.Should().Throw<SerializationException>();
}
[Fact]
public void ValueTypeReferencesSelf3()
{
using MemoryStream stream = new();
BinaryWriter writer = new(stream, Encoding.UTF8);
WriteSerializedStreamHeader(writer);
WriteBinaryLibrary(writer, LibraryId, typeof(StructWithTwoObjects).Assembly.FullName!);
WriteClassInfo(writer, ClassId, typeof(StructWithTwoObjects).FullName!, ["Value", "Value2"]);
writer.Write((byte)BinaryType.Object);
writer.Write((byte)BinaryType.Object);
writer.Write(LibraryId);
WriteMemberReference(writer, ClassId);
WriteMemberReference(writer, ClassId);
WriteMessageEnd(writer);
// Both deserializers create this where every boxed struct is the exact same boxed instance.
stream.Position = 0;
Action action = () => Deserialize(stream);
action.Should().Throw<SerializationException>();
}
[Fact]
public virtual void ValueTypeReferencesSelf4()
{
using MemoryStream stream = new();
BinaryWriter writer = new(stream, Encoding.UTF8);
WriteSerializedStreamHeader(writer);
WriteBinaryLibrary(writer, LibraryId, typeof(StructWithTwoObjectsISerializable).Assembly.FullName!);
WriteClassInfo(writer, ClassId, typeof(StructWithTwoObjectsISerializable).FullName!, ["Value", "Value2"]);
writer.Write((byte)BinaryType.Object);
writer.Write((byte)BinaryType.Object);
writer.Write(LibraryId);
WriteMemberReference(writer, ClassId);
WriteMemberReference(writer, ClassId);
WriteMessageEnd(writer);
stream.Position = 0;
Deserialize(stream);
}
[Fact]
public virtual void ValueTypeReferencesSelf5()
{
const int NextClassId = 3;
using MemoryStream stream = new();
BinaryWriter writer = new(stream, Encoding.UTF8);
WriteSerializedStreamHeader(writer);
WriteBinaryLibrary(writer, LibraryId, typeof(StructWithTwoObjectsISerializable).Assembly.FullName!);
WriteClassInfo(writer, ClassId, typeof(StructWithTwoObjectsISerializable).FullName!, ["Value", "Value2"]);
writer.Write((byte)BinaryType.Object);
writer.Write((byte)BinaryType.Object);
writer.Write(LibraryId);
WriteMemberReference(writer, NextClassId);
WriteMemberReference(writer, NextClassId);
// ClassWithId
writer.Write((byte)RecordType.ClassWithId);
writer.Write(NextClassId); // id
writer.Write(ClassId); // id of the class that provides metadata
WriteMemberReference(writer, ClassId);
WriteMemberReference(writer, NextClassId);
WriteMessageEnd(writer);
stream.Position = 0;
Deserialize(stream);
}
}

View file

@ -0,0 +1,142 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Resources.Extensions.Tests.Common.TestTypes;
namespace System.Resources.Extensions.Tests.Common;
public abstract class CycleTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public void SelfReferencingISerializableObject()
{
NodeWithValueISerializable node = new()
{
Value = 42
};
node.Node = node;
Stream stream = Serialize(node);
var deserialized = (NodeWithValueISerializable)Deserialize(stream);
deserialized.Value.Should().Be(42);
deserialized.Node.Should().BeSameAs(deserialized);
}
[Fact]
public void SimpleLoopingISerializableObjects()
{
NodeWithValueISerializable node1 = new()
{
Value = 42
};
NodeWithValueISerializable node2 = new()
{
Value = 43
};
node1.Node = node2;
node2.Node = node1;
Stream stream = Serialize(node1);
var deserialized = (NodeWithValueISerializable)Deserialize(stream);
deserialized.Value.Should().Be(42);
deserialized.Node!.Value.Should().Be(43);
deserialized.Node.Node.Should().BeSameAs(deserialized);
}
[Fact]
public virtual void BackPointerToISerializableClass()
{
ClassWithValueISerializable<StructWithReferenceISerializable<object>> @object = new();
StructWithReferenceISerializable<object> structValue = new() { Value = 42, Reference = @object };
@object.Value = structValue;
Stream stream = Serialize(@object);
// BinaryFormatter doesn't handle this round trip.
var deserialized = (ClassWithValueISerializable<StructWithReferenceISerializable<object>>)Deserialize(stream);
deserialized.Value.Value.Should().Be(42);
deserialized.Value.Reference.Should().BeSameAs(deserialized);
}
[Fact]
public void BackPointerToArray()
{
var nints = new StructWithSelfArrayReferenceISerializable[3];
nints[0] = new() { Value = 42, Array = nints };
nints[1] = new() { Value = 43, Array = nints };
nints[2] = new() { Value = 44, Array = nints };
Stream stream = Serialize(nints);
var deserialized = (StructWithSelfArrayReferenceISerializable[])Deserialize(stream);
deserialized[0].Value.Should().Be(42);
deserialized[1].Value.Should().Be(43);
deserialized[2].Value.Should().Be(44);
deserialized[0].Array.Should().BeSameAs(deserialized);
}
[Fact]
public void BackPointerFromNestedStruct()
{
NodeWithNodeStruct node = new() { Value = "Root" };
node.NodeStruct = new NodeStruct { Node = node };
NodeWithNodeStruct deserialized = (NodeWithNodeStruct)Deserialize(Serialize(node));
deserialized.NodeStruct.Node.Should().BeSameAs(deserialized);
deserialized.Value.Should().Be("Root");
}
[Fact]
public void IndirectBackPointerFromNestedStruct()
{
NodeWithNodeStruct node = new() { Value = "Root" };
NodeWithNodeStruct node2 = new() { Value = "Node2" };
node.NodeStruct = new() { Node = node2 };
node2.NodeStruct = new() { Node = node };
NodeWithNodeStruct deserialized = (NodeWithNodeStruct)Deserialize(Serialize(node));
deserialized.Value.Should().Be("Root");
deserialized.NodeStruct.Node!.NodeStruct.Node.Should().BeSameAs(deserialized);
deserialized.NodeStruct.Node!.Value.Should().Be("Node2");
}
[Fact]
public void BinaryTreeCycles()
{
BinaryTreeNode root = new();
root.Left = root;
BinaryTreeNode deserialized = (BinaryTreeNode)Deserialize(Serialize(root));
deserialized.Left.Should().BeSameAs(deserialized);
deserialized.Right.Should().BeNull();
root.Right = root.Left;
deserialized = (BinaryTreeNode)Deserialize(Serialize(root));
deserialized.Left.Should().BeSameAs(deserialized);
deserialized.Right.Should().BeSameAs(deserialized);
}
[Fact]
public void BinaryTreeCycles_ISerializable()
{
BinaryTreeNodeISerializable root = new();
root.Left = root;
var deserialized = (BinaryTreeNodeISerializable)Deserialize(Serialize(root));
deserialized.Left.Should().BeSameAs(deserialized);
deserialized.Right.Should().BeNull();
root.Right = root.Left;
deserialized = (BinaryTreeNodeISerializable)Deserialize(Serialize(root));
deserialized.Left.Should().BeSameAs(deserialized);
deserialized.Right.Should().BeSameAs(deserialized);
}
}

View file

@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.Serialization;
namespace System.Resources.Extensions.Tests.Common;
internal class DelegateBinder : SerializationBinder
{
public Func<string, string, Type>? BindToTypeDelegate;
public override Type? BindToType(string assemblyName, string typeName) => BindToTypeDelegate?.Invoke(assemblyName, typeName);
}

View file

@ -0,0 +1,132 @@
// 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.Resources.Extensions.Tests.Common;
public abstract class EventTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public void SerializationEvents_FireAsExpected()
{
IncrementCountsDuringRoundtrip obj = new (null);
Assert.Equal(0, obj.IncrementedDuringOnSerializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod);
Stream stream = Serialize(obj);
Assert.Equal(1, obj.IncrementedDuringOnSerializingMethod);
Assert.Equal(1, obj.IncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod);
var result = (IncrementCountsDuringRoundtrip)Deserialize(stream);
Assert.Equal(1, obj.IncrementedDuringOnSerializingMethod);
Assert.Equal(1, obj.IncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod);
Assert.Equal(1, result.IncrementedDuringOnSerializingMethod);
Assert.Equal(0, result.IncrementedDuringOnSerializedMethod);
Assert.Equal(1, result.IncrementedDuringOnDeserializingMethod);
Assert.Equal(1, result.IncrementedDuringOnDeserializedMethod);
}
[Fact]
public void SerializationEvents_DerivedTypeWithEvents_FireAsExpected()
{
DerivedIncrementCountsDuringRoundtrip obj = new(null);
Assert.Equal(0, obj.IncrementedDuringOnSerializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnSerializingMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnDeserializedMethod);
Stream stream = Serialize(obj);
Assert.Equal(1, obj.IncrementedDuringOnSerializingMethod);
Assert.Equal(1, obj.IncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod);
Assert.Equal(1, obj._derivedIncrementedDuringOnSerializingMethod);
Assert.Equal(1, obj._derivedIncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnDeserializedMethod);
var result = (DerivedIncrementCountsDuringRoundtrip)Deserialize(stream);
Assert.Equal(1, obj.IncrementedDuringOnSerializingMethod);
Assert.Equal(1, obj.IncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod);
Assert.Equal(1, obj._derivedIncrementedDuringOnSerializingMethod);
Assert.Equal(1, obj._derivedIncrementedDuringOnSerializedMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnDeserializingMethod);
Assert.Equal(0, obj._derivedIncrementedDuringOnDeserializedMethod);
Assert.Equal(1, result.IncrementedDuringOnSerializingMethod);
Assert.Equal(0, result.IncrementedDuringOnSerializedMethod);
Assert.Equal(1, result.IncrementedDuringOnDeserializingMethod);
Assert.Equal(1, result.IncrementedDuringOnDeserializedMethod);
Assert.Equal(1, result._derivedIncrementedDuringOnSerializingMethod);
Assert.Equal(0, result._derivedIncrementedDuringOnSerializedMethod);
Assert.Equal(1, result._derivedIncrementedDuringOnDeserializingMethod);
Assert.Equal(1, result._derivedIncrementedDuringOnDeserializedMethod);
}
[Serializable]
public class IncrementCountsDuringRoundtrip
{
public int IncrementedDuringOnSerializingMethod;
public int IncrementedDuringOnSerializedMethod;
[NonSerialized] public int IncrementedDuringOnDeserializingMethod;
public int IncrementedDuringOnDeserializedMethod;
// non-default ctor so that we can observe changes from OnDeserializing
public IncrementCountsDuringRoundtrip(string? ignored) { _ = ignored; }
[OnSerializing]
private void OnSerializingMethod(StreamingContext context) => IncrementedDuringOnSerializingMethod++;
[OnSerialized]
private void OnSerializedMethod(StreamingContext context) => IncrementedDuringOnSerializedMethod++;
[OnDeserializing]
private void OnDeserializingMethod(StreamingContext context) => IncrementedDuringOnDeserializingMethod++;
[OnDeserialized]
private void OnDeserializedMethod(StreamingContext context) => IncrementedDuringOnDeserializedMethod++;
}
[Serializable]
public sealed class DerivedIncrementCountsDuringRoundtrip : IncrementCountsDuringRoundtrip
{
internal int _derivedIncrementedDuringOnSerializingMethod;
internal int _derivedIncrementedDuringOnSerializedMethod;
[NonSerialized] internal int _derivedIncrementedDuringOnDeserializingMethod;
internal int _derivedIncrementedDuringOnDeserializedMethod;
public DerivedIncrementCountsDuringRoundtrip(string? ignored) : base(ignored) { }
[OnSerializing]
private void OnSerializingMethod(StreamingContext context) => _derivedIncrementedDuringOnSerializingMethod++;
[OnSerialized]
private void OnSerializedMethod(StreamingContext context) => _derivedIncrementedDuringOnSerializedMethod++;
[OnDeserializing]
private void OnDeserializingMethod(StreamingContext context) => _derivedIncrementedDuringOnDeserializingMethod++;
[OnDeserialized]
private void OnDeserializedMethod(StreamingContext context) => _derivedIncrementedDuringOnDeserializedMethod++;
}
}

View file

@ -0,0 +1,69 @@
// 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.Formatters;
namespace System.Resources.Extensions.Tests.Common;
public abstract class FieldTests<T> : SerializationTest<T> where T : ISerializer
{
[Theory]
[InlineData(FormatterAssemblyStyle.Simple, false)]
[InlineData(FormatterAssemblyStyle.Full, true)]
public void MissingField_FailsWithAppropriateStyle(FormatterAssemblyStyle assemblyMatching, bool exceptionExpected)
{
Stream stream = Serialize(new Version1ClassWithoutField());
var binder = new DelegateBinder
{
BindToTypeDelegate = (_, _) => typeof(Version2ClassWithoutOptionalField)
};
if (exceptionExpected)
{
Assert.Throws<SerializationException>(() => Deserialize(stream, binder, assemblyMatching));
}
else
{
var result = (Version2ClassWithoutOptionalField)Deserialize(stream, binder, assemblyMatching);
Assert.NotNull(result);
Assert.Null(result.Value);
}
}
[Theory]
[InlineData(FormatterAssemblyStyle.Simple)]
[InlineData(FormatterAssemblyStyle.Full)]
public void OptionalField_Missing_Success(FormatterAssemblyStyle assemblyMatching)
{
Stream stream = Serialize(new Version1ClassWithoutField());
var binder = new DelegateBinder
{
BindToTypeDelegate = (_, _) => typeof(Version2ClassWithOptionalField)
};
var result = (Version2ClassWithOptionalField)Deserialize(stream, binder, assemblyMatching);
Assert.NotNull(result);
Assert.Null(result.Value);
}
[Serializable]
public class Version1ClassWithoutField
{
}
[Serializable]
public class Version2ClassWithoutOptionalField
{
public object? Value;
}
[Serializable]
public class Version2ClassWithOptionalField
{
[OptionalField(VersionAdded = 2)]
public object? Value;
}
}

View file

@ -0,0 +1,36 @@
// 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.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
namespace System.Resources.Extensions.Tests.Common;
public interface ISerializer
{
static virtual Stream Serialize(
object value,
SerializationBinder? binder = null,
ISurrogateSelector? surrogateSelector = null,
FormatterTypeStyle typeStyle = FormatterTypeStyle.TypesAlways)
{
MemoryStream stream = new();
BinaryFormatter formatter = new()
{
SurrogateSelector = surrogateSelector,
TypeFormat = typeStyle,
Binder = binder
};
formatter.Serialize(stream, value);
stream.Position = 0;
return stream;
}
static abstract object Deserialize(
Stream stream,
SerializationBinder? binder = null,
FormatterAssemblyStyle assemblyMatching = FormatterAssemblyStyle.Simple,
ISurrogateSelector? surrogateSelector = null);
}

View file

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common;
public abstract class JaggedArrayTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public void IntegerArraysTwoLevels()
{
int[][] jaggedArray = [[0, 1], [10, 11]];
Stream stream = Serialize(jaggedArray);
object deserialized = Deserialize(stream);
deserialized.Should().BeEquivalentTo(jaggedArray);
}
[Fact]
public void IntegerArraysThreeLevels()
{
int[][][] jaggedArray = [[[0, 1], [10, 11]], [[100, 101], [110, 111]]];
Stream stream = Serialize(jaggedArray);
object deserialized = Deserialize(stream);
deserialized.Should().BeEquivalentTo(jaggedArray);
}
[Fact]
public void JaggedEmpty()
{
int[][] jaggedEmpty = new int[1][];
object deserialized = Deserialize(Serialize(jaggedEmpty));
deserialized.Should().BeEquivalentTo(jaggedEmpty);
}
}

View file

@ -0,0 +1,127 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Resources.Extensions.Tests.Common;
public abstract class MultidimensionalArrayTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public void StringArrays()
{
string[,] twoDimensions = new string[2, 2];
twoDimensions[0, 0] = "00";
twoDimensions[0, 1] = "01";
twoDimensions[1, 0] = "10";
twoDimensions[1, 1] = "11";
// Raw data will be 0, 1, 10, 11 in memory and in the binary stream
Stream stream = Serialize(twoDimensions);
object deserialized = Deserialize(stream);
deserialized.Should().BeEquivalentTo(twoDimensions);
}
[Fact]
public void IntegerArrays_Basic()
{
int[,] twoDimensions = new int[2, 2];
twoDimensions[0, 0] = 0;
twoDimensions[0, 1] = 1;
twoDimensions[1, 0] = 10;
twoDimensions[1, 1] = 11;
// Raw data will be 0, 1, 10, 11 in memory and in the binary stream
object deserialized = Deserialize(Serialize(twoDimensions));
deserialized.Should().BeEquivalentTo(twoDimensions);
int[,,] threeDimensions = new int[2, 2, 2];
threeDimensions[0, 0, 0] = 888;
threeDimensions[0, 0, 1] = 881;
threeDimensions[0, 1, 0] = 810;
threeDimensions[0, 1, 1] = 811;
threeDimensions[1, 0, 0] = 100;
threeDimensions[1, 0, 1] = 101;
threeDimensions[1, 1, 0] = 110;
threeDimensions[1, 1, 1] = 111;
deserialized = Deserialize(Serialize(threeDimensions));
deserialized.Should().BeEquivalentTo(threeDimensions);
}
[Fact]
public void EmptyDimensions()
{
// Didn't even know this was possible.
int[,] twoDimensionOneEmpty = new int[1, 0];
object deserialized = Deserialize(Serialize(twoDimensionOneEmpty));
deserialized.Should().BeEquivalentTo(twoDimensionOneEmpty);
int[,] twoDimensionEmptyOne = new int[0, 1];
deserialized = Deserialize(Serialize(twoDimensionEmptyOne));
deserialized.Should().BeEquivalentTo(twoDimensionEmptyOne);
int[,] twoDimensionEmpty = new int[0, 0];
deserialized = Deserialize(Serialize(twoDimensionEmpty));
deserialized.Should().BeEquivalentTo(twoDimensionEmpty);
int[,,] threeDimension = new int[1, 0, 1];
deserialized = Deserialize(Serialize(threeDimension));
deserialized.Should().BeEquivalentTo(threeDimension);
}
[Theory]
[MemberData(nameof(DimensionLengthsTestData))]
public void IntegerArrays(int[] lengths)
{
Array array = Array.CreateInstance(typeof(int), lengths);
InitArray(array);
Array deserialized = (Array)Deserialize(Serialize(array));
deserialized.Should().BeEquivalentTo(deserialized);
}
public static TheoryData<int[]> DimensionLengthsTestData { get; } = new()
{
new int[] { 2, 2 },
new int[] { 3, 2 },
new int[] { 2, 3 },
new int[] { 3, 3, 3 },
new int[] { 2, 3, 4 },
new int[] { 4, 3, 2 },
new int[] { 2, 3, 4, 5 }
};
[Fact]
public void MaxDimensions()
{
int[] lengths = new int[32];
// Even at 2 in every dimension it would be uint.MaxValue in LongLength and 16GB of memory.
lengths.AsSpan().Fill(1);
Array array = Array.CreateInstance(typeof(int), lengths);
InitArray(array);
Array deserialized = (Array)Deserialize(Serialize(array));
deserialized.Should().BeEquivalentTo(deserialized);
}
private static void InitArray(Array array)
{
ref byte arrayDataRef = ref MemoryMarshal.GetArrayDataReference(array);
ref int elementRef = ref Unsafe.As<byte, int>(ref arrayDataRef);
nuint flattenedIndex = 0;
for (int i = 0; i < array.LongLength; i++)
{
ref int offsetElementRef = ref Unsafe.Add(ref elementRef, flattenedIndex);
offsetElementRef = i;
flattenedIndex++;
}
}
}

View file

@ -0,0 +1,128 @@
// 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.Text;
namespace System.Resources.Extensions.Tests.Common;
public abstract class ObjectReferenceTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public void DBNull_Deserialize()
{
object deserialized = RoundTrip(DBNull.Value);
deserialized.Should().BeSameAs(DBNull.Value);
}
[Theory]
[MemberData(nameof(SupportedTypesTestData))]
public void SupportedTypes_Deserialize(object value)
{
object deserialized = RoundTrip(value);
deserialized.Should().NotBeNull();
}
public static TheoryData<object> SupportedTypesTestData { get; } = new()
{
ObjectReferenceNoFields.Value,
new SerializableWithNestedSurrogate { Message = "Hello"}
};
[Fact]
public void Singleton_NoFields_Deserialize()
{
// Representing singletons is the most common pattern for IObjectReference.
object deserialized = RoundTrip(ObjectReferenceNoFields.Value);
deserialized.Should().BeSameAs(ObjectReferenceNoFields.Value);
}
[Serializable]
public sealed class ObjectReferenceNoFields : IObjectReference
{
public static ObjectReferenceNoFields Value { get; } = new();
private ObjectReferenceNoFields() { }
object IObjectReference.GetRealObject(StreamingContext context) => Value;
}
[Fact]
public void NestedSurrogate_Deserialize()
{
object deserialized = RoundTrip(new SerializableWithNestedSurrogate { Message = "Hello" });
deserialized.Should().BeOfType<SerializableWithNestedSurrogate>().Which.Message.Should().Be("Hello");
}
[Serializable]
#pragma warning disable CA2229 // Implement serialization constructors
public sealed class SerializableWithNestedSurrogate : ISerializable
#pragma warning restore CA2229
{
public string Message { get; set; } = string.Empty;
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(SerializationSurrogate));
info.AddValue(nameof(Message), Encoding.UTF8.GetBytes(Message));
}
[Serializable]
private sealed class SerializationSurrogate : IObjectReference, ISerializable
{
private readonly byte[] _bytes;
private SerializationSurrogate(SerializationInfo info, StreamingContext context)
{
_bytes = (byte[])(info.GetValue(nameof(Message), typeof(byte[])) ?? throw new InvalidOperationException());
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) =>
throw new InvalidOperationException();
object IObjectReference.GetRealObject(StreamingContext context)
=> new SerializableWithNestedSurrogate() { Message = Encoding.UTF8.GetString(_bytes) };
}
}
[Fact]
public void NestedSurrogate_NullableEnum_Deserialize()
{
object deserialized = RoundTrip(new SerializableWithNestedSurrogate_NullableEnum());
deserialized.Should().BeOfType<SerializableWithNestedSurrogate_NullableEnum>().Which.Day.Should().BeNull();
deserialized = RoundTrip(new SerializableWithNestedSurrogate_NullableEnum { Day = DayOfWeek.Monday });
deserialized.Should().BeOfType<SerializableWithNestedSurrogate_NullableEnum>().Which.Day.Should().Be(DayOfWeek.Monday);
}
[Serializable]
#pragma warning disable CA2229 // Implement serialization constructors
public sealed class SerializableWithNestedSurrogate_NullableEnum : ISerializable
#pragma warning restore CA2229
{
public DayOfWeek? Day { get; set; }
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.SetType(typeof(SerializationSurrogate));
info.AddValue(nameof(Day), Day);
}
[Serializable]
private sealed class SerializationSurrogate : IObjectReference, ISerializable
{
private readonly DayOfWeek? _day;
private SerializationSurrogate(SerializationInfo info, StreamingContext context)
{
_day = (DayOfWeek?)(info.GetValue(nameof(Day), typeof(DayOfWeek?)));
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) =>
throw new InvalidOperationException();
object IObjectReference.GetRealObject(StreamingContext context)
=> new SerializableWithNestedSurrogate_NullableEnum() { Day = _day };
}
}
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common;
public abstract class PrimitiveTests<T> : SerializationTest<T> where T : ISerializer
{
}

View file

@ -0,0 +1,136 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
using System.Runtime.Serialization.Formatters;
namespace System.Resources.Extensions.Tests.Common;
public abstract class SerializationTest<TSerializer> where TSerializer : ISerializer
{
public static TheoryData<FormatterTypeStyle, FormatterAssemblyStyle> FormatterOptions => new()
{
// XsdString always writes strings inline (never as a record). Despite FormatterTypeStyle
// not having [Flags] it is treated as flags in the serializer. If you don't explicitly set
// TypesAlways, TypesWhenNeeded is the default.
{ FormatterTypeStyle.TypesWhenNeeded, FormatterAssemblyStyle.Full },
{ FormatterTypeStyle.TypesWhenNeeded, FormatterAssemblyStyle.Simple },
{ FormatterTypeStyle.TypesAlways, FormatterAssemblyStyle.Full },
{ FormatterTypeStyle.TypesAlways, FormatterAssemblyStyle.Simple },
{ FormatterTypeStyle.TypesAlways | FormatterTypeStyle.XsdString, FormatterAssemblyStyle.Full },
{ FormatterTypeStyle.TypesAlways | FormatterTypeStyle.XsdString, FormatterAssemblyStyle.Simple },
{ FormatterTypeStyle.TypesWhenNeeded | FormatterTypeStyle.XsdString, FormatterAssemblyStyle.Full },
{ FormatterTypeStyle.TypesWhenNeeded | FormatterTypeStyle.XsdString, FormatterAssemblyStyle.Simple },
};
private protected static Stream Serialize(
object value,
SerializationBinder? binder = null,
ISurrogateSelector? surrogateSelector = null,
FormatterTypeStyle typeStyle = FormatterTypeStyle.TypesAlways) =>
TSerializer.Serialize(value, binder, surrogateSelector, typeStyle);
private protected static object Deserialize(
Stream stream,
SerializationBinder? binder = null,
FormatterAssemblyStyle assemblyMatching = FormatterAssemblyStyle.Simple,
ISurrogateSelector? surrogateSelector = null) =>
TSerializer.Deserialize(stream, binder, assemblyMatching, surrogateSelector);
private protected static TObject RoundTrip<TObject>(
TObject value,
SerializationBinder? binder = null,
ISurrogateSelector? surrogateSelector = null,
FormatterTypeStyle typeStyle = FormatterTypeStyle.TypesAlways,
FormatterAssemblyStyle assemblyMatching = FormatterAssemblyStyle.Simple) where TObject : notnull
{
// TODO: Use array pool
return (TObject)Deserialize(Serialize(value, binder, surrogateSelector, typeStyle), binder, assemblyMatching, surrogateSelector);
}
private protected static object DeserializeFromBase64Chars(
ReadOnlySpan<char> chars,
SerializationBinder? binder = null,
FormatterAssemblyStyle assemblyMatching = FormatterAssemblyStyle.Simple,
ISurrogateSelector? surrogateSelector = null)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(chars.Length);
if (!Convert.TryFromBase64Chars(chars, buffer, out _))
{
throw new InvalidOperationException();
}
MemoryStream stream = new(buffer);
try
{
return Deserialize(stream, binder, assemblyMatching, surrogateSelector);
}
finally
{
stream.Dispose();
ArrayPool<byte>.Shared.Return(buffer);
}
}
private protected static SurrogateSelector CreateSurrogateSelector<TSurrogated>(ISerializationSurrogate surrogate)
{
SurrogateSelector selector = new();
selector.AddSurrogate(
typeof(TSurrogated),
new StreamingContext(StreamingContextStates.All),
surrogate);
return selector;
}
public static bool IsBinaryFormatterDeserializer => false;
protected static void WriteSerializedStreamHeader(BinaryWriter writer, int major = 1, int minor = 0)
{
writer.Write((byte)RecordType.SerializedStreamHeader);
writer.Write(1); // root ID
writer.Write(1); // header ID
writer.Write(major); // major version
writer.Write(minor); // minor version
}
protected static void WriteBinaryLibrary(BinaryWriter writer, int objectId, string libraryName)
{
writer.Write((byte)RecordType.BinaryLibrary);
writer.Write(objectId);
writer.Write(libraryName);
}
protected static void WriteClassInfo(BinaryWriter writer, int objectId, string typeName, params string[] memberNames)
{
writer.Write((byte)RecordType.ClassWithMembersAndTypes);
writer.Write(objectId);
writer.Write(typeName);
writer.Write(memberNames.Length);
foreach (string memberName in memberNames)
{
writer.Write(memberName);
}
}
protected static void WriteClassFieldInfo(BinaryWriter writer, string typeName, int libraryId)
{
writer.Write((byte)BinaryType.Class);
writer.Write(typeName);
writer.Write(libraryId);
}
protected static void WriteMemberReference(BinaryWriter writer, int referencedObjectId)
{
writer.Write((byte)RecordType.MemberReference);
writer.Write(referencedObjectId);
}
protected static void WriteMessageEnd(BinaryWriter writer)
{
writer.Write((byte)RecordType.MessageEnd);
}
}

View file

@ -0,0 +1,29 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Resources.Extensions.Tests.Common.TestTypes;
namespace System.Resources.Extensions.Tests.Common;
public abstract class StressTests<T> : SerializationTest<T> where T : ISerializer
{
[Theory]
[InlineData(1000)]
[InlineData(10000)]
// This takes a few seconds
// [InlineData(100000)]
public void GraphDepth(int depth)
{
SimpleNode root = new();
SimpleNode current = root;
for (int i = 1; i < depth; i++)
{
current.Next = new();
current = current.Next;
}
SimpleNode deserialized = (SimpleNode)Deserialize(Serialize(root));
deserialized.Next.Should().NotBeNull();
deserialized.Next.Should().NotBeSameAs(deserialized);
}
}

View file

@ -0,0 +1,118 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Resources.Extensions.Tests.Common.TestTypes;
namespace System.Resources.Extensions.Tests.Common;
public abstract class SurrogateTests<T> : SerializationTest<T> where T : ISerializer
{
[Fact]
public void SerializePointWithSurrogate_NonRefChange()
{
Point point = new(42, 43);
// Surrogates cannot change the equality of types that they change. BinaryFormattedObject would allow this
// unless you wrap the selector by calling FormatterServices.GetSurrogateForCyclicalReference. Ours always
// allows value types to change as they are always applied as a fixup.
SurrogateSelector selector = CreateSurrogateSelector<Point>(new PointSerializationSurrogate(refUnbox: false));
Stream stream = Serialize(point);
Deserialize(stream, surrogateSelector: selector);
}
[Fact]
public void SerializePointWithSurrogate_RefChange()
{
Point point = new(42, 43);
Stream stream = Serialize(point);
// Surrogates can change the equality of the structs they are passed
// if they unbox as ref (using Unsafe).
SurrogateSelector selector = CreateSurrogateSelector<Point>(new PointSerializationSurrogate(refUnbox: true));
Point deserialized = (Point)Deserialize(stream, surrogateSelector: selector);
deserialized.Should().Be(point);
}
public class PointSerializationSurrogate : ISerializationSurrogate
{
private readonly bool _refUnbox;
public PointSerializationSurrogate(bool refUnbox) => _refUnbox = refUnbox;
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) =>
throw new NotImplementedException();
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector? selector)
{
if (_refUnbox)
{
ref Point pointRef = ref Unsafe.Unbox<Point>(obj);
pointRef.X = info.GetInt32("x");
pointRef.Y = info.GetInt32("y");
return obj;
}
Point point = (Point)obj;
point.X = info.GetInt32("x");
point.Y = info.GetInt32("y");
return point;
}
}
[Fact]
public void SerializePointWithNullSurrogate()
{
Point point = new(42, 43);
Stream stream = Serialize(point);
// Not sure why one would want to do this, but returning null will skip setting the value back.
SurrogateSelector selector = CreateSurrogateSelector<Point>(new NullSurrogate());
Point deserialized = (Point)Deserialize(stream, surrogateSelector: selector);
deserialized.X.Should().Be(0);
deserialized.Y.Should().Be(0);
}
public class NullSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) =>
throw new NotImplementedException();
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector? selector) =>
null!;
}
public class EqualsButDifferentSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) =>
throw new NotImplementedException();
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector? selector)
{
return null!;
}
}
[Fact]
public void SerializeNonSerializableTypeWithSurrogate()
{
NonSerializablePair<int, string> pair = new() { Value1 = 1, Value2 = "2" };
pair.GetType().IsSerializable.Should().BeFalse();
Action action = () => Serialize(pair);
action.Should().Throw<SerializationException>();
SurrogateSelector selector = CreateSurrogateSelector<NonSerializablePair<int, string>>(new NonSerializablePairSurrogate());
var deserialized = RoundTrip(pair, surrogateSelector: selector);
deserialized.Should().NotBeSameAs(pair);
deserialized.Value1.Should().Be(pair.Value1);
deserialized.Value2.Should().Be(pair.Value2);
}
}

View file

@ -0,0 +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.Drawing;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Versioning;
namespace System.Resources.Extensions.Tests.Common;
[ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsDrawingSupported))]
public abstract class SystemDrawingTests<T> : SerializationTest<T> where T : ISerializer
{
[Theory]
[MemberData(nameof(FormatterOptions))]
[SupportedOSPlatform("windows")]
public void Bitmap_RoundTrip(FormatterTypeStyle typeStyle, FormatterAssemblyStyle assemblyMatching)
{
using Bitmap bitmap = new(10, 10);
using var deserialized = RoundTrip(bitmap, typeStyle: typeStyle, assemblyMatching: assemblyMatching);
deserialized.Size.Should().Be(bitmap.Size);
}
[Theory]
[MemberData(nameof(FormatterOptions))]
[SupportedOSPlatform("windows")]
public void Png_RoundTrip(FormatterTypeStyle typeStyle, FormatterAssemblyStyle assemblyMatching)
{
byte[] rawInlineImageBytes = Convert.FromBase64String(TestResources.TestPng);
using Bitmap bitmap = new(new MemoryStream(rawInlineImageBytes));
using var deserialized = RoundTrip(bitmap, typeStyle: typeStyle, assemblyMatching: assemblyMatching);
deserialized.Size.Should().Be(bitmap.Size);
deserialized.RawFormat.Should().Be(bitmap.RawFormat);
}
}

View file

@ -0,0 +1,39 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class BasicISerializableObject : ISerializable
{
private readonly NonSerializablePair<int, string> _data;
public BasicISerializableObject(int value1, string value2)
{
_data = new NonSerializablePair<int, string> { Value1 = value1, Value2 = value2 };
}
protected BasicISerializableObject(SerializationInfo info, StreamingContext context)
{
_data = new NonSerializablePair<int, string> { Value1 = info.GetInt32("Value1"), Value2 = info.GetString("Value2")! };
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Value1", _data.Value1);
info.AddValue("Value2", _data.Value2);
}
public override bool Equals(object? obj)
{
if (obj is not BasicISerializableObject o)
return false;
if (_data is null || o._data is null)
return _data == o._data;
return _data.Value1 == o._data.Value1 && _data.Value2 == o._data.Value2;
}
public override int GetHashCode() => 1;
}

View file

@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class BinaryTreeNode
{
public BinaryTreeNode? Left { get; set; }
public BinaryTreeNode? Right { get; set; }
public string? Value { get; set; }
}

View file

@ -0,0 +1,26 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class BinaryTreeNodeISerializable : BinaryTreeNode, ISerializable
{
public BinaryTreeNodeISerializable() { }
protected BinaryTreeNodeISerializable(SerializationInfo info, StreamingContext context)
{
Value = info.GetString(nameof(Value));
Left = (BinaryTreeNode)info.GetValue(nameof(Left), typeof(BinaryTreeNode))!;
Right = (BinaryTreeNode)info.GetValue(nameof(Right), typeof(BinaryTreeNode))!;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Value", Value);
info.AddValue("Left", Left, typeof(BinaryTreeNode));
info.AddValue("Right", Right, typeof(BinaryTreeNode));
}
}

View file

@ -0,0 +1,46 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class BinaryTreeNodeWithEvents : IDeserializationCallback, BinaryTreeNodeWithEventsBase
{
public string Name { get; set; } = string.Empty;
public BinaryTreeNodeWithEvents? Left { get; set; }
public BinaryTreeNodeWithEvents? Right { get; set; }
public ValueTypeBase? Value { get; set; }
public BinaryTreeNodeWithEvents() { }
[OnDeserialized]
private void OnDeserialized(StreamingContext context) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}p");
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Name), Name);
info.AddValue(nameof(Left), Left);
info.AddValue(nameof(Right), Right);
info.AddValue(nameof(Value), Value);
}
public void OnDeserialization(object? sender) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}i");
}
public class BinaryTreeNodeWithEventsSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) => throw new NotImplementedException();
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector? selector)
{
BinaryTreeNodeWithEvents node = (BinaryTreeNodeWithEvents)obj;
node.Name = info.GetString("<Name>k__BackingField")!;
node.Left = (BinaryTreeNodeWithEvents)info.GetValue("<Left>k__BackingField", typeof(BinaryTreeNodeWithEvents))!;
node.Right = (BinaryTreeNodeWithEvents)info.GetValue("<Right>k__BackingField", typeof(BinaryTreeNodeWithEvents))!;
node.Value = (ValueTypeBase)info.GetValue("<Value>k__BackingField", typeof(ValueTypeBase))!;
BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{node.Name}s");
return node;
}
}

View file

@ -0,0 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common.TestTypes;
public interface BinaryTreeNodeWithEventsBase { }

View file

@ -0,0 +1,44 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class BinaryTreeNodeWithEventsISerializable : ISerializable, IDeserializationCallback, BinaryTreeNodeWithEventsBase
{
public string Name { get; set; } = string.Empty;
public BinaryTreeNodeWithEventsISerializable? Left { get; set; }
public BinaryTreeNodeWithEventsISerializable? Right { get; set; }
public ValueTypeBase? Value { get; set; }
public BinaryTreeNodeWithEventsISerializable() { }
protected BinaryTreeNodeWithEventsISerializable(SerializationInfo serializationInfo, StreamingContext streamingContext)
{
Name = serializationInfo.GetString(nameof(Name))!;
Left = (BinaryTreeNodeWithEventsISerializable?)serializationInfo.GetValue(nameof(Left), typeof(BinaryTreeNodeWithEventsISerializable));
Right = (BinaryTreeNodeWithEventsISerializable?)serializationInfo.GetValue(nameof(Right), typeof(BinaryTreeNodeWithEventsISerializable));
Value = (ValueTypeBase?)serializationInfo.GetValue(nameof(Value), typeof(ValueTypeBase));
BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}s");
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}p");
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Name), Name);
info.AddValue(nameof(Left), Left);
info.AddValue(nameof(Right), Right);
info.AddValue(nameof(Value), Value);
}
public void OnDeserialization(object? sender) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}i");
}
public class BinaryTreeNodeWithEventsTracker
{
public static List<string> DeserializationOrder { get; } = new();
}

View file

@ -0,0 +1,24 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class ClassWithValueISerializable<T> : ISerializable
{
public T? Value { get; set; }
public ClassWithValueISerializable() { }
protected ClassWithValueISerializable(SerializationInfo info, StreamingContext context)
{
Value = (T)info.GetValue(nameof(Value), typeof(T))!;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Value), Value);
}
}

View file

@ -0,0 +1,13 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public sealed class DerivedISerializableWithNonPublicDeserializationCtor : BasicISerializableObject
{
public DerivedISerializableWithNonPublicDeserializationCtor(int value1, string value2) : base(value1, value2) { }
private DerivedISerializableWithNonPublicDeserializationCtor(SerializationInfo info, StreamingContext context) : base(info, context) { }
}

View file

@ -0,0 +1,58 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public struct StructWithObject
{
public object? Value;
}
[Serializable]
public struct StructWithTwoObjects
{
public object? Value;
public object? Value2;
}
[Serializable]
public struct StructWithTwoObjectsISerializable : ISerializable
{
public object? Value;
public object? Value2;
public StructWithTwoObjectsISerializable() { }
private StructWithTwoObjectsISerializable(SerializationInfo info, StreamingContext context)
{
Value = info.GetValue(nameof(Value), typeof(object));
Value2 = info.GetValue(nameof(Value2), typeof(object));
}
public readonly void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Value), typeof(object));
info.AddValue(nameof(Value2), typeof(object));
}
}
[Serializable]
public struct NodeStruct : ISerializable
{
public NodeStruct() { }
private NodeStruct(SerializationInfo info, StreamingContext context)
{
Node = (NodeWithNodeStruct)info.GetValue(nameof(Node), typeof(NodeWithNodeStruct))!;
}
public NodeWithNodeStruct? Node { get; set; }
public readonly void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Node), Node, typeof(NodeWithNodeStruct));
}
}

View file

@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class NodeWithNodeStruct
{
public string? Value { get; set; }
public NodeStruct NodeStruct { get; set; }
}

View file

@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.Serialization;
namespace System.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class NodeWithValueISerializable : ISerializable
{
public NodeWithValueISerializable() { }
protected NodeWithValueISerializable(SerializationInfo info, StreamingContext context)
{
Node = (NodeWithValueISerializable?)info.GetValue(nameof(Node), typeof(NodeWithValueISerializable));
Value = info.GetInt32(nameof(Value));
}
public NodeWithValueISerializable? Node { get; set; }
public int Value { get; set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Node), Node);
info.AddValue(nameof(Value), Value);
}
}

View file

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common.TestTypes;
internal sealed class NonSerializablePair<T1, T2>
{
public T1? Value1;
public T2? Value2;
}

View file

@ -0,0 +1,24 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
internal sealed class NonSerializablePairSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var pair = (NonSerializablePair<int, string>)obj;
info.AddValue("Value1", pair.Value1);
info.AddValue("Value2", pair.Value2);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector? selector)
{
var pair = (NonSerializablePair<int, string>)obj;
pair.Value1 = info.GetInt32("Value1");
pair.Value2 = info.GetString("Value2")!;
return pair;
}
}

View file

@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public class SimpleNode
{
public SimpleNode? Next { get; set; }
}

View file

@ -0,0 +1,23 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public struct StructThatImplementsIDeserializationCallback : ValueTypeBase
{
public StructThatImplementsIDeserializationCallback()
{
}
public string Name { get; set; } = string.Empty;
public BinaryTreeNodeWithEventsBase? Reference { get; set; }
public readonly void OnDeserialization(object? sender) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}i");
[OnDeserialized]
private readonly void OnDeserialized(StreamingContext context) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}p");
}

View file

@ -0,0 +1,26 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public struct StructWithArrayReferenceISerializable<T> : ISerializable
where T : class
{
public nint Value { get; set; }
public T[]? Reference { get; set; }
private StructWithArrayReferenceISerializable(SerializationInfo info, StreamingContext context)
{
Reference = (T[]?)info.GetValue(nameof(Reference), typeof(T));
Value = (nint)info.GetValue(nameof(Value), typeof(nint))!;
}
public readonly void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Reference), Reference);
info.AddValue(nameof(Value), Value, typeof(nint));
}
}

View file

@ -0,0 +1,26 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public struct StructWithReferenceISerializable<T> : ISerializable
where T : class
{
public nint Value { get; set; }
public T? Reference { get; set; }
private StructWithReferenceISerializable(SerializationInfo info, StreamingContext context)
{
Reference = (T?)info.GetValue(nameof(Reference), typeof(T));
Value = (nint)info.GetValue(nameof(Value), typeof(nint))!;
}
public readonly void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Reference), Reference);
info.AddValue(nameof(Value), Value, typeof(nint));
}
}

View file

@ -0,0 +1,27 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.Serialization;
namespace System.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public struct StructWithSelfArrayReferenceISerializable : ISerializable
{
public nint Value { get; set; }
public StructWithSelfArrayReferenceISerializable[]? Array { get; set; }
public StructWithSelfArrayReferenceISerializable() { }
private StructWithSelfArrayReferenceISerializable(SerializationInfo info, StreamingContext context)
{
Array = (StructWithSelfArrayReferenceISerializable[]?)info.GetValue(nameof(Array), typeof(StructWithSelfArrayReferenceISerializable[]))!;
Value = (nint)info.GetValue(nameof(Value), typeof(nint))!;
}
public readonly void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Array), Array);
info.AddValue(nameof(Value), Value, typeof(nint));
}
}

View file

@ -0,0 +1,13 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
public interface ValueTypeBase : IDeserializationCallback
{
public string Name { get; set; }
public BinaryTreeNodeWithEventsBase? Reference { get; set; }
}

View file

@ -0,0 +1,31 @@
// 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.Resources.Extensions.Tests.Common.TestTypes;
[Serializable]
public struct ValueTypeISerializable : ISerializable, ValueTypeBase
{
public string Name { get; set; } = string.Empty;
public BinaryTreeNodeWithEventsBase? Reference { get; set; }
private ValueTypeISerializable(SerializationInfo serializationInfo, StreamingContext streamingContext)
{
Name = serializationInfo.GetString(nameof(Name))!;
Reference = (BinaryTreeNodeWithEventsISerializable?)serializationInfo.GetValue(nameof(Reference), typeof(BinaryTreeNodeWithEventsISerializable));
BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}s");
}
public readonly void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Name), Name);
info.AddValue(nameof(Reference), Reference);
}
[OnDeserialized]
private readonly void OnDeserialized(StreamingContext context) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}p");
public readonly void OnDeserialization(object? sender) => BinaryTreeNodeWithEventsTracker.DeserializationOrder.Add($"{Name}i");
}

View file

@ -0,0 +1,59 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Drawing;
using System.Linq;
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class ArrayTests : Common.ArrayTests<FormattedObjectSerializer>
{
public override void Roundtrip_ArrayContainingArrayAtNonZeroLowerBound()
{
Action action = base.Roundtrip_ArrayContainingArrayAtNonZeroLowerBound;
action.Should().Throw<NotSupportedException>();
}
[Theory]
[MemberData(nameof(StringArray_Parse_Data))]
public void StringArray_Parse(string?[] strings)
{
BinaryFormattedObject format = new(Serialize(strings));
var arrayRecord = (ArrayRecord<string>)format.RootRecord;
arrayRecord.GetArray().Should().BeEquivalentTo(strings);
}
public static TheoryData<string?[]> StringArray_Parse_Data => new()
{
new string?[] { "one", "two" },
new string?[] { "yes", "no", null },
new string?[] { "same", "same", "same" }
};
[Theory]
[MemberData(nameof(PrimitiveArray_Parse_Data))]
public void PrimitiveArray_Parse(Array array)
{
BinaryFormattedObject format = new(Serialize(array));
var arrayRecord = (ArrayRecord)format.RootRecord;
arrayRecord.GetArray(expectedArrayType: array.GetType()).Should().BeEquivalentTo(array);
}
public static TheoryData<Array> PrimitiveArray_Parse_Data => new()
{
new int[] { 1, 2, 3 },
new int[] { 1, 2, 1 },
new float[] { 1.0f, float.NaN, float.PositiveInfinity },
new DateTime[] { DateTime.MaxValue }
};
public static IEnumerable<object[]> Array_TestData => StringArray_Parse_Data.Concat(PrimitiveArray_Parse_Data);
public static TheoryData<Array> Array_UnsupportedTestData => new()
{
new Point[] { new() },
new object[] { new() },
};
}

View file

@ -0,0 +1,42 @@
// 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 BinaryFormatTests;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class BasicObjectTests : Common.BasicObjectTests<FormattedObjectSerializer>
{
private protected override bool SkipOffsetArrays => true;
[Theory]
[MemberData(nameof(SerializableObjects))]
public void BasicObjectsRoundTripAndMatch(object value, TypeSerializableValue[] _)
{
// We need to round trip through the BinaryFormatter as a few objects in tests remove
// serialized data on deserialization.
BinaryFormatter formatter = new();
MemoryStream serialized = new();
formatter.Serialize(serialized, value);
serialized.Position = 0;
object bfdeserialized = formatter.Deserialize(serialized);
serialized.Position = 0;
serialized.SetLength(0);
formatter.Serialize(serialized, bfdeserialized);
serialized.Position = 0;
// Now deserialize with BinaryFormattedObject
object deserialized = Deserialize(serialized);
// And reserialize what we serialized with the BinaryFormatter
MemoryStream deserializedSerialized = new();
formatter.Serialize(deserializedSerialized, deserialized);
deserializedSerialized.Position = 0;
serialized.Position = 0;
// Now compare the two streams to ensure they are identical
deserializedSerialized.Length.Should().Be(serialized.Length);
}
}

View file

@ -0,0 +1,329 @@
// 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.Linq;
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization.BinaryFormat;
using System.Resources.Extensions.Tests.Common;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class BinaryFormattedObjectTests : SerializationTest<FormattedObjectSerializer>
{
[Fact]
public void ReadHeader()
{
BinaryFormattedObject format = new(Serialize("Hello World."));
format.RootRecord.ObjectId.Should().Be(1);
}
[Theory]
[InlineData("Hello there.")]
[InlineData("")]
[InlineData("Embedded\0 Null.")]
public void ReadBinaryObjectString(string testString)
{
BinaryFormattedObject format = new(Serialize(testString));
PrimitiveTypeRecord<string> stringRecord = (PrimitiveTypeRecord<string>)format[1];
stringRecord.ObjectId.Should().Be(1);
stringRecord.Value.Should().Be(testString);
}
[Fact]
public void ReadEmptyHashTable()
{
BinaryFormattedObject format = new(Serialize(new Hashtable()));
ClassRecord systemClass = (ClassRecord)format[1];
VerifyHashTable(systemClass, expectedVersion: 0, expectedHashSize: 3);
ArrayRecord<object> keys = (ArrayRecord<object>)systemClass.GetSerializationRecord("Keys")!;
keys.ObjectId.Should().Be(2);
keys.Length.Should().Be(0);
ArrayRecord<object> values = (ArrayRecord<object>)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.TypeName.FullName.Should().Be("System.Collections.Hashtable");
systemClass.MemberNames.Should().BeEquivalentTo(
[
"LoadFactor",
"Version",
"Comparer",
"HashCodeProvider",
"HashSize",
"Keys",
"Values"
]);
systemClass.GetSingle("LoadFactor").Should().Be(0.72f);
systemClass.GetInt32("Version").Should().Be(expectedVersion);
systemClass.GetRawValue("Comparer").Should().BeNull();
systemClass.GetRawValue("HashCodeProvider").Should().BeNull();
systemClass.GetInt32("HashSize").Should().Be(expectedHashSize);
}
[Fact]
public void ReadHashTableWithStringPair()
{
BinaryFormattedObject format = new(Serialize(new Hashtable()
{
{ "This", "That" }
}));
ClassRecord systemClass = (ClassRecord)format[1];
VerifyHashTable(systemClass, expectedVersion: 1, expectedHashSize: 3);
ArrayRecord<object> keys = (ArrayRecord<object>)format[2];
keys.ObjectId.Should().Be(2);
keys.Length.Should().Be(1);
keys.GetArray().Single().Should().Be("This");
ArrayRecord<object> values = (ArrayRecord<object>)format[3];
values.ObjectId.Should().Be(3);
values.Length.Should().Be(1);
values.GetArray().Single().Should().Be("That");
}
[Fact]
public void ReadHashTableWithRepeatedStrings()
{
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 keys is a string reference.
ArrayRecord<object> array = (ArrayRecord<object>)systemClass.GetSerializationRecord("Keys")!;
array.ObjectId.Should().Be(2);
array.GetArray().Should().BeEquivalentTo(["This", "TheOther", "That"]);
}
[Fact]
public void ReadHashTableWithNullValues()
{
BinaryFormattedObject format = new(Serialize(new Hashtable()
{
{ "Yowza", null },
{ "Youza", null },
{ "Meeza", null }
}));
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 keys is a string reference.
ArrayRecord<object> keys = (ArrayRecord<object>)systemClass.GetSerializationRecord("Keys")!;
keys.ObjectId.Should().Be(2);
keys.GetArray().Should().BeEquivalentTo(new object[] { "Yowza", "Youza", "Meeza" });
ArrayRecord<object?> values = (ArrayRecord<object?>)systemClass.GetSerializationRecord("Values")!;
values.ObjectId.Should().Be(3);
values.GetArray().Should().BeEquivalentTo(new object?[] { null, null, null });
}
[Fact]
public void ReadObject()
{
BinaryFormattedObject format = new(Serialize(new object()));
format[1].RecordType.Should().Be(RecordType.SystemClassWithMembersAndTypes);
}
[Fact]
public void ReadStruct()
{
ValueTuple<int> tuple = new(355);
BinaryFormattedObject format = new(Serialize(tuple));
format[1].RecordType.Should().Be(RecordType.SystemClassWithMembersAndTypes);
}
[Fact]
public void ReadSimpleSerializableObject()
{
BinaryFormattedObject format = new(Serialize(new SimpleSerializableObject()));
ClassRecord @class = (ClassRecord)format.RootRecord;
@class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
@class.ObjectId.Should().Be(1);
@class.TypeName.FullName.Should().Be(typeof(SimpleSerializableObject).FullName);
@class.TypeName.AssemblyName!.FullName.Should().Be(typeof(SimpleSerializableObject).Assembly.FullName);
@class.MemberNames.Should().BeEmpty();
}
[Fact]
public void ReadNestedSerializableObject()
{
BinaryFormattedObject format = new(Serialize(new NestedSerializableObject()));
ClassRecord @class = (ClassRecord)format.RootRecord;
@class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
@class.ObjectId.Should().Be(1);
@class.TypeName.FullName.Should().Be(typeof(NestedSerializableObject).FullName);
@class.TypeName.AssemblyName!.FullName.Should().Be(typeof(NestedSerializableObject).Assembly.FullName);
@class.MemberNames.Should().BeEquivalentTo(["_object", "_meaning"]);
@class.GetRawValue("_object").Should().NotBeNull();
@class.GetInt32("_meaning").Should().Be(42);
}
[Fact]
public void ReadTwoIntObject()
{
BinaryFormattedObject format = new(Serialize(new TwoIntSerializableObject()));
ClassRecord @class = (ClassRecord)format.RootRecord;
@class.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
@class.ObjectId.Should().Be(1);
@class.TypeName.FullName.Should().Be(typeof(TwoIntSerializableObject).FullName);
@class.TypeName.AssemblyName!.FullName.Should().Be(typeof(TwoIntSerializableObject).Assembly.FullName);
@class.MemberNames.Should().BeEquivalentTo(["_value", "_meaning"]);
@class.GetRawValue("_value").Should().Be(1970);
@class.GetInt32("_meaning").Should().Be(42);
}
[Fact]
public void ReadRepeatedNestedObject()
{
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.GetRawValue("_value").Should().Be(1970);
classWithId.GetInt32("_meaning").Should().Be(42);
}
[Fact]
public void ReadPrimitiveArray()
{
int[] input = [10, 9, 8, 7];
BinaryFormattedObject format = new(Serialize(input));
ArrayRecord<int> array = (ArrayRecord<int>)format[1];
array.Length.Should().Be(4);
array.GetArray().Should().BeEquivalentTo(input);
}
[Fact]
public void ReadStringArray()
{
string[] input = ["Monday", "Tuesday", "Wednesday"];
BinaryFormattedObject format = new(Serialize(input));
ArrayRecord<string> array = (ArrayRecord<string>)format[1];
array.ObjectId.Should().Be(1);
array.Length.Should().Be(3);
array.GetArray().Should().BeEquivalentTo(input);
format.RecordMap.Count.Should().Be(4);
}
[Fact]
public void ReadStringArrayWithNulls()
{
string?[] input = ["Monday", null, "Wednesday", null, null, null];
BinaryFormattedObject format = new(Serialize(input));
ArrayRecord<string?> array = (ArrayRecord<string?>)format[1];
array.ObjectId.Should().Be(1);
array.Length.Should().Be(6);
array.GetArray().Should().BeEquivalentTo(input);
}
[Fact]
public void ReadDuplicatedStringArray()
{
string[] input = ["Monday", "Tuesday", "Monday"];
BinaryFormattedObject format = new(Serialize(input));
ArrayRecord<string> array = (ArrayRecord<string>)format[1];
array.ObjectId.Should().Be(1);
array.Length.Should().Be(3);
array.GetArray().Should().BeEquivalentTo(input);
format.RecordMap.Count.Should().Be(3);
}
[Fact]
public void ReadObjectWithNullableObjects()
{
BinaryFormattedObject format = new(Serialize(new ObjectWithNullableObjects()));
ClassRecord classRecord = (ClassRecord)format.RootRecord;
classRecord.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
classRecord.TypeName.AssemblyName!.FullName.Should().Be(typeof(ObjectWithNullableObjects).Assembly.FullName);
}
[Fact]
public void ReadNestedObjectWithNullableObjects()
{
BinaryFormattedObject format = new(Serialize(new NestedObjectWithNullableObjects()));
ClassRecord classRecord = (ClassRecord)format.RootRecord;
classRecord.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
classRecord.TypeName.AssemblyName!.FullName.Should().Be(typeof(NestedObjectWithNullableObjects).Assembly.FullName);
}
[Serializable]
private class SimpleSerializableObject
{
}
#pragma warning disable IDE0052 // Remove unread private members
#pragma warning disable IDE0051 // Remove unused private members
#pragma warning disable CS0414 // Field is assigned but its value is never used
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value null
#pragma warning disable CA1823 // Avoid unused private fields
[Serializable]
private class ObjectWithNullableObjects
{
public object? First;
public object? Second;
public object? Third;
}
[Serializable]
private class NestedObjectWithNullableObjects
{
public ObjectWithNullableObjects? First;
public ObjectWithNullableObjects? Second;
public ObjectWithNullableObjects? Third = new();
}
[Serializable]
private class NestedSerializableObject
{
private readonly SimpleSerializableObject _object = new();
private readonly int _meaning = 42;
}
[Serializable]
private class TwoIntSerializableObject
{
private readonly int _value = 1970;
private readonly int _meaning = 42;
}
[Serializable]
private class RepeatedNestedSerializableObject
{
private readonly TwoIntSerializableObject _first = new();
private readonly TwoIntSerializableObject _second = new();
}
#pragma warning restore IDE0052 // Remove unread private members
#pragma warning restore IDE0051 // Remove unused private members
#pragma warning restore CS0414 // Field is assigned but its value is never used
#pragma warning restore CS0649 // Field is never assigned to, and will always have its default value null
#pragma warning restore CA1823 // Avoid unused private fields
}

View file

@ -0,0 +1,59 @@
// 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.ComponentModel;
using System.Drawing;
using System.Runtime.Serialization;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class BinaryFormattedTypes
{
[Theory]
[MemberData(nameof(BinaryFormattedTypes_TestData))]
public void Types_UseBinaryFormatter(Type type)
{
bool iSerializable = type.IsAssignableTo(typeof(ISerializable));
bool serializable = type.IsSerializable;
Assert.True(iSerializable || serializable, "Type should either implement ISerializable or be marked as [Serializable]");
var converter = TypeDescriptor.GetConverter(type);
Assert.False(
converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(typeof(string)),
"Type should not be convertable back and forth to string.");
Assert.False(
converter.CanConvertFrom(typeof(byte[])) && converter.CanConvertTo(typeof(byte[])),
"Type should not be convertable back and forth to byte[].");
}
public static TheoryData<Type> BinaryFormattedTypes_TestData => new()
{
typeof(Hashtable),
typeof(ArrayList),
typeof(PointF),
typeof(RectangleF),
typeof(List<string>),
};
[Theory]
[MemberData(nameof(NotBinaryFormattedTypes_TestData))]
public void Types_DoNotUseBinaryFormatter(Type type)
{
var converter = TypeDescriptor.GetConverter(type);
Assert.True(
(converter.CanConvertFrom(typeof(string)) && converter.CanConvertTo(typeof(string)))
|| (converter.CanConvertFrom(typeof(byte[])) && converter.CanConvertTo(typeof(byte[]))),
"Type should be convertable back and forth to string or byte[].");
}
public static TheoryData<Type> NotBinaryFormattedTypes_TestData => new()
{
typeof(Point),
typeof(Size),
typeof(SizeF),
typeof(Rectangle),
typeof(Color)
};
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class ComparerTests : Common.ComparerTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class CycleTests : Common.CycleTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class EventOrderTests : Common.EventOrderTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class EventTests : Common.EventTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization.BinaryFormat;
using System.Resources.Extensions.Tests.Common;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class ExceptionTests : SerializationTest<FormattedObjectSerializer>
{
[Fact]
public void NotSupportedException_Parse()
{
BinaryFormattedObject format = new(Serialize(new NotSupportedException()));
var systemClass = (ClassRecord)format.RootRecord;
systemClass.TypeName.FullName.Should().Be(typeof(NotSupportedException).FullName);
systemClass.MemberNames.Should().BeEquivalentTo(
[
"ClassName",
"Message",
"Data",
"InnerException",
"HelpURL",
"StackTraceString",
"RemoteStackTraceString",
"RemoteStackIndex",
"ExceptionMethod",
"HResult",
"Source",
"WatsonBuckets"
]);
systemClass.GetString("ClassName").Should().Be("System.NotSupportedException");
systemClass.GetString("Message").Should().Be("Specified method is not supported.");
systemClass.GetRawValue("Data").Should().BeNull();
systemClass.GetRawValue("InnerException").Should().BeNull();
systemClass.GetRawValue("HelpURL").Should().BeNull();
systemClass.GetRawValue("StackTraceString").Should().BeNull();
systemClass.GetRawValue("RemoteStackTraceString").Should().BeNull();
systemClass.GetInt32("RemoteStackIndex").Should().Be(0);
systemClass.GetRawValue("ExceptionMethod").Should().BeNull();
systemClass.GetInt32("HResult").Should().Be(unchecked((int)0x80131515));
systemClass.GetRawValue("Source").Should().BeNull();
systemClass.GetRawValue("WatsonBuckets").Should().BeNull();
}
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class FieldTests : Common.FieldTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,30 @@
// 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.Formatters;
using System.Resources.Extensions.BinaryFormat;
using System.Resources.Extensions.Tests.Common;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class FormattedObjectSerializer : ISerializer
{
public static object Deserialize(
Stream stream,
SerializationBinder? binder = null,
FormatterAssemblyStyle assemblyMatching = FormatterAssemblyStyle.Simple,
ISurrogateSelector? surrogateSelector = null)
{
BinaryFormattedObject format = new(
stream,
new()
{
Binder = binder,
SurrogateSelector = surrogateSelector,
AssemblyMatching = assemblyMatching
});
return format.Deserialize();
}
}

View file

@ -0,0 +1,212 @@
// 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.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization;
using System.Runtime.Serialization.BinaryFormat;
using System.Resources.Extensions.Tests.Common;
namespace System.Resources.Extensions.Tests.FormattedObject;
#pragma warning disable CS0618 // Type or member is obsolete
public class HashtableTests : SerializationTest<FormattedObjectSerializer>
{
[Fact]
public void HashTable_GetObjectData()
{
Hashtable hashtable = new()
{
{ "This", "That" }
};
// The converter isn't used for this scenario and can be a no-op.
SerializationInfo info = new(typeof(Hashtable), FormatterConverterStub.Instance);
hashtable.GetObjectData(info, default);
info.MemberCount.Should().Be(7);
var enumerator = info.GetEnumerator();
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("LoadFactor");
enumerator.Current.Value.Should().Be(0.72f);
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("Version");
enumerator.Current.Value.Should().Be(1);
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("Comparer");
enumerator.Current.Value.Should().BeNull();
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("HashCodeProvider");
enumerator.Current.Value.Should().BeNull();
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("HashSize");
enumerator.Current.Value.Should().Be(3);
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("Keys");
enumerator.Current.Value.Should().BeEquivalentTo(new object[] { "This" });
enumerator.MoveNext().Should().BeTrue();
enumerator.Current.Name.Should().Be("Values");
enumerator.Current.Value.Should().BeEquivalentTo(new object[] { "That" });
}
[Fact]
public void HashTable_CustomComparer()
{
Hashtable hashtable = new(new CustomHashCodeProvider(), StringComparer.OrdinalIgnoreCase)
{
{ "This", "That" }
};
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<ClassRecord>().Which.TypeName.FullName.Should().Be("System.OrdinalComparer");
systemClass.GetSerializationRecord("HashCodeProvider")!.Should().BeAssignableTo<ClassRecord>().Which.TypeName.FullName.Should().Be("System.Resources.Extensions.Tests.FormattedObject.HashtableTests+CustomHashCodeProvider");
systemClass.GetSerializationRecord("Keys")!.Should().BeAssignableTo<ArrayRecord<object>>();
systemClass.GetSerializationRecord("Values")!.Should().BeAssignableTo<ArrayRecord<object>>();
}
[Serializable]
public class CustomHashCodeProvider : IHashCodeProvider
{
public int GetHashCode(object obj) => HashCode.Combine(obj);
}
public static TheoryData<Hashtable> Hashtables_TestData => new()
{
new Hashtable(),
new Hashtable()
{
{ "This", "That" }
},
new Hashtable()
{
{ "Meaning", 42 }
},
new Hashtable()
{
{ 42, 42 }
},
new Hashtable()
{
{ 42, 42 },
{ 43, 42 }
},
new Hashtable()
{
{ "Hastings", new DateTime(1066, 10, 14) }
},
new Hashtable()
{
{ "Decimal", decimal.MaxValue }
},
new Hashtable()
{
{ "This", "That" },
{ "TheOther", "This" },
{ "That", "This" }
},
new Hashtable()
{
{ "Yowza", null },
{ "Youza", null },
{ "Meeza", null }
},
new Hashtable()
{
{ "Yowza", null },
{ "Youza", "Binks" },
{ "Meeza", null }
},
new Hashtable()
{
{ "Yowza", "Binks" },
{ "Youza", "Binks" },
{ "Meeza", null }
},
new Hashtable()
{
{ decimal.MinValue, decimal.MaxValue },
{ float.MinValue, float.MaxValue },
{ DateTime.MinValue, DateTime.MaxValue },
{ TimeSpan.MinValue, TimeSpan.MaxValue }
},
// Stress the string interning
MakeRepeatedHashtable(50, "Ditto"),
MakeRepeatedHashtable(100, "..."),
// Cross over into ObjectNullMultiple
MakeRepeatedHashtable(255, null),
MakeRepeatedHashtable(256, null),
MakeRepeatedHashtable(257, null)
};
public static TheoryData<Hashtable> Hashtables_UnsupportedTestData => new()
{
new Hashtable()
{
{ new object(), new object() }
},
new Hashtable()
{
{ "Foo", new object() }
},
new Hashtable()
{
{ "Foo", new Point() }
},
new Hashtable()
{
{ "Foo", new PointF() }
},
new Hashtable()
{
{ "Foo", (nint)42 }
},
};
private static Hashtable MakeRepeatedHashtable(int countOfEntries, object? value)
{
Hashtable result = new(countOfEntries);
for (int i = 1; i <= countOfEntries; i++)
{
result.Add($"Entry{i}", value);
}
return result;
}
private sealed class FormatterConverterStub : IFormatterConverter
{
public static IFormatterConverter Instance { get; } = new FormatterConverterStub();
public object Convert(object value, Type type) => throw new NotImplementedException();
public object Convert(object value, TypeCode typeCode) => throw new NotImplementedException();
public bool ToBoolean(object value) => throw new NotImplementedException();
public byte ToByte(object value) => throw new NotImplementedException();
public char ToChar(object value) => throw new NotImplementedException();
public DateTime ToDateTime(object value) => throw new NotImplementedException();
public decimal ToDecimal(object value) => throw new NotImplementedException();
public double ToDouble(object value) => throw new NotImplementedException();
public short ToInt16(object value) => throw new NotImplementedException();
public int ToInt32(object value) => throw new NotImplementedException();
public long ToInt64(object value) => throw new NotImplementedException();
public sbyte ToSByte(object value) => throw new NotImplementedException();
public float ToSingle(object value) => throw new NotImplementedException();
public string? ToString(object value) => throw new NotImplementedException();
public ushort ToUInt16(object value) => throw new NotImplementedException();
public uint ToUInt32(object value) => throw new NotImplementedException();
public ulong ToUInt64(object value) => throw new NotImplementedException();
}
}
#pragma warning restore CS0618 // Type or member is obsolete

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class JaggedArrayTests : Common.JaggedArrayTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,174 @@
// 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.Linq;
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization.BinaryFormat;
using System.Resources.Extensions.Tests.Common;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class ListTests : SerializationTest<FormattedObjectSerializer>
{
[Fact]
public void BinaryFormattedObject_ParseEmptyArrayList()
{
BinaryFormattedObject format = new(Serialize(new ArrayList()));
VerifyArrayList((ClassRecord)format[1]);
format[2].Should().BeAssignableTo<ArrayRecord<object>>();
}
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<ArrayRecord<object>>();
}
[Theory]
[MemberData(nameof(ArrayList_Primitive_Data))]
public void BinaryFormattedObject_ParsePrimitivesArrayList(object value)
{
BinaryFormattedObject format = new(Serialize(new ArrayList()
{
value
}));
ClassRecord listRecord = (ClassRecord)format[1];
VerifyArrayList(listRecord);
ArrayRecord<object> array = (ArrayRecord<object>)format[2];
array.GetArray().Take(listRecord.GetInt32("_size")).Should().BeEquivalentTo(new[] { value });
}
[Fact]
public void BinaryFormattedObject_ParseStringArrayList()
{
BinaryFormattedObject format = new(Serialize(new ArrayList()
{
"JarJar"
}));
ClassRecord listRecord = (ClassRecord)format[1];
VerifyArrayList(listRecord);
ArrayRecord<object> array = (ArrayRecord<object>)format[2];
array.GetArray().Take(listRecord.GetInt32("_size")).ToArray().Should().BeEquivalentTo(new object[] { "JarJar" });
}
public static TheoryData<object> ArrayList_Primitive_Data => new()
{
int.MaxValue,
uint.MaxValue,
long.MaxValue,
ulong.MaxValue,
short.MaxValue,
ushort.MaxValue,
byte.MaxValue,
sbyte.MaxValue,
true,
float.MaxValue,
double.MaxValue,
char.MaxValue,
TimeSpan.MaxValue,
DateTime.MaxValue,
decimal.MaxValue,
};
public static TheoryData<ArrayList> ArrayLists_TestData => new()
{
new ArrayList(),
new ArrayList()
{
int.MaxValue,
uint.MaxValue,
long.MaxValue,
ulong.MaxValue,
short.MaxValue,
ushort.MaxValue,
byte.MaxValue,
sbyte.MaxValue,
true,
float.MaxValue,
double.MaxValue,
char.MaxValue,
TimeSpan.MaxValue,
DateTime.MaxValue,
decimal.MaxValue,
"You betcha"
},
new ArrayList() { "Same", "old", "same", "old" }
};
public static TheoryData<ArrayList> ArrayLists_UnsupportedTestData => new()
{
new ArrayList()
{
new object(),
},
new ArrayList()
{
int.MaxValue,
new Point()
}
};
[Fact]
public void BinaryFormattedObject_ParseEmptyIntList()
{
BinaryFormattedObject format = new(Serialize(new List<int>()));
ClassRecord classInfo = (ClassRecord)format[1];
classInfo.RecordType.Should().Be(RecordType.SystemClassWithMembersAndTypes);
// Note that T types are serialized as the mscorlib type.
classInfo.TypeName.FullName.Should().Be(
"System.Collections.Generic.List`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]");
classInfo.MemberNames.Should().BeEquivalentTo(
[
"_items",
// This is something that wouldn't be needed if List<T> implemented ISerializable. If we format
// we can save any extra unused array spots.
"_size",
// It is a bit silly that _version gets serialized, it's only use is as a check to see if
// the collection is modified while it is being enumerated.
"_version"
]);
classInfo.GetSerializationRecord("_items").Should().BeAssignableTo<ArrayRecord<int>>();
classInfo.GetInt32("_size").Should().Be(0);
classInfo.GetInt32("_version").Should().Be(0);
ArrayRecord<int> array = (ArrayRecord<int>)format[2];
array.Length.Should().Be(0);
}
[Fact]
public void BinaryFormattedObject_ParseEmptyStringList()
{
BinaryFormattedObject format = new(Serialize(new List<string>()));
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<ArrayRecord<string>>();
ArrayRecord<string> array = (ArrayRecord<string>)format[2];
array.Length.Should().Be(0);
}
public static TheoryData<IList> Lists_UnsupportedTestData => new()
{
new List<object>(),
new List<nint>(),
new List<(int, int)>()
};
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class MultidimensionalArrayTests : Common.MultidimensionalArrayTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class ObjectReferenceTests : Common.ObjectReferenceTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class PrimitiveTests : Common.PrimitiveTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,122 @@
// 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.Resources.Extensions.BinaryFormat;
using System.Resources.Extensions.Tests.Common;
using PrimitiveType = System.Runtime.Serialization.BinaryFormat.PrimitiveType;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class PrimitiveTypeTests : SerializationTest<FormattedObjectSerializer>
{
public static TheoryData<byte, object> RoundTrip_Data => new()
{
{ (byte)PrimitiveType.Int64, 0L },
{ (byte)PrimitiveType.Int64, -1L },
{ (byte)PrimitiveType.Int64, 1L },
{ (byte)PrimitiveType.Int64, long.MaxValue },
{ (byte)PrimitiveType.Int64, long.MinValue },
{ (byte)PrimitiveType.UInt64, ulong.MaxValue },
{ (byte)PrimitiveType.UInt64, ulong.MinValue },
{ (byte)PrimitiveType.Int32, 0 },
{ (byte)PrimitiveType.Int32, -1 },
{ (byte)PrimitiveType.Int32, 1 },
{ (byte)PrimitiveType.Int32, int.MaxValue },
{ (byte)PrimitiveType.Int32, int.MinValue },
{ (byte)PrimitiveType.UInt32, uint.MaxValue },
{ (byte)PrimitiveType.UInt32, uint.MinValue },
{ (byte)PrimitiveType.Int16, (short)0 },
{ (byte)PrimitiveType.Int16, (short)-1 },
{ (byte)PrimitiveType.Int16, (short)1 },
{ (byte)PrimitiveType.Int16, short.MaxValue },
{ (byte)PrimitiveType.Int16, short.MinValue },
{ (byte)PrimitiveType.UInt16, ushort.MaxValue },
{ (byte)PrimitiveType.UInt16, ushort.MinValue },
{ (byte)PrimitiveType.SByte, (sbyte)0 },
{ (byte)PrimitiveType.SByte, (sbyte)-1 },
{ (byte)PrimitiveType.SByte, (sbyte)1 },
{ (byte)PrimitiveType.SByte, sbyte.MaxValue },
{ (byte)PrimitiveType.SByte, sbyte.MinValue },
{ (byte)PrimitiveType.Byte, byte.MinValue },
{ (byte)PrimitiveType.Byte, byte.MaxValue },
{ (byte)PrimitiveType.Boolean, true },
{ (byte)PrimitiveType.Boolean, false },
{ (byte)PrimitiveType.Single, 0.0f },
{ (byte)PrimitiveType.Single, -1.0f },
{ (byte)PrimitiveType.Single, 1.0f },
{ (byte)PrimitiveType.Single, float.MaxValue },
{ (byte)PrimitiveType.Single, float.MinValue },
{ (byte)PrimitiveType.Single, float.NegativeZero },
{ (byte)PrimitiveType.Single, float.NaN },
{ (byte)PrimitiveType.Single, float.NegativeInfinity },
{ (byte)PrimitiveType.Double, 0.0d },
{ (byte)PrimitiveType.Double, -1.0d },
{ (byte)PrimitiveType.Double, 1.0d },
{ (byte)PrimitiveType.Double, double.MaxValue },
{ (byte)PrimitiveType.Double, double.MinValue },
{ (byte)PrimitiveType.Double, double.NegativeZero },
{ (byte)PrimitiveType.Double, double.NaN },
{ (byte)PrimitiveType.Double, double.NegativeInfinity },
{ (byte)PrimitiveType.TimeSpan, TimeSpan.MinValue },
{ (byte)PrimitiveType.TimeSpan, TimeSpan.MaxValue },
{ (byte)PrimitiveType.DateTime, DateTime.MinValue },
{ (byte)PrimitiveType.DateTime, DateTime.MaxValue },
};
[Theory]
[MemberData(nameof(Primitive_Data))]
public void PrimitiveTypeMemberName(object value)
{
BinaryFormattedObject format = new(Serialize(value));
VerifyNonGeneric(value, format[1]);
}
[Theory]
[MemberData(nameof(Primitive_Data))]
[MemberData(nameof(Primitive_ExtendedData))]
public void BinaryFormattedObject_ReadPrimitive(object value)
{
BinaryFormattedObject formattedObject = new(Serialize(value));
formattedObject.RootRecord.GetMemberPrimitiveTypedValue().Should().Be(value);
}
public static TheoryData<object> Primitive_Data => new()
{
int.MaxValue,
uint.MaxValue,
long.MaxValue,
ulong.MaxValue,
short.MaxValue,
ushort.MaxValue,
byte.MaxValue,
sbyte.MaxValue,
true,
float.MaxValue,
double.MaxValue,
char.MaxValue
};
public static TheoryData<object> Primitive_ExtendedData => new()
{
TimeSpan.MaxValue,
DateTime.MaxValue,
decimal.MaxValue,
(nint)1918,
(nuint)2020,
"Roundabout"
};
private static void VerifyNonGeneric(object value, SerializationRecord record)
{
typeof(PrimitiveTypeTests)
.GetMethod(nameof(VerifyGeneric), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!
.MakeGenericMethod(value.GetType()).Invoke(null, [value, record]);
}
private static void VerifyGeneric<T>(T value, SerializationRecord record) where T : unmanaged
{
record.As<PrimitiveTypeRecord<T>>().Value.Should().Be(value);
}
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class StressTests : Common.StressTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.Resources.Extensions.Tests.FormattedObject;
public class SurrogateTests : Common.SurrogateTests<FormattedObjectSerializer>
{
}

View file

@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Drawing;
using System.Resources.Extensions.BinaryFormat;
using System.Runtime.Serialization.BinaryFormat;
namespace System.Resources.Extensions.Tests.FormattedObject;
public class SystemDrawingTests : Common.SystemDrawingTests<FormattedObjectSerializer>
{
[Fact]
public void PointF_Parse()
{
PointF input = new() { X = 123.5f, Y = 456.1f };
BinaryFormattedObject format = new(Serialize(input));
ClassRecord classInfo = (ClassRecord)format.RootRecord;
classInfo.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
classInfo.ObjectId.Should().Be(1);
classInfo.TypeName.FullName.Should().Be("System.Drawing.PointF");
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);
}
[Fact]
public void RectangleF_Parse()
{
RectangleF input = new(x: 123.5f, y: 456.1f, width: 100.25f, height: 200.75f);
BinaryFormattedObject format = new(Serialize(input));
ClassRecord classInfo = (ClassRecord)format.RootRecord;
classInfo.RecordType.Should().Be(RecordType.ClassWithMembersAndTypes);
classInfo.ObjectId.Should().Be(1);
classInfo.TypeName.FullName.Should().Be("System.Drawing.RectangleF");
classInfo.MemberNames.Should().BeEquivalentTo(["x", "y", "width", "height"]);
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<object> SystemDrawing_TestData => new()
{
new PointF(),
new RectangleF()
};
}

View file

@ -0,0 +1,9 @@
// 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;
global using System.Collections.Generic;
global using System.Diagnostics;
global using System.IO;
global using Xunit;
global using FluentAssertions;

View file

@ -0,0 +1,136 @@
// 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;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
namespace BinaryFormatTests;
public static class BinaryFormatterHelpers
{
internal static T Clone<T>(T obj,
ISerializationSurrogate? surrogate = null,
FormatterAssemblyStyle assemblyFormat = FormatterAssemblyStyle.Full,
FormatterTypeStyle typeFormat = FormatterTypeStyle.TypesAlways)
{
ArgumentNullException.ThrowIfNull(obj);
BinaryFormatter f;
if (surrogate is null)
{
f = new BinaryFormatter();
}
else
{
var c = new StreamingContext();
var s = new SurrogateSelector();
s.AddSurrogate(obj.GetType(), c, surrogate);
f = new BinaryFormatter(s, c);
}
f.AssemblyFormat = assemblyFormat;
f.TypeFormat = typeFormat;
using (var s = new MemoryStream())
{
f.Serialize(s, obj);
Assert.NotEqual(0, s.Position);
s.Position = 0;
return (T)f.Deserialize(s);
}
}
public static void AssertExceptionDeserializationFails<T>() where T : Exception
{
AssertExceptionDeserializationFails(typeof(T));
}
public static void AssertExceptionDeserializationFails(Type exceptionType)
{
// .NET Core and .NET Native throw PlatformNotSupportedExceptions when deserializing many exceptions.
// The .NET Framework supports full deserialization.
if (PlatformDetection.IsNetFramework)
{
return;
}
// Construct a valid serialization payload. This is necessary as most constructors call
// the base constructor before throwing a PlatformNotSupportedException, and the base
// constructor validates the SerializationInfo passed.
var info = new SerializationInfo(exceptionType, new FormatterConverter());
info.AddValue("ClassName", "ClassName");
info.AddValue("Message", "Message");
info.AddValue("InnerException", null);
info.AddValue("HelpURL", null);
info.AddValue("StackTraceString", null);
info.AddValue("RemoteStackTraceString", null);
info.AddValue("RemoteStackIndex", 5);
info.AddValue("HResult", 5);
info.AddValue("Source", null);
info.AddValue("ExceptionMethod", null);
// Serialization constructors are of the form .ctor(SerializationInfo, StreamingContext).
ConstructorInfo? constructor = null;
foreach (ConstructorInfo c in exceptionType.GetTypeInfo().DeclaredConstructors)
{
ParameterInfo[] parameters = c.GetParameters();
if (parameters.Length == 2 && parameters[0].ParameterType == typeof(SerializationInfo) && parameters[1].ParameterType == typeof(StreamingContext))
{
constructor = c;
break;
}
}
// .NET Native prevents reflection on private constructors on non-serializable types.
if (constructor is null)
{
return;
}
Exception ex = Assert.Throws<TargetInvocationException>(() => constructor.Invoke([info, new StreamingContext()]));
Assert.IsType<PlatformNotSupportedException>(ex.InnerException);
}
public static byte[] ToByteArray(
object obj,
FormatterAssemblyStyle assemblyStyle = FormatterAssemblyStyle.Full)
{
BinaryFormatter bf = new()
{
AssemblyFormat = assemblyStyle
};
using MemoryStream ms = new();
bf.Serialize(ms, obj);
return ms.ToArray();
}
public static string ToBase64String(
object obj,
FormatterAssemblyStyle assemblyStyle = FormatterAssemblyStyle.Full)
{
byte[] raw = ToByteArray(obj, assemblyStyle);
return Convert.ToBase64String(raw);
}
public static object FromByteArray(byte[] raw,
FormatterAssemblyStyle assemblyStyle = FormatterAssemblyStyle.Full)
{
BinaryFormatter binaryFormatter = new()
{
AssemblyFormat = assemblyStyle
};
using var serializedStream = new MemoryStream(raw);
return binaryFormatter.Deserialize(serializedStream);
}
public static object FromBase64String(string base64Str,
FormatterAssemblyStyle assemblyStyle = FormatterAssemblyStyle.Full)
{
byte[] raw = Convert.FromBase64String(base64Str);
return FromByteArray(raw, assemblyStyle);
}
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,352 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
namespace BinaryFormatTests.FormatterTests;
public partial class BinaryFormatterTests
{
[Theory]
[MemberData(nameof(SerializableObjects_MemberData))]
public void ValidateAgainstBlobs(object obj, TypeSerializableValue[] blobs)
=> ValidateAndRoundtrip(obj, blobs, isEqualityComparer: false);
[Theory]
[MemberData(nameof(SerializableEqualityComparers_MemberData))]
public void ValidateEqualityComparersAgainstBlobs(object obj, TypeSerializableValue[] blobs)
=> ValidateAndRoundtrip(obj, blobs, isEqualityComparer: true);
private static void ValidateAndRoundtrip(object obj, TypeSerializableValue[] blobs, bool isEqualityComparer)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj), "The serializable object must not be null");
}
if (blobs is null || blobs.Length == 0)
{
throw new ArgumentOutOfRangeException($"Type {obj} has no blobs to deserialize and test equality against. Blob: " +
BinaryFormatterHelpers.ToBase64String(obj, FormatterAssemblyStyle.Full));
}
// Check if the passed in value in a serialization entry is assignable by the passed in type.
if (obj is ISerializable serializable)
{
CheckObjectTypeIntegrity(serializable);
}
// TimeZoneInfo objects have three properties (DisplayName, StandardName, DaylightName)
// that are localized. Since the blobs were generated from the invariant culture, they
// will have English strings embedded. Thus, we can only test them against English
// language cultures or the invariant culture.
if (obj is TimeZoneInfo && (
CultureInfo.CurrentUICulture.TwoLetterISOLanguageName != "en" ||
CultureInfo.CurrentUICulture.Name.Length != 0))
{
return;
}
SanityCheckBlob(obj, blobs);
if (!isEqualityComparer)
{
// Test moved to BasicObjectTests
return;
}
// ReflectionTypeLoadException and LicenseException aren't deserializable from Desktop --> Core.
// Therefore we remove the second blob which is the one from Desktop.
if (obj is ReflectionTypeLoadException or LicenseException)
{
var tmpList = new List<TypeSerializableValue>(blobs);
tmpList.RemoveAt(1);
int index = tmpList.FindIndex(b => b.Platform.ToString().StartsWith("netfx", StringComparison.Ordinal));
if (index >= 0)
tmpList.RemoveAt(index);
blobs = [.. tmpList];
}
// We store our framework blobs in index 1
int platformBlobIndex = blobs.GetPlatformIndex();
for (int i = 0; i < blobs.Length; i++)
{
// Check if the current blob is from the current running platform.
bool isSamePlatform = i == platformBlobIndex;
ValidateEqualityComparer(BinaryFormatterHelpers.FromBase64String(blobs[i].Base64Blob, FormatterAssemblyStyle.Simple));
ValidateEqualityComparer(BinaryFormatterHelpers.FromBase64String(blobs[i].Base64Blob, FormatterAssemblyStyle.Full));
}
}
[Fact]
public void RegexExceptionSerializable()
{
try
{
#pragma warning disable RE0001 // Regex issue: {0}
#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
_ = new Regex("*"); // parsing "*" - Quantifier {x,y} following nothing.
#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
#pragma warning restore RE0001 // Regex issue: {0}
}
catch (ArgumentException ex)
{
Assert.Equal("RegexParseException", ex.GetType().Name);
ArgumentException clone = BinaryFormatterHelpers.Clone(ex);
Assert.IsType<ArgumentException>(clone);
}
}
[Fact]
public void ArraySegmentDefaultCtor()
{
// This is workaround for Xunit bug which tries to pretty print test case name and enumerate this object.
// When inner array is not initialized it throws an exception when this happens.
object obj = new ArraySegment<int>();
string corefxBlob = "AAEAAAD/////AQAAAAAAAAAEAQAAAHJTeXN0ZW0uQXJyYXlTZWdtZW50YDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0DAAAABl9hcnJheQdfb2Zmc2V0Bl9jb3VudAcAAAgICAoAAAAAAAAAAAs=";
string netfxBlob = "AAEAAAD/////AQAAAAAAAAAEAQAAAHJTeXN0ZW0uQXJyYXlTZWdtZW50YDFbW1N5c3RlbS5JbnQzMiwgbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0DAAAABl9hcnJheQdfb2Zmc2V0Bl9jb3VudAcAAAgICAoAAAAAAAAAAAs=";
EqualityExtensions.CheckEquals(obj, BinaryFormatterHelpers.FromBase64String(corefxBlob, FormatterAssemblyStyle.Full), isSamePlatform: true);
EqualityExtensions.CheckEquals(obj, BinaryFormatterHelpers.FromBase64String(netfxBlob, FormatterAssemblyStyle.Full), isSamePlatform: true);
}
[Theory]
[MemberData(nameof(NonSerializableTypes_MemberData))]
public void ValidateNonSerializableTypes(object obj, FormatterAssemblyStyle assemblyFormat, FormatterTypeStyle typeFormat)
{
var f = new BinaryFormatter()
{
AssemblyFormat = assemblyFormat,
TypeFormat = typeFormat
};
using var s = new MemoryStream();
Assert.Throws<SerializationException>(() => f.Serialize(s, obj));
}
[Fact]
public void SerializeDeserialize_InvalidArguments_ThrowsException()
{
var f = new BinaryFormatter();
Assert.Throws<ArgumentNullException>(() => f.Serialize(null!, new object()));
Assert.Throws<ArgumentNullException>(() => f.Deserialize(null!));
Assert.Throws<SerializationException>(() => f.Deserialize(new MemoryStream())); // seekable, 0-length
}
[Fact]
public void ObjectReference_RealObjectSerialized()
{
var obj = new ObjRefReturnsObj { Real = 42 };
object real = BinaryFormatterHelpers.Clone<object>(obj);
Assert.Equal(42, real);
}
[Fact]
public void Deserialize_EndOfStream_ThrowsException()
{
var f = new BinaryFormatter();
var s = new MemoryStream();
f.Serialize(s, 1024);
for (long i = s.Length - 1; i >= 0; i--)
{
s.Position = 0;
byte[] data = new byte[i];
Assert.Equal(data.Length, s.Read(data, 0, data.Length));
Assert.Throws<SerializationException>(() => f.Deserialize(new MemoryStream(data)));
}
}
private static void ValidateEqualityComparer(object obj)
{
Type objType = obj.GetType();
Assert.True(objType.IsGenericType, $"Type `{objType.FullName}` must be generic.");
Assert.Equal("System.Collections.Generic.ObjectEqualityComparer`1", objType.GetGenericTypeDefinition().FullName);
Assert.Equal(obj.GetType().GetGenericArguments()[0], objType.GetGenericArguments()[0]);
}
private static void CheckObjectTypeIntegrity(ISerializable serializable)
{
SerializationInfo testData = new(serializable.GetType(), new FormatterConverter());
serializable.GetObjectData(testData, new StreamingContext(StreamingContextStates.Other));
foreach (SerializationEntry entry in testData)
{
if (entry.Value is not null)
{
Assert.IsAssignableFrom(entry.ObjectType, entry.Value);
}
}
}
private static void SanityCheckBlob(object obj, TypeSerializableValue[] blobs)
{
// These types are unstable during serialization and produce different blobs.
string name = obj.GetType().FullName!;
if (obj is WeakReference<Point>
|| obj is System.Collections.Specialized.HybridDictionary
|| obj is Color
|| name == "System.Collections.SortedList+SyncSortedList"
// Due to non-deterministic field ordering the types below will fail when using IL Emit-based Invoke.
// The types above may also be failing for the same reason.
// Remove these cases once https://github.com/dotnet/runtime/issues/46272 is fixed.
|| name == "System.Collections.Comparer"
|| name == "System.Collections.Hashtable"
|| name == "System.Collections.SortedList"
|| name == "System.Collections.Specialized.ListDictionary"
|| name == "System.CultureAwareComparer"
|| name == "System.Globalization.CompareInfo"
|| name == "System.Net.Cookie"
|| name == "System.Net.CookieCollection"
|| name == "System.Net.CookieContainer")
{
return;
}
if (obj is DataSet or DataTable)
{
// The blobs may not be identical (the output is not deterministic), but still valid.
return;
}
// The blobs aren't identical because of different implementations on Unix vs. Windows.
if (obj is Bitmap or Icon or Metafile)
{
return;
}
// In most cases exceptions in Core have a different layout than in Desktop,
// therefore we are skipping the string comparison of the blobs.
if (obj is Exception)
{
return;
}
// Check if runtime generated blob is the same as the stored one
int frameworkBlobNumber = blobs.GetPlatformIndex();
if (frameworkBlobNumber < blobs.Length)
{
string runtimeBlob = BinaryFormatterHelpers.ToBase64String(obj, FormatterAssemblyStyle.Full);
string storedComparableBlob = CreateComparableBlobInfo(blobs[frameworkBlobNumber].Base64Blob);
string runtimeComparableBlob = CreateComparableBlobInfo(runtimeBlob);
if (storedComparableBlob != runtimeComparableBlob)
{
Debug.WriteLine($"NEW BLOB {obj.GetType().FullName}: {runtimeBlob}");
if (Debugger.IsAttached)
{
Debugger.Break();
}
}
Assert.True(storedComparableBlob == runtimeComparableBlob, $"""
The stored blob with index {frameworkBlobNumber} for type {obj.GetType().FullName} is outdated and needs to be updated.
-------------------- Stored blob ---------------------
Encoded: {blobs[frameworkBlobNumber].Base64Blob}
Decoded: {storedComparableBlob}
--------------- Runtime generated blob ---------------
Encoded: {runtimeBlob}
Decoded: {runtimeComparableBlob}
""");
}
}
private static string CreateComparableBlobInfo(string base64Blob)
{
string lineSeparator = ((char)0x2028).ToString();
string paragraphSeparator = ((char)0x2029).ToString();
byte[] data = Convert.FromBase64String(base64Blob);
base64Blob = Encoding.UTF8.GetString(data);
#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
return Regex.Replace(base64Blob, @"Version=\d.\d.\d.\d.", "Version=0.0.0.0", RegexOptions.Multiline)
// Ignore the old Test key and Open public keys.
.Replace("PublicKeyToken=cc7b13ffcd2ddd51", "PublicKeyToken=null")
.Replace("PublicKeyToken=9d77cc7ad39b68eb", "PublicKeyToken=null")
.Replace("\r\n", string.Empty)
.Replace("\n", string.Empty)
.Replace("\r", string.Empty)
.Replace(lineSeparator, string.Empty)
.Replace(paragraphSeparator, string.Empty);
#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'.
}
private static (int blobs, int foundBlobs, int updatedBlobs) UpdateCoreTypeBlobs(string testDataFilePath, string[] blobs)
{
// Replace existing test data blobs with updated ones
string[] testDataLines = File.ReadAllLines(testDataFilePath);
List<string> updatedTestDataLines = [];
int numberOfBlobs = 0;
int numberOfFoundBlobs = 0;
int numberOfUpdatedBlobs = 0;
for (int i = 0; i < testDataLines.Length; i++)
{
string testDataLine = testDataLines[i];
if (!testDataLine.Trim().StartsWith("yield", StringComparison.Ordinal) || numberOfBlobs >= blobs.Length)
{
updatedTestDataLines.Add(testDataLine);
continue;
}
string? replacement;
string? pattern;
if (PlatformDetection.IsNetFramework)
{
pattern = ", \"AAEAAAD[^\"]+\"(?!,)";
replacement = $", \"{blobs[numberOfBlobs]}\"";
}
else
{
pattern = "\"AAEAAAD[^\"]+\",";
replacement = $"\"{blobs[numberOfBlobs]}\",";
}
Regex regex = new(pattern);
if (regex.IsMatch(testDataLine))
{
numberOfFoundBlobs++;
}
string updatedLine = regex.Replace(testDataLine, replacement);
if (testDataLine != updatedLine)
{
numberOfUpdatedBlobs++;
}
testDataLine = updatedLine;
updatedTestDataLines.Add(testDataLine);
numberOfBlobs++;
}
// Check if all blobs were recognized and write updates to file
Assert.Equal(numberOfBlobs, blobs.Length);
File.WriteAllLines(testDataFilePath, updatedTestDataLines);
return (numberOfBlobs, numberOfFoundBlobs, numberOfUpdatedBlobs);
}
public struct MyStruct
{
public int A;
}
}

View file

@ -0,0 +1,44 @@
// 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.Linq;
namespace Xunit;
/// <summary>
/// Theory data for tuple enumeration.
/// </summary>
public class EnumerableTupleTheoryData<T1, T2> : IReadOnlyCollection<object[]>
where T1 : notnull
where T2 : notnull
{
private readonly IEnumerable<(T1, T2)> _data;
public int Count => _data.Count();
public EnumerableTupleTheoryData(IEnumerable<(T1, T2)> data) => _data = data;
public IEnumerator<object[]> GetEnumerator() =>
_data.Select(i => new object[] { i.Item1, i.Item2 }).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <inheritdoc cref="EnumerableTupleTheoryData{T1, T2}"/>
public class EnumerableTupleTheoryData<T1, T2, T3> : IReadOnlyCollection<object[]>
where T1 : notnull
where T2 : notnull
where T3 : notnull
{
private readonly IEnumerable<(T1, T2, T3)> _data;
public int Count => _data.Count();
public EnumerableTupleTheoryData(IEnumerable<(T1, T2, T3)> data) => _data = data;
public IEnumerator<object[]> GetEnumerator() =>
_data.Select(i => new object[] { i.Item1, i.Item2, i.Item3 }).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View file

@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace BinaryFormatTests.FormatterTests;
internal static class PlatformExtensions
{
public static int GetPlatformIndex(this TypeSerializableValue[] blobs)
{
List<TypeSerializableValue> blobList = [.. blobs];
int index;
// Check if a specialized blob for >=netcoreapp3.0 is present and return if found.
index = blobList.FindIndex(b => b.Platform == TargetFrameworkMoniker.netcoreapp30);
if (index >= 0)
{
return index;
}
// Check if a specialized blob for netcoreapp2.1 is present and return if found.
index = blobList.FindIndex(b => b.Platform == TargetFrameworkMoniker.netcoreapp21);
if (index >= 0)
{
return index;
}
// If no newer blob for >=netcoreapp2.1 is present use existing one.
// If no netcoreapp blob is present then -1 will be returned.
return blobList.FindIndex(b => b.Platform == TargetFrameworkMoniker.netcoreapp20);
}
}

View file

@ -0,0 +1,451 @@
// 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.CompilerServices;
using System.Runtime.Serialization;
namespace BinaryFormatTests.FormatterTests;
[Serializable]
public sealed class SealedObjectWithIntStringFields
{
public int Member1;
public string? Member2;
public string? Member3;
public override bool Equals(object? obj)
{
if (obj is not SealedObjectWithIntStringFields o)
return false;
return
EqualityComparer<int>.Default.Equals(Member1, o.Member1) &&
EqualityComparer<string>.Default.Equals(Member2, o.Member2) &&
EqualityComparer<string>.Default.Equals(Member3, o.Member3);
}
public override int GetHashCode() => 1;
}
[Serializable]
public class ObjectWithIntStringUShortUIntULongAndCustomObjectFields
{
public int Member1;
public string? Member2;
public string? _member3;
public SealedObjectWithIntStringFields? Member4;
public SealedObjectWithIntStringFields? Member4shared;
public SealedObjectWithIntStringFields? Member5;
public string? Member6;
public string? str1;
public string? str2;
public string? str3;
public string? str4;
public ushort u16;
public uint u32;
public ulong u64;
public override bool Equals(object? obj)
{
if (obj is not ObjectWithIntStringUShortUIntULongAndCustomObjectFields o)
return false;
return
EqualityComparer<int>.Default.Equals(Member1, o.Member1) &&
EqualityComparer<string>.Default.Equals(Member2, o.Member2) &&
EqualityComparer<string>.Default.Equals(_member3, o._member3) &&
EqualityComparer<SealedObjectWithIntStringFields>.Default.Equals(Member4, o.Member4) &&
EqualityComparer<SealedObjectWithIntStringFields>.Default.Equals(Member4shared, o.Member4shared) &&
EqualityComparer<SealedObjectWithIntStringFields>.Default.Equals(Member5, o.Member5) &&
EqualityComparer<string>.Default.Equals(Member6, o.Member6) &&
EqualityComparer<string>.Default.Equals(str1, o.str1) &&
EqualityComparer<string>.Default.Equals(str2, o.str2) &&
EqualityComparer<string>.Default.Equals(str3, o.str3) &&
EqualityComparer<string>.Default.Equals(str4, o.str4) &&
EqualityComparer<ushort>.Default.Equals(u16, o.u16) &&
EqualityComparer<uint>.Default.Equals(u16, o.u16) &&
EqualityComparer<ulong>.Default.Equals(u64, o.u64) &&
// make sure shared members are the same object
ReferenceEquals(Member4, Member4shared) &&
ReferenceEquals(o.Member4, o.Member4shared);
}
public override int GetHashCode() => 1;
}
[Serializable]
public class Point : IComparable<Point>, IEquatable<Point>
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
public int CompareTo(object obj) => CompareTo(obj as Point);
public int CompareTo(Point? other)
{
return other is null ? 1 : 0;
}
public override bool Equals(object? obj) => Equals(obj as Point);
public bool Equals(Point? other)
{
return other is not null &&
X == other.X &&
Y == other.Y;
}
public override int GetHashCode() => 1;
}
[Serializable]
public class Tree<T>
{
public Tree(T value, Tree<T> left, Tree<T> right)
{
Value = value;
Left = left;
Right = right;
}
public T Value { get; }
public Tree<T> Left { get; }
public Tree<T> Right { get; }
public override bool Equals(object? obj)
{
if (obj is not Tree<T> o)
return false;
return
EqualityComparer<T>.Default.Equals(Value, o.Value) &&
EqualityComparer<Tree<T>>.Default.Equals(Left, o.Left) &&
EqualityComparer<Tree<T>>.Default.Equals(Right, o.Right) &&
// make sure the branches aren't actually the exact same object
(Left is null || !ReferenceEquals(Left, o.Left)) &&
(Right is null || !ReferenceEquals(Right, o.Right));
}
public override int GetHashCode() => 1;
}
[Serializable]
public class Graph<T>
{
public T? Value;
public Graph<T>[]? Links;
public override bool Equals(object? obj)
{
if (obj is not Graph<T> o)
return false;
var toExplore = new Stack<KeyValuePair<Graph<T>, Graph<T>>>();
toExplore.Push(new KeyValuePair<Graph<T>, Graph<T>>(this, o));
var seen1 = new HashSet<Graph<T>>(ReferenceEqualityComparer.Instance);
while (toExplore.Count > 0)
{
var cur = toExplore.Pop();
if (!seen1.Add(cur.Key))
{
continue;
}
if (!EqualityComparer<T>.Default.Equals(cur.Key.Value, cur.Value.Value))
{
return false;
}
if (Links is null || o.Links is null)
{
if (Links != o.Links)
return false;
continue;
}
if (Links.Length != o.Links.Length)
{
return false;
}
for (int i = 0; i < Links.Length; i++)
{
toExplore.Push(new KeyValuePair<Graph<T>, Graph<T>>(Links[i], o.Links[i]));
}
}
return true;
}
public override int GetHashCode() => 1;
}
[Serializable]
public sealed class ObjectWithArrays
{
public int[]? IntArray;
public string[]? StringArray;
public Tree<int>[]? TreeArray;
public byte[]? ByteArray;
public int[][]? JaggedArray;
public int[,]? MultiDimensionalArray;
public override bool Equals(object? obj)
{
if (obj is not ObjectWithArrays o)
return false;
return
EqualityHelpers.ArraysAreEqual(IntArray, o.IntArray) &&
EqualityHelpers.ArraysAreEqual(StringArray, o.StringArray) &&
EqualityHelpers.ArraysAreEqual(TreeArray, o.TreeArray) &&
EqualityHelpers.ArraysAreEqual(ByteArray, o.ByteArray) &&
EqualityHelpers.ArraysAreEqual(JaggedArray, o.JaggedArray) &&
EqualityHelpers.ArraysAreEqual(MultiDimensionalArray, o.MultiDimensionalArray);
}
public override int GetHashCode() => 1;
}
[Serializable]
public enum Colors
{
Red,
Orange,
Yellow,
Green,
Blue,
Purple
}
[Serializable] public enum ByteEnum : byte { }
[Serializable] public enum SByteEnum : sbyte { }
[Serializable] public enum Int16Enum : short { }
[Serializable] public enum UInt16Enum : ushort { }
[Serializable] public enum Int32Enum : int { }
[Serializable] public enum UInt32Enum : uint { }
[Serializable] public enum Int64Enum : long { }
[Serializable] public enum UInt64Enum : ulong { }
public struct NonSerializableStruct
{
public int Value;
}
public class NonSerializableClass
{
public int Value;
}
[Serializable]
public class SerializableClassDerivedFromNonSerializableClass : NonSerializableClass
{
public int AnotherValue;
}
[Serializable]
public class SerializableClassWithBadField
{
public NonSerializableClass? Value;
}
[Serializable]
public struct EmptyStruct { }
[Serializable]
public struct StructWithIntField
{
public int X;
}
[Serializable]
public struct StructWithStringFields
{
public string String1;
public string String2;
}
[Serializable]
public struct StructContainingOtherStructs
{
public StructWithStringFields Nested1;
public StructWithStringFields Nested2;
}
[Serializable]
public struct StructContainingArraysOfOtherStructs
{
public StructContainingOtherStructs[] Nested;
public override readonly bool Equals(object? obj)
{
return obj is StructContainingArraysOfOtherStructs sca && EqualityHelpers.ArraysAreEqual(Nested, sca.Nested);
}
public override readonly int GetHashCode() => 1;
}
[Serializable]
public sealed class ObjRefReturnsObj : IObjectReference
{
public object? Real;
public object GetRealObject(StreamingContext context) => Real!;
}
[Serializable]
public class ObjectWithStateAndMethod
{
public int State;
public int GetState() => State;
}
[Serializable]
public sealed class PointEqualityComparer : IEqualityComparer<Point>
{
public bool Equals(Point? x, Point? y)
{
return x is null ? y is null : y is not null && (x.X == y.X) && (x.Y == y.Y);
}
public int GetHashCode(Point obj) => RuntimeHelpers.GetHashCode(obj);
}
[Serializable]
public class SimpleKeyedCollection : System.Collections.ObjectModel.KeyedCollection<int, Point>
{
protected override int GetKeyForItem(Point item)
{
return item.Y;
}
}
[Serializable]
internal class GenericTypeWithArg<T>
{
public T? Test;
public override bool Equals(object? obj)
{
if (obj is null || GetType() != obj.GetType())
return false;
var p = (GenericTypeWithArg<T>)obj;
return Test!.Equals(p.Test);
}
public override int GetHashCode()
{
return Test is null ? 0 : Test.GetHashCode();
}
}
[Serializable]
internal class SomeType
{
public int SomeField;
public override bool Equals(object? obj)
{
if (obj is null || GetType() != obj.GetType())
return false;
var p = (SomeType)obj;
return SomeField.Equals(p.SomeField);
}
public override int GetHashCode()
{
return SomeField;
}
}
internal static class EqualityHelpers
{
public static bool ArraysAreEqual<T>(T[]? array1, T[]? array2)
{
if (array1 is null || array2 is null)
return array1 == array2;
if (array1.Length != array2.Length)
return false;
for (int i = 0; i < array1.Length; i++)
{
if (!EqualityComparer<T>.Default.Equals(array1[i], array2[i]))
{
return false;
}
}
return true;
}
public static bool ArraysAreEqual(Array? array1, Array? array2)
{
if (array1 is null || array2 is null)
return array1 == array2;
if (array1.Length != array2.Length)
return false;
if (array1.Rank != array2.Rank)
return false;
for (int i = 0; i < array1.Rank; i++)
{
if (array1.GetLength(i) != array2.GetLength(i))
return false;
}
var e1 = array1.GetEnumerator();
var e2 = array2.GetEnumerator();
while (e1.MoveNext())
{
e2.MoveNext();
if (!EqualityComparer<object>.Default.Equals(e1.Current, e2.Current))
{
return false;
}
}
return true;
}
public static bool ArraysAreEqual<T>(T[][]? array1, T[][]? array2)
{
if (array1 is null || array2 is null)
return array1 == array2;
if (array1.Length != array2.Length)
return false;
for (int i = 0; i < array1.Length; i++)
{
T[] sub1 = array1[i], sub2 = array2[i];
if (sub1 is null || (sub2 is null && (sub1 != sub2)))
return false;
if (sub1.Length != sub2.Length)
return false;
for (int j = 0; j < sub1.Length; j++)
{
if (!EqualityComparer<T>.Default.Equals(sub1[j], sub2[j]))
{
return false;
}
}
}
return true;
}
}
#pragma warning disable 0618 // obsolete warning
[Serializable]
internal class HashCodeProvider : IHashCodeProvider
{
public int GetHashCode(object obj)
{
return 8;
}
}

View file

@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace BinaryFormatTests;
// The values represent platforms where there was change in the serialization for one or more types.
public enum TargetFrameworkMoniker
{
netfx461,
netfx471,
netfx472,
netfx472_3260,
netcoreapp20,
netcoreapp21,
netcoreapp30
}

View file

@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Xunit;
internal static class TheoryDataExtensions
{
/// <summary>
/// Converts an IEnumerable<typeparamref name="T"/> into an Xunit theory compatible enumerable.
/// </summary>
public static TheoryData<T> ToTheoryData<T>(this IEnumerable<T> data)
{
TheoryData<T> theoryData = [];
foreach (var item in data)
{
theoryData.Add(item);
}
return theoryData;
}
/// <summary>
/// Converts an IEnumerable into an Xunit theory compatible enumerable.
/// </summary>
public static TheoryData<T1, T2> ToTheoryData<T1, T2>(this IEnumerable<(T1, T2)> data)
{
TheoryData<T1, T2> theoryData = [];
foreach (var item in data)
{
theoryData.Add(item.Item1, item.Item2);
}
return theoryData;
}
/// <summary>
/// Converts an IEnumerable into an Xunit theory compatible enumerable.
/// </summary>
public static TheoryData<T1, T2, T3> ToTheoryData<T1, T2, T3>(this IEnumerable<(T1, T2, T3)> data)
{
TheoryData<T1, T2, T3> theoryData = [];
foreach (var item in data)
{
theoryData.Add(item.Item1, item.Item2, item.Item3);
}
return theoryData;
}
}

View file

@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace BinaryFormatTests;
public readonly struct TypeSerializableValue
{
public string Base64Blob { get; }
// This is the minimum version, when the blob changed.
public readonly TargetFrameworkMoniker Platform { get; }
public TypeSerializableValue(string base64Blob, TargetFrameworkMoniker platform)
{
Base64Blob = base64Blob;
Platform = platform;
}
}

View file

@ -0,0 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
[Serializable]
#pragma warning disable CA1050 // Declare types in namespaces
public struct TypeWithoutNamespace { }
#pragma warning restore CA1050

View file

@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;</TargetFrameworks>
<!--
Turning off a number of warnings that would otherwise litter this project due to special
test type cases for binary formatting.
SYSLIB0011: BinaryFormatter obsolete
SYSLIB0050: Obsolete attribute
SYSLIB0051: Formatters obsolete
-->
<NoWarn>$(NoWarn);CS1574;CS1580;CA1036;CA1051;CA1066;SYSLIB0011;SYSLIB0050;SYSLIB0051;xUnit1013;CS0649</NoWarn>
<StringResourcesPath>$(LibrariesProjectRoot)\System.Resources.Extensions\src\Resources\Strings.resx</StringResourcesPath>
</PropertyGroup>
<ItemGroup>
<Compile Include="**\*.cs" />
</ItemGroup>
<ItemGroup>
<Compile Update="TestResources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>TestResources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="TestResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>TestResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="TestResources.resx" LogicalName="TestResources.resources" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common.TestData" Version="$(SystemDrawingCommonTestDataVersion)" />
<PackageReference Include="FluentAssertions" Version="$(FluentAssertionsVersion)" />
<ProjectReference Include="..\..\src\System.Resources.Extensions.csproj" />
<PackageReference Include="System.Drawing.Common" Version="$(SystemDrawingCommonVersion)" />
</ItemGroup>
<!-- when System.Runtime.Serialization.BinaryFormat gets approved, this whole block will be repalced with a reference to package -->
<ItemGroup>
<Compile Include="$(LibrariesProjectRoot)\System.Runtime.Serialization.BinaryFormat\src\**\*.cs" LinkBase="System.Runtime.Serialization.BinaryFormat" />
<ProjectReference Include="$(LibrariesProjectRoot)\System.IO.Hashing\src\System.IO.Hashing.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)\System.Reflection.Metadata\src\System.Reflection.Metadata.csproj" />
<PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
</ItemGroup>
<ItemGroup>
<Compile Include="$(LibrariesProjectRoot)\System.Resources.Extensions\src\System\Resources\Extensions\BinaryFormat\**\*.cs" LinkBase="System.Resources.Extensions" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,71 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class TestResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal TestResources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestResources", typeof(TestResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to iVBORw0KGgoAAAANSUhEUgAAAG4AAABkCAIAAADoopLKAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAIABJREFUeJzcuQdUU+u67825Z++z99qrr2VvCCi9954EQkioCQRCEgiE3nvvvfeOgEhXQBCkiKJiAcSCUqSDNJGO9G7ug9nbs+65e5xx7rjfGHecb47/eMczZxKc+c3/U97IYpLxRD24go0UwE0Ldrn1vHhk/dbUVvWnXcuCO9d7p28NzmW0vLn+6E3ti/6WrtHXo4t3X3d/YjA+bO5UtDzaZDA2GV8Wt9d3GIe7DAZoj8HYP2LsHzAYX0BHoL2d3aODw6+n/6v+mx9f/reDxbWsK+jusHX+Y83QQu2IAmLcDfPcmujWbv/6tpqpjfrpjett/TVvx1/PbkxsMNYYjN6VrUUG49XUXErZrWUGY/XoaP3L [rest of string was truncated]&quot;;.
/// </summary>
internal static string TestPng {
get {
return ResourceManager.GetString("TestPng", resourceCulture);
}
}
}

Some files were not shown because too many files have changed in this diff Show more