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:
parent
3073a0326a
commit
82aba8203f
160 changed files with 16615 additions and 13 deletions
|
@ -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}
|
||||
|
|
|
@ -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>
|
|
@ -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'">
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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; }
|
||||
}
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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++;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 { }
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) { }
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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() },
|
||||
};
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
};
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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)>()
|
||||
};
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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>
|
||||
{
|
||||
}
|
|
@ -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()
|
||||
};
|
||||
}
|
|
@ -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;
|
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
71
src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/TestResources.Designer.cs
generated
Normal file
71
src/libraries/System.Resources.Extensions/tests/BinaryFormatTests/TestResources.Designer.cs
generated
Normal 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]";.
|
||||
/// </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
Loading…
Add table
Add a link
Reference in a new issue