From d06d207c47e1108f375fa6d94b67d9f668c4d91d Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 12 Jul 2016 17:09:43 -0400 Subject: [PATCH] Add BinaryFormatter to corefx This ports BinaryFormatter from desktop, along with all of the supporting types and general Formatter-related types. I've added a bunch of tests, bringing code coverage up to ~80%, though there's still more that can/should be done. I did not port remoting-specific functionality, though there are still some remnants I've left as they were enabled in the runtime even when remoting wasn't compiled in, and it wasn't clear if they were serving additional purposes. --- ...System.Runtime.Serialization.Formatters.cs | 202 ++ .../ref/project.json | 3 + .../src/Resources/Strings.resx | 219 +++ ...em.Runtime.Serialization.Formatters.csproj | 56 +- .../src/System/NonSerializedAttribute.cs | 1 - .../DeserializationEventHandler.cs | 12 + .../System/Runtime/Serialization/Formatter.cs | 192 ++ .../Serialization/FormatterConverter.cs | 124 ++ .../Serialization/FormatterServices.cs | 299 +++ .../Formatters/Binary/BinaryArray.cs | 158 ++ .../Formatters/Binary/BinaryAssembly.cs | 33 + .../Formatters/Binary/BinaryAssemblyInfo.cs | 38 + .../Binary/BinaryCrossAppDomainAssembly.cs | 29 + .../Binary/BinaryCrossAppDomainMap.cs | 22 + .../Binary/BinaryCrossAppDomainString.cs | 29 + .../Formatters/Binary/BinaryEnums.cs | 188 ++ .../Formatters/Binary/BinaryFormatter.cs | 119 ++ .../Binary/BinaryFormatterWriter.cs | 527 ++++++ .../Formatters/Binary/BinaryObject.cs | 34 + .../Formatters/Binary/BinaryObjectInfo.cs | 720 ++++++++ .../Formatters/Binary/BinaryObjectReader.cs | 1088 +++++++++++ .../Formatters/Binary/BinaryObjectString.cs | 33 + .../Formatters/Binary/BinaryObjectWithMap.cs | 72 + .../Binary/BinaryObjectWithMapTyped.cs | 106 ++ .../Formatters/Binary/BinaryObjectWriter.cs | 1051 +++++++++++ .../Formatters/Binary/BinaryParser.cs | 1048 +++++++++++ .../Formatters/Binary/BinaryTypeConverter.cs | 249 +++ .../Formatters/Binary/BinaryUtilClasses.cs | 589 ++++++ .../Formatters/Binary/Converter.cs | 391 ++++ .../Formatters/Binary/IStreamable.cs | 13 + .../Formatters/Binary/MemberPrimitiveTyped.cs | 33 + .../Binary/MemberPrimitiveUntyped.cs | 37 + .../Formatters/Binary/MemberReference.cs | 30 + .../Formatters/Binary/MessageEnd.cs | 26 + .../Formatters/Binary/ObjectMap.cs | 114 ++ .../Formatters/Binary/ObjectNull.cs | 58 + .../Formatters/Binary/ObjectProgress.cs | 122 ++ .../Binary/SerializationHeaderRecord.cs | 69 + .../Serialization/Formatters/CommonEnums.cs | 27 + .../Serialization/Formatters/IFieldInfo.cs | 12 + .../Serialization/IDeserializationCallback.cs | 2 - .../Runtime/Serialization/IFormatter.cs | 17 + .../Serialization/IFormatterConverter.cs | 8 +- .../Runtime/Serialization/IObjectReference.cs | 11 + .../Runtime/Serialization/ISerializable.cs | 4 +- .../Serialization/ISerializationSurrogate.cs | 12 + .../Serialization/ISurrogateSelector.cs | 13 + .../Runtime/Serialization/MemberHolder.cs | 33 + .../Serialization/ObjectIDGenerator.cs | 163 ++ .../Runtime/Serialization/ObjectManager.cs | 1627 +++++++++++++++++ .../Serialization/OptionalFieldAttribute.cs | 25 + .../SafeSerializationEventArgs.cs | 42 + .../Serialization/SerializationBinder.cs | 18 + .../Serialization/SerializationEventsCache.cs | 100 + .../Serialization/SerializationInfo.cs | 301 ++- .../SerializationInfoEnumerator.cs | 106 +- .../SerializationObjectManager.cs | 49 + .../Serialization/SurrogateSelector.cs | 225 +++ .../Serialization/ValueTypeFixupInfo.cs | 74 + .../src/System/SerializableAttribute.cs | 8 +- .../src/System/TemporaryStubs.cs | 134 ++ .../src/project.json | 9 +- .../tests/BinaryFormatterTests.cs | 598 ++++++ .../tests/FormatterConverterTests.cs | 75 + .../tests/FormatterServicesTests.cs | 93 + .../tests/FormatterTests.cs | 148 ++ .../tests/SerializationBinderTests.cs | 52 + ...asicTests.cs => SerializationInfoTests.cs} | 102 +- .../tests/SerializationTypes.cs | 459 +++++ .../tests/SurrogateSelectorTests.cs | 95 + ...time.Serialization.Formatters.Tests.csproj | 11 +- .../tests/project.json | 1 + 72 files changed, 12416 insertions(+), 372 deletions(-) create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/DeserializationEventHandler.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterConverter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryArray.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssembly.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssemblyInfo.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainAssembly.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainMap.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainString.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryEnums.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatterWriter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObject.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectString.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMap.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMapTyped.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryUtilClasses.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/Converter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/IStreamable.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveTyped.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveUntyped.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberReference.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MessageEnd.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectNull.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectProgress.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/SerializationHeaderRecord.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/CommonEnums.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/IFieldInfo.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IObjectReference.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializationSurrogate.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISurrogateSelector.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectIDGenerator.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/OptionalFieldAttribute.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SafeSerializationEventArgs.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationBinder.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SurrogateSelector.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ValueTypeFixupInfo.cs create mode 100644 src/System.Runtime.Serialization.Formatters/src/System/TemporaryStubs.cs create mode 100644 src/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs create mode 100644 src/System.Runtime.Serialization.Formatters/tests/FormatterConverterTests.cs create mode 100644 src/System.Runtime.Serialization.Formatters/tests/FormatterServicesTests.cs create mode 100644 src/System.Runtime.Serialization.Formatters/tests/FormatterTests.cs create mode 100644 src/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs rename src/System.Runtime.Serialization.Formatters/tests/{BasicTests.cs => SerializationInfoTests.cs} (67%) create mode 100644 src/System.Runtime.Serialization.Formatters/tests/SerializationTypes.cs create mode 100644 src/System.Runtime.Serialization.Formatters/tests/SurrogateSelectorTests.cs diff --git a/src/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs b/src/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs index 7e28f2e27b28..957d8d31e98a 100644 --- a/src/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs +++ b/src/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs @@ -29,6 +29,12 @@ public SerializableAttribute() namespace System.Runtime.Serialization { + [System.AttributeUsageAttribute(System.AttributeTargets.Field, Inherited = false)] + public sealed partial class OptionalFieldAttribute : System.Attribute + { + public OptionalFieldAttribute() { } + public int VersionAdded { get { return default(int); } set { } } + } [CLSCompliant(false)] public interface IFormatterConverter { @@ -137,3 +143,199 @@ private SerializationInfoEnumerator() { } public void Reset() { throw null; } } } +namespace System.Runtime.Serialization +{ + [System.CLSCompliantAttribute(false)] + public abstract partial class Formatter : System.Runtime.Serialization.IFormatter + { + protected System.Runtime.Serialization.ObjectIDGenerator m_idGenerator; + protected System.Collections.Queue m_objectQueue; + protected Formatter() { } + public abstract System.Runtime.Serialization.SerializationBinder Binder { get; set; } + public abstract System.Runtime.Serialization.StreamingContext Context { get; set; } + public abstract System.Runtime.Serialization.ISurrogateSelector SurrogateSelector { get; set; } + public abstract object Deserialize(System.IO.Stream serializationStream); + protected virtual object GetNext(out long objID) { objID = default(long); return default(object); } + protected virtual long Schedule(object obj) { return default(long); } + public abstract void Serialize(System.IO.Stream serializationStream, object graph); + protected abstract void WriteArray(object obj, string name, System.Type memberType); + protected abstract void WriteBoolean(bool val, string name); + protected abstract void WriteByte(byte val, string name); + protected abstract void WriteChar(char val, string name); + protected abstract void WriteDateTime(System.DateTime val, string name); + protected abstract void WriteDecimal(decimal val, string name); + protected abstract void WriteDouble(double val, string name); + protected abstract void WriteInt16(short val, string name); + protected abstract void WriteInt32(int val, string name); + protected abstract void WriteInt64(long val, string name); + protected virtual void WriteMember(string memberName, object data) { } + protected abstract void WriteObjectRef(object obj, string name, System.Type memberType); + [System.CLSCompliantAttribute(false)] + protected abstract void WriteSByte(sbyte val, string name); + protected abstract void WriteSingle(float val, string name); + protected abstract void WriteTimeSpan(System.TimeSpan val, string name); + [System.CLSCompliantAttribute(false)] + protected abstract void WriteUInt16(ushort val, string name); + [System.CLSCompliantAttribute(false)] + protected abstract void WriteUInt32(uint val, string name); + [System.CLSCompliantAttribute(false)] + protected abstract void WriteUInt64(ulong val, string name); + protected abstract void WriteValueType(object obj, string name, System.Type memberType); + } + public partial class FormatterConverter : System.Runtime.Serialization.IFormatterConverter + { + public FormatterConverter() { } + public object Convert(object value, System.Type type) { return default(object); } + public object Convert(object value, System.TypeCode typeCode) { return default(object); } + public bool ToBoolean(object value) { return default(bool); } + public byte ToByte(object value) { return default(byte); } + public char ToChar(object value) { return default(char); } + public System.DateTime ToDateTime(object value) { return default(System.DateTime); } + public decimal ToDecimal(object value) { return default(decimal); } + public double ToDouble(object value) { return default(double); } + public short ToInt16(object value) { return default(short); } + public int ToInt32(object value) { return default(int); } + public long ToInt64(object value) { return default(long); } + [System.CLSCompliantAttribute(false)] + public sbyte ToSByte(object value) { return default(sbyte); } + public float ToSingle(object value) { return default(float); } + public string ToString(object value) { return default(string); } + [System.CLSCompliantAttribute(false)] + public ushort ToUInt16(object value) { return default(ushort); } + [System.CLSCompliantAttribute(false)] + public uint ToUInt32(object value) { return default(uint); } + [System.CLSCompliantAttribute(false)] + public ulong ToUInt64(object value) { return default(ulong); } + } + public static partial class FormatterServices + { + public static void CheckTypeSecurity(System.Type t, System.Runtime.Serialization.Formatters.TypeFilterLevel securityLevel) { } + public static object[] GetObjectData(object obj, System.Reflection.MemberInfo[] members) { return default(object[]); } + public static object GetSafeUninitializedObject(System.Type type) { return default(object); } + public static System.Reflection.MemberInfo[] GetSerializableMembers(System.Type type) { return default(System.Reflection.MemberInfo[]); } + public static System.Reflection.MemberInfo[] GetSerializableMembers(System.Type type, System.Runtime.Serialization.StreamingContext context) { return default(System.Reflection.MemberInfo[]); } + public static System.Runtime.Serialization.ISerializationSurrogate GetSurrogateForCyclicalReference(System.Runtime.Serialization.ISerializationSurrogate innerSurrogate) { return default(System.Runtime.Serialization.ISerializationSurrogate); } + public static System.Type GetTypeFromAssembly(System.Reflection.Assembly assem, string name) { return default(System.Type); } + public static object GetUninitializedObject(System.Type type) { return default(object); } + public static object PopulateObjectMembers(object obj, System.Reflection.MemberInfo[] members, object[] data) { return default(object); } + } + public partial interface IFormatter + { + System.Runtime.Serialization.SerializationBinder Binder { get; set; } + System.Runtime.Serialization.StreamingContext Context { get; set; } + System.Runtime.Serialization.ISurrogateSelector SurrogateSelector { get; set; } + object Deserialize(System.IO.Stream serializationStream); + void Serialize(System.IO.Stream serializationStream, object graph); + } + public partial interface ISerializationSurrogate + { + void GetObjectData(object obj, System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context); + object SetObjectData(object obj, System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context, System.Runtime.Serialization.ISurrogateSelector selector); + } + public partial interface ISurrogateSelector + { + void ChainSelector(System.Runtime.Serialization.ISurrogateSelector selector); + System.Runtime.Serialization.ISurrogateSelector GetNextSelector(); + System.Runtime.Serialization.ISerializationSurrogate GetSurrogate(System.Type type, System.Runtime.Serialization.StreamingContext context, out System.Runtime.Serialization.ISurrogateSelector selector); + } + public partial class ObjectIDGenerator + { + public ObjectIDGenerator() { } + public virtual long GetId(object obj, out bool firstTime) { firstTime = default(bool); return default(long); } + public virtual long HasId(object obj, out bool firstTime) { firstTime = default(bool); return default(long); } + } + public partial class ObjectManager + { + public ObjectManager(System.Runtime.Serialization.ISurrogateSelector selector, System.Runtime.Serialization.StreamingContext context) { } + public virtual void DoFixups() { } + public virtual object GetObject(long objectID) { return default(object); } + public virtual void RaiseDeserializationEvent() { } + public void RaiseOnDeserializingEvent(object obj) { } + public virtual void RecordArrayElementFixup(long arrayToBeFixed, int index, long objectRequired) { } + public virtual void RecordArrayElementFixup(long arrayToBeFixed, int[] indices, long objectRequired) { } + public virtual void RecordDelayedFixup(long objectToBeFixed, string memberName, long objectRequired) { } + public virtual void RecordFixup(long objectToBeFixed, System.Reflection.MemberInfo member, long objectRequired) { } + public virtual void RegisterObject(object obj, long objectID) { } + public void RegisterObject(object obj, long objectID, System.Runtime.Serialization.SerializationInfo info) { } + public void RegisterObject(object obj, long objectID, System.Runtime.Serialization.SerializationInfo info, long idOfContainingObj, System.Reflection.MemberInfo member) { } + public void RegisterObject(object obj, long objectID, System.Runtime.Serialization.SerializationInfo info, long idOfContainingObj, System.Reflection.MemberInfo member, int[] arrayIndex) { } + } + public abstract partial class SerializationBinder + { + protected SerializationBinder() { } + public virtual void BindToName(System.Type serializedType, out string assemblyName, out string typeName) { assemblyName = default(string); typeName = default(string); } + public abstract System.Type BindToType(string assemblyName, string typeName); + } + public sealed partial class SerializationObjectManager + { + public SerializationObjectManager(System.Runtime.Serialization.StreamingContext context) { } + public void RaiseOnSerializedEvent() { } + public void RegisterObject(object obj) { } + } + public partial class SurrogateSelector : System.Runtime.Serialization.ISurrogateSelector + { + public SurrogateSelector() { } + public virtual void AddSurrogate(System.Type type, System.Runtime.Serialization.StreamingContext context, System.Runtime.Serialization.ISerializationSurrogate surrogate) { } + public virtual void ChainSelector(System.Runtime.Serialization.ISurrogateSelector selector) { } + public virtual System.Runtime.Serialization.ISurrogateSelector GetNextSelector() { return default(System.Runtime.Serialization.ISurrogateSelector); } + public virtual System.Runtime.Serialization.ISerializationSurrogate GetSurrogate(System.Type type, System.Runtime.Serialization.StreamingContext context, out System.Runtime.Serialization.ISurrogateSelector selector) { selector = default(System.Runtime.Serialization.ISurrogateSelector); return default(System.Runtime.Serialization.ISerializationSurrogate); } + public virtual void RemoveSurrogate(System.Type type, System.Runtime.Serialization.StreamingContext context) { } + } +} // end of System.Runtime.Serialization +namespace System.Runtime.Serialization.Formatters +{ + public enum FormatterAssemblyStyle + { + Full = 1, + Simple = 0, + } + public enum FormatterTypeStyle + { + TypesAlways = 1, + TypesWhenNeeded = 0, + XsdString = 2, + } + public partial interface IFieldInfo + { + string[] FieldNames { get; set; } + System.Type[] FieldTypes { get; set; } + } + public enum TypeFilterLevel + { + Full = 3, + Low = 2, + } +} // end of System.Runtime.Serialization.Formatters +namespace System.Runtime.Serialization.Formatters.Binary +{ + public sealed partial class BinaryFormatter : System.Runtime.Serialization.IFormatter + { + public BinaryFormatter() { } + public BinaryFormatter(System.Runtime.Serialization.ISurrogateSelector selector, System.Runtime.Serialization.StreamingContext context) { } + public System.Runtime.Serialization.Formatters.FormatterAssemblyStyle AssemblyFormat { get { return default(System.Runtime.Serialization.Formatters.FormatterAssemblyStyle); } set { } } + public System.Runtime.Serialization.SerializationBinder Binder { get { return default(System.Runtime.Serialization.SerializationBinder); } set { } } + public System.Runtime.Serialization.StreamingContext Context { get { return default(System.Runtime.Serialization.StreamingContext); } set { } } + public System.Runtime.Serialization.Formatters.TypeFilterLevel FilterLevel { get { return default(System.Runtime.Serialization.Formatters.TypeFilterLevel); } set { } } + public System.Runtime.Serialization.ISurrogateSelector SurrogateSelector { get { return default(System.Runtime.Serialization.ISurrogateSelector); } set { } } + public System.Runtime.Serialization.Formatters.FormatterTypeStyle TypeFormat { get { return default(System.Runtime.Serialization.Formatters.FormatterTypeStyle); } set { } } + public object Deserialize(System.IO.Stream serializationStream) { return default(object); } + public object Deserialize(System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) { return default(object); } + public void Serialize(System.IO.Stream serializationStream, object graph) { } + public void Serialize(System.IO.Stream serializationStream, object graph, System.Runtime.Remoting.Messaging.Header[] headers) { } + public object UnsafeDeserialize(System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) { return default(object); } + } +} // end of System.Runtime.Serialization.Formatters.Binary +namespace System.Runtime.Remoting.Messaging +{ + public partial class Header + { + public string HeaderNamespace; + public bool MustUnderstand; + public string Name; + public object Value; + public Header(string _Name, object _Value) { } + public Header(string _Name, object _Value, bool _MustUnderstand) { } + public Header(string _Name, object _Value, bool _MustUnderstand, string _HeaderNamespace) { } + } + public delegate object HeaderHandler(System.Runtime.Remoting.Messaging.Header[] headers); +} \ No newline at end of file diff --git a/src/System.Runtime.Serialization.Formatters/ref/project.json b/src/System.Runtime.Serialization.Formatters/ref/project.json index 30f6e3acca92..0c3d0845831d 100644 --- a/src/System.Runtime.Serialization.Formatters/ref/project.json +++ b/src/System.Runtime.Serialization.Formatters/ref/project.json @@ -1,5 +1,8 @@ { "dependencies": { + "System.Collections.NonGeneric": "4.0.0", + "System.IO": "4.0.10", + "System.Reflection": "4.1.0", "System.Runtime": "4.0.20", "System.Runtime.Extensions": "4.0.10", "System.Runtime.Serialization.Primitives": "4.1.1" diff --git a/src/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx b/src/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx index 859b6cfd9b97..cdffbcec4b46 100644 --- a/src/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx +++ b/src/System.Runtime.Serialization.Formatters/src/Resources/Strings.resx @@ -129,4 +129,223 @@ Enumeration has either not started or has already finished. + + Type '{0}' in Assembly '{1}' is not marked as serializable. + + + Parameters 'members' and 'data' must have the same length. + + + Parameters 'members' and 'data' must have the same length. + + + Only FieldInfo, PropertyInfo, and SerializationMemberInfo are recognized. + + + Object has never been assigned an objectID. + + + Object cannot be null. + + + The internal array cannot expand to greater than Int32.MaxValue elements. + + + The FieldInfo object is not valid. + + + A fixup is registered to the object with ID {0}, but the object does not appear in the graph. + + + The object with ID {0} implements the IObjectReference interface for which all dependencies cannot be resolved. The likely cause is two instances of IObjectReference that have a mutual dependency on each other. + + + The object with ID {0} was referenced in a fixup but does not exist. + + + {0}.SetObjectData returns a value that is neither null nor equal to the first parameter. Such Surrogates cannot be part of cyclical reference. + + + The implementation of the IObjectReference interface returns too many nested references to other objects that implement IObjectReference. + + + The object with ID {0} was referenced in a fixup but has not been registered. + + + A fixup on an object implementing ISerializable or having a surrogate was discovered for an object which does not have a SerializationInfo available. + + + Unable to load type {0} required for deserialization. + + + ValueType fixup on Arrays is not implemented. + + + Fixing up a partially available ValueType chain is not implemented. + + + Cannot perform fixup. + + + objectID cannot be less than or equal to zero. + + + An object cannot be registered twice. + + + The given object does not implement the ISerializable interface. + + + The constructor to deserialize an object of type '{0}' was not found. + + + The object with ID {0} was referenced in a fixup but does not exist. + + + Unable to load type {0} required for deserialization. + + + The ObjectManager found an invalid number of fixups. This usually indicates a problem in the Formatter. + + + A member fixup was registered for an object which implements ISerializable or has a surrogate. In this situation, a delayed fixup must be used. + + + Object IDs must be greater than zero. + + + The ID of the containing object cannot be the same as the object ID. + + + Only system-provided types can be passed to the GetUninitializedObject method. '{0}' is not a valid instance of a type. + + + Array must not be of length zero. + + + When supplying the ID of a containing object, the FieldInfo that identifies the current field within that object must also be supplied. + + + Cannot supply both a MemberInfo and an Array to indicate the parent of a value type. + + + Invalid BinaryFormatter stream. + + + Header reflection error: number of value members: {0}. + + + Parameter '{0}' cannot be null. + + + Attempting to deserialize an empty stream. + + + Binary stream '{0}' does not contain a valid BinaryHeader. Possible causes are invalid stream or object version change between serialization and deserialization. + + + Invalid expected type. + + + End of Stream encountered before parsing was completed. + + + Cross-AppDomain BinaryFormatter error; expected '{0}' but received '{1}'. + + + No map for object '{0}'. + + + No assembly information is available for object on the wire, '{0}'. + + + Invalid ObjectTypeEnum {0}. + + + No assembly ID for object type '{0}'. + + + Invalid ObjectTypeEnum {0}. + + + Invalid array type '{0}'. + + + Invalid type code in stream '{0}'. + + + Invalid write type request '{0}'. + + + Invalid read type request '{0}'. + + + Unable to find assembly '{0}'. + + + The input stream is not a valid binary format. The starting contents (in bytes) are: {0} ... + + + Stream cannot be null. + + + No top object. + + + Invalid element '{0}'. + + + Top object cannot be instantiated for element '{0}'. + + + Array element type is Object, 'dt' attribute is null. + + + Type is missing for member of type Object '{0}'. + + + Object Graph cannot be null. + + + Object {0} has never been assigned an objectID. + + + MemberInfo type {0} cannot be serialized. + + + When supplying a FieldInfo for fixing up a nested type, a valid ID for that containing object must also be supplied. + + + Parse error. Current element is not compatible with the next element, {0}. + + + MemberInfo requested for ISerializable type. + + + MemberInfo cannot be obtained for ISerialized Object '{0}'. + + + Types not available for ISerializable object '{0}'. + + + Member '{0}' in class '{1}' is not present in the serialized stream and is not marked with {2}. + + + No MemberInfo for Object {0}. + + + Selector is already on the list of checked selectors. + + + Type {0} and the types derived from it (such as {1}) are not permitted to be deserialized at this security level. + + + Selector contained a cycle. + + + Adding selector will introduce a cycle. + + + Unable to read beyond the end of the stream. + \ No newline at end of file diff --git a/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj b/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj index dff3e166d3bf..021342f09eba 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj +++ b/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj @@ -12,18 +12,66 @@ .NETStandard,Version=v1.4 - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Runtime.Serialization.Formatters/src/System/NonSerializedAttribute.cs b/src/System.Runtime.Serialization.Formatters/src/System/NonSerializedAttribute.cs index 0d8e5dacfbea..b65789c14ebe 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/NonSerializedAttribute.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/NonSerializedAttribute.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; namespace System { diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/DeserializationEventHandler.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/DeserializationEventHandler.cs new file mode 100644 index 000000000000..2d960a3948c1 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/DeserializationEventHandler.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization +{ + [Serializable] + internal delegate void DeserializationEventHandler(object sender); + + [Serializable] + internal delegate void SerializationEventHandler(StreamingContext context); +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs new file mode 100644 index 000000000000..3ae49ca5526b --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs @@ -0,0 +1,192 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Globalization; +using System.Collections; +using System.Reflection; + +namespace System.Runtime.Serialization +{ + [Serializable] + [CLSCompliant(false)] + public abstract class Formatter : IFormatter + { + protected readonly ObjectIDGenerator m_idGenerator; + protected readonly Queue m_objectQueue; + + protected Formatter() + { + m_objectQueue = new Queue(); + m_idGenerator = new ObjectIDGenerator(); + } + + public abstract object Deserialize(Stream serializationStream); + + protected virtual object GetNext(out long objID) + { + if (m_objectQueue.Count == 0) + { + objID = 0; + return null; + } + + object obj = m_objectQueue.Dequeue(); + + bool isNew; + objID = m_idGenerator.HasId(obj, out isNew); + if (isNew) + { + throw new SerializationException(SR.Serialization_NoID); + } + + return obj; + } + + protected virtual long Schedule(object obj) + { + if (obj == null) + { + return 0; + } + + bool isNew; + long id = m_idGenerator.GetId(obj, out isNew); + + if (isNew) + { + m_objectQueue.Enqueue(obj); + } + return id; + } + + public abstract void Serialize(Stream serializationStream, object graph); + + protected abstract void WriteArray(object obj, string name, Type memberType); + + protected abstract void WriteBoolean(bool val, string name); + + protected abstract void WriteByte(byte val, string name); + + protected abstract void WriteChar(char val, string name); + + protected abstract void WriteDateTime(DateTime val, string name); + + protected abstract void WriteDecimal(decimal val, string name); + + protected abstract void WriteDouble(double val, string name); + + protected abstract void WriteInt16(short val, string name); + + protected abstract void WriteInt32(int val, string name); + + protected abstract void WriteInt64(long val, string name); + + protected abstract void WriteObjectRef(object obj, string name, Type memberType); + + protected virtual void WriteMember(string memberName, object data) + { + if (data == null) + { + WriteObjectRef(data, memberName, typeof(object)); + return; + } + + Type varType = data.GetType(); + + if (varType == typeof(bool)) + { + WriteBoolean(Convert.ToBoolean(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(char)) + { + WriteChar(Convert.ToChar(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(sbyte)) + { + WriteSByte(Convert.ToSByte(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(byte)) + { + WriteByte(Convert.ToByte(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(short)) + { + WriteInt16(Convert.ToInt16(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(int)) + { + WriteInt32(Convert.ToInt32(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(long)) + { + WriteInt64(Convert.ToInt64(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(float)) + { + WriteSingle(Convert.ToSingle(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(double)) + { + WriteDouble(Convert.ToDouble(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(DateTime)) + { + WriteDateTime(Convert.ToDateTime(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(decimal)) + { + WriteDecimal(Convert.ToDecimal(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(ushort)) + { + WriteUInt16(Convert.ToUInt16(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(uint)) + { + WriteUInt32(Convert.ToUInt32(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType == typeof(ulong)) + { + WriteUInt64(Convert.ToUInt64(data, CultureInfo.InvariantCulture), memberName); + } + else if (varType.IsArray) + { + WriteArray(data, memberName, varType); + } + else if (varType.GetTypeInfo().IsValueType) + { + WriteValueType(data, memberName, varType); + } + else + { + WriteObjectRef(data, memberName, varType); + } + } + + [CLSCompliant(false)] + protected abstract void WriteSByte(sbyte val, string name); + + protected abstract void WriteSingle(float val, string name); + + protected abstract void WriteTimeSpan(TimeSpan val, string name); + + [CLSCompliant(false)] + protected abstract void WriteUInt16(ushort val, string name); + + [CLSCompliant(false)] + protected abstract void WriteUInt32(uint val, string name); + + [CLSCompliant(false)] + protected abstract void WriteUInt64(ulong val, string name); + + protected abstract void WriteValueType(object obj, string name, Type memberType); + + public abstract ISurrogateSelector SurrogateSelector { get; set; } + + public abstract SerializationBinder Binder { get; set; } + + public abstract StreamingContext Context { get; set; } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterConverter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterConverter.cs new file mode 100644 index 000000000000..d928b28fce3b --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterConverter.cs @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace System.Runtime.Serialization +{ + public class FormatterConverter : IFormatterConverter + { + public object Convert(object value, Type type) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ChangeType(value, type, CultureInfo.InvariantCulture); + } + + public object Convert(object value, TypeCode typeCode) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ChangeType(value, typeCode, CultureInfo.InvariantCulture); + } + + public bool ToBoolean(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToBoolean(value, CultureInfo.InvariantCulture); + } + + public char ToChar(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToChar(value, CultureInfo.InvariantCulture); + } + + [CLSCompliant(false)] + public sbyte ToSByte(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToSByte(value, CultureInfo.InvariantCulture); + } + + public byte ToByte(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToByte(value, CultureInfo.InvariantCulture); + } + + public short ToInt16(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToInt16(value, CultureInfo.InvariantCulture); + } + + [CLSCompliant(false)] + public ushort ToUInt16(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToUInt16(value, CultureInfo.InvariantCulture); + } + + public int ToInt32(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToInt32(value, CultureInfo.InvariantCulture); + } + + [CLSCompliant(false)] + public uint ToUInt32(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToUInt32(value, CultureInfo.InvariantCulture); + } + + public long ToInt64(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToInt64(value, CultureInfo.InvariantCulture); + } + + [CLSCompliant(false)] + public ulong ToUInt64(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToUInt64(value, CultureInfo.InvariantCulture); + } + + public float ToSingle(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToSingle(value, CultureInfo.InvariantCulture); + } + + public double ToDouble(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToDouble(value, CultureInfo.InvariantCulture); + } + + public decimal ToDecimal(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToDecimal(value, CultureInfo.InvariantCulture); + } + + public DateTime ToDateTime(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToDateTime(value, CultureInfo.InvariantCulture); + } + + public string ToString(object value) + { + if (value == null) ThrowValueNullException(); + return System.Convert.ToString(value, CultureInfo.InvariantCulture); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowValueNullException() + { + throw new ArgumentNullException("value"); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs new file mode 100644 index 000000000000..51ecf99a4fe0 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs @@ -0,0 +1,299 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Text; +using System.Globalization; +using System.Diagnostics; +using System.Runtime.Serialization.Formatters; + +namespace System.Runtime.Serialization +{ + public static class FormatterServices + { + private static readonly ConcurrentDictionary s_memberInfoTable = new ConcurrentDictionary(); + + private static FieldInfo[] GetSerializableFields(Type type) + { + if (type.GetTypeInfo().IsInterface) + { + return Array.Empty(); + } + + if (!type.GetTypeInfo().IsSerializable) + { + throw new SerializationException(SR.Format(SR.Serialization_NonSerType, type.GetTypeInfo().FullName, type.GetTypeInfo().Assembly.FullName)); + } + + var results = new List(); + for (Type t = type; t != typeof(object); t = t.GetTypeInfo().BaseType) + { + foreach (FieldInfo field in t.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + if ((field.Attributes & FieldAttributes.NotSerialized) != FieldAttributes.NotSerialized) + { + results.Add(field); + } + } + } + return results.ToArray(); + } + + public static MemberInfo[] GetSerializableMembers(Type type) + { + return GetSerializableMembers(type, new StreamingContext(StreamingContextStates.All)); + } + + public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + // If we've already gathered the members for this type, just return them. + // Otherwise, get them and add them. + var mh = new MemberHolder(type, context); + MemberInfo[] members; + if (!s_memberInfoTable.TryGetValue(mh, out members)) + { + members = GetSerializableFields(type); + s_memberInfoTable.TryAdd(mh, members); + } + return members; + } + + public static void CheckTypeSecurity(Type t, TypeFilterLevel securityLevel) + { + // nop + } + + // TODO #8133: Fix this to avoid reflection + private static readonly Func s_getUninitializedObjectDelegate = (Func) + typeof(string).GetTypeInfo().Assembly.GetType("System.Runtime.Serialization.FormatterServices") + ?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) + ?.CreateDelegate(typeof(Func)); + + public static object GetUninitializedObject(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return s_getUninitializedObjectDelegate(type); + } + + public static object GetSafeUninitializedObject(Type type) => GetUninitializedObject(type); + + internal static bool UnsafeTypeForwardersIsEnabled() => true; + + internal static void SerializationSetValue(MemberInfo fi, object target, object value) + { + Debug.Assert(fi != null); + + var serField = fi as FieldInfo; + if (serField != null) + { + serField.SetValue(target, value); + return; + } + + throw new ArgumentException(SR.Argument_InvalidFieldInfo); + } + + public static object PopulateObjectMembers(object obj, MemberInfo[] members, object[] data) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + if (members == null) + { + throw new ArgumentNullException(nameof(members)); + } + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + if (members.Length != data.Length) + { + throw new ArgumentException(SR.Argument_DataLengthDifferent); + } + + for (int i = 0; i < members.Length; i++) + { + MemberInfo member = members[i]; + if (member == null) + { + throw new ArgumentNullException(nameof(members), SR.Format(SR.ArgumentNull_NullMember, i)); + } + + // If we find an empty, it means that the value was never set during deserialization. + // This is either a forward reference or a null. In either case, this may break some of the + // invariants mantained by the setter, so we'll do nothing with it for right now. + object value = data[i]; + if (value == null) + { + continue; + } + + // If it's a field, set its value. + FieldInfo field = member as FieldInfo; + if (field != null) + { + field.SetValue(obj, data[i]); + continue; + } + + // Otherwise, it's not supported. + throw new SerializationException(SR.Serialization_UnknownMemberInfo); + } + + return obj; + } + + public static object[] GetObjectData(object obj, MemberInfo[] members) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + if (members == null) + { + throw new ArgumentNullException(nameof(members)); + } + + object[] data = new object[members.Length]; + for (int i = 0; i < members.Length; i++) + { + MemberInfo member = members[i]; + if (member == null) + { + throw new ArgumentNullException(nameof(members), SR.Format(SR.ArgumentNull_NullMember, i)); + } + + FieldInfo field = member as FieldInfo; + if (field == null) + { + throw new SerializationException(SR.Serialization_UnknownMemberInfo); + } + + data[i] = field.GetValue(obj); + } + return data; + } + + public static ISerializationSurrogate GetSurrogateForCyclicalReference(ISerializationSurrogate innerSurrogate) + { + if (innerSurrogate == null) + { + throw new ArgumentNullException(nameof(innerSurrogate)); + } + return new SurrogateForCyclicalReference(innerSurrogate); + } + + public static Type GetTypeFromAssembly(Assembly assem, string name) + { + if (assem == null) + { + throw new ArgumentNullException(nameof(assem)); + } + return assem.GetType(name, throwOnError: false, ignoreCase: false); + } + + internal static Assembly LoadAssemblyFromString(string assemblyName) + { + return Assembly.Load(new AssemblyName(assemblyName)); + } + + internal static Assembly LoadAssemblyFromStringNoThrow(string assemblyName) + { + try + { + return LoadAssemblyFromString(assemblyName); + } + catch (Exception) { } + return null; + } + + internal static string GetClrAssemblyName(Type type, out bool hasTypeForwardedFrom) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + foreach (Attribute first in type.GetTypeInfo().GetCustomAttributes(typeof(TypeForwardedFromAttribute), false)) + { + hasTypeForwardedFrom = true; + return ((TypeForwardedFromAttribute)first).AssemblyFullName; + } + + hasTypeForwardedFrom = false; + return type.GetTypeInfo().Assembly.FullName; + } + + internal static string GetClrTypeFullName(Type type) + { + return type.IsArray ? + GetClrTypeFullNameForArray(type) : + GetClrTypeFullNameForNonArrayTypes(type); + } + + private static string GetClrTypeFullNameForArray(Type type) + { + int rank = type.GetArrayRank(); + Debug.Assert(rank >= 1); + string typeName = GetClrTypeFullName(type.GetElementType()); + return rank == 1 ? + typeName + "[]" : + typeName + "[" + new string(',', rank - 1) + "]"; + } + + private static string GetClrTypeFullNameForNonArrayTypes(Type type) + { + if (!type.GetTypeInfo().IsGenericType) + { + return type.FullName; + } + + var builder = new StringBuilder(type.GetGenericTypeDefinition().FullName).Append("["); + + bool hasTypeForwardedFrom; + foreach (Type genericArgument in type.GetGenericArguments()) + { + builder.Append("[").Append(GetClrTypeFullName(genericArgument)).Append(", "); + builder.Append(GetClrAssemblyName(genericArgument, out hasTypeForwardedFrom)).Append("],"); + } + + //remove the last comma and close typename for generic with a close bracket + return builder.Remove(builder.Length - 1, 1).Append("]").ToString(); + } + } + + internal sealed class SurrogateForCyclicalReference : ISerializationSurrogate + { + private readonly ISerializationSurrogate _innerSurrogate; + + internal SurrogateForCyclicalReference(ISerializationSurrogate innerSurrogate) + { + Debug.Assert(innerSurrogate != null); + _innerSurrogate = innerSurrogate; + } + + public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) + { + _innerSurrogate.GetObjectData(obj, info, context); + } + + public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) + { + return _innerSurrogate.SetObjectData(obj, info, context, selector); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryArray.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryArray.cs new file mode 100644 index 000000000000..1d5e39c43d38 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryArray.cs @@ -0,0 +1,158 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryArray : IStreamable + { + internal int _objectId; + internal int _rank; + internal int[] _lengthA; + internal int[] _lowerBoundA; + internal BinaryTypeEnum _binaryTypeEnum; + internal object _typeInformation; + internal int _assemId = 0; + private BinaryHeaderEnum _binaryHeaderEnum; + internal BinaryArrayTypeEnum _binaryArrayTypeEnum; + + internal BinaryArray() { } + + internal BinaryArray(BinaryHeaderEnum binaryHeaderEnum) + { + _binaryHeaderEnum = binaryHeaderEnum; + } + + internal void Set(int objectId, int rank, int[] lengthA, int[] lowerBoundA, BinaryTypeEnum binaryTypeEnum, object typeInformation, BinaryArrayTypeEnum binaryArrayTypeEnum, int assemId) + { + _objectId = objectId; + _binaryArrayTypeEnum = binaryArrayTypeEnum; + _rank = rank; + _lengthA = lengthA; + _lowerBoundA = lowerBoundA; + _binaryTypeEnum = binaryTypeEnum; + _typeInformation = typeInformation; + _assemId = assemId; + + _binaryHeaderEnum = BinaryHeaderEnum.Array; + if (binaryArrayTypeEnum == BinaryArrayTypeEnum.Single) + { + if (binaryTypeEnum == BinaryTypeEnum.Primitive) + { + _binaryHeaderEnum = BinaryHeaderEnum.ArraySinglePrimitive; + } + else if (binaryTypeEnum == BinaryTypeEnum.String) + { + _binaryHeaderEnum = BinaryHeaderEnum.ArraySingleString; + } + else if (binaryTypeEnum == BinaryTypeEnum.Object) + { + _binaryHeaderEnum = BinaryHeaderEnum.ArraySingleObject; + } + } + } + + public void Write(BinaryFormatterWriter output) + { + switch (_binaryHeaderEnum) + { + case BinaryHeaderEnum.ArraySinglePrimitive: + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_objectId); + output.WriteInt32(_lengthA[0]); + output.WriteByte((byte)((InternalPrimitiveTypeE)_typeInformation)); + break; + case BinaryHeaderEnum.ArraySingleString: + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_objectId); + output.WriteInt32(_lengthA[0]); + break; + case BinaryHeaderEnum.ArraySingleObject: + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_objectId); + output.WriteInt32(_lengthA[0]); + break; + default: + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_objectId); + output.WriteByte((byte)_binaryArrayTypeEnum); + output.WriteInt32(_rank); + for (int i = 0; i < _rank; i++) + { + output.WriteInt32(_lengthA[i]); + } + if ((_binaryArrayTypeEnum == BinaryArrayTypeEnum.SingleOffset) || + (_binaryArrayTypeEnum == BinaryArrayTypeEnum.JaggedOffset) || + (_binaryArrayTypeEnum == BinaryArrayTypeEnum.RectangularOffset)) + { + for (int i = 0; i < _rank; i++) + { + output.WriteInt32(_lowerBoundA[i]); + } + } + output.WriteByte((byte)_binaryTypeEnum); + BinaryTypeConverter.WriteTypeInfo(_binaryTypeEnum, _typeInformation, _assemId, output); + break; + } + } + + public void Read(BinaryParser input) + { + switch (_binaryHeaderEnum) + { + case BinaryHeaderEnum.ArraySinglePrimitive: + _objectId = input.ReadInt32(); + _lengthA = new int[1]; + _lengthA[0] = input.ReadInt32(); + _binaryArrayTypeEnum = BinaryArrayTypeEnum.Single; + _rank = 1; + _lowerBoundA = new int[_rank]; + _binaryTypeEnum = BinaryTypeEnum.Primitive; + _typeInformation = (InternalPrimitiveTypeE)input.ReadByte(); + break; + case BinaryHeaderEnum.ArraySingleString: + _objectId = input.ReadInt32(); + _lengthA = new int[1]; + _lengthA[0] = input.ReadInt32(); + _binaryArrayTypeEnum = BinaryArrayTypeEnum.Single; + _rank = 1; + _lowerBoundA = new int[_rank]; + _binaryTypeEnum = BinaryTypeEnum.String; + _typeInformation = null; + break; + case BinaryHeaderEnum.ArraySingleObject: + _objectId = input.ReadInt32(); + _lengthA = new int[1]; + _lengthA[0] = input.ReadInt32(); + _binaryArrayTypeEnum = BinaryArrayTypeEnum.Single; + _rank = 1; + _lowerBoundA = new int[_rank]; + _binaryTypeEnum = BinaryTypeEnum.Object; + _typeInformation = null; + break; + default: + _objectId = input.ReadInt32(); + _binaryArrayTypeEnum = (BinaryArrayTypeEnum)input.ReadByte(); + _rank = input.ReadInt32(); + _lengthA = new int[_rank]; + _lowerBoundA = new int[_rank]; + for (int i = 0; i < _rank; i++) + { + _lengthA[i] = input.ReadInt32(); + } + if ((_binaryArrayTypeEnum == BinaryArrayTypeEnum.SingleOffset) || + (_binaryArrayTypeEnum == BinaryArrayTypeEnum.JaggedOffset) || + (_binaryArrayTypeEnum == BinaryArrayTypeEnum.RectangularOffset)) + { + for (int i = 0; i < _rank; i++) + { + _lowerBoundA[i] = input.ReadInt32(); + } + } + _binaryTypeEnum = (BinaryTypeEnum)input.ReadByte(); + _typeInformation = BinaryTypeConverter.ReadTypeInfo(_binaryTypeEnum, input, out _assemId); + break; + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssembly.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssembly.cs new file mode 100644 index 000000000000..14b357b1d77e --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssembly.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryAssembly : IStreamable + { + internal int _assemId; + internal string _assemblyString; + + internal BinaryAssembly() { } + + internal void Set(int assemId, string assemblyString) + { + _assemId = assemId; + _assemblyString = assemblyString; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.Assembly); + output.WriteInt32(_assemId); + output.WriteString(_assemblyString); + } + + public void Read(BinaryParser input) + { + _assemId = input.ReadInt32(); + _assemblyString = input.ReadString(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssemblyInfo.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssemblyInfo.cs new file mode 100644 index 000000000000..b99501741473 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryAssemblyInfo.cs @@ -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. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryAssemblyInfo + { + internal string _assemblyString; + private Assembly _assembly; + + internal BinaryAssemblyInfo(string assemblyString) + { + _assemblyString = assemblyString; + } + + internal BinaryAssemblyInfo(string assemblyString, Assembly assembly) : this(assemblyString) + { + _assembly = assembly; + } + + internal Assembly GetAssembly() + { + if (_assembly == null) + { + _assembly = FormatterServices.LoadAssemblyFromStringNoThrow(_assemblyString); + if (_assembly == null) + { + throw new SerializationException(SR.Format(SR.Serialization_AssemblyNotFound, _assemblyString)); + } + } + return _assembly; + } + } +} + diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainAssembly.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainAssembly.cs new file mode 100644 index 000000000000..8acb6a062849 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainAssembly.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryCrossAppDomainAssembly : IStreamable + { + internal int _assemId; + internal int _assemblyIndex; + + internal BinaryCrossAppDomainAssembly() + { + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.CrossAppDomainAssembly); + output.WriteInt32(_assemId); + output.WriteInt32(_assemblyIndex); + } + + public void Read(BinaryParser input) + { + _assemId = input.ReadInt32(); + _assemblyIndex = input.ReadInt32(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainMap.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainMap.cs new file mode 100644 index 000000000000..f0b3537ecef4 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainMap.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryCrossAppDomainMap : IStreamable + { + internal int _crossAppDomainArrayIndex; + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.CrossAppDomainMap); + output.WriteInt32(_crossAppDomainArrayIndex); + } + + public void Read(BinaryParser input) + { + _crossAppDomainArrayIndex = input.ReadInt32(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainString.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainString.cs new file mode 100644 index 000000000000..9ac4a9a6866f --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryCrossAppDomainString.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryCrossAppDomainString : IStreamable + { + internal int _objectId; + internal int _value; + + internal BinaryCrossAppDomainString() + { + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.CrossAppDomainString); + output.WriteInt32(_objectId); + output.WriteInt32(_value); + } + + public void Read(BinaryParser input) + { + _objectId = input.ReadInt32(); + _value = input.ReadInt32(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryEnums.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryEnums.cs new file mode 100644 index 000000000000..376245005850 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryEnums.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // BinaryHeaderEnum is the first byte on binary records (except for primitive types which do not have a header) + [Serializable] + internal enum BinaryHeaderEnum + { + SerializedStreamHeader = 0, + Object = 1, + ObjectWithMap = 2, + ObjectWithMapAssemId = 3, + ObjectWithMapTyped = 4, + ObjectWithMapTypedAssemId = 5, + ObjectString = 6, + Array = 7, + MemberPrimitiveTyped = 8, + MemberReference = 9, + ObjectNull = 10, + MessageEnd = 11, + Assembly = 12, + ObjectNullMultiple256 = 13, + ObjectNullMultiple = 14, + ArraySinglePrimitive = 15, + ArraySingleObject = 16, + ArraySingleString = 17, + CrossAppDomainMap = 18, + CrossAppDomainString = 19, + CrossAppDomainAssembly = 20, + MethodCall = 21, + MethodReturn = 22, + } + + // BinaryTypeEnum is used specify the type on the wire. Additional information is transmitted with Primitive and Object types + [Serializable] + internal enum BinaryTypeEnum + { + Primitive = 0, + String = 1, + Object = 2, + ObjectUrt = 3, + ObjectUser = 4, + ObjectArray = 5, + StringArray = 6, + PrimitiveArray = 7, + } + + [Serializable] + internal enum BinaryArrayTypeEnum + { + Single = 0, + Jagged = 1, + Rectangular = 2, + SingleOffset = 3, + JaggedOffset = 4, + RectangularOffset = 5, + } + + // Enums are for internal use by the XML and Binary Serializers + + // Formatter Enums + [Serializable] + internal enum InternalSerializerTypeE + { + Soap = 1, + Binary = 2, + } + + // ParseRecord Enums + [Serializable] + internal enum InternalParseTypeE + { + Empty = 0, + SerializedStreamHeader = 1, + Object = 2, + Member = 3, + ObjectEnd = 4, + MemberEnd = 5, + Headers = 6, + HeadersEnd = 7, + SerializedStreamHeaderEnd = 8, + Envelope = 9, + EnvelopeEnd = 10, + Body = 11, + BodyEnd = 12, + } + + [Serializable] + internal enum InternalObjectTypeE + { + Empty = 0, + Object = 1, + Array = 2, + } + + [Serializable] + internal enum InternalObjectPositionE + { + Empty = 0, + Top = 1, + Child = 2, + Headers = 3, + } + + [Serializable] + internal enum InternalArrayTypeE + { + Empty = 0, + Single = 1, + Jagged = 2, + Rectangular = 3, + Base64 = 4, + } + + [Serializable] + internal enum InternalMemberTypeE + { + Empty = 0, + Header = 1, + Field = 2, + Item = 3, + } + + [Serializable] + internal enum InternalMemberValueE + { + Empty = 0, + InlineValue = 1, + Nested = 2, + Reference = 3, + Null = 4, + } + + // Data Type Enums + [Serializable] + internal enum InternalPrimitiveTypeE + { + Invalid = 0, + Boolean = 1, + Byte = 2, + Char = 3, + Currency = 4, + Decimal = 5, + Double = 6, + Int16 = 7, + Int32 = 8, + Int64 = 9, + SByte = 10, + Single = 11, + TimeSpan = 12, + DateTime = 13, + UInt16 = 14, + UInt32 = 15, + UInt64 = 16, + + // Used in only for MethodCall or MethodReturn header + Null = 17, + String = 18, + } + + // ValueType Fixup Enum + [Serializable] + internal enum ValueFixupEnum + { + Empty = 0, + Array = 1, + Header = 2, + Member = 3, + } + + // name space + [Serializable] + internal enum InternalNameSpaceE + { + None = 0, + Soap = 1, + XdrPrimitive = 2, + XdrString = 3, + UrtSystem = 4, + UrtUser = 5, + UserNameSpace = 6, + MemberName = 7, + Interop = 8, + CallElement = 9 + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs new file mode 100644 index 000000000000..05d7fd961457 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Collections.Generic; +using System.Runtime.Remoting.Messaging; +using System.Runtime.InteropServices; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + public sealed class BinaryFormatter : IFormatter + { + private static readonly Dictionary s_typeNameCache = new Dictionary(); + + internal ISurrogateSelector _surrogates; + internal StreamingContext _context; + internal SerializationBinder _binder; + internal FormatterTypeStyle _typeFormat = FormatterTypeStyle.TypesAlways; // For version resiliency, always put out types + internal FormatterAssemblyStyle _assemblyFormat = FormatterAssemblyStyle.Simple; + internal TypeFilterLevel _securityLevel = TypeFilterLevel.Full; + internal object[] _crossAppDomainArray = null; + + public FormatterTypeStyle TypeFormat { get { return _typeFormat; } set { _typeFormat = value; } } + public FormatterAssemblyStyle AssemblyFormat { get { return _assemblyFormat; } set { _assemblyFormat = value; } } + public TypeFilterLevel FilterLevel { get { return _securityLevel; } set { _securityLevel = value; } } + public ISurrogateSelector SurrogateSelector { get { return _surrogates; } set { _surrogates = value; } } + public SerializationBinder Binder { get { return _binder; } set { _binder = value; } } + public StreamingContext Context { get { return _context; } set { _context = value; } } + + public BinaryFormatter() : this(null, new StreamingContext(StreamingContextStates.All)) + { + } + + public BinaryFormatter(ISurrogateSelector selector, StreamingContext context) + { + _surrogates = selector; + _context = context; + } + + public object Deserialize(Stream serializationStream) => Deserialize(serializationStream, null); + + internal object Deserialize(Stream serializationStream, HeaderHandler handler, bool check) + { + if (serializationStream == null) + { + throw new ArgumentNullException(nameof(serializationStream), SR.Format(SR.ArgumentNull_WithParamName, serializationStream)); + } + if (serializationStream.CanSeek && (serializationStream.Length == 0)) + { + throw new SerializationException(SR.Serialization_Stream); + } + + var formatterEnums = new InternalFE() + { + _FEtypeFormat = _typeFormat, + _FEserializerTypeEnum = InternalSerializerTypeE.Binary, + _FEassemblyFormat = _assemblyFormat, + _FEsecurityLevel = _securityLevel, + }; + + var reader = new ObjectReader(serializationStream, _surrogates, _context, formatterEnums, _binder) + { + _crossAppDomainArray = _crossAppDomainArray + }; + var parser = new BinaryParser(serializationStream, reader); + return reader.Deserialize(handler, parser, check); + } + + public object Deserialize(Stream serializationStream, HeaderHandler handler) => + Deserialize(serializationStream, handler, check: true); + + [ComVisible(false)] + public object UnsafeDeserialize(Stream serializationStream, HeaderHandler handler) => + Deserialize(serializationStream, handler, check: false); + + public void Serialize(Stream serializationStream, object graph) => + Serialize(serializationStream, graph, headers: null); + + public void Serialize(Stream serializationStream, object graph, Header[] headers) => + Serialize(serializationStream, graph, headers, check: true); + + internal void Serialize(Stream serializationStream, object graph, Header[] headers, bool check) + { + if (serializationStream == null) + { + throw new ArgumentNullException(nameof(serializationStream), SR.Format(SR.ArgumentNull_WithParamName, serializationStream)); + } + + var formatterEnums = new InternalFE() + { + _FEtypeFormat = _typeFormat, + _FEserializerTypeEnum = InternalSerializerTypeE.Binary, + _FEassemblyFormat = _assemblyFormat, + }; + + var sow = new ObjectWriter(_surrogates, _context, formatterEnums, _binder); + BinaryFormatterWriter binaryWriter = new BinaryFormatterWriter(serializationStream, sow, _typeFormat); + sow.Serialize(graph, headers, binaryWriter, check); + _crossAppDomainArray = sow._crossAppDomainArray; + } + + internal static TypeInformation GetTypeInformation(Type type) + { + lock (s_typeNameCache) + { + TypeInformation typeInformation; + if (!s_typeNameCache.TryGetValue(type, out typeInformation)) + { + bool hasTypeForwardedFrom; + string assemblyName = FormatterServices.GetClrAssemblyName(type, out hasTypeForwardedFrom); + typeInformation = new TypeInformation(FormatterServices.GetClrTypeFullName(type), assemblyName, hasTypeForwardedFrom); + s_typeNameCache.Add(type, typeInformation); + } + return typeInformation; + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatterWriter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatterWriter.cs new file mode 100644 index 000000000000..ea449f62351c --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatterWriter.cs @@ -0,0 +1,527 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryFormatterWriter + { + private const int ChunkSize = 4096; + + private readonly Stream _outputStream; + private readonly FormatterTypeStyle _formatterTypeStyle; + private readonly ObjectWriter _objectWriter = null; + private readonly BinaryWriter _dataWriter = null; + + private int _consecutiveNullArrayEntryCount = 0; + private Dictionary _objectMapTable; + + private BinaryObject _binaryObject; + private BinaryObjectWithMap _binaryObjectWithMap; + private BinaryObjectWithMapTyped _binaryObjectWithMapTyped; + private BinaryObjectString _binaryObjectString; + private BinaryArray _binaryArray; + private byte[] _byteBuffer = null; + private MemberPrimitiveUnTyped _memberPrimitiveUnTyped; + private MemberPrimitiveTyped _memberPrimitiveTyped; + private ObjectNull _objectNull; + private MemberReference _memberReference; + private BinaryAssembly _binaryAssembly; + + internal BinaryFormatterWriter(Stream outputStream, ObjectWriter objectWriter, FormatterTypeStyle formatterTypeStyle) + { + _outputStream = outputStream; + _formatterTypeStyle = formatterTypeStyle; + _objectWriter = objectWriter; + _dataWriter = new BinaryWriter(outputStream, Encoding.UTF8); + } + + internal void WriteBegin() { } + + internal void WriteEnd() + { + _dataWriter.Flush(); + } + + internal void WriteBoolean(bool value) => _dataWriter.Write(value); + + internal void WriteByte(byte value) => _dataWriter.Write(value); + + private void WriteBytes(byte[] value) => _dataWriter.Write(value); + + private void WriteBytes(byte[] byteA, int offset, int size) => _dataWriter.Write(byteA, offset, size); + + internal void WriteChar(char value) => _dataWriter.Write(value); + + internal void WriteChars(char[] value) => _dataWriter.Write(value); + + internal void WriteDecimal(decimal value) => WriteString(value.ToString(CultureInfo.InvariantCulture)); + + internal void WriteSingle(float value) => _dataWriter.Write(value); + + internal void WriteDouble(double value) => _dataWriter.Write(value); + + internal void WriteInt16(short value) => _dataWriter.Write(value); + + internal void WriteInt32(int value) => _dataWriter.Write(value); + + internal void WriteInt64(long value) => _dataWriter.Write(value); + + internal void WriteSByte(sbyte value) => WriteByte((byte)value); + + internal void WriteString(string value) => _dataWriter.Write(value); + + internal void WriteTimeSpan(TimeSpan value) => WriteInt64(value.Ticks); + + internal void WriteDateTime(DateTime value) => WriteInt64(value.Ticks); // in desktop, this uses ToBinaryRaw + + internal void WriteUInt16(ushort value) => _dataWriter.Write(value); + + internal void WriteUInt32(uint value) => _dataWriter.Write(value); + + internal void WriteUInt64(ulong value) => _dataWriter.Write(value); + + internal void WriteObjectEnd(NameInfo memberNameInfo, NameInfo typeNameInfo) { } + + internal void WriteSerializationHeaderEnd() + { + var record = new MessageEnd(); + record.Write(this); + } + + internal void WriteSerializationHeader(int topId, int headerId, int minorVersion, int majorVersion) + { + var record = new SerializationHeaderRecord(BinaryHeaderEnum.SerializedStreamHeader, topId, headerId, minorVersion, majorVersion); + record.Write(this); + } + + internal void WriteObject(NameInfo nameInfo, NameInfo typeNameInfo, int numMembers, string[] memberNames, Type[] memberTypes, WriteObjectInfo[] memberObjectInfos) + { + InternalWriteItemNull(); + int assemId; + int objectId = (int)nameInfo._NIobjectId; + + string objectName = objectId < 0 ? + objectName = typeNameInfo.NIname : // Nested Object + objectName = nameInfo.NIname; // Non-Nested + + if (_objectMapTable == null) + { + _objectMapTable = new Dictionary(); + } + + ObjectMapInfo objectMapInfo; + if (_objectMapTable.TryGetValue(objectName, out objectMapInfo) && + objectMapInfo.IsCompatible(numMembers, memberNames, memberTypes)) + { + // Object + if (_binaryObject == null) + { + _binaryObject = new BinaryObject(); + } + + _binaryObject.Set(objectId, objectMapInfo._objectId); + _binaryObject.Write(this); + } + else if (!typeNameInfo._NItransmitTypeOnObject) + { + // ObjectWithMap + if (_binaryObjectWithMap == null) + { + _binaryObjectWithMap = new BinaryObjectWithMap(); + } + + // BCL types are not placed into table + assemId = (int)typeNameInfo._NIassemId; + _binaryObjectWithMap.Set(objectId, objectName, numMembers, memberNames, assemId); + + _binaryObjectWithMap.Write(this); + if (objectMapInfo == null) + { + _objectMapTable.Add(objectName, new ObjectMapInfo(objectId, numMembers, memberNames, memberTypes)); + } + } + else + { + // ObjectWithMapTyped + var binaryTypeEnumA = new BinaryTypeEnum[numMembers]; + var typeInformationA = new object[numMembers]; + var assemIdA = new int[numMembers]; + for (int i = 0; i < numMembers; i++) + { + object typeInformation = null; + binaryTypeEnumA[i] = BinaryTypeConverter.GetBinaryTypeInfo(memberTypes[i], memberObjectInfos[i], null, _objectWriter, out typeInformation, out assemId); + typeInformationA[i] = typeInformation; + assemIdA[i] = assemId; + } + + if (_binaryObjectWithMapTyped == null) + { + _binaryObjectWithMapTyped = new BinaryObjectWithMapTyped(); + } + + // BCL types are not placed in table + assemId = (int)typeNameInfo._NIassemId; + _binaryObjectWithMapTyped.Set(objectId, objectName, numMembers, memberNames, binaryTypeEnumA, typeInformationA, assemIdA, assemId); + _binaryObjectWithMapTyped.Write(this); + if (objectMapInfo == null) + { + _objectMapTable.Add(objectName, new ObjectMapInfo(objectId, numMembers, memberNames, memberTypes)); + } + } + } + + internal void WriteObjectString(int objectId, string value) + { + InternalWriteItemNull(); + + if (_binaryObjectString == null) + { + _binaryObjectString = new BinaryObjectString(); + } + + _binaryObjectString.Set(objectId, value); + _binaryObjectString.Write(this); + } + + internal void WriteSingleArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, int length, int lowerBound, Array array) + { + InternalWriteItemNull(); + BinaryArrayTypeEnum binaryArrayTypeEnum; + var lengthA = new int[1]; + lengthA[0] = length; + int[] lowerBoundA = null; + object typeInformation = null; + + if (lowerBound == 0) + { + binaryArrayTypeEnum = BinaryArrayTypeEnum.Single; + } + else + { + binaryArrayTypeEnum = BinaryArrayTypeEnum.SingleOffset; + lowerBoundA = new int[1]; + lowerBoundA[0] = lowerBound; + } + + int assemId; + BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetBinaryTypeInfo( + arrayElemTypeNameInfo._NItype, objectInfo, arrayElemTypeNameInfo.NIname, _objectWriter, out typeInformation, out assemId); + + if (_binaryArray == null) + { + _binaryArray = new BinaryArray(); + } + _binaryArray.Set((int)arrayNameInfo._NIobjectId, 1, lengthA, lowerBoundA, binaryTypeEnum, typeInformation, binaryArrayTypeEnum, assemId); + + _binaryArray.Write(this); + + if (Converter.IsWriteAsByteArray(arrayElemTypeNameInfo._NIprimitiveTypeEnum) && (lowerBound == 0)) + { + //array is written out as an array of bytes + if (arrayElemTypeNameInfo._NIprimitiveTypeEnum == InternalPrimitiveTypeE.Byte) + { + WriteBytes((byte[])array); + } + else if (arrayElemTypeNameInfo._NIprimitiveTypeEnum == InternalPrimitiveTypeE.Char) + { + WriteChars((char[])array); + } + else + { + WriteArrayAsBytes(array, Converter.TypeLength(arrayElemTypeNameInfo._NIprimitiveTypeEnum)); + } + } + } + + private void WriteArrayAsBytes(Array array, int typeLength) + { + InternalWriteItemNull(); + int byteLength = array.Length * typeLength; + int arrayOffset = 0; + if (_byteBuffer == null) + { + _byteBuffer = new byte[ChunkSize]; + } + + while (arrayOffset < array.Length) + { + int numArrayItems = Math.Min(ChunkSize / typeLength, array.Length - arrayOffset); + int bufferUsed = numArrayItems * typeLength; + Buffer.BlockCopy(array, arrayOffset * typeLength, _byteBuffer, 0, bufferUsed); + if (!BitConverter.IsLittleEndian) + { + // we know that we are writing a primitive type, so just do a simple swap + for (int i = 0; i < bufferUsed; i += typeLength) + { + for (int j = 0; j < typeLength / 2; j++) + { + byte tmp = _byteBuffer[i + j]; + _byteBuffer[i + j] = _byteBuffer[i + typeLength - 1 - j]; + _byteBuffer[i + typeLength - 1 - j] = tmp; + } + } + } + WriteBytes(_byteBuffer, 0, bufferUsed); + arrayOffset += numArrayItems; + } + } + + internal void WriteJaggedArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, int length, int lowerBound) + { + InternalWriteItemNull(); + BinaryArrayTypeEnum binaryArrayTypeEnum; + var lengthA = new int[1]; + lengthA[0] = length; + int[] lowerBoundA = null; + object typeInformation = null; + int assemId = 0; + + if (lowerBound == 0) + { + binaryArrayTypeEnum = BinaryArrayTypeEnum.Jagged; + } + else + { + binaryArrayTypeEnum = BinaryArrayTypeEnum.JaggedOffset; + lowerBoundA = new int[1]; + lowerBoundA[0] = lowerBound; + } + + BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetBinaryTypeInfo(arrayElemTypeNameInfo._NItype, objectInfo, arrayElemTypeNameInfo.NIname, _objectWriter, out typeInformation, out assemId); + + if (_binaryArray == null) + { + _binaryArray = new BinaryArray(); + } + _binaryArray.Set((int)arrayNameInfo._NIobjectId, 1, lengthA, lowerBoundA, binaryTypeEnum, typeInformation, binaryArrayTypeEnum, assemId); + + _binaryArray.Write(this); + } + + internal void WriteRectangleArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, int rank, int[] lengthA, int[] lowerBoundA) + { + InternalWriteItemNull(); + + BinaryArrayTypeEnum binaryArrayTypeEnum = BinaryArrayTypeEnum.Rectangular; + object typeInformation = null; + int assemId = 0; + BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetBinaryTypeInfo(arrayElemTypeNameInfo._NItype, objectInfo, arrayElemTypeNameInfo.NIname, _objectWriter, out typeInformation, out assemId); + + if (_binaryArray == null) + { + _binaryArray = new BinaryArray(); + } + + for (int i = 0; i < rank; i++) + { + if (lowerBoundA[i] != 0) + { + binaryArrayTypeEnum = BinaryArrayTypeEnum.RectangularOffset; + break; + } + } + + _binaryArray.Set((int)arrayNameInfo._NIobjectId, rank, lengthA, lowerBoundA, binaryTypeEnum, typeInformation, binaryArrayTypeEnum, assemId); + _binaryArray.Write(this); + } + + internal void WriteObjectByteArray(NameInfo memberNameInfo, NameInfo arrayNameInfo, WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, int length, int lowerBound, byte[] byteA) + { + InternalWriteItemNull(); + WriteSingleArray(memberNameInfo, arrayNameInfo, objectInfo, arrayElemTypeNameInfo, length, lowerBound, byteA); + } + + internal void WriteMember(NameInfo memberNameInfo, NameInfo typeNameInfo, object value) + { + InternalWriteItemNull(); + InternalPrimitiveTypeE typeInformation = typeNameInfo._NIprimitiveTypeEnum; + + // Writes Members with primitive values + if (memberNameInfo._NItransmitTypeOnMember) + { + if (_memberPrimitiveTyped == null) + { + _memberPrimitiveTyped = new MemberPrimitiveTyped(); + } + _memberPrimitiveTyped.Set(typeInformation, value); + _memberPrimitiveTyped.Write(this); + } + else + { + if (_memberPrimitiveUnTyped == null) + { + _memberPrimitiveUnTyped = new MemberPrimitiveUnTyped(); + } + _memberPrimitiveUnTyped.Set(typeInformation, value); + _memberPrimitiveUnTyped.Write(this); + } + } + + internal void WriteNullMember(NameInfo memberNameInfo, NameInfo typeNameInfo) + { + InternalWriteItemNull(); + if (_objectNull == null) + { + _objectNull = new ObjectNull(); + } + + if (!memberNameInfo._NIisArrayItem) + { + _objectNull.SetNullCount(1); + _objectNull.Write(this); + _consecutiveNullArrayEntryCount = 0; + } + } + + internal void WriteMemberObjectRef(NameInfo memberNameInfo, int idRef) + { + InternalWriteItemNull(); + if (_memberReference == null) + { + _memberReference = new MemberReference(); + } + _memberReference.Set(idRef); + _memberReference.Write(this); + } + + internal void WriteMemberNested(NameInfo memberNameInfo) + { + InternalWriteItemNull(); + } + + internal void WriteMemberString(NameInfo memberNameInfo, NameInfo typeNameInfo, string value) + { + InternalWriteItemNull(); + WriteObjectString((int)typeNameInfo._NIobjectId, value); + } + + internal void WriteItem(NameInfo itemNameInfo, NameInfo typeNameInfo, object value) + { + InternalWriteItemNull(); + WriteMember(itemNameInfo, typeNameInfo, value); + } + + internal void WriteNullItem(NameInfo itemNameInfo, NameInfo typeNameInfo) + { + _consecutiveNullArrayEntryCount++; + InternalWriteItemNull(); + } + + internal void WriteDelayedNullItem() + { + _consecutiveNullArrayEntryCount++; + } + + internal void WriteItemEnd() => InternalWriteItemNull(); + + private void InternalWriteItemNull() + { + if (_consecutiveNullArrayEntryCount > 0) + { + if (_objectNull == null) + { + _objectNull = new ObjectNull(); + } + _objectNull.SetNullCount(_consecutiveNullArrayEntryCount); + _objectNull.Write(this); + _consecutiveNullArrayEntryCount = 0; + } + } + + internal void WriteItemObjectRef(NameInfo nameInfo, int idRef) + { + InternalWriteItemNull(); + WriteMemberObjectRef(nameInfo, idRef); + } + + internal void WriteAssembly(Type type, string assemblyString, int assemId, bool isNew) + { + //If the file being tested wasn't built as an assembly, then we're going to get null back + //for the assembly name. This is very unfortunate. + InternalWriteItemNull(); + if (assemblyString == null) + { + assemblyString = string.Empty; + } + + if (isNew) + { + if (_binaryAssembly == null) + { + _binaryAssembly = new BinaryAssembly(); + } + _binaryAssembly.Set(assemId, assemblyString); + _binaryAssembly.Write(this); + } + } + + // Method to write a value onto a stream given its primitive type code + internal void WriteValue(InternalPrimitiveTypeE code, object value) + { + switch (code) + { + case InternalPrimitiveTypeE.Boolean: WriteBoolean(Convert.ToBoolean(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Byte: WriteByte(Convert.ToByte(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Char: WriteChar(Convert.ToChar(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Double: WriteDouble(Convert.ToDouble(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Int16: WriteInt16(Convert.ToInt16(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Int32: WriteInt32(Convert.ToInt32(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Int64: WriteInt64(Convert.ToInt64(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.SByte: WriteSByte(Convert.ToSByte(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Single: WriteSingle(Convert.ToSingle(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.UInt16: WriteUInt16(Convert.ToUInt16(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.UInt32: WriteUInt32(Convert.ToUInt32(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.UInt64: WriteUInt64(Convert.ToUInt64(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.Decimal: WriteDecimal(Convert.ToDecimal(value, CultureInfo.InvariantCulture)); break; + case InternalPrimitiveTypeE.TimeSpan: WriteTimeSpan((TimeSpan)value); break; + case InternalPrimitiveTypeE.DateTime: WriteDateTime((DateTime)value); break; + default: throw new SerializationException(SR.Format(SR.Serialization_TypeCode, code.ToString())); + } + } + + private sealed class ObjectMapInfo + { + internal readonly int _objectId; + private readonly int _numMembers; + private readonly string[] _memberNames; + private readonly Type[] _memberTypes; + + internal ObjectMapInfo(int objectId, int numMembers, string[] memberNames, Type[] memberTypes) + { + _objectId = objectId; + _numMembers = numMembers; + _memberNames = memberNames; + _memberTypes = memberTypes; + } + + internal bool IsCompatible(int numMembers, string[] memberNames, Type[] memberTypes) + { + if (_numMembers != numMembers) + { + return false; + } + + for (int i = 0; i < numMembers; i++) + { + if (!(_memberNames[i].Equals(memberNames[i]))) + { + return false; + } + + if ((memberTypes != null) && (_memberTypes[i] != memberTypes[i])) + { + return false; + } + } + + return true; + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObject.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObject.cs new file mode 100644 index 000000000000..d1656e89b6b6 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObject.cs @@ -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. +// See the LICENSE file in the project root for more information. + + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryObject : IStreamable + { + internal int _objectId; + internal int _mapId; + + internal BinaryObject() { } + + internal void Set(int objectId, int mapId) + { + _objectId = objectId; + _mapId = mapId; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.Object); + output.WriteInt32(_objectId); + output.WriteInt32(_mapId); + } + + public void Read(BinaryParser input) + { + _objectId = input.ReadInt32(); + _mapId = input.ReadInt32(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs new file mode 100644 index 000000000000..7265ee7b93ed --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs @@ -0,0 +1,720 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Collections.Generic; +using System.Reflection; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // This class contains information about an object. It is used so that + // the rest of the Formatter routines can use a common interface for + // a normal object, an ISerializable object, and a surrogate object + internal sealed class WriteObjectInfo + { + internal int _objectInfoId; + internal object _obj; + internal Type _objectType; + + internal bool _isSi = false; + internal bool _isNamed = false; + internal bool _isArray = false; + + internal SerializationInfo _si = null; + internal SerObjectInfoCache _cache = null; + + internal object[] _memberData = null; + internal ISerializationSurrogate _serializationSurrogate = null; + internal StreamingContext _context; + internal SerObjectInfoInit _serObjectInfoInit = null; + + // Writing and Parsing information + internal long _objectId; + internal long _assemId; + + // Binder information + private string _binderTypeName; + private string _binderAssemblyString; + + internal WriteObjectInfo() { } + + internal void ObjectEnd() + { + PutObjectInfo(_serObjectInfoInit, this); + } + + private void InternalInit() + { + _obj = null; + _objectType = null; + _isSi = false; + _isNamed = false; + _isArray = false; + _si = null; + _cache = null; + _memberData = null; + + // Writing and Parsing information + _objectId = 0; + _assemId = 0; + + // Binder information + _binderTypeName = null; + _binderAssemblyString = null; + } + + internal static WriteObjectInfo Serialize(object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) + { + WriteObjectInfo woi = GetObjectInfo(serObjectInfoInit); + woi.InitSerialize(obj, surrogateSelector, context, serObjectInfoInit, converter, objectWriter, binder); + return woi; + } + + // Write constructor + internal void InitSerialize(object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder binder) + { + _context = context; + _obj = obj; + _serObjectInfoInit = serObjectInfoInit; + _objectType = obj.GetType(); + + if (_objectType.IsArray) + { + _isArray = true; + InitNoMembers(); + return; + } + + InvokeSerializationBinder(binder); + objectWriter.ObjectManager.RegisterObject(obj); + + ISurrogateSelector surrogateSelectorTemp; + if (surrogateSelector != null && (_serializationSurrogate = surrogateSelector.GetSurrogate(_objectType, context, out surrogateSelectorTemp)) != null) + { + _si = new SerializationInfo(_objectType, converter); + if (!_objectType.GetTypeInfo().IsPrimitive) + { + _serializationSurrogate.GetObjectData(obj, _si, context); + } + InitSiWrite(); + } + else if (obj is ISerializable) + { + if (!_objectType.GetTypeInfo().IsSerializable) + { + throw new SerializationException(SR.Format(SR.Serialization_NonSerType, _objectType.FullName, _objectType.GetTypeInfo().Assembly.FullName)); + } + _si = new SerializationInfo(_objectType, converter, !FormatterServices.UnsafeTypeForwardersIsEnabled()); + ((ISerializable)obj).GetObjectData(_si, context); + InitSiWrite(); + CheckTypeForwardedFrom(_cache, _objectType, _binderAssemblyString); + } + else + { + InitMemberInfo(); + CheckTypeForwardedFrom(_cache, _objectType, _binderAssemblyString); + } + } + + internal static WriteObjectInfo Serialize(Type objectType, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SerializationBinder binder) + { + WriteObjectInfo woi = GetObjectInfo(serObjectInfoInit); + woi.InitSerialize(objectType, surrogateSelector, context, serObjectInfoInit, converter, binder); + return woi; + } + + // Write Constructor used for array types or null members + internal void InitSerialize(Type objectType, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SerializationBinder binder) + { + _objectType = objectType; + _context = context; + _serObjectInfoInit = serObjectInfoInit; + + if (objectType.IsArray) + { + InitNoMembers(); + return; + } + + InvokeSerializationBinder(binder); + + ISurrogateSelector surrogateSelectorTemp = null; + if (surrogateSelector != null) + { + _serializationSurrogate = surrogateSelector.GetSurrogate(objectType, context, out surrogateSelectorTemp); + } + + if (_serializationSurrogate != null) + { + // surrogate does not have this problem since user has pass in through the BF's ctor + _si = new SerializationInfo(objectType, converter); + _cache = new SerObjectInfoCache(objectType); + _isSi = true; + } + else if (!ReferenceEquals(objectType, Converter.s_typeofObject) && Converter.s_typeofISerializable.IsAssignableFrom(objectType)) + { + _si = new SerializationInfo(objectType, converter, !FormatterServices.UnsafeTypeForwardersIsEnabled()); + _cache = new SerObjectInfoCache(objectType); + CheckTypeForwardedFrom(_cache, objectType, _binderAssemblyString); + _isSi = true; + } + + if (!_isSi) + { + InitMemberInfo(); + CheckTypeForwardedFrom(_cache, objectType, _binderAssemblyString); + } + } + + private void InitSiWrite() + { + SerializationInfoEnumerator siEnum = null; + _isSi = true; + siEnum = _si.GetEnumerator(); + int infoLength = 0; + + infoLength = _si.MemberCount; + + int count = infoLength; + + // For ISerializable cache cannot be saved because each object instance can have different values + // BinaryWriter only puts the map on the wire if the ISerializable map cannot be reused. + TypeInformation typeInformation = null; + string fullTypeName = _si.FullTypeName; + string assemblyString = _si.AssemblyName; + bool hasTypeForwardedFrom = false; + + if (!_si.IsFullTypeNameSetExplicit) + { + typeInformation = BinaryFormatter.GetTypeInformation(_si.ObjectType); + fullTypeName = typeInformation.FullTypeName; + hasTypeForwardedFrom = typeInformation.HasTypeForwardedFrom; + } + + if (!_si.IsAssemblyNameSetExplicit) + { + if (typeInformation == null) + { + typeInformation = BinaryFormatter.GetTypeInformation(_si.ObjectType); + } + assemblyString = typeInformation.AssemblyString; + hasTypeForwardedFrom = typeInformation.HasTypeForwardedFrom; + } + + _cache = new SerObjectInfoCache(fullTypeName, assemblyString, hasTypeForwardedFrom); + + _cache._memberNames = new string[count]; + _cache._memberTypes = new Type[count]; + _memberData = new object[count]; + + siEnum = _si.GetEnumerator(); + for (int i = 0; siEnum.MoveNext(); i++) + { + _cache._memberNames[i] = siEnum.Name; + _cache._memberTypes[i] = siEnum.ObjectType; + _memberData[i] = siEnum.Value; + } + + _isNamed = true; + } + + private static void CheckTypeForwardedFrom(SerObjectInfoCache cache, Type objectType, string binderAssemblyString) + { + // nop + } + + private void InitNoMembers() + { + if (!_serObjectInfoInit._seenBeforeTable.TryGetValue(_objectType, out _cache)) + { + _cache = new SerObjectInfoCache(_objectType); + _serObjectInfoInit._seenBeforeTable.Add(_objectType, _cache); + } + } + + private void InitMemberInfo() + { + if (!_serObjectInfoInit._seenBeforeTable.TryGetValue(_objectType, out _cache)) + { + _cache = new SerObjectInfoCache(_objectType); + + _cache._memberInfos = FormatterServices.GetSerializableMembers(_objectType, _context); + int count = _cache._memberInfos.Length; + _cache._memberNames = new string[count]; + _cache._memberTypes = new Type[count]; + + // Calculate new arrays + for (int i = 0; i < count; i++) + { + _cache._memberNames[i] = _cache._memberInfos[i].Name; + _cache._memberTypes[i] = ((FieldInfo)_cache._memberInfos[i]).FieldType; + } + _serObjectInfoInit._seenBeforeTable.Add(_objectType, _cache); + } + + if (_obj != null) + { + _memberData = FormatterServices.GetObjectData(_obj, _cache._memberInfos); + } + + _isNamed = true; + } + + internal string GetTypeFullName() => _binderTypeName ?? _cache._fullTypeName; + + internal string GetAssemblyString() => _binderAssemblyString ?? _cache._assemblyString; + + private void InvokeSerializationBinder(SerializationBinder binder) => + binder?.BindToName(_objectType, out _binderAssemblyString, out _binderTypeName); + + internal void GetMemberInfo(out string[] outMemberNames, out Type[] outMemberTypes, out object[] outMemberData) + { + outMemberNames = _cache._memberNames; + outMemberTypes = _cache._memberTypes; + outMemberData = _memberData; + + if (_isSi && !_isNamed) + { + throw new SerializationException(SR.Serialization_ISerializableMemberInfo); + } + } + + private static WriteObjectInfo GetObjectInfo(SerObjectInfoInit serObjectInfoInit) + { + WriteObjectInfo objectInfo; + + if (!serObjectInfoInit._oiPool.IsEmpty()) + { + objectInfo = (WriteObjectInfo)serObjectInfoInit._oiPool.Pop(); + objectInfo.InternalInit(); + } + else + { + objectInfo = new WriteObjectInfo(); + objectInfo._objectInfoId = serObjectInfoInit._objectInfoIdCount++; + } + + return objectInfo; + } + + private static void PutObjectInfo(SerObjectInfoInit serObjectInfoInit, WriteObjectInfo objectInfo) => + serObjectInfoInit._oiPool.Push(objectInfo); + } + + internal sealed class ReadObjectInfo + { + internal int _objectInfoId; + internal static int _readObjectInfoCounter; + + internal Type _objectType; + + internal ObjectManager _objectManager; + + internal int _count; + + internal bool _isSi = false; + internal bool _isTyped = false; + internal bool _isSimpleAssembly = false; + + internal SerObjectInfoCache _cache; + + internal string[] _wireMemberNames; + internal Type[] _wireMemberTypes; + + private int _lastPosition = 0; + + internal ISerializationSurrogate _serializationSurrogate = null; + internal StreamingContext _context; + + // Si Read + internal List _memberTypesList; + internal SerObjectInfoInit _serObjectInfoInit = null; + internal IFormatterConverter _formatterConverter; + + internal ReadObjectInfo() { } + + internal void ObjectEnd() { } + + internal void PrepareForReuse() + { + _lastPosition = 0; + } + + internal static ReadObjectInfo Create(Type objectType, ISurrogateSelector surrogateSelector, StreamingContext context, ObjectManager objectManager, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, bool bSimpleAssembly) + { + ReadObjectInfo roi = GetObjectInfo(serObjectInfoInit); + roi.Init(objectType, surrogateSelector, context, objectManager, serObjectInfoInit, converter, bSimpleAssembly); + return roi; + } + + internal void Init(Type objectType, ISurrogateSelector surrogateSelector, StreamingContext context, ObjectManager objectManager, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, bool bSimpleAssembly) + { + _objectType = objectType; + _objectManager = objectManager; + _context = context; + _serObjectInfoInit = serObjectInfoInit; + _formatterConverter = converter; + _isSimpleAssembly = bSimpleAssembly; + + InitReadConstructor(objectType, surrogateSelector, context); + } + + internal static ReadObjectInfo Create(Type objectType, string[] memberNames, Type[] memberTypes, ISurrogateSelector surrogateSelector, StreamingContext context, ObjectManager objectManager, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, bool bSimpleAssembly) + { + ReadObjectInfo roi = GetObjectInfo(serObjectInfoInit); + roi.Init(objectType, memberNames, memberTypes, surrogateSelector, context, objectManager, serObjectInfoInit, converter, bSimpleAssembly); + return roi; + } + + internal void Init(Type objectType, string[] memberNames, Type[] memberTypes, ISurrogateSelector surrogateSelector, StreamingContext context, ObjectManager objectManager, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, bool bSimpleAssembly) + { + _objectType = objectType; + _objectManager = objectManager; + _wireMemberNames = memberNames; + _wireMemberTypes = memberTypes; + _context = context; + _serObjectInfoInit = serObjectInfoInit; + _formatterConverter = converter; + _isSimpleAssembly = bSimpleAssembly; + if (memberTypes != null) + { + _isTyped = true; + } + if (objectType != null) + { + InitReadConstructor(objectType, surrogateSelector, context); + } + } + + private void InitReadConstructor(Type objectType, ISurrogateSelector surrogateSelector, StreamingContext context) + { + if (objectType.IsArray) + { + InitNoMembers(); + return; + } + + ISurrogateSelector surrogateSelectorTemp = null; + if (surrogateSelector != null) + { + _serializationSurrogate = surrogateSelector.GetSurrogate(objectType, context, out surrogateSelectorTemp); + } + + if (_serializationSurrogate != null) + { + _isSi = true; + } + else if (!ReferenceEquals(objectType, Converter.s_typeofObject) && Converter.s_typeofISerializable.IsAssignableFrom(objectType)) + { + _isSi = true; + } + + if (_isSi) + { + InitSiRead(); + } + else + { + InitMemberInfo(); + } + } + + private void InitSiRead() + { + if (_memberTypesList != null) + { + _memberTypesList = new List(20); + } + } + + private void InitNoMembers() + { + _cache = new SerObjectInfoCache(_objectType); + } + + private void InitMemberInfo() + { + _cache = new SerObjectInfoCache(_objectType); + _cache._memberInfos = FormatterServices.GetSerializableMembers(_objectType, _context); + _count = _cache._memberInfos.Length; + _cache._memberNames = new string[_count]; + _cache._memberTypes = new Type[_count]; + + // Calculate new arrays + for (int i = 0; i < _count; i++) + { + _cache._memberNames[i] = _cache._memberInfos[i].Name; + _cache._memberTypes[i] = GetMemberType(_cache._memberInfos[i]); + } + + _isTyped = true; + } + + // Get the memberInfo for a memberName + internal MemberInfo GetMemberInfo(string name) + { + if (_cache == null) + { + return null; + } + + if (_isSi) + { + throw new SerializationException(SR.Format(SR.Serialization_MemberInfo, _objectType + " " + name)); + } + + if (_cache._memberInfos == null) + { + throw new SerializationException(SR.Format(SR.Serialization_NoMemberInfo, _objectType + " " + name)); + } + + int position = Position(name); + return position != -1 ? _cache._memberInfos[position] : null; + } + + // Get the ObjectType for a memberName + internal Type GetType(string name) + { + int position = Position(name); + if (position == -1) + { + return null; + } + + Type type = _isTyped ? _cache._memberTypes[position] : _memberTypesList[position]; + if (type == null) + { + throw new SerializationException(SR.Format(SR.Serialization_ISerializableTypes, _objectType + " " + name)); + } + + return type; + } + + // Adds the value for a memberName + internal void AddValue(string name, object value, ref SerializationInfo si, ref object[] memberData) + { + if (_isSi) + { + si.AddValue(name, value); + } + else + { + // If a member in the stream is not found, ignore it + int position = Position(name); + if (position != -1) + { + memberData[position] = value; + } + } + } + + internal void InitDataStore(ref SerializationInfo si, ref object[] memberData) + { + if (_isSi) + { + if (si == null) + { + si = new SerializationInfo(_objectType, _formatterConverter); + } + } + else + { + if (memberData == null && _cache != null) + { + memberData = new object[_cache._memberNames.Length]; + } + } + } + + // Records an objectId in a member when the actual object for that member is not yet known + internal void RecordFixup(long objectId, string name, long idRef) + { + if (_isSi) + { + _objectManager.RecordDelayedFixup(objectId, name, idRef); + } + else + { + int position = Position(name); + if (position != -1) + { + _objectManager.RecordFixup(objectId, _cache._memberInfos[position], idRef); + } + } + } + + // Fills in the values for an object + internal void PopulateObjectMembers(object obj, object[] memberData) + { + if (!_isSi && memberData != null) + { + FormatterServices.PopulateObjectMembers(obj, _cache._memberInfos, memberData); + } + } + + // Specifies the position in the memberNames array of this name + private int Position(string name) + { + if (_cache == null) + { + return -1; + } + + if (_cache._memberNames.Length > 0 && _cache._memberNames[_lastPosition].Equals(name)) + { + return _lastPosition; + } + else if ((++_lastPosition < _cache._memberNames.Length) && (_cache._memberNames[_lastPosition].Equals(name))) + { + return _lastPosition; + } + else + { + // Search for name + for (int i = 0; i < _cache._memberNames.Length; i++) + { + if (_cache._memberNames[i].Equals(name)) + { + _lastPosition = i; + return _lastPosition; + } + } + + _lastPosition = 0; + return -1; + } + } + + // Return the member Types in order of memberNames + internal Type[] GetMemberTypes(string[] inMemberNames, Type objectType) + { + if (_isSi) + { + throw new SerializationException(SR.Format(SR.Serialization_ISerializableTypes, objectType)); + } + + if (_cache == null) + { + return null; + } + + if (_cache._memberTypes == null) + { + _cache._memberTypes = new Type[_count]; + for (int i = 0; i < _count; i++) + { + _cache._memberTypes[i] = GetMemberType(_cache._memberInfos[i]); + } + } + + bool memberMissing = false; + if (inMemberNames.Length < _cache._memberInfos.Length) + { + memberMissing = true; + } + + Type[] outMemberTypes = new Type[_cache._memberInfos.Length]; + bool isFound = false; + for (int i = 0; i < _cache._memberInfos.Length; i++) + { + if (!memberMissing && inMemberNames[i].Equals(_cache._memberInfos[i].Name)) + { + outMemberTypes[i] = _cache._memberTypes[i]; + } + else + { + // MemberNames on wire in different order then memberInfos returned by reflection + isFound = false; + for (int j = 0; j < inMemberNames.Length; j++) + { + if (_cache._memberInfos[i].Name.Equals(inMemberNames[j])) + { + outMemberTypes[i] = _cache._memberTypes[i]; + isFound = true; + break; + } + } + if (!isFound) + { + // A field on the type isn't found. See if the field has OptionalFieldAttribute. We only throw + // when the assembly format is set appropriately. + if (!_isSimpleAssembly && + _cache._memberInfos[i].GetCustomAttribute(typeof(OptionalFieldAttribute), inherit: false) == null) + { + throw new SerializationException(SR.Format(SR.Serialization_MissingMember, _cache._memberNames[i], objectType, typeof(OptionalFieldAttribute).FullName)); + } + } + } + } + + return outMemberTypes; + } + + // Retrieves the member type from the MemberInfo + internal Type GetMemberType(MemberInfo objMember) + { + if (objMember is FieldInfo) + { + return ((FieldInfo)objMember).FieldType; + } + + throw new SerializationException(SR.Format(SR.Serialization_SerMemberInfo, objMember.GetType())); + } + + + private static ReadObjectInfo GetObjectInfo(SerObjectInfoInit serObjectInfoInit) + { + ReadObjectInfo roi = new ReadObjectInfo(); + roi._objectInfoId = Interlocked.Increment(ref _readObjectInfoCounter); + return roi; + } + } + + internal sealed class SerObjectInfoInit + { + internal readonly Dictionary _seenBeforeTable = new Dictionary(); + internal int _objectInfoIdCount = 1; + internal SerStack _oiPool = new SerStack("SerObjectInfo Pool"); + } + + internal sealed class SerObjectInfoCache + { + internal readonly string _fullTypeName; + internal readonly string _assemblyString; + internal readonly bool _hasTypeForwardedFrom; + + internal MemberInfo[] _memberInfos; + internal string[] _memberNames; + internal Type[] _memberTypes; + + internal SerObjectInfoCache(string typeName, string assemblyName, bool hasTypeForwardedFrom) + { + _fullTypeName = typeName; + _assemblyString = assemblyName; + _hasTypeForwardedFrom = hasTypeForwardedFrom; + } + + internal SerObjectInfoCache(Type type) + { + TypeInformation typeInformation = BinaryFormatter.GetTypeInformation(type); + _fullTypeName = typeInformation.FullTypeName; + _assemblyString = typeInformation.AssemblyString; + _hasTypeForwardedFrom = typeInformation.HasTypeForwardedFrom; + } + } + + internal sealed class TypeInformation + { + internal TypeInformation(string fullTypeName, string assemblyString, bool hasTypeForwardedFrom) + { + FullTypeName = fullTypeName; + AssemblyString = assemblyString; + HasTypeForwardedFrom = hasTypeForwardedFrom; + } + + internal string FullTypeName { get; } + internal string AssemblyString { get; } + internal bool HasTypeForwardedFrom { get; } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs new file mode 100644 index 000000000000..03c16fefce20 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs @@ -0,0 +1,1088 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Runtime.Remoting.Messaging; +using System.Diagnostics; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class ObjectReader + { + // System.Serializer information + internal Stream _stream; + internal ISurrogateSelector _surrogates; + internal StreamingContext _context; + internal ObjectManager _objectManager; + internal InternalFE _formatterEnums; + internal SerializationBinder _binder; + + // Top object and headers + internal long _topId; + internal bool _isSimpleAssembly = false; + internal object _handlerObject; + internal object _topObject; + internal Header[] _headers; + internal HeaderHandler _handler; + internal SerObjectInfoInit _serObjectInfoInit; + internal IFormatterConverter _formatterConverter; + + // Stack of Object ParseRecords + internal SerStack _stack; + + // ValueType Fixup Stack + private SerStack _valueFixupStack; + + // Cross AppDomain + internal object[] _crossAppDomainArray; //Set by the BinaryFormatter + + //MethodCall and MethodReturn are handled special for perf reasons + private bool _fullDeserialization; + + private SerStack ValueFixupStack => _valueFixupStack ?? (_valueFixupStack = new SerStack("ValueType Fixup Stack")); + + // Older formatters generate ids for valuetypes using a different counter than ref types. Newer ones use + // a single counter, only value types have a negative value. Need a way to handle older formats. + private const int ThresholdForValueTypeIds = int.MaxValue; + private bool _oldFormatDetected = false; + private IntSizedArray _valTypeObjectIdTable; + + private readonly NameCache _typeCache = new NameCache(); + + internal object TopObject + { + get { return _topObject; } + set + { + _topObject = value; + if (_objectManager != null) + { + _objectManager.TopObject = value; + } + } + } + + internal ObjectReader(Stream stream, ISurrogateSelector selector, StreamingContext context, InternalFE formatterEnums, SerializationBinder binder) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream), SR.ArgumentNull_Stream); + } + + _stream = stream; + _surrogates = selector; + _context = context; + _binder = binder; + _formatterEnums = formatterEnums; + } + + internal object Deserialize(HeaderHandler handler, BinaryParser serParser, bool fCheck) + { + if (serParser == null) + { + throw new ArgumentNullException(nameof(serParser), SR.Format(SR.ArgumentNull_WithParamName, serParser)); + } + + _fullDeserialization = false; + TopObject = null; + _topId = 0; + + _isSimpleAssembly = (_formatterEnums._FEassemblyFormat == FormatterAssemblyStyle.Simple); + + _handler = handler; + + if (_fullDeserialization) + { + // Reinitialize + _objectManager = new ObjectManager(_surrogates, _context, false, false); + _serObjectInfoInit = new SerObjectInfoInit(); + } + + // Will call back to ParseObject, ParseHeader for each object found + serParser.Run(); + + if (_fullDeserialization) + { + _objectManager.DoFixups(); + } + + if (TopObject == null) + { + throw new SerializationException(SR.Serialization_TopObject); + } + + //if TopObject has a surrogate then the actual object may be changed during special fixup + //So refresh it using topID. + if (HasSurrogate(TopObject.GetType()) && _topId != 0)//Not yet resolved + { + TopObject = _objectManager.GetObject(_topId); + } + + if (TopObject is IObjectReference) + { + TopObject = ((IObjectReference)TopObject).GetRealObject(_context); + } + + if (_fullDeserialization) + { + _objectManager.RaiseDeserializationEvent(); // This will raise both IDeserialization and [OnDeserialized] events + } + + // Return the headers if there is a handler + if (handler != null) + { + _handlerObject = handler(_headers); + } + + return TopObject; + } + + private bool HasSurrogate(Type t) + { + ISurrogateSelector ignored; + return _surrogates != null && _surrogates.GetSurrogate(t, _context, out ignored) != null; + } + + private void CheckSerializable(Type t) + { + if (!t.GetTypeInfo().IsSerializable && !HasSurrogate(t)) + { + throw new SerializationException(string.Format(CultureInfo.InvariantCulture, SR.Serialization_NonSerType, t.FullName, t.GetTypeInfo().Assembly.FullName)); + } + } + + private void InitFullDeserialization() + { + _fullDeserialization = true; + _stack = new SerStack("ObjectReader Object Stack"); + _objectManager = new ObjectManager(_surrogates, _context, false, false); + if (_formatterConverter == null) + { + _formatterConverter = new FormatterConverter(); + } + } + + internal object CrossAppDomainArray(int index) + { + Debug.Assert(index < _crossAppDomainArray.Length, "[System.Runtime.Serialization.Formatters.BinaryObjectReader index out of range for CrossAppDomainArray]"); + return _crossAppDomainArray[index]; + } + + internal ReadObjectInfo CreateReadObjectInfo(Type objectType) + { + return ReadObjectInfo.Create(objectType, _surrogates, _context, _objectManager, _serObjectInfoInit, _formatterConverter, _isSimpleAssembly); + } + + internal ReadObjectInfo CreateReadObjectInfo(Type objectType, string[] memberNames, Type[] memberTypes) + { + return ReadObjectInfo.Create(objectType, memberNames, memberTypes, _surrogates, _context, _objectManager, _serObjectInfoInit, _formatterConverter, _isSimpleAssembly); + } + + internal void Parse(ParseRecord pr) + { + switch (pr._PRparseTypeEnum) + { + case InternalParseTypeE.SerializedStreamHeader: + ParseSerializedStreamHeader(pr); + break; + case InternalParseTypeE.SerializedStreamHeaderEnd: + ParseSerializedStreamHeaderEnd(pr); + break; + case InternalParseTypeE.Object: + ParseObject(pr); + break; + case InternalParseTypeE.ObjectEnd: + ParseObjectEnd(pr); + break; + case InternalParseTypeE.Member: + ParseMember(pr); + break; + case InternalParseTypeE.MemberEnd: + ParseMemberEnd(pr); + break; + case InternalParseTypeE.Body: + case InternalParseTypeE.BodyEnd: + case InternalParseTypeE.Envelope: + case InternalParseTypeE.EnvelopeEnd: + break; + case InternalParseTypeE.Empty: + default: + throw new SerializationException(SR.Format(SR.Serialization_XMLElement, pr._PRname)); + } + } + + // Styled ParseError output + private void ParseError(ParseRecord processing, ParseRecord onStack) + { + throw new SerializationException(SR.Format(SR.Serialization_ParseError, onStack._PRname + " " + onStack._PRparseTypeEnum + " " + processing._PRname + " " + processing._PRparseTypeEnum)); + } + + // Parse the SerializedStreamHeader element. This is the first element in the stream if present + private void ParseSerializedStreamHeader(ParseRecord pr) => _stack.Push(pr); + + // Parse the SerializedStreamHeader end element. This is the last element in the stream if present + private void ParseSerializedStreamHeaderEnd(ParseRecord pr) => _stack.Pop(); + + // New object encountered in stream + private void ParseObject(ParseRecord pr) + { + if (!_fullDeserialization) + { + InitFullDeserialization(); + } + + if (pr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + _topId = pr._PRobjectId; + } + + if (pr._PRparseTypeEnum == InternalParseTypeE.Object) + { + _stack.Push(pr); // Nested objects member names are already on stack + } + + if (pr._PRobjectTypeEnum == InternalObjectTypeE.Array) + { + ParseArray(pr); + return; + } + + // If the Type is null, this means we have a typeload issue + // mark the object with TypeLoadExceptionHolder + if (pr._PRdtType == null) + { + pr._PRnewObj = new TypeLoadExceptionHolder(pr._PRkeyDt); + return; + } + + if (ReferenceEquals(pr._PRdtType, Converter.s_typeofString)) + { + // String as a top level object + if (pr._PRvalue != null) + { + pr._PRnewObj = pr._PRvalue; + if (pr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + TopObject = pr._PRnewObj; + return; + } + else + { + _stack.Pop(); + RegisterObject(pr._PRnewObj, pr, (ParseRecord)_stack.Peek()); + return; + } + } + else + { + // xml Doesn't have the value until later + return; + } + } + else + { + CheckSerializable(pr._PRdtType); + pr._PRnewObj = FormatterServices.GetUninitializedObject(pr._PRdtType); + + // Run the OnDeserializing methods + _objectManager.RaiseOnDeserializingEvent(pr._PRnewObj); + } + + if (pr._PRnewObj == null) + { + throw new SerializationException(SR.Format(SR.Serialization_TopObjectInstantiate, pr._PRdtType)); + } + + if (pr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + TopObject = pr._PRnewObj; + } + + if (pr._PRobjectInfo == null) + { + pr._PRobjectInfo = ReadObjectInfo.Create(pr._PRdtType, _surrogates, _context, _objectManager, _serObjectInfoInit, _formatterConverter, _isSimpleAssembly); + } + } + + // End of object encountered in stream + private void ParseObjectEnd(ParseRecord pr) + { + ParseRecord objectPr = (ParseRecord)_stack.Peek() ?? pr; + + if (objectPr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + if (ReferenceEquals(objectPr._PRdtType, Converter.s_typeofString)) + { + objectPr._PRnewObj = objectPr._PRvalue; + TopObject = objectPr._PRnewObj; + return; + } + } + + _stack.Pop(); + ParseRecord parentPr = (ParseRecord)_stack.Peek(); + + if (objectPr._PRnewObj == null) + { + return; + } + + if (objectPr._PRobjectTypeEnum == InternalObjectTypeE.Array) + { + if (objectPr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + TopObject = objectPr._PRnewObj; + } + + RegisterObject(objectPr._PRnewObj, objectPr, parentPr); + return; + } + + objectPr._PRobjectInfo.PopulateObjectMembers(objectPr._PRnewObj, objectPr._PRmemberData); + + // Registration is after object is populated + if ((!objectPr._PRisRegistered) && (objectPr._PRobjectId > 0)) + { + RegisterObject(objectPr._PRnewObj, objectPr, parentPr); + } + + if (objectPr._PRisValueTypeFixup) + { + ValueFixup fixup = (ValueFixup)ValueFixupStack.Pop(); //Value fixup + fixup.Fixup(objectPr, parentPr); // Value fixup + } + + if (objectPr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + TopObject = objectPr._PRnewObj; + } + + objectPr._PRobjectInfo.ObjectEnd(); + } + + // Array object encountered in stream + private void ParseArray(ParseRecord pr) + { + long genId = pr._PRobjectId; + + if (pr._PRarrayTypeEnum == InternalArrayTypeE.Base64) + { + // ByteArray + pr._PRnewObj = pr._PRvalue.Length > 0 ? + Convert.FromBase64String(pr._PRvalue) : + Array.Empty(); + + if (_stack.Peek() == pr) + { + _stack.Pop(); + } + if (pr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + TopObject = pr._PRnewObj; + } + + ParseRecord parentPr = (ParseRecord)_stack.Peek(); + + // Base64 can be registered at this point because it is populated + RegisterObject(pr._PRnewObj, pr, parentPr); + } + else if ((pr._PRnewObj != null) && Converter.IsWriteAsByteArray(pr._PRarrayElementTypeCode)) + { + // Primtive typed Array has already been read + if (pr._PRobjectPositionEnum == InternalObjectPositionE.Top) + { + TopObject = pr._PRnewObj; + } + + ParseRecord parentPr = (ParseRecord)_stack.Peek(); + + // Primitive typed array can be registered at this point because it is populated + RegisterObject(pr._PRnewObj, pr, parentPr); + } + else if ((pr._PRarrayTypeEnum == InternalArrayTypeE.Jagged) || (pr._PRarrayTypeEnum == InternalArrayTypeE.Single)) + { + // Multidimensional jagged array or single array + bool couldBeValueType = true; + if ((pr._PRlowerBoundA == null) || (pr._PRlowerBoundA[0] == 0)) + { + if (ReferenceEquals(pr._PRarrayElementType, Converter.s_typeofString)) + { + pr._PRobjectA = new string[pr._PRlengthA[0]]; + pr._PRnewObj = pr._PRobjectA; + couldBeValueType = false; + } + else if (ReferenceEquals(pr._PRarrayElementType, Converter.s_typeofObject)) + { + pr._PRobjectA = new object[pr._PRlengthA[0]]; + pr._PRnewObj = pr._PRobjectA; + couldBeValueType = false; + } + else if (pr._PRarrayElementType != null) + { + pr._PRnewObj = Array.CreateInstance(pr._PRarrayElementType, pr._PRlengthA[0]); + } + pr._PRisLowerBound = false; + } + else + { + if (pr._PRarrayElementType != null) + { + pr._PRnewObj = Array.CreateInstance(pr._PRarrayElementType, pr._PRlengthA, pr._PRlowerBoundA); + } + pr._PRisLowerBound = true; + } + + if (pr._PRarrayTypeEnum == InternalArrayTypeE.Single) + { + if (!pr._PRisLowerBound && (Converter.IsWriteAsByteArray(pr._PRarrayElementTypeCode))) + { + pr._PRprimitiveArray = new PrimitiveArray(pr._PRarrayElementTypeCode, (Array)pr._PRnewObj); + } + else if (couldBeValueType && pr._PRarrayElementType != null) + { + if (!pr._PRarrayElementType.GetTypeInfo().IsValueType && !pr._PRisLowerBound) + { + pr._PRobjectA = (object[])pr._PRnewObj; + } + } + } + + // For binary, headers comes in as an array of header objects + if (pr._PRobjectPositionEnum == InternalObjectPositionE.Headers) + { + _headers = (Header[])pr._PRnewObj; + } + + pr._PRindexMap = new int[1]; + } + else if (pr._PRarrayTypeEnum == InternalArrayTypeE.Rectangular) + { + // Rectangle array + + pr._PRisLowerBound = false; + if (pr._PRlowerBoundA != null) + { + for (int i = 0; i < pr._PRrank; i++) + { + if (pr._PRlowerBoundA[i] != 0) + { + pr._PRisLowerBound = true; + } + } + } + + if (pr._PRarrayElementType != null) + { + pr._PRnewObj = !pr._PRisLowerBound ? + Array.CreateInstance(pr._PRarrayElementType, pr._PRlengthA) : + Array.CreateInstance(pr._PRarrayElementType, pr._PRlengthA, pr._PRlowerBoundA); + } + + // Calculate number of items + int sum = 1; + for (int i = 0; i < pr._PRrank; i++) + { + sum = sum * pr._PRlengthA[i]; + } + pr._PRindexMap = new int[pr._PRrank]; + pr._PRrectangularMap = new int[pr._PRrank]; + pr._PRlinearlength = sum; + } + else + { + throw new SerializationException(SR.Format(SR.Serialization_ArrayType, pr._PRarrayTypeEnum)); + } + } + + // Builds a map for each item in an incoming rectangle array. The map specifies where the item is placed in the output Array Object + private void NextRectangleMap(ParseRecord pr) + { + // For each invocation, calculate the next rectangular array position + // example + // indexMap 0 [0,0,0] + // indexMap 1 [0,0,1] + // indexMap 2 [0,0,2] + // indexMap 3 [0,0,3] + // indexMap 4 [0,1,0] + for (int irank = pr._PRrank - 1; irank > -1; irank--) + { + // Find the current or lower dimension which can be incremented. + if (pr._PRrectangularMap[irank] < pr._PRlengthA[irank] - 1) + { + // The current dimension is at maximum. Increase the next lower dimension by 1 + pr._PRrectangularMap[irank]++; + if (irank < pr._PRrank - 1) + { + // The current dimension and higher dimensions are zeroed. + for (int i = irank + 1; i < pr._PRrank; i++) + { + pr._PRrectangularMap[i] = 0; + } + } + Array.Copy(pr._PRrectangularMap, 0, pr._PRindexMap, 0, pr._PRrank); + break; + } + } + } + + + // Array object item encountered in stream + private void ParseArrayMember(ParseRecord pr) + { + ParseRecord objectPr = (ParseRecord)_stack.Peek(); + + // Set up for inserting value into correct array position + if (objectPr._PRarrayTypeEnum == InternalArrayTypeE.Rectangular) + { + if (objectPr._PRmemberIndex > 0) + { + NextRectangleMap(objectPr); // Rectangle array, calculate position in array + } + if (objectPr._PRisLowerBound) + { + for (int i = 0; i < objectPr._PRrank; i++) + { + objectPr._PRindexMap[i] = objectPr._PRrectangularMap[i] + objectPr._PRlowerBoundA[i]; + } + } + } + else + { + objectPr._PRindexMap[0] = !objectPr._PRisLowerBound ? + objectPr._PRmemberIndex : // Zero based array + objectPr._PRlowerBoundA[0] + objectPr._PRmemberIndex; // Lower Bound based array + } + + // Set Array element according to type of element + + if (pr._PRmemberValueEnum == InternalMemberValueE.Reference) + { + // Object Reference + + // See if object has already been instantiated + object refObj = _objectManager.GetObject(pr._PRidRef); + if (refObj == null) + { + // Object not instantiated + // Array fixup manager + int[] fixupIndex = new int[objectPr._PRrank]; + Array.Copy(objectPr._PRindexMap, 0, fixupIndex, 0, objectPr._PRrank); + + _objectManager.RecordArrayElementFixup(objectPr._PRobjectId, fixupIndex, pr._PRidRef); + } + else + { + if (objectPr._PRobjectA != null) + { + objectPr._PRobjectA[objectPr._PRindexMap[0]] = refObj; + } + else + { + ((Array)objectPr._PRnewObj).SetValue(refObj, objectPr._PRindexMap); // Object has been instantiated + } + } + } + else if (pr._PRmemberValueEnum == InternalMemberValueE.Nested) + { + //Set up dtType for ParseObject + if (pr._PRdtType == null) + { + pr._PRdtType = objectPr._PRarrayElementType; + } + + ParseObject(pr); + _stack.Push(pr); + + if (objectPr._PRarrayElementType != null) + { + if ((objectPr._PRarrayElementType.GetTypeInfo().IsValueType) && (pr._PRarrayElementTypeCode == InternalPrimitiveTypeE.Invalid)) + { + pr._PRisValueTypeFixup = true; //Valuefixup + ValueFixupStack.Push(new ValueFixup((Array)objectPr._PRnewObj, objectPr._PRindexMap)); //valuefixup + } + else + { + if (objectPr._PRobjectA != null) + { + objectPr._PRobjectA[objectPr._PRindexMap[0]] = pr._PRnewObj; + } + else + { + ((Array)objectPr._PRnewObj).SetValue(pr._PRnewObj, objectPr._PRindexMap); + } + } + } + } + else if (pr._PRmemberValueEnum == InternalMemberValueE.InlineValue) + { + if ((ReferenceEquals(objectPr._PRarrayElementType, Converter.s_typeofString)) || (ReferenceEquals(pr._PRdtType, Converter.s_typeofString))) + { + // String in either a string array, or a string element of an object array + ParseString(pr, objectPr); + if (objectPr._PRobjectA != null) + { + objectPr._PRobjectA[objectPr._PRindexMap[0]] = pr._PRvalue; + } + else + { + ((Array)objectPr._PRnewObj).SetValue(pr._PRvalue, objectPr._PRindexMap); + } + } + else if (objectPr._PRisArrayVariant) + { + // Array of type object + if (pr._PRkeyDt == null) + { + throw new SerializationException(SR.Serialization_ArrayTypeObject); + } + + object var = null; + + if (ReferenceEquals(pr._PRdtType, Converter.s_typeofString)) + { + ParseString(pr, objectPr); + var = pr._PRvalue; + } + else if (ReferenceEquals(pr._PRdtTypeCode, InternalPrimitiveTypeE.Invalid)) + { + CheckSerializable(pr._PRdtType); + // Not nested and invalid, so it is an empty object + var = FormatterServices.GetUninitializedObject(pr._PRdtType); + } + else + { + var = pr._PRvarValue != null ? + pr._PRvarValue : + Converter.FromString(pr._PRvalue, pr._PRdtTypeCode); + } + if (objectPr._PRobjectA != null) + { + objectPr._PRobjectA[objectPr._PRindexMap[0]] = var; + } + else + { + ((Array)objectPr._PRnewObj).SetValue(var, objectPr._PRindexMap); // Primitive type + } + } + else + { + // Primitive type + if (objectPr._PRprimitiveArray != null) + { + // Fast path for Soap primitive arrays. Binary was handled in the BinaryParser + objectPr._PRprimitiveArray.SetValue(pr._PRvalue, objectPr._PRindexMap[0]); + } + else + { + object var = pr._PRvarValue != null ? + pr._PRvarValue : + Converter.FromString(pr._PRvalue, objectPr._PRarrayElementTypeCode); + if (objectPr._PRobjectA != null) + { + objectPr._PRobjectA[objectPr._PRindexMap[0]] = var; + } + else + { + ((Array)objectPr._PRnewObj).SetValue(var, objectPr._PRindexMap); // Primitive type + } + } + } + } + else if (pr._PRmemberValueEnum == InternalMemberValueE.Null) + { + objectPr._PRmemberIndex += pr._consecutiveNullArrayEntryCount - 1; //also incremented again below + } + else + { + ParseError(pr, objectPr); + } + + objectPr._PRmemberIndex++; + } + + private void ParseArrayMemberEnd(ParseRecord pr) + { + // If this is a nested array object, then pop the stack + if (pr._PRmemberValueEnum == InternalMemberValueE.Nested) + { + ParseObjectEnd(pr); + } + } + + // Object member encountered in stream + private void ParseMember(ParseRecord pr) + { + ParseRecord objectPr = (ParseRecord)_stack.Peek(); + string objName = objectPr?._PRname; + + switch (pr._PRmemberTypeEnum) + { + case InternalMemberTypeE.Item: + ParseArrayMember(pr); + return; + case InternalMemberTypeE.Field: + break; + } + + //if ((pr.PRdtType == null) && !objectPr.PRobjectInfo.isSi) + if (pr._PRdtType == null && objectPr._PRobjectInfo._isTyped) + { + pr._PRdtType = objectPr._PRobjectInfo.GetType(pr._PRname); + + if (pr._PRdtType != null) + { + pr._PRdtTypeCode = Converter.ToCode(pr._PRdtType); + } + } + + if (pr._PRmemberValueEnum == InternalMemberValueE.Null) + { + // Value is Null + objectPr._PRobjectInfo.AddValue(pr._PRname, null, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + else if (pr._PRmemberValueEnum == InternalMemberValueE.Nested) + { + ParseObject(pr); + _stack.Push(pr); + + if ((pr._PRobjectInfo != null) && pr._PRobjectInfo._objectType != null && (pr._PRobjectInfo._objectType.GetTypeInfo().IsValueType)) + { + pr._PRisValueTypeFixup = true; //Valuefixup + ValueFixupStack.Push(new ValueFixup(objectPr._PRnewObj, pr._PRname, objectPr._PRobjectInfo));//valuefixup + } + else + { + objectPr._PRobjectInfo.AddValue(pr._PRname, pr._PRnewObj, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + } + else if (pr._PRmemberValueEnum == InternalMemberValueE.Reference) + { + // See if object has already been instantiated + object refObj = _objectManager.GetObject(pr._PRidRef); + if (refObj == null) + { + objectPr._PRobjectInfo.AddValue(pr._PRname, null, ref objectPr._PRsi, ref objectPr._PRmemberData); + objectPr._PRobjectInfo.RecordFixup(objectPr._PRobjectId, pr._PRname, pr._PRidRef); // Object not instantiated + } + else + { + objectPr._PRobjectInfo.AddValue(pr._PRname, refObj, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + } + + else if (pr._PRmemberValueEnum == InternalMemberValueE.InlineValue) + { + // Primitive type or String + if (ReferenceEquals(pr._PRdtType, Converter.s_typeofString)) + { + ParseString(pr, objectPr); + objectPr._PRobjectInfo.AddValue(pr._PRname, pr._PRvalue, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + else if (pr._PRdtTypeCode == InternalPrimitiveTypeE.Invalid) + { + // The member field was an object put the value is Inline either bin.Base64 or invalid + if (pr._PRarrayTypeEnum == InternalArrayTypeE.Base64) + { + objectPr._PRobjectInfo.AddValue(pr._PRname, Convert.FromBase64String(pr._PRvalue), ref objectPr._PRsi, ref objectPr._PRmemberData); + } + else if (ReferenceEquals(pr._PRdtType, Converter.s_typeofObject)) + { + throw new SerializationException(SR.Format(SR.Serialization_TypeMissing, pr._PRname)); + } + else + { + ParseString(pr, objectPr); // Register the object if it has an objectId + // Object Class with no memberInfo data + // only special case where AddValue is needed? + if (ReferenceEquals(pr._PRdtType, Converter.s_typeofSystemVoid)) + { + objectPr._PRobjectInfo.AddValue(pr._PRname, pr._PRdtType, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + else if (objectPr._PRobjectInfo._isSi) + { + // ISerializable are added as strings, the conversion to type is done by the + // ISerializable object + objectPr._PRobjectInfo.AddValue(pr._PRname, pr._PRvalue, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + } + } + else + { + object var = pr._PRvarValue != null ? + pr._PRvarValue : + Converter.FromString(pr._PRvalue, pr._PRdtTypeCode); + objectPr._PRobjectInfo.AddValue(pr._PRname, var, ref objectPr._PRsi, ref objectPr._PRmemberData); + } + } + else + { + ParseError(pr, objectPr); + } + } + + // Object member end encountered in stream + private void ParseMemberEnd(ParseRecord pr) + { + switch (pr._PRmemberTypeEnum) + { + case InternalMemberTypeE.Item: + ParseArrayMemberEnd(pr); + return; + case InternalMemberTypeE.Field: + if (pr._PRmemberValueEnum == InternalMemberValueE.Nested) + { + ParseObjectEnd(pr); + } + break; + default: + ParseError(pr, (ParseRecord)_stack.Peek()); + break; + } + } + + // Processes a string object by getting an internal ID for it and registering it with the objectManager + private void ParseString(ParseRecord pr, ParseRecord parentPr) + { + // Process String class + if ((!pr._PRisRegistered) && (pr._PRobjectId > 0)) + { + // String is treated as an object if it has an id + //m_objectManager.RegisterObject(pr.PRvalue, pr.PRobjectId); + RegisterObject(pr._PRvalue, pr, parentPr, true); + } + } + + private void RegisterObject(object obj, ParseRecord pr, ParseRecord objectPr) + { + RegisterObject(obj, pr, objectPr, false); + } + + private void RegisterObject(object obj, ParseRecord pr, ParseRecord objectPr, bool bIsString) + { + if (!pr._PRisRegistered) + { + pr._PRisRegistered = true; + + SerializationInfo si = null; + long parentId = 0; + MemberInfo memberInfo = null; + int[] indexMap = null; + + if (objectPr != null) + { + indexMap = objectPr._PRindexMap; + parentId = objectPr._PRobjectId; + + if (objectPr._PRobjectInfo != null) + { + if (!objectPr._PRobjectInfo._isSi) + { + // ParentId is only used if there is a memberInfo + memberInfo = objectPr._PRobjectInfo.GetMemberInfo(pr._PRname); + } + } + } + // SerializationInfo is always needed for ISerialization + si = pr._PRsi; + + if (bIsString) + { + _objectManager.RegisterString((string)obj, pr._PRobjectId, si, parentId, memberInfo); + } + else + { + _objectManager.RegisterObject(obj, pr._PRobjectId, si, parentId, memberInfo, indexMap); + } + } + } + + // Assigns an internal ID associated with the binary id number + internal long GetId(long objectId) + { + if (!_fullDeserialization) + { + InitFullDeserialization(); + } + + if (objectId > 0) + { + return objectId; + } + + if (_oldFormatDetected || objectId == -1) + { + // Alarm bells. This is an old format. Deal with it. + _oldFormatDetected = true; + if (_valTypeObjectIdTable == null) + { + _valTypeObjectIdTable = new IntSizedArray(); + } + + long tempObjId = 0; + if ((tempObjId = _valTypeObjectIdTable[(int)objectId]) == 0) + { + tempObjId = ThresholdForValueTypeIds + objectId; + _valTypeObjectIdTable[(int)objectId] = (int)tempObjId; + } + return tempObjId; + } + + return -1 * objectId; + } + + internal Type Bind(string assemblyString, string typeString) + { + Type type = null; + if (_binder != null) + { + type = _binder.BindToType(assemblyString, typeString); + } + if (type == null) + { + type = FastBindToType(assemblyString, typeString); + } + return type; + } + + internal sealed class TypeNAssembly + { + public Type Type; + public string AssemblyName; + } + + internal Type FastBindToType(string assemblyName, string typeName) + { + Type type = null; + + TypeNAssembly entry = (TypeNAssembly)_typeCache.GetCachedValue(typeName); + + if (entry == null || entry.AssemblyName != assemblyName) + { + Assembly assm = null; + if (_isSimpleAssembly) + { + try + { + Assembly.Load(new AssemblyName(assemblyName)); + } + catch { } + + if (assm == null) + { + return null; + } + + GetSimplyNamedTypeFromAssembly(assm, typeName, ref type); + } + else + { + try + { + assm = Assembly.Load(new AssemblyName(assemblyName)); + } + catch { } + + if (assm == null) + { + return null; + } + + type = FormatterServices.GetTypeFromAssembly(assm, typeName); + } + + if (type == null) + { + return null; + } + + // before adding it to cache, let us do the security check + CheckTypeForwardedTo(assm, type.GetTypeInfo().Assembly, type); + + entry = new TypeNAssembly(); + entry.Type = type; + entry.AssemblyName = assemblyName; + _typeCache.SetCachedValue(entry); + } + return entry.Type; + } + + private static void GetSimplyNamedTypeFromAssembly(Assembly assm, string typeName, ref Type type) + { + // 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 + { + type = FormatterServices.GetTypeFromAssembly(assm, typeName); + } + catch (TypeLoadException) { } + catch (FileNotFoundException) { } + catch (FileLoadException) { } + catch (BadImageFormatException) { } + } + + private string _previousAssemblyString; + private string _previousName; + private Type _previousType; + + internal Type GetType(BinaryAssemblyInfo assemblyInfo, string name) + { + Type objectType = null; + + if (((_previousName != null) && (_previousName.Length == name.Length) && (_previousName.Equals(name))) && + ((_previousAssemblyString != null) && (_previousAssemblyString.Length == assemblyInfo._assemblyString.Length) && (_previousAssemblyString.Equals(assemblyInfo._assemblyString)))) + { + objectType = _previousType; + } + else + { + objectType = Bind(assemblyInfo._assemblyString, name); + if (objectType == null) + { + Assembly sourceAssembly = assemblyInfo.GetAssembly(); + + if (_isSimpleAssembly) + { + GetSimplyNamedTypeFromAssembly(sourceAssembly, name, ref objectType); + } + else + { + objectType = FormatterServices.GetTypeFromAssembly(sourceAssembly, name); + } + + // here let us do the security check + if (objectType != null) + { + CheckTypeForwardedTo(sourceAssembly, objectType.GetTypeInfo().Assembly, objectType); + } + } + + _previousAssemblyString = assemblyInfo._assemblyString; + _previousName = name; + _previousType = objectType; + } + //Console.WriteLine("name "+name+" assembly "+assemblyInfo.assemblyString+" objectType "+objectType); + return objectType; + } + + private static void CheckTypeForwardedTo(Assembly sourceAssembly, Assembly destAssembly, Type resolvedType) + { + // nop on core + } + + internal sealed class TopLevelAssemblyTypeResolver + { + private readonly Assembly _topLevelAssembly; + + public TopLevelAssemblyTypeResolver(Assembly topLevelAssembly) + { + _topLevelAssembly = topLevelAssembly; + } + + public Type ResolveType(Assembly assembly, string simpleTypeName, bool ignoreCase) => + (assembly ?? _topLevelAssembly).GetType(simpleTypeName, false, ignoreCase); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectString.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectString.cs new file mode 100644 index 000000000000..ebcfc97740a9 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectString.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryObjectString : IStreamable + { + internal int _objectId; + internal string _value; + + internal BinaryObjectString() { } + + internal void Set(int objectId, string value) + { + _objectId = objectId; + _value = value; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.ObjectString); + output.WriteInt32(_objectId); + output.WriteString(_value); + } + + public void Read(BinaryParser input) + { + _objectId = input.ReadInt32(); + _value = input.ReadString(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMap.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMap.cs new file mode 100644 index 000000000000..c25e5e85f5f3 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMap.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryObjectWithMap : IStreamable + { + internal BinaryHeaderEnum _binaryHeaderEnum; + internal int _objectId; + internal string _name; + internal int _numMembers; + internal string[] _memberNames; + internal int _assemId; + + internal BinaryObjectWithMap() { } + + internal BinaryObjectWithMap(BinaryHeaderEnum binaryHeaderEnum) + { + _binaryHeaderEnum = binaryHeaderEnum; + } + + internal void Set(int objectId, string name, int numMembers, string[] memberNames, int assemId) + { + _objectId = objectId; + _name = name; + _numMembers = numMembers; + _memberNames = memberNames; + _assemId = assemId; + + _binaryHeaderEnum = assemId > 0 ? + BinaryHeaderEnum.ObjectWithMapAssemId : + BinaryHeaderEnum.ObjectWithMap; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_objectId); + output.WriteString(_name); + + output.WriteInt32(_numMembers); + for (int i = 0; i < _numMembers; i++) + { + output.WriteString(_memberNames[i]); + } + + if (_assemId > 0) + { + output.WriteInt32(_assemId); + } + } + + public void Read(BinaryParser input) + { + _objectId = input.ReadInt32(); + _name = input.ReadString(); + _numMembers = input.ReadInt32(); + + _memberNames = new string[_numMembers]; + for (int i = 0; i < _numMembers; i++) + { + _memberNames[i] = input.ReadString(); + } + + if (_binaryHeaderEnum == BinaryHeaderEnum.ObjectWithMapAssemId) + { + _assemId = input.ReadInt32(); + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMapTyped.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMapTyped.cs new file mode 100644 index 000000000000..df140dfc0d94 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWithMapTyped.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryObjectWithMapTyped : IStreamable + { + internal BinaryHeaderEnum _binaryHeaderEnum; + internal int _objectId; + internal string _name; + internal int _numMembers; + internal string[] _memberNames; + internal BinaryTypeEnum[] _binaryTypeEnumA; + internal object[] _typeInformationA; + internal int[] _memberAssemIds; + internal int _assemId; + + internal BinaryObjectWithMapTyped() { } + + internal BinaryObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) + { + _binaryHeaderEnum = binaryHeaderEnum; + } + + internal void Set(int objectId, string name, int numMembers, string[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, object[] typeInformationA, int[] memberAssemIds, int assemId) + { + _objectId = objectId; + _assemId = assemId; + _name = name; + _numMembers = numMembers; + _memberNames = memberNames; + _binaryTypeEnumA = binaryTypeEnumA; + _typeInformationA = typeInformationA; + _memberAssemIds = memberAssemIds; + _assemId = assemId; + + _binaryHeaderEnum = assemId > 0 ? + BinaryHeaderEnum.ObjectWithMapTypedAssemId : + BinaryHeaderEnum.ObjectWithMapTyped; + } + + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_objectId); + output.WriteString(_name); + + output.WriteInt32(_numMembers); + for (int i = 0; i < _numMembers; i++) + { + output.WriteString(_memberNames[i]); + } + for (int i = 0; i < _numMembers; i++) + { + output.WriteByte((byte)_binaryTypeEnumA[i]); + } + for (int i = 0; i < _numMembers; i++) + { + BinaryTypeConverter.WriteTypeInfo(_binaryTypeEnumA[i], _typeInformationA[i], _memberAssemIds[i], output); + } + + if (_assemId > 0) + { + output.WriteInt32(_assemId); + } + } + + public void Read(BinaryParser input) + { + // binaryHeaderEnum has already been read + _objectId = input.ReadInt32(); + _name = input.ReadString(); + _numMembers = input.ReadInt32(); + _memberNames = new string[_numMembers]; + _binaryTypeEnumA = new BinaryTypeEnum[_numMembers]; + _typeInformationA = new object[_numMembers]; + _memberAssemIds = new int[_numMembers]; + for (int i = 0; i < _numMembers; i++) + { + _memberNames[i] = input.ReadString(); + } + for (int i = 0; i < _numMembers; i++) + { + _binaryTypeEnumA[i] = (BinaryTypeEnum)input.ReadByte(); + } + for (int i = 0; i < _numMembers; i++) + { + if (_binaryTypeEnumA[i] != BinaryTypeEnum.ObjectUrt && _binaryTypeEnumA[i] != BinaryTypeEnum.ObjectUser) + { + _typeInformationA[i] = BinaryTypeConverter.ReadTypeInfo(_binaryTypeEnumA[i], input, out _memberAssemIds[i]); + } + else + { + BinaryTypeConverter.ReadTypeInfo(_binaryTypeEnumA[i], input, out _memberAssemIds[i]); + } + } + + if (_binaryHeaderEnum == BinaryHeaderEnum.ObjectWithMapTypedAssemId) + { + _assemId = input.ReadInt32(); + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs new file mode 100644 index 000000000000..3f0f8b831f48 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs @@ -0,0 +1,1051 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Collections.Generic; +using System.Runtime.Remoting.Messaging; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class ObjectWriter + { + private Queue _objectQueue; + private ObjectIDGenerator _idGenerator; + private int _currentId; + + private ISurrogateSelector _surrogates; + private StreamingContext _context; + private BinaryFormatterWriter _serWriter; + private SerializationObjectManager _objectManager; + + private long _topId; + private string _topName = null; + private Header[] _headers; + + private InternalFE _formatterEnums; + private SerializationBinder _binder; + + private SerObjectInfoInit _serObjectInfoInit; + + private IFormatterConverter _formatterConverter; + + internal object[] _crossAppDomainArray = null; + internal List _internalCrossAppDomainArray = null; + + private object _previousObj = null; + private long _previousId = 0; + + private Type _previousType = null; + private InternalPrimitiveTypeE _previousCode = InternalPrimitiveTypeE.Invalid; + + internal ObjectWriter(ISurrogateSelector selector, StreamingContext context, InternalFE formatterEnums, SerializationBinder binder) + { + _currentId = 1; + _surrogates = selector; + _context = context; + _binder = binder; + _formatterEnums = formatterEnums; + _objectManager = new SerializationObjectManager(context); + } + + // Commences the process of serializing the entire graph. + // initialize the graph walker. + internal void Serialize(object graph, Header[] inHeaders, BinaryFormatterWriter serWriter, bool fCheck) + { + if (graph == null) + { + throw new ArgumentNullException(nameof(graph), SR.ArgumentNull_Graph); + } + if (serWriter == null) + { + throw new ArgumentNullException(nameof(serWriter), SR.Format(SR.ArgumentNull_WithParamName, "serWriter")); + } + + _serWriter = serWriter; + _headers = inHeaders; + + serWriter.WriteBegin(); + long headerId = 0; + object obj; + long objectId; + bool isNew; + + // allocations if methodCall or methodResponse and no graph + _idGenerator = new ObjectIDGenerator(); + _objectQueue = new Queue(); + _formatterConverter = new FormatterConverter(); + _serObjectInfoInit = new SerObjectInfoInit(); + + _topId = InternalGetId(graph, false, null, out isNew); + headerId = _headers != null ? InternalGetId(_headers, false, null, out isNew) : -1; + WriteSerializedStreamHeader(_topId, headerId); + + // Write out SerializedStream header + if ((_headers != null) && (_headers.Length > 0)) + { + _objectQueue.Enqueue(_headers); + } + + _objectQueue.Enqueue(graph); + while ((obj = GetNext(out objectId)) != null) + { + WriteObjectInfo objectInfo = null; + + // GetNext will return either an object or a WriteObjectInfo. + // A WriteObjectInfo is returned if this object was member of another object + if (obj is WriteObjectInfo) + { + objectInfo = (WriteObjectInfo)obj; + } + else + { + objectInfo = WriteObjectInfo.Serialize(obj, _surrogates, _context, _serObjectInfoInit, _formatterConverter, this, _binder); + objectInfo._assemId = GetAssemblyId(objectInfo); + } + + objectInfo._objectId = objectId; + NameInfo typeNameInfo = TypeToNameInfo(objectInfo); + Write(objectInfo, typeNameInfo, typeNameInfo); + PutNameInfo(typeNameInfo); + objectInfo.ObjectEnd(); + } + + serWriter.WriteSerializationHeaderEnd(); + serWriter.WriteEnd(); + + // Invoke OnSerialized Event + _objectManager.RaiseOnSerializedEvent(); + } + + internal SerializationObjectManager ObjectManager => _objectManager; + + // Writes a given object to the stream. + private void Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo) + { + object obj = objectInfo._obj; + if (obj == null) + { + throw new ArgumentNullException(nameof(objectInfo) + "." + nameof(objectInfo._obj), SR.ArgumentNull_Obj); + } + Type objType = objectInfo._objectType; + long objectId = objectInfo._objectId; + + if (ReferenceEquals(objType, Converter.s_typeofString)) + { + // Top level String + memberNameInfo._NIobjectId = objectId; + _serWriter.WriteObjectString((int)objectId, obj.ToString()); + } + else + { + if (objectInfo._isArray) + { + WriteArray(objectInfo, memberNameInfo, null); + } + else + { + string[] memberNames; + Type[] memberTypes; + object[] memberData; + + objectInfo.GetMemberInfo(out memberNames, out memberTypes, out memberData); + + // Only Binary needs to transmit types for ISerializable because the binary formatter transmits the types in URT format. + // Soap transmits all types as strings, so it is up to the ISerializable object to convert the string back to its URT type + if (objectInfo._isSi || CheckTypeFormat(_formatterEnums._FEtypeFormat, FormatterTypeStyle.TypesAlways)) + { + memberNameInfo._NItransmitTypeOnObject = true; + memberNameInfo._NIisParentTypeOnObject = true; + typeNameInfo._NItransmitTypeOnObject = true; + typeNameInfo._NIisParentTypeOnObject = true; + } + + var memberObjectInfos = new WriteObjectInfo[memberNames.Length]; + + // Get assembly information + // Binary Serializer, assembly names need to be + // written before objects are referenced. + // GetAssemId here will write out the + // assemblyStrings at the right Binary + // Serialization object boundary. + for (int i = 0; i < memberTypes.Length; i++) + { + Type type = + memberTypes[i] != null ? memberTypes[i] : + memberData[i] != null ? GetType(memberData[i]) : + Converter.s_typeofObject; + + InternalPrimitiveTypeE code = ToCode(type); + if ((code == InternalPrimitiveTypeE.Invalid) && + (!ReferenceEquals(type, Converter.s_typeofString))) + { + if (memberData[i] != null) + { + memberObjectInfos[i] = WriteObjectInfo.Serialize( + memberData[i], + _surrogates, + _context, + _serObjectInfoInit, + _formatterConverter, + this, + _binder); + memberObjectInfos[i]._assemId = GetAssemblyId(memberObjectInfos[i]); + } + else + { + memberObjectInfos[i] = WriteObjectInfo.Serialize( + memberTypes[i], + _surrogates, + _context, + _serObjectInfoInit, + _formatterConverter, + _binder); + memberObjectInfos[i]._assemId = GetAssemblyId(memberObjectInfos[i]); + } + } + } + Write(objectInfo, memberNameInfo, typeNameInfo, memberNames, memberTypes, memberData, memberObjectInfos); + } + } + } + + // Writes a given object to the stream. + private void Write(WriteObjectInfo objectInfo, + NameInfo memberNameInfo, + NameInfo typeNameInfo, + string[] memberNames, + Type[] memberTypes, + object[] memberData, + WriteObjectInfo[] memberObjectInfos) + { + int numItems = memberNames.Length; + NameInfo topNameInfo = null; + + if (memberNameInfo != null) + { + memberNameInfo._NIobjectId = objectInfo._objectId; + _serWriter.WriteObject(memberNameInfo, typeNameInfo, numItems, memberNames, memberTypes, memberObjectInfos); + } + else if ((objectInfo._objectId == _topId) && (_topName != null)) + { + topNameInfo = MemberToNameInfo(_topName); + topNameInfo._NIobjectId = objectInfo._objectId; + _serWriter.WriteObject(topNameInfo, typeNameInfo, numItems, memberNames, memberTypes, memberObjectInfos); + } + else + { + if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString)) + { + typeNameInfo._NIobjectId = objectInfo._objectId; + _serWriter.WriteObject(typeNameInfo, null, numItems, memberNames, memberTypes, memberObjectInfos); + } + } + + if (memberNameInfo._NIisParentTypeOnObject) + { + memberNameInfo._NItransmitTypeOnObject = true; + memberNameInfo._NIisParentTypeOnObject = false; + } + else + { + memberNameInfo._NItransmitTypeOnObject = false; + } + + // Write members + for (int i = 0; i < numItems; i++) + { + WriteMemberSetup(objectInfo, memberNameInfo, typeNameInfo, memberNames[i], memberTypes[i], memberData[i], memberObjectInfos[i]); + } + + if (memberNameInfo != null) + { + memberNameInfo._NIobjectId = objectInfo._objectId; + _serWriter.WriteObjectEnd(memberNameInfo, typeNameInfo); + } + else if ((objectInfo._objectId == _topId) && (_topName != null)) + { + _serWriter.WriteObjectEnd(topNameInfo, typeNameInfo); + PutNameInfo(topNameInfo); + } + else if (!ReferenceEquals(objectInfo._objectType, Converter.s_typeofString)) + { + _serWriter.WriteObjectEnd(typeNameInfo, typeNameInfo); + } + } + + private void WriteMemberSetup(WriteObjectInfo objectInfo, + NameInfo memberNameInfo, + NameInfo typeNameInfo, + string memberName, + Type memberType, + object memberData, + WriteObjectInfo memberObjectInfo) + { + NameInfo newMemberNameInfo = MemberToNameInfo(memberName); // newMemberNameInfo contains the member type + + if (memberObjectInfo != null) + { + newMemberNameInfo._NIassemId = memberObjectInfo._assemId; + } + newMemberNameInfo._NItype = memberType; + + // newTypeNameInfo contains the data type + NameInfo newTypeNameInfo = null; + if (memberObjectInfo == null) + { + newTypeNameInfo = TypeToNameInfo(memberType); + } + else + { + newTypeNameInfo = TypeToNameInfo(memberObjectInfo); + } + + newMemberNameInfo._NItransmitTypeOnObject = memberNameInfo._NItransmitTypeOnObject; + newMemberNameInfo._NIisParentTypeOnObject = memberNameInfo._NIisParentTypeOnObject; + WriteMembers(newMemberNameInfo, newTypeNameInfo, memberData, objectInfo, typeNameInfo, memberObjectInfo); + PutNameInfo(newMemberNameInfo); + PutNameInfo(newTypeNameInfo); + } + + // Writes the members of an object + private void WriteMembers(NameInfo memberNameInfo, + NameInfo memberTypeNameInfo, + object memberData, + WriteObjectInfo objectInfo, + NameInfo typeNameInfo, + WriteObjectInfo memberObjectInfo) + { + Type memberType = memberNameInfo._NItype; + bool assignUniqueIdToValueType = false; + + // Types are transmitted for a member as follows: + // The member is of type object + // The member object of type is ISerializable and + // Binary - Types always transmitted. + + if (ReferenceEquals(memberType, Converter.s_typeofObject) || Nullable.GetUnderlyingType(memberType) != null) + { + memberTypeNameInfo._NItransmitTypeOnMember = true; + memberNameInfo._NItransmitTypeOnMember = true; + } + + if (CheckTypeFormat(_formatterEnums._FEtypeFormat, FormatterTypeStyle.TypesAlways) || (objectInfo._isSi)) + { + memberTypeNameInfo._NItransmitTypeOnObject = true; + memberNameInfo._NItransmitTypeOnObject = true; + memberNameInfo._NIisParentTypeOnObject = true; + } + + if (CheckForNull(objectInfo, memberNameInfo, memberTypeNameInfo, memberData)) + { + return; + } + + object outObj = memberData; + Type outType = null; + + // If member type does not equal data type, transmit type on object. + if (memberTypeNameInfo._NIprimitiveTypeEnum == InternalPrimitiveTypeE.Invalid) + { + outType = GetType(outObj); + if (!ReferenceEquals(memberType, outType)) + { + memberTypeNameInfo._NItransmitTypeOnMember = true; + memberNameInfo._NItransmitTypeOnMember = true; + } + } + + if (ReferenceEquals(memberType, Converter.s_typeofObject)) + { + assignUniqueIdToValueType = true; + memberType = GetType(memberData); + if (memberObjectInfo == null) + { + TypeToNameInfo(memberType, memberTypeNameInfo); + } + else + { + TypeToNameInfo(memberObjectInfo, memberTypeNameInfo); + } + } + + if (memberObjectInfo != null && memberObjectInfo._isArray) + { + // Array + long arrayId = 0; + if (outType == null) + { + outType = GetType(outObj); + } + + // outObj is an array. It can never be a value type.. + arrayId = Schedule(outObj, false, null, memberObjectInfo); + if (arrayId > 0) + { + // Array as object + memberNameInfo._NIobjectId = arrayId; + WriteObjectRef(memberNameInfo, arrayId); + } + else + { + // Nested Array + _serWriter.WriteMemberNested(memberNameInfo); + + memberObjectInfo._objectId = arrayId; + memberNameInfo._NIobjectId = arrayId; + WriteArray(memberObjectInfo, memberNameInfo, memberObjectInfo); + objectInfo.ObjectEnd(); + } + return; + } + + if (!WriteKnownValueClass(memberNameInfo, memberTypeNameInfo, memberData)) + { + if (outType == null) + { + outType = GetType(outObj); + } + + long memberObjectId = Schedule(outObj, assignUniqueIdToValueType, outType, memberObjectInfo); + if (memberObjectId < 0) + { + // Nested object + memberObjectInfo._objectId = memberObjectId; + NameInfo newTypeNameInfo = TypeToNameInfo(memberObjectInfo); + newTypeNameInfo._NIobjectId = memberObjectId; + Write(memberObjectInfo, memberNameInfo, newTypeNameInfo); + PutNameInfo(newTypeNameInfo); + memberObjectInfo.ObjectEnd(); + } + else + { + // Object reference + memberNameInfo._NIobjectId = memberObjectId; + WriteObjectRef(memberNameInfo, memberObjectId); + } + } + } + + // Writes out an array + private void WriteArray(WriteObjectInfo objectInfo, NameInfo memberNameInfo, WriteObjectInfo memberObjectInfo) + { + bool isAllocatedMemberNameInfo = false; + if (memberNameInfo == null) + { + memberNameInfo = TypeToNameInfo(objectInfo); + isAllocatedMemberNameInfo = true; + } + + memberNameInfo._NIisArray = true; + + long objectId = objectInfo._objectId; + memberNameInfo._NIobjectId = objectInfo._objectId; + + // Get array type + Array array = (Array)objectInfo._obj; + //Type arrayType = array.GetType(); + Type arrayType = objectInfo._objectType; + + // Get type of array element + Type arrayElemType = arrayType.GetElementType(); + WriteObjectInfo arrayElemObjectInfo = null; + if (!arrayElemType.GetTypeInfo().IsPrimitive) + { + arrayElemObjectInfo = WriteObjectInfo.Serialize(arrayElemType, _surrogates, _context, _serObjectInfoInit, _formatterConverter, _binder); + arrayElemObjectInfo._assemId = GetAssemblyId(arrayElemObjectInfo); + } + + NameInfo arrayElemTypeNameInfo = arrayElemObjectInfo == null ? + TypeToNameInfo(arrayElemType) : + TypeToNameInfo(arrayElemObjectInfo); + arrayElemTypeNameInfo._NIisArray = arrayElemTypeNameInfo._NItype.IsArray; + + NameInfo arrayNameInfo = memberNameInfo; + arrayNameInfo._NIobjectId = objectId; + arrayNameInfo._NIisArray = true; + arrayElemTypeNameInfo._NIobjectId = objectId; + arrayElemTypeNameInfo._NItransmitTypeOnMember = memberNameInfo._NItransmitTypeOnMember; + arrayElemTypeNameInfo._NItransmitTypeOnObject = memberNameInfo._NItransmitTypeOnObject; + arrayElemTypeNameInfo._NIisParentTypeOnObject = memberNameInfo._NIisParentTypeOnObject; + + // Get rank and length information + int rank = array.Rank; + int[] lengthA = new int[rank]; + int[] lowerBoundA = new int[rank]; + int[] upperBoundA = new int[rank]; + for (int i = 0; i < rank; i++) + { + lengthA[i] = array.GetLength(i); + lowerBoundA[i] = array.GetLowerBound(i); + upperBoundA[i] = array.GetUpperBound(i); + } + + InternalArrayTypeE arrayEnum; + if (arrayElemTypeNameInfo._NIisArray) + { + arrayEnum = rank == 1 ? InternalArrayTypeE.Jagged : InternalArrayTypeE.Rectangular; + } + else if (rank == 1) + { + arrayEnum = InternalArrayTypeE.Single; + } + else + { + arrayEnum = InternalArrayTypeE.Rectangular; + } + arrayElemTypeNameInfo._NIarrayEnum = arrayEnum; + + // Byte array + if ((ReferenceEquals(arrayElemType, Converter.s_typeofByte)) && (rank == 1) && (lowerBoundA[0] == 0)) + { + _serWriter.WriteObjectByteArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, lengthA[0], lowerBoundA[0], (byte[])array); + return; + } + + if (ReferenceEquals(arrayElemType, Converter.s_typeofObject) || Nullable.GetUnderlyingType(arrayElemType) != null) + { + memberNameInfo._NItransmitTypeOnMember = true; + arrayElemTypeNameInfo._NItransmitTypeOnMember = true; + } + + if (CheckTypeFormat(_formatterEnums._FEtypeFormat, FormatterTypeStyle.TypesAlways)) + { + memberNameInfo._NItransmitTypeOnObject = true; + arrayElemTypeNameInfo._NItransmitTypeOnObject = true; + } + + if (arrayEnum == InternalArrayTypeE.Single) + { + // Single Dimensional array + + // BinaryFormatter array of primitive types is written out in the WriteSingleArray statement + // as a byte buffer + _serWriter.WriteSingleArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, lengthA[0], lowerBoundA[0], array); + + if (!(Converter.IsWriteAsByteArray(arrayElemTypeNameInfo._NIprimitiveTypeEnum) && (lowerBoundA[0] == 0))) + { + object[] objectA = null; + if (!arrayElemType.GetTypeInfo().IsValueType) + { + // Non-primitive type array + objectA = (object[])array; + } + + int upperBound = upperBoundA[0] + 1; + for (int i = lowerBoundA[0]; i < upperBound; i++) + { + if (objectA == null) + { + WriteArrayMember(objectInfo, arrayElemTypeNameInfo, array.GetValue(i)); + } + else + { + WriteArrayMember(objectInfo, arrayElemTypeNameInfo, objectA[i]); + } + } + _serWriter.WriteItemEnd(); + } + } + else if (arrayEnum == InternalArrayTypeE.Jagged) + { + // Jagged Array + + arrayNameInfo._NIobjectId = objectId; + + _serWriter.WriteJaggedArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, lengthA[0], lowerBoundA[0]); + + var objectA = (object[])array; + for (int i = lowerBoundA[0]; i < upperBoundA[0] + 1; i++) + { + WriteArrayMember(objectInfo, arrayElemTypeNameInfo, objectA[i]); + } + _serWriter.WriteItemEnd(); + } + else + { + // Rectangle Array + // Get the length for all the ranks + + arrayNameInfo._NIobjectId = objectId; + _serWriter.WriteRectangleArray(memberNameInfo, arrayNameInfo, arrayElemObjectInfo, arrayElemTypeNameInfo, rank, lengthA, lowerBoundA); + + // Check for a length of zero + bool bzero = false; + for (int i = 0; i < rank; i++) + { + if (lengthA[i] == 0) + { + bzero = true; + break; + } + } + + if (!bzero) + { + WriteRectangle(objectInfo, rank, lengthA, array, arrayElemTypeNameInfo, lowerBoundA); + } + _serWriter.WriteItemEnd(); + } + + _serWriter.WriteObjectEnd(memberNameInfo, arrayNameInfo); + + PutNameInfo(arrayElemTypeNameInfo); + if (isAllocatedMemberNameInfo) + { + PutNameInfo(memberNameInfo); + } + } + + // Writes out an array element + private void WriteArrayMember(WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, object data) + { + arrayElemTypeNameInfo._NIisArrayItem = true; + + if (CheckForNull(objectInfo, arrayElemTypeNameInfo, arrayElemTypeNameInfo, data)) + { + return; + } + + NameInfo actualTypeInfo = null; + Type dataType = null; + bool isObjectOnMember = false; + + if (arrayElemTypeNameInfo._NItransmitTypeOnMember) + { + isObjectOnMember = true; + } + + if (!isObjectOnMember && !arrayElemTypeNameInfo.IsSealed) + { + dataType = GetType(data); + if (!ReferenceEquals(arrayElemTypeNameInfo._NItype, dataType)) + { + isObjectOnMember = true; + } + } + + if (isObjectOnMember) + { + // Object array, need type of member + if (dataType == null) + { + dataType = GetType(data); + } + actualTypeInfo = TypeToNameInfo(dataType); + actualTypeInfo._NItransmitTypeOnMember = true; + actualTypeInfo._NIobjectId = arrayElemTypeNameInfo._NIobjectId; + actualTypeInfo._NIassemId = arrayElemTypeNameInfo._NIassemId; + actualTypeInfo._NIisArrayItem = true; + } + else + { + actualTypeInfo = arrayElemTypeNameInfo; + actualTypeInfo._NIisArrayItem = true; + } + + if (!WriteKnownValueClass(arrayElemTypeNameInfo, actualTypeInfo, data)) + { + object obj = data; + bool assignUniqueIdForValueTypes = false; + if (ReferenceEquals(arrayElemTypeNameInfo._NItype, Converter.s_typeofObject)) + { + assignUniqueIdForValueTypes = true; + } + + long arrayId = Schedule(obj, assignUniqueIdForValueTypes, actualTypeInfo._NItype); + arrayElemTypeNameInfo._NIobjectId = arrayId; + actualTypeInfo._NIobjectId = arrayId; + if (arrayId < 1) + { + WriteObjectInfo newObjectInfo = WriteObjectInfo.Serialize(obj, _surrogates, _context, _serObjectInfoInit, _formatterConverter, this, _binder); + newObjectInfo._objectId = arrayId; + newObjectInfo._assemId = !ReferenceEquals(arrayElemTypeNameInfo._NItype, Converter.s_typeofObject) && Nullable.GetUnderlyingType(arrayElemTypeNameInfo._NItype) == null ? + actualTypeInfo._NIassemId : + GetAssemblyId(newObjectInfo); + NameInfo typeNameInfo = TypeToNameInfo(newObjectInfo); + typeNameInfo._NIobjectId = arrayId; + newObjectInfo._objectId = arrayId; + Write(newObjectInfo, actualTypeInfo, typeNameInfo); + newObjectInfo.ObjectEnd(); + } + else + { + _serWriter.WriteItemObjectRef(arrayElemTypeNameInfo, (int)arrayId); + } + } + if (arrayElemTypeNameInfo._NItransmitTypeOnMember) + { + PutNameInfo(actualTypeInfo); + } + } + + // Iterates over a Rectangle array, for each element of the array invokes WriteArrayMember + private void WriteRectangle(WriteObjectInfo objectInfo, int rank, int[] maxA, Array array, NameInfo arrayElemNameTypeInfo, int[] lowerBoundA) + { + int[] currentA = new int[rank]; + int[] indexMap = null; + bool isLowerBound = false; + if (lowerBoundA != null) + { + for (int i = 0; i < rank; i++) + { + if (lowerBoundA[i] != 0) + { + isLowerBound = true; + } + } + } + if (isLowerBound) + { + indexMap = new int[rank]; + } + + bool isLoop = true; + while (isLoop) + { + isLoop = false; + if (isLowerBound) + { + for (int i = 0; i < rank; i++) + { + indexMap[i] = currentA[i] + lowerBoundA[i]; + } + + WriteArrayMember(objectInfo, arrayElemNameTypeInfo, array.GetValue(indexMap)); + } + else + { + WriteArrayMember(objectInfo, arrayElemNameTypeInfo, array.GetValue(currentA)); + } + + for (int irank = rank - 1; irank > -1; irank--) + { + // Find the current or lower dimension which can be incremented. + if (currentA[irank] < maxA[irank] - 1) + { + // The current dimension is at maximum. Increase the next lower dimension by 1 + currentA[irank]++; + if (irank < rank - 1) + { + // The current dimension and higher dimensions are zeroed. + for (int i = irank + 1; i < rank; i++) + { + currentA[i] = 0; + } + } + isLoop = true; + break; + } + } + } + } + + // This gives back the next object to be serialized. Objects + // are returned in a FIFO order based on how they were passed + // to Schedule. The id of the object is put into the objID parameter + // and the Object itself is returned from the function. + private object GetNext(out long objID) + { + bool isNew; + + //The Queue is empty here. We'll throw if we try to dequeue the empty queue. + if (_objectQueue.Count == 0) + { + objID = 0; + return null; + } + + object obj = _objectQueue.Dequeue(); + + // A WriteObjectInfo is queued if this object was a member of another object + object realObj = obj is WriteObjectInfo ? ((WriteObjectInfo)obj)._obj : obj; + objID = _idGenerator.HasId(realObj, out isNew); + if (isNew) + { + throw new SerializationException(SR.Format(SR.Serialization_ObjNoID, realObj)); + } + + return obj; + } + + // If the type is a value type, we dont attempt to generate a unique id, unless its a boxed entity + // (in which case, there might be 2 references to the same boxed obj. in a graph.) + // "assignUniqueIdToValueType" is true, if the field type holding reference to "obj" is Object. + private long InternalGetId(object obj, bool assignUniqueIdToValueType, Type type, out bool isNew) + { + if (obj == _previousObj) + { + // good for benchmarks + isNew = false; + return _previousId; + } + _idGenerator._currentCount = _currentId; + if (type != null && type.GetTypeInfo().IsValueType) + { + if (!assignUniqueIdToValueType) + { + isNew = false; + return -1 * _currentId++; + } + } + _currentId++; + long retId = _idGenerator.GetId(obj, out isNew); + + _previousObj = obj; + _previousId = retId; + return retId; + } + + + // Schedules an object for later serialization if it hasn't already been scheduled. + // We get an ID for obj and put it on the queue for later serialization + // if this is a new object id. + + private long Schedule(object obj, bool assignUniqueIdToValueType, Type type) => + Schedule(obj, assignUniqueIdToValueType, type, null); + + private long Schedule(object obj, bool assignUniqueIdToValueType, Type type, WriteObjectInfo objectInfo) + { + long id = 0; + if (obj != null) + { + bool isNew; + id = InternalGetId(obj, assignUniqueIdToValueType, type, out isNew); + if (isNew && id > 0) + { + _objectQueue.Enqueue(objectInfo ?? obj); + } + } + return id; + } + + // Determines if a type is a primitive type, if it is it is written + private bool WriteKnownValueClass(NameInfo memberNameInfo, NameInfo typeNameInfo, object data) + { + if (ReferenceEquals(typeNameInfo._NItype, Converter.s_typeofString)) + { + WriteString(memberNameInfo, typeNameInfo, data); + } + else + { + if (typeNameInfo._NIprimitiveTypeEnum == InternalPrimitiveTypeE.Invalid) + { + return false; + } + else + { + if (typeNameInfo._NIisArray) // null if an array + { + _serWriter.WriteItem(memberNameInfo, typeNameInfo, data); + } + else + { + _serWriter.WriteMember(memberNameInfo, typeNameInfo, data); + } + } + } + + return true; + } + + + // Writes an object reference to the stream. + private void WriteObjectRef(NameInfo nameInfo, long objectId) => + _serWriter.WriteMemberObjectRef(nameInfo, (int)objectId); + + // Writes a string into the XML stream + private void WriteString(NameInfo memberNameInfo, NameInfo typeNameInfo, object stringObject) + { + bool isFirstTime = true; + + long stringId = -1; + + if (!CheckTypeFormat(_formatterEnums._FEtypeFormat, FormatterTypeStyle.XsdString)) + { + stringId = InternalGetId(stringObject, false, null, out isFirstTime); + } + typeNameInfo._NIobjectId = stringId; + + if ((isFirstTime) || (stringId < 0)) + { + _serWriter.WriteMemberString(memberNameInfo, typeNameInfo, (string)stringObject); + } + else + { + WriteObjectRef(memberNameInfo, stringId); + } + } + + // Writes a null member into the stream + private bool CheckForNull(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, object data) + { + bool isNull = data == null; + + // Optimization, Null members are only written for Binary + if ((isNull) && (((_formatterEnums._FEserializerTypeEnum == InternalSerializerTypeE.Binary)) || + memberNameInfo._NIisArrayItem || + memberNameInfo._NItransmitTypeOnObject || + memberNameInfo._NItransmitTypeOnMember || + objectInfo._isSi || + (CheckTypeFormat(_formatterEnums._FEtypeFormat, FormatterTypeStyle.TypesAlways)))) + { + if (typeNameInfo._NIisArrayItem) + { + if (typeNameInfo._NIarrayEnum == InternalArrayTypeE.Single) + { + _serWriter.WriteDelayedNullItem(); + } + else + { + _serWriter.WriteNullItem(memberNameInfo, typeNameInfo); + } + } + else + { + _serWriter.WriteNullMember(memberNameInfo, typeNameInfo); + } + } + + return isNull; + } + + + // Writes the SerializedStreamHeader + private void WriteSerializedStreamHeader(long topId, long headerId) => + _serWriter.WriteSerializationHeader((int)topId, (int)headerId, 1, 0); + + // Transforms a type to the serialized string form. URT Primitive types are converted to XMLData Types + private NameInfo TypeToNameInfo(Type type, WriteObjectInfo objectInfo, InternalPrimitiveTypeE code, NameInfo nameInfo) + { + if (nameInfo == null) + { + nameInfo = GetNameInfo(); + } + else + { + nameInfo.Init(); + } + + if (code == InternalPrimitiveTypeE.Invalid) + { + if (objectInfo != null) + { + nameInfo.NIname = objectInfo.GetTypeFullName(); + nameInfo._NIassemId = objectInfo._assemId; + } + } + nameInfo._NIprimitiveTypeEnum = code; + nameInfo._NItype = type; + + return nameInfo; + } + + private NameInfo TypeToNameInfo(Type type) => + TypeToNameInfo(type, null, ToCode(type), null); + + private NameInfo TypeToNameInfo(WriteObjectInfo objectInfo) => + TypeToNameInfo(objectInfo._objectType, objectInfo, ToCode(objectInfo._objectType), null); + + private NameInfo TypeToNameInfo(WriteObjectInfo objectInfo, NameInfo nameInfo) => + TypeToNameInfo(objectInfo._objectType, objectInfo, ToCode(objectInfo._objectType), nameInfo); + + private void TypeToNameInfo(Type type, NameInfo nameInfo) => + TypeToNameInfo(type, null, ToCode(type), nameInfo); + + private NameInfo MemberToNameInfo(string name) + { + NameInfo memberNameInfo = GetNameInfo(); + memberNameInfo.NIname = name; + return memberNameInfo; + } + + internal InternalPrimitiveTypeE ToCode(Type type) + { + if (ReferenceEquals(_previousType, type)) + { + return _previousCode; + } + else + { + InternalPrimitiveTypeE code = Converter.ToCode(type); + if (code != InternalPrimitiveTypeE.Invalid) + { + _previousType = type; + _previousCode = code; + } + return code; + } + } + + private Dictionary _assemblyToIdTable = null; + private long GetAssemblyId(WriteObjectInfo objectInfo) + { + //use objectInfo to get assembly string with new criteria + if (_assemblyToIdTable == null) + { + _assemblyToIdTable = new Dictionary(); + } + + long assemId = 0; + string assemblyString = objectInfo.GetAssemblyString(); + + string serializedAssemblyString = assemblyString; + if (assemblyString.Length == 0) + { + assemId = 0; + } + else if (assemblyString.Equals(Converter.s_urtAssemblyString)) + { + // Urt type is an assemId of 0. No assemblyString needs + // to be sent + assemId = 0; + } + else + { + // Assembly needs to be sent + // Need to prefix assembly string to separate the string names from the + // assemblyName string names. That is a string can have the same value + // as an assemblyNameString, but it is serialized differently + bool isNew = false; + if (_assemblyToIdTable.TryGetValue(assemblyString, out assemId)) + { + isNew = false; + } + else + { + assemId = InternalGetId("___AssemblyString___" + assemblyString, false, null, out isNew); + _assemblyToIdTable[assemblyString] = assemId; + } + + _serWriter.WriteAssembly(objectInfo._objectType, serializedAssemblyString, (int)assemId, isNew); + } + return assemId; + } + + private Type GetType(object obj) => obj.GetType(); + + private SerStack _niPool = new SerStack("NameInfo Pool"); + + private NameInfo GetNameInfo() + { + NameInfo nameInfo = null; + + if (!_niPool.IsEmpty()) + { + nameInfo = (NameInfo)_niPool.Pop(); + nameInfo.Init(); + } + else + { + nameInfo = new NameInfo(); + } + + return nameInfo; + } + + private bool CheckTypeFormat(FormatterTypeStyle test, FormatterTypeStyle want) => (test & want) == want; + + private void PutNameInfo(NameInfo nameInfo) => _niPool.Push(nameInfo); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs new file mode 100644 index 000000000000..c7e46d16f62c --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs @@ -0,0 +1,1048 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class BinaryParser + { + private const int ChunkSize = 4096; + private static readonly Encoding s_encoding = new UTF8Encoding(false, true); + + internal ObjectReader _objectReader; + internal Stream _input; + internal long _topId; + internal long _headerId; + internal SizedArray _objectMapIdTable; + internal SizedArray _assemIdToAssemblyTable; // Used to hold assembly information + internal SerStack _stack = new SerStack("ObjectProgressStack"); + + internal BinaryTypeEnum _expectedType = BinaryTypeEnum.ObjectUrt; + internal object _expectedTypeInformation; + internal ParseRecord _PRS; + + private BinaryAssemblyInfo _systemAssemblyInfo; + private BinaryReader _dataReader; + private SerStack _opPool; + + private BinaryObject _binaryObject; + private BinaryObjectWithMap _bowm; + private BinaryObjectWithMapTyped _bowmt; + + internal BinaryObjectString objectString; + internal BinaryCrossAppDomainString crossAppDomainString; + internal MemberPrimitiveTyped memberPrimitiveTyped; + private byte[] _byteBuffer; + internal MemberPrimitiveUnTyped memberPrimitiveUnTyped; + internal MemberReference _memberReference; + internal ObjectNull _objectNull; + internal static volatile MessageEnd _messageEnd; + + internal BinaryParser(Stream stream, ObjectReader objectReader) + { + _input = stream; + _objectReader = objectReader; + _dataReader = new BinaryReader(_input, s_encoding); + } + + internal BinaryAssemblyInfo SystemAssemblyInfo => + _systemAssemblyInfo ?? (_systemAssemblyInfo = new BinaryAssemblyInfo(Converter.s_urtAssemblyString, Converter.s_urtAssembly)); + + internal SizedArray ObjectMapIdTable => + _objectMapIdTable ?? (_objectMapIdTable = new SizedArray()); + + internal SizedArray AssemIdToAssemblyTable => + _assemIdToAssemblyTable ?? (_assemIdToAssemblyTable = new SizedArray(2)); + + internal ParseRecord PRs => + _PRS ?? (_PRS = new ParseRecord()); + + // Parse the input + // Reads each record from the input stream. If the record is a primitive type (A number) + // then it doesn't have a BinaryHeaderEnum byte. For this case the expected type + // has been previously set to Primitive + internal void Run() + { + try + { + bool isLoop = true; + ReadBegin(); + ReadSerializationHeaderRecord(); + while (isLoop) + { + BinaryHeaderEnum binaryHeaderEnum = BinaryHeaderEnum.Object; + switch (_expectedType) + { + case BinaryTypeEnum.ObjectUrt: + case BinaryTypeEnum.ObjectUser: + case BinaryTypeEnum.String: + case BinaryTypeEnum.Object: + case BinaryTypeEnum.ObjectArray: + case BinaryTypeEnum.StringArray: + case BinaryTypeEnum.PrimitiveArray: + byte inByte = _dataReader.ReadByte(); + binaryHeaderEnum = (BinaryHeaderEnum)inByte; + switch (binaryHeaderEnum) + { + case BinaryHeaderEnum.Assembly: + case BinaryHeaderEnum.CrossAppDomainAssembly: + ReadAssembly(binaryHeaderEnum); + break; + case BinaryHeaderEnum.Object: + ReadObject(); + break; + case BinaryHeaderEnum.CrossAppDomainMap: + ReadCrossAppDomainMap(); + break; + case BinaryHeaderEnum.ObjectWithMap: + case BinaryHeaderEnum.ObjectWithMapAssemId: + ReadObjectWithMap(binaryHeaderEnum); + break; + case BinaryHeaderEnum.ObjectWithMapTyped: + case BinaryHeaderEnum.ObjectWithMapTypedAssemId: + ReadObjectWithMapTyped(binaryHeaderEnum); + break; + case BinaryHeaderEnum.ObjectString: + case BinaryHeaderEnum.CrossAppDomainString: + ReadObjectString(binaryHeaderEnum); + break; + case BinaryHeaderEnum.Array: + case BinaryHeaderEnum.ArraySinglePrimitive: + case BinaryHeaderEnum.ArraySingleObject: + case BinaryHeaderEnum.ArraySingleString: + ReadArray(binaryHeaderEnum); + break; + case BinaryHeaderEnum.MemberPrimitiveTyped: + ReadMemberPrimitiveTyped(); + break; + case BinaryHeaderEnum.MemberReference: + ReadMemberReference(); + break; + case BinaryHeaderEnum.ObjectNull: + case BinaryHeaderEnum.ObjectNullMultiple256: + case BinaryHeaderEnum.ObjectNullMultiple: + ReadObjectNull(binaryHeaderEnum); + break; + case BinaryHeaderEnum.MessageEnd: + isLoop = false; + ReadMessageEnd(); + ReadEnd(); + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_BinaryHeader, inByte)); + } + break; + case BinaryTypeEnum.Primitive: + ReadMemberPrimitiveUnTyped(); + break; + default: + throw new SerializationException(SR.Serialization_TypeExpected); + } + + // If an assembly is encountered, don't advance + // object Progress, + if (binaryHeaderEnum != BinaryHeaderEnum.Assembly) + { + // End of parse loop. + bool isData = false; + + // Set up loop for next iteration. + // If this is an object, and the end of object has been reached, then parse object end. + while (!isData) + { + ObjectProgress op = (ObjectProgress)_stack.Peek(); + if (op == null) + { + // No more object on stack, then the next record is a top level object + _expectedType = BinaryTypeEnum.ObjectUrt; + _expectedTypeInformation = null; + isData = true; + } + else + { + // Find out what record is expected next + isData = op.GetNext(out op._expectedType, out op._expectedTypeInformation); + _expectedType = op._expectedType; + _expectedTypeInformation = op._expectedTypeInformation; + + if (!isData) + { + // No record is expected next, this is the end of an object or array + PRs.Init(); + if (op._memberValueEnum == InternalMemberValueE.Nested) + { + // Nested object + PRs._PRparseTypeEnum = InternalParseTypeE.MemberEnd; + PRs._PRmemberTypeEnum = op._memberTypeEnum; + PRs._PRmemberValueEnum = op._memberValueEnum; + _objectReader.Parse(PRs); + } + else + { + // Top level object + PRs._PRparseTypeEnum = InternalParseTypeE.ObjectEnd; + PRs._PRmemberTypeEnum = op._memberTypeEnum; + PRs._PRmemberValueEnum = op._memberValueEnum; + _objectReader.Parse(PRs); + } + _stack.Pop(); + PutOp(op); + } + } + } + } + } + } + catch (EndOfStreamException) + { + // EOF should never be thrown since there is a MessageEnd record to stop parsing + throw new SerializationException(SR.Serialization_StreamEnd); + } + } + + internal void ReadBegin() { } + + internal void ReadEnd() { } + + // Primitive Reads from Stream + + internal bool ReadBoolean() => _dataReader.ReadBoolean(); + + internal byte ReadByte() => _dataReader.ReadByte(); + + internal byte[] ReadBytes(int length) => _dataReader.ReadBytes(length); + + internal void ReadBytes(byte[] byteA, int offset, int size) + { + while (size > 0) + { + int n = _dataReader.Read(byteA, offset, size); + if (n == 0) + { + throw new EndOfStreamException(SR.IO_EOF_ReadBeyondEOF); + } + offset += n; + size -= n; + } + } + + internal char ReadChar() => _dataReader.ReadChar(); + + internal char[] ReadChars(int length) => _dataReader.ReadChars(length); + + internal decimal ReadDecimal() => decimal.Parse(_dataReader.ReadString(), CultureInfo.InvariantCulture); + + internal float ReadSingle() => _dataReader.ReadSingle(); + + internal double ReadDouble() => _dataReader.ReadDouble(); + + internal short ReadInt16() => _dataReader.ReadInt16(); + + internal int ReadInt32() => _dataReader.ReadInt32(); + + internal long ReadInt64() => _dataReader.ReadInt64(); + + internal sbyte ReadSByte() => (sbyte)ReadByte(); + + internal string ReadString() => _dataReader.ReadString(); + + internal TimeSpan ReadTimeSpan() => new TimeSpan(ReadInt64()); + + internal DateTime ReadDateTime() => FromBinaryRaw(ReadInt64()); + + private static DateTime FromBinaryRaw(long dateData) + { + const long TicksMask = 0x3FFFFFFFFFFFFFFF; + return new DateTime(dateData & TicksMask); + } + + internal ushort ReadUInt16() => _dataReader.ReadUInt16(); + + internal uint ReadUInt32() => _dataReader.ReadUInt32(); + + internal ulong ReadUInt64() => _dataReader.ReadUInt64(); + + // Binary Stream Record Reads + internal void ReadSerializationHeaderRecord() + { + var record = new SerializationHeaderRecord(); + record.Read(this); + _topId = (record._topId > 0 ? _objectReader.GetId(record._topId) : record._topId); + _headerId = (record._headerId > 0 ? _objectReader.GetId(record._headerId) : record._headerId); + } + + internal void ReadAssembly(BinaryHeaderEnum binaryHeaderEnum) + { + var record = new BinaryAssembly(); + if (binaryHeaderEnum == BinaryHeaderEnum.CrossAppDomainAssembly) + { + var crossAppDomainAssembly = new BinaryCrossAppDomainAssembly(); + crossAppDomainAssembly.Read(this); + record._assemId = crossAppDomainAssembly._assemId; + record._assemblyString = _objectReader.CrossAppDomainArray(crossAppDomainAssembly._assemblyIndex) as string; + if (record._assemblyString == null) + { + throw new SerializationException(SR.Format(SR.Serialization_CrossAppDomainError, "String", crossAppDomainAssembly._assemblyIndex)); + } + } + else + { + record.Read(this); + } + + AssemIdToAssemblyTable[record._assemId] = new BinaryAssemblyInfo(record._assemblyString); + } + + private void ReadObject() + { + if (_binaryObject == null) + { + _binaryObject = new BinaryObject(); + } + _binaryObject.Read(this); + + ObjectMap objectMap = (ObjectMap)ObjectMapIdTable[_binaryObject._mapId]; + if (objectMap == null) + { + throw new SerializationException(SR.Format(SR.Serialization_Map, _binaryObject._mapId)); + } + + ObjectProgress op = GetOp(); + ParseRecord pr = op._pr; + _stack.Push(op); + + op._objectTypeEnum = InternalObjectTypeE.Object; + op._binaryTypeEnumA = objectMap._binaryTypeEnumA; + op._memberNames = objectMap._memberNames; + op._memberTypes = objectMap._memberTypes; + op._typeInformationA = objectMap._typeInformationA; + op._memberLength = op._binaryTypeEnumA.Length; + ObjectProgress objectOp = (ObjectProgress)_stack.PeekPeek(); + if ((objectOp == null) || (objectOp._isInitial)) + { + // Non-Nested Object + op._name = objectMap._objectName; + pr._PRparseTypeEnum = InternalParseTypeE.Object; + op._memberValueEnum = InternalMemberValueE.Empty; + } + else + { + // Nested Object + pr._PRparseTypeEnum = InternalParseTypeE.Member; + pr._PRmemberValueEnum = InternalMemberValueE.Nested; + op._memberValueEnum = InternalMemberValueE.Nested; + + switch (objectOp._objectTypeEnum) + { + case InternalObjectTypeE.Object: + pr._PRname = objectOp._name; + pr._PRmemberTypeEnum = InternalMemberTypeE.Field; + op._memberTypeEnum = InternalMemberTypeE.Field; + break; + case InternalObjectTypeE.Array: + pr._PRmemberTypeEnum = InternalMemberTypeE.Item; + op._memberTypeEnum = InternalMemberTypeE.Item; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_Map, objectOp._objectTypeEnum.ToString())); + } + } + + pr._PRobjectId = _objectReader.GetId(_binaryObject._objectId); + pr._PRobjectInfo = objectMap.CreateObjectInfo(ref pr._PRsi, ref pr._PRmemberData); + + if (pr._PRobjectId == _topId) + { + pr._PRobjectPositionEnum = InternalObjectPositionE.Top; + } + + pr._PRobjectTypeEnum = InternalObjectTypeE.Object; + pr._PRkeyDt = objectMap._objectName; + pr._PRdtType = objectMap._objectType; + pr._PRdtTypeCode = InternalPrimitiveTypeE.Invalid; + _objectReader.Parse(pr); + } + + internal void ReadCrossAppDomainMap() + { + BinaryCrossAppDomainMap record = new BinaryCrossAppDomainMap(); + record.Read(this); + object mapObject = _objectReader.CrossAppDomainArray(record._crossAppDomainArrayIndex); + BinaryObjectWithMap binaryObjectWithMap = mapObject as BinaryObjectWithMap; + if (binaryObjectWithMap != null) + { + ReadObjectWithMap(binaryObjectWithMap); + } + else + { + BinaryObjectWithMapTyped binaryObjectWithMapTyped = mapObject as BinaryObjectWithMapTyped; + if (binaryObjectWithMapTyped != null) + { + ReadObjectWithMapTyped(binaryObjectWithMapTyped); + } + else + { + throw new SerializationException(SR.Format(SR.Serialization_CrossAppDomainError, "BinaryObjectMap", mapObject)); + } + } + } + + internal void ReadObjectWithMap(BinaryHeaderEnum binaryHeaderEnum) + { + if (_bowm == null) + { + _bowm = new BinaryObjectWithMap(binaryHeaderEnum); + } + else + { + _bowm._binaryHeaderEnum = binaryHeaderEnum; + } + _bowm.Read(this); + ReadObjectWithMap(_bowm); + } + + private void ReadObjectWithMap(BinaryObjectWithMap record) + { + BinaryAssemblyInfo assemblyInfo = null; + ObjectProgress op = GetOp(); + ParseRecord pr = op._pr; + _stack.Push(op); + + if (record._binaryHeaderEnum == BinaryHeaderEnum.ObjectWithMapAssemId) + { + if (record._assemId < 1) + { + throw new SerializationException(SR.Format(SR.Serialization_Assembly, record._name)); + } + + assemblyInfo = ((BinaryAssemblyInfo)AssemIdToAssemblyTable[record._assemId]); + + if (assemblyInfo == null) + { + throw new SerializationException(SR.Format(SR.Serialization_Assembly, record._assemId + " " + record._name)); + } + } + else if (record._binaryHeaderEnum == BinaryHeaderEnum.ObjectWithMap) + { + assemblyInfo = SystemAssemblyInfo; //Urt assembly + } + + Type objectType = _objectReader.GetType(assemblyInfo, record._name); + + ObjectMap objectMap = ObjectMap.Create(record._name, objectType, record._memberNames, _objectReader, record._objectId, assemblyInfo); + ObjectMapIdTable[record._objectId] = objectMap; + + op._objectTypeEnum = InternalObjectTypeE.Object; + op._binaryTypeEnumA = objectMap._binaryTypeEnumA; + op._typeInformationA = objectMap._typeInformationA; + op._memberLength = op._binaryTypeEnumA.Length; + op._memberNames = objectMap._memberNames; + op._memberTypes = objectMap._memberTypes; + + ObjectProgress objectOp = (ObjectProgress)_stack.PeekPeek(); + + if ((objectOp == null) || (objectOp._isInitial)) + { + // Non-Nested Object + op._name = record._name; + pr._PRparseTypeEnum = InternalParseTypeE.Object; + op._memberValueEnum = InternalMemberValueE.Empty; + } + else + { + // Nested Object + pr._PRparseTypeEnum = InternalParseTypeE.Member; + pr._PRmemberValueEnum = InternalMemberValueE.Nested; + op._memberValueEnum = InternalMemberValueE.Nested; + + switch (objectOp._objectTypeEnum) + { + case InternalObjectTypeE.Object: + pr._PRname = objectOp._name; + pr._PRmemberTypeEnum = InternalMemberTypeE.Field; + op._memberTypeEnum = InternalMemberTypeE.Field; + break; + case InternalObjectTypeE.Array: + pr._PRmemberTypeEnum = InternalMemberTypeE.Item; + op._memberTypeEnum = InternalMemberTypeE.Field; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_ObjectTypeEnum, objectOp._objectTypeEnum.ToString())); + } + } + pr._PRobjectTypeEnum = InternalObjectTypeE.Object; + pr._PRobjectId = _objectReader.GetId(record._objectId); + pr._PRobjectInfo = objectMap.CreateObjectInfo(ref pr._PRsi, ref pr._PRmemberData); + + if (pr._PRobjectId == _topId) + { + pr._PRobjectPositionEnum = InternalObjectPositionE.Top; + } + + pr._PRkeyDt = record._name; + pr._PRdtType = objectMap._objectType; + pr._PRdtTypeCode = InternalPrimitiveTypeE.Invalid; + _objectReader.Parse(pr); + } + + internal void ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) + { + if (_bowmt == null) + { + _bowmt = new BinaryObjectWithMapTyped(binaryHeaderEnum); + } + else + { + _bowmt._binaryHeaderEnum = binaryHeaderEnum; + } + _bowmt.Read(this); + ReadObjectWithMapTyped(_bowmt); + } + + private void ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) + { + BinaryAssemblyInfo assemblyInfo = null; + ObjectProgress op = GetOp(); + ParseRecord pr = op._pr; + _stack.Push(op); + + if (record._binaryHeaderEnum == BinaryHeaderEnum.ObjectWithMapTypedAssemId) + { + if (record._assemId < 1) + { + throw new SerializationException(SR.Format(SR.Serialization_AssemblyId, record._name)); + } + + assemblyInfo = (BinaryAssemblyInfo)AssemIdToAssemblyTable[record._assemId]; + if (assemblyInfo == null) + { + throw new SerializationException(SR.Format(SR.Serialization_AssemblyId, record._assemId + " " + record._name)); + } + } + else if (record._binaryHeaderEnum == BinaryHeaderEnum.ObjectWithMapTyped) + { + assemblyInfo = SystemAssemblyInfo; // Urt assembly + } + + ObjectMap objectMap = ObjectMap.Create(record._name, record._memberNames, record._binaryTypeEnumA, record._typeInformationA, record._memberAssemIds, _objectReader, record._objectId, assemblyInfo, AssemIdToAssemblyTable); + ObjectMapIdTable[record._objectId] = objectMap; + op._objectTypeEnum = InternalObjectTypeE.Object; + op._binaryTypeEnumA = objectMap._binaryTypeEnumA; + op._typeInformationA = objectMap._typeInformationA; + op._memberLength = op._binaryTypeEnumA.Length; + op._memberNames = objectMap._memberNames; + op._memberTypes = objectMap._memberTypes; + + ObjectProgress objectOp = (ObjectProgress)_stack.PeekPeek(); + + if ((objectOp == null) || (objectOp._isInitial)) + { + // Non-Nested Object + op._name = record._name; + pr._PRparseTypeEnum = InternalParseTypeE.Object; + op._memberValueEnum = InternalMemberValueE.Empty; + } + else + { + // Nested Object + pr._PRparseTypeEnum = InternalParseTypeE.Member; + pr._PRmemberValueEnum = InternalMemberValueE.Nested; + op._memberValueEnum = InternalMemberValueE.Nested; + + switch (objectOp._objectTypeEnum) + { + case InternalObjectTypeE.Object: + pr._PRname = objectOp._name; + pr._PRmemberTypeEnum = InternalMemberTypeE.Field; + op._memberTypeEnum = InternalMemberTypeE.Field; + break; + case InternalObjectTypeE.Array: + pr._PRmemberTypeEnum = InternalMemberTypeE.Item; + op._memberTypeEnum = InternalMemberTypeE.Item; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_ObjectTypeEnum, objectOp._objectTypeEnum.ToString())); + } + } + + pr._PRobjectTypeEnum = InternalObjectTypeE.Object; + pr._PRobjectInfo = objectMap.CreateObjectInfo(ref pr._PRsi, ref pr._PRmemberData); + pr._PRobjectId = _objectReader.GetId(record._objectId); + if (pr._PRobjectId == _topId) + { + pr._PRobjectPositionEnum = InternalObjectPositionE.Top; + } + pr._PRkeyDt = record._name; + pr._PRdtType = objectMap._objectType; + pr._PRdtTypeCode = InternalPrimitiveTypeE.Invalid; + _objectReader.Parse(pr); + } + + private void ReadObjectString(BinaryHeaderEnum binaryHeaderEnum) + { + if (objectString == null) + { + objectString = new BinaryObjectString(); + } + + if (binaryHeaderEnum == BinaryHeaderEnum.ObjectString) + { + objectString.Read(this); + } + else + { + if (crossAppDomainString == null) + { + crossAppDomainString = new BinaryCrossAppDomainString(); + } + crossAppDomainString.Read(this); + objectString._value = _objectReader.CrossAppDomainArray(crossAppDomainString._value) as string; + if (objectString._value == null) + { + throw new SerializationException(SR.Format(SR.Serialization_CrossAppDomainError, "String", crossAppDomainString._value)); + } + + objectString._objectId = crossAppDomainString._objectId; + } + + PRs.Init(); + PRs._PRparseTypeEnum = InternalParseTypeE.Object; + PRs._PRobjectId = _objectReader.GetId(objectString._objectId); + + if (PRs._PRobjectId == _topId) + { + PRs._PRobjectPositionEnum = InternalObjectPositionE.Top; + } + + PRs._PRobjectTypeEnum = InternalObjectTypeE.Object; + + ObjectProgress objectOp = (ObjectProgress)_stack.Peek(); + + PRs._PRvalue = objectString._value; + PRs._PRkeyDt = "System.String"; + PRs._PRdtType = Converter.s_typeofString; + PRs._PRdtTypeCode = InternalPrimitiveTypeE.Invalid; + PRs._PRvarValue = objectString._value; //Need to set it because ObjectReader is picking up value from variant, not pr.PRvalue + + if (objectOp == null) + { + // Top level String + PRs._PRparseTypeEnum = InternalParseTypeE.Object; + PRs._PRname = "System.String"; + } + else + { + // Nested in an Object + + PRs._PRparseTypeEnum = InternalParseTypeE.Member; + PRs._PRmemberValueEnum = InternalMemberValueE.InlineValue; + + switch (objectOp._objectTypeEnum) + { + case InternalObjectTypeE.Object: + PRs._PRname = objectOp._name; + PRs._PRmemberTypeEnum = InternalMemberTypeE.Field; + break; + case InternalObjectTypeE.Array: + PRs._PRmemberTypeEnum = InternalMemberTypeE.Item; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_ObjectTypeEnum, objectOp._objectTypeEnum.ToString())); + } + } + + _objectReader.Parse(PRs); + } + + private void ReadMemberPrimitiveTyped() + { + if (memberPrimitiveTyped == null) + { + memberPrimitiveTyped = new MemberPrimitiveTyped(); + } + memberPrimitiveTyped.Read(this); + + PRs._PRobjectTypeEnum = InternalObjectTypeE.Object; //Get rid of + ObjectProgress objectOp = (ObjectProgress)_stack.Peek(); + + PRs.Init(); + PRs._PRvarValue = memberPrimitiveTyped._value; + PRs._PRkeyDt = Converter.ToComType(memberPrimitiveTyped._primitiveTypeEnum); + PRs._PRdtType = Converter.ToType(memberPrimitiveTyped._primitiveTypeEnum); + PRs._PRdtTypeCode = memberPrimitiveTyped._primitiveTypeEnum; + + if (objectOp == null) + { + // Top level boxed primitive + PRs._PRparseTypeEnum = InternalParseTypeE.Object; + PRs._PRname = "System.Variant"; + } + else + { + // Nested in an Object + + PRs._PRparseTypeEnum = InternalParseTypeE.Member; + PRs._PRmemberValueEnum = InternalMemberValueE.InlineValue; + + switch (objectOp._objectTypeEnum) + { + case InternalObjectTypeE.Object: + PRs._PRname = objectOp._name; + PRs._PRmemberTypeEnum = InternalMemberTypeE.Field; + break; + case InternalObjectTypeE.Array: + PRs._PRmemberTypeEnum = InternalMemberTypeE.Item; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_ObjectTypeEnum, objectOp._objectTypeEnum.ToString())); + } + } + + _objectReader.Parse(PRs); + } + + private void ReadArray(BinaryHeaderEnum binaryHeaderEnum) + { + BinaryAssemblyInfo assemblyInfo = null; + BinaryArray record = new BinaryArray(binaryHeaderEnum); + record.Read(this); + + if (record._binaryTypeEnum == BinaryTypeEnum.ObjectUser) + { + if (record._assemId < 1) + { + throw new SerializationException(SR.Format(SR.Serialization_AssemblyId, record._typeInformation)); + } + assemblyInfo = (BinaryAssemblyInfo)AssemIdToAssemblyTable[record._assemId]; + } + else + { + assemblyInfo = SystemAssemblyInfo; //Urt assembly + } + + ObjectProgress op = GetOp(); + ParseRecord pr = op._pr; + + op._objectTypeEnum = InternalObjectTypeE.Array; + op._binaryTypeEnum = record._binaryTypeEnum; + op._typeInformation = record._typeInformation; + + ObjectProgress objectOp = (ObjectProgress)_stack.PeekPeek(); + if ((objectOp == null) || (record._objectId > 0)) + { + // Non-Nested Object + op._name = "System.Array"; + pr._PRparseTypeEnum = InternalParseTypeE.Object; + op._memberValueEnum = InternalMemberValueE.Empty; + } + else + { + // Nested Object + pr._PRparseTypeEnum = InternalParseTypeE.Member; + pr._PRmemberValueEnum = InternalMemberValueE.Nested; + op._memberValueEnum = InternalMemberValueE.Nested; + + switch (objectOp._objectTypeEnum) + { + case InternalObjectTypeE.Object: + pr._PRname = objectOp._name; + pr._PRmemberTypeEnum = InternalMemberTypeE.Field; + op._memberTypeEnum = InternalMemberTypeE.Field; + pr._PRkeyDt = objectOp._name; + pr._PRdtType = objectOp._dtType; + break; + case InternalObjectTypeE.Array: + pr._PRmemberTypeEnum = InternalMemberTypeE.Item; + op._memberTypeEnum = InternalMemberTypeE.Item; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_ObjectTypeEnum, objectOp._objectTypeEnum.ToString())); + } + } + + pr._PRobjectId = _objectReader.GetId(record._objectId); + if (pr._PRobjectId == _topId) + { + pr._PRobjectPositionEnum = InternalObjectPositionE.Top; + } + else if ((_headerId > 0) && (pr._PRobjectId == _headerId)) + { + pr._PRobjectPositionEnum = InternalObjectPositionE.Headers; // Headers are an array of header objects + } + else + { + pr._PRobjectPositionEnum = InternalObjectPositionE.Child; + } + + pr._PRobjectTypeEnum = InternalObjectTypeE.Array; + + BinaryTypeConverter.TypeFromInfo(record._binaryTypeEnum, record._typeInformation, _objectReader, assemblyInfo, + out pr._PRarrayElementTypeCode, out pr._PRarrayElementTypeString, + out pr._PRarrayElementType, out pr._PRisArrayVariant); + + pr._PRdtTypeCode = InternalPrimitiveTypeE.Invalid; + + pr._PRrank = record._rank; + pr._PRlengthA = record._lengthA; + pr._PRlowerBoundA = record._lowerBoundA; + bool isPrimitiveArray = false; + + switch (record._binaryArrayTypeEnum) + { + case BinaryArrayTypeEnum.Single: + case BinaryArrayTypeEnum.SingleOffset: + op._numItems = record._lengthA[0]; + pr._PRarrayTypeEnum = InternalArrayTypeE.Single; + if (Converter.IsWriteAsByteArray(pr._PRarrayElementTypeCode) && + (record._lowerBoundA[0] == 0)) + { + isPrimitiveArray = true; + ReadArrayAsBytes(pr); + } + break; + case BinaryArrayTypeEnum.Jagged: + case BinaryArrayTypeEnum.JaggedOffset: + op._numItems = record._lengthA[0]; + pr._PRarrayTypeEnum = InternalArrayTypeE.Jagged; + break; + case BinaryArrayTypeEnum.Rectangular: + case BinaryArrayTypeEnum.RectangularOffset: + int arrayLength = 1; + for (int i = 0; i < record._rank; i++) + arrayLength = arrayLength * record._lengthA[i]; + op._numItems = arrayLength; + pr._PRarrayTypeEnum = InternalArrayTypeE.Rectangular; + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_ArrayType, record._binaryArrayTypeEnum.ToString())); + } + + if (!isPrimitiveArray) + { + _stack.Push(op); + } + else + { + PutOp(op); + } + + _objectReader.Parse(pr); + + if (isPrimitiveArray) + { + pr._PRparseTypeEnum = InternalParseTypeE.ObjectEnd; + _objectReader.Parse(pr); + } + } + + private void ReadArrayAsBytes(ParseRecord pr) + { + if (pr._PRarrayElementTypeCode == InternalPrimitiveTypeE.Byte) + { + pr._PRnewObj = ReadBytes(pr._PRlengthA[0]); + } + else if (pr._PRarrayElementTypeCode == InternalPrimitiveTypeE.Char) + { + pr._PRnewObj = ReadChars(pr._PRlengthA[0]); + } + else + { + int typeLength = Converter.TypeLength(pr._PRarrayElementTypeCode); + + pr._PRnewObj = Converter.CreatePrimitiveArray(pr._PRarrayElementTypeCode, pr._PRlengthA[0]); + Debug.Assert((pr._PRnewObj != null), "[BinaryParser expected a Primitive Array]"); + + Array array = (Array)pr._PRnewObj; + int arrayOffset = 0; + if (_byteBuffer == null) + { + _byteBuffer = new byte[ChunkSize]; + } + + while (arrayOffset < array.Length) + { + int numArrayItems = Math.Min(ChunkSize / typeLength, array.Length - arrayOffset); + int bufferUsed = numArrayItems * typeLength; + ReadBytes(_byteBuffer, 0, bufferUsed); + if (!BitConverter.IsLittleEndian) + { + // we know that we are reading a primitive type, so just do a simple swap + for (int i = 0; i < bufferUsed; i += typeLength) + { + for (int j = 0; j < typeLength / 2; j++) + { + byte tmp = _byteBuffer[i + j]; + _byteBuffer[i + j] = _byteBuffer[i + typeLength - 1 - j]; + _byteBuffer[i + typeLength - 1 - j] = tmp; + } + } + } + Buffer.BlockCopy(_byteBuffer, 0, array, arrayOffset * typeLength, bufferUsed); + arrayOffset += numArrayItems; + } + } + } + + private void ReadMemberPrimitiveUnTyped() + { + ObjectProgress objectOp = (ObjectProgress)_stack.Peek(); + if (memberPrimitiveUnTyped == null) + { + memberPrimitiveUnTyped = new MemberPrimitiveUnTyped(); + } + memberPrimitiveUnTyped.Set((InternalPrimitiveTypeE)_expectedTypeInformation); + memberPrimitiveUnTyped.Read(this); + + PRs.Init(); + PRs._PRvarValue = memberPrimitiveUnTyped._value; + + PRs._PRdtTypeCode = (InternalPrimitiveTypeE)_expectedTypeInformation; + PRs._PRdtType = Converter.ToType(PRs._PRdtTypeCode); + PRs._PRparseTypeEnum = InternalParseTypeE.Member; + PRs._PRmemberValueEnum = InternalMemberValueE.InlineValue; + + if (objectOp._objectTypeEnum == InternalObjectTypeE.Object) + { + PRs._PRmemberTypeEnum = InternalMemberTypeE.Field; + PRs._PRname = objectOp._name; + } + else + { + PRs._PRmemberTypeEnum = InternalMemberTypeE.Item; + } + + _objectReader.Parse(PRs); + } + + private void ReadMemberReference() + { + if (_memberReference == null) + { + _memberReference = new MemberReference(); + } + _memberReference.Read(this); + + ObjectProgress objectOp = (ObjectProgress)_stack.Peek(); + + PRs.Init(); + PRs._PRidRef = _objectReader.GetId(_memberReference._idRef); + PRs._PRparseTypeEnum = InternalParseTypeE.Member; + PRs._PRmemberValueEnum = InternalMemberValueE.Reference; + + if (objectOp._objectTypeEnum == InternalObjectTypeE.Object) + { + PRs._PRmemberTypeEnum = InternalMemberTypeE.Field; + PRs._PRname = objectOp._name; + PRs._PRdtType = objectOp._dtType; + } + else + { + PRs._PRmemberTypeEnum = InternalMemberTypeE.Item; + } + + _objectReader.Parse(PRs); + } + + private void ReadObjectNull(BinaryHeaderEnum binaryHeaderEnum) + { + if (_objectNull == null) + { + _objectNull = new ObjectNull(); + } + _objectNull.Read(this, binaryHeaderEnum); + + ObjectProgress objectOp = (ObjectProgress)_stack.Peek(); + + PRs.Init(); + PRs._PRparseTypeEnum = InternalParseTypeE.Member; + PRs._PRmemberValueEnum = InternalMemberValueE.Null; + + if (objectOp._objectTypeEnum == InternalObjectTypeE.Object) + { + PRs._PRmemberTypeEnum = InternalMemberTypeE.Field; + PRs._PRname = objectOp._name; + PRs._PRdtType = objectOp._dtType; + } + else + { + PRs._PRmemberTypeEnum = InternalMemberTypeE.Item; + PRs._consecutiveNullArrayEntryCount = _objectNull._nullCount; + //only one null position has been incremented by GetNext + //The position needs to be reset for the rest of the nulls + objectOp.ArrayCountIncrement(_objectNull._nullCount - 1); + } + _objectReader.Parse(PRs); + } + + private void ReadMessageEnd() + { + if (_messageEnd == null) + { + _messageEnd = new MessageEnd(); + } + _messageEnd.Read(this); + + if (!_stack.IsEmpty()) + { + throw new SerializationException(SR.Serialization_StreamEnd); + } + } + + // ReadValue from stream using InternalPrimitiveTypeE code + internal object ReadValue(InternalPrimitiveTypeE code) + { + object var = null; + switch (code) + { + case InternalPrimitiveTypeE.Boolean: var = ReadBoolean(); break; + case InternalPrimitiveTypeE.Byte: var = ReadByte(); break; + case InternalPrimitiveTypeE.Char: var = ReadChar(); break; + case InternalPrimitiveTypeE.Double: var = ReadDouble(); break; + case InternalPrimitiveTypeE.Int16: var = ReadInt16(); break; + case InternalPrimitiveTypeE.Int32: var = ReadInt32(); break; + case InternalPrimitiveTypeE.Int64: var = ReadInt64(); break; + case InternalPrimitiveTypeE.SByte: var = ReadSByte(); break; + case InternalPrimitiveTypeE.Single: var = ReadSingle(); break; + case InternalPrimitiveTypeE.UInt16: var = ReadUInt16(); break; + case InternalPrimitiveTypeE.UInt32: var = ReadUInt32(); break; + case InternalPrimitiveTypeE.UInt64: var = ReadUInt64(); break; + case InternalPrimitiveTypeE.Decimal: var = ReadDecimal(); break; + case InternalPrimitiveTypeE.TimeSpan: var = ReadTimeSpan(); break; + case InternalPrimitiveTypeE.DateTime: var = ReadDateTime(); break; + default: throw new SerializationException(SR.Format(SR.Serialization_TypeCode, code.ToString())); + } + return var; + } + + private ObjectProgress GetOp() + { + ObjectProgress op = null; + + if (_opPool != null && !_opPool.IsEmpty()) + { + op = (ObjectProgress)_opPool.Pop(); + op.Init(); + } + else + { + op = new ObjectProgress(); + } + + return op; + } + + private void PutOp(ObjectProgress op) + { + if (_opPool == null) + { + _opPool = new SerStack("opPool"); + } + _opPool.Push(op); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs new file mode 100644 index 000000000000..179e016af8b3 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs @@ -0,0 +1,249 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Diagnostics; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // Routines to convert between the runtime type and the type as it appears on the wire + internal static class BinaryTypeConverter + { + // From the type create the BinaryTypeEnum and typeInformation which describes the type on the wire + internal static BinaryTypeEnum GetBinaryTypeInfo(Type type, WriteObjectInfo objectInfo, string typeName, ObjectWriter objectWriter, out object typeInformation, out int assemId) + { + BinaryTypeEnum binaryTypeEnum; + + assemId = 0; + typeInformation = null; + + if (ReferenceEquals(type, Converter.s_typeofString)) + { + binaryTypeEnum = BinaryTypeEnum.String; + } + else if (((objectInfo == null) || ((objectInfo != null) && !objectInfo._isSi)) && (ReferenceEquals(type, Converter.s_typeofObject))) + { + // If objectInfo.Si then can be a surrogate which will change the type + binaryTypeEnum = BinaryTypeEnum.Object; + } + else if (ReferenceEquals(type, Converter.s_typeofStringArray)) + { + binaryTypeEnum = BinaryTypeEnum.StringArray; + } + else if (ReferenceEquals(type, Converter.s_typeofObjectArray)) + { + binaryTypeEnum = BinaryTypeEnum.ObjectArray; + } + else if (Converter.IsPrimitiveArray(type, out typeInformation)) + { + binaryTypeEnum = BinaryTypeEnum.PrimitiveArray; + } + else + { + InternalPrimitiveTypeE primitiveTypeEnum = objectWriter.ToCode(type); + switch (primitiveTypeEnum) + { + case InternalPrimitiveTypeE.Invalid: + string assembly = null; + if (objectInfo == null) + { + assembly = type.GetTypeInfo().Assembly.FullName; + typeInformation = type.FullName; + } + else + { + assembly = objectInfo.GetAssemblyString(); + typeInformation = objectInfo.GetTypeFullName(); + } + + if (assembly.Equals(Converter.s_urtAssemblyString)) + { + binaryTypeEnum = BinaryTypeEnum.ObjectUrt; + assemId = 0; + } + else + { + binaryTypeEnum = BinaryTypeEnum.ObjectUser; + Debug.Assert(objectInfo != null, "[BinaryConverter.GetBinaryTypeInfo]objectInfo null for user object"); + assemId = (int)objectInfo._assemId; + if (assemId == 0) + { + throw new SerializationException(SR.Format(SR.Serialization_AssemblyId, typeInformation)); + } + } + break; + default: + binaryTypeEnum = BinaryTypeEnum.Primitive; + typeInformation = primitiveTypeEnum; + break; + } + } + + return binaryTypeEnum; + } + + // Used for non Si types when Parsing + internal static BinaryTypeEnum GetParserBinaryTypeInfo(Type type, out object typeInformation) + { + BinaryTypeEnum binaryTypeEnum; + typeInformation = null; + + if (ReferenceEquals(type, Converter.s_typeofString)) + { + binaryTypeEnum = BinaryTypeEnum.String; + } + else if (ReferenceEquals(type, Converter.s_typeofObject)) + { + binaryTypeEnum = BinaryTypeEnum.Object; + } + else if (ReferenceEquals(type, Converter.s_typeofObjectArray)) + { + binaryTypeEnum = BinaryTypeEnum.ObjectArray; + } + else if (ReferenceEquals(type, Converter.s_typeofStringArray)) + { + binaryTypeEnum = BinaryTypeEnum.StringArray; + } + else if (Converter.IsPrimitiveArray(type, out typeInformation)) + { + binaryTypeEnum = BinaryTypeEnum.PrimitiveArray; + } + else + { + InternalPrimitiveTypeE primitiveTypeEnum = Converter.ToCode(type); + switch (primitiveTypeEnum) + { + case InternalPrimitiveTypeE.Invalid: + binaryTypeEnum = type.GetTypeInfo().Assembly == Converter.s_urtAssembly ? + BinaryTypeEnum.ObjectUrt : + BinaryTypeEnum.ObjectUser; + typeInformation = type.FullName; + break; + default: + binaryTypeEnum = BinaryTypeEnum.Primitive; + typeInformation = primitiveTypeEnum; + break; + } + } + + return binaryTypeEnum; + } + + // Writes the type information on the wire + internal static void WriteTypeInfo(BinaryTypeEnum binaryTypeEnum, object typeInformation, int assemId, BinaryFormatterWriter output) + { + switch (binaryTypeEnum) + { + case BinaryTypeEnum.Primitive: + case BinaryTypeEnum.PrimitiveArray: + Debug.Assert(typeInformation != null, "[BinaryConverter.WriteTypeInfo]typeInformation!=null"); + output.WriteByte((byte)((InternalPrimitiveTypeE)typeInformation)); + break; + case BinaryTypeEnum.String: + case BinaryTypeEnum.Object: + case BinaryTypeEnum.StringArray: + case BinaryTypeEnum.ObjectArray: + break; + case BinaryTypeEnum.ObjectUrt: + Debug.Assert(typeInformation != null, "[BinaryConverter.WriteTypeInfo]typeInformation!=null"); + output.WriteString(typeInformation.ToString()); + break; + case BinaryTypeEnum.ObjectUser: + Debug.Assert(typeInformation != null, "[BinaryConverter.WriteTypeInfo]typeInformation!=null"); + output.WriteString(typeInformation.ToString()); + output.WriteInt32(assemId); + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_TypeWrite, binaryTypeEnum.ToString())); + } + } + + // Reads the type information from the wire + internal static object ReadTypeInfo(BinaryTypeEnum binaryTypeEnum, BinaryParser input, out int assemId) + { + object var = null; + int readAssemId = 0; + + switch (binaryTypeEnum) + { + case BinaryTypeEnum.Primitive: + case BinaryTypeEnum.PrimitiveArray: + var = (InternalPrimitiveTypeE)input.ReadByte(); + break; + case BinaryTypeEnum.String: + case BinaryTypeEnum.Object: + case BinaryTypeEnum.StringArray: + case BinaryTypeEnum.ObjectArray: + break; + case BinaryTypeEnum.ObjectUrt: + var = input.ReadString(); + break; + case BinaryTypeEnum.ObjectUser: + var = input.ReadString(); + readAssemId = input.ReadInt32(); + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_TypeRead, binaryTypeEnum.ToString())); + } + assemId = readAssemId; + return var; + } + + // Given the wire type information, returns the actual type and additional information + internal static void TypeFromInfo(BinaryTypeEnum binaryTypeEnum, + object typeInformation, + ObjectReader objectReader, + BinaryAssemblyInfo assemblyInfo, + out InternalPrimitiveTypeE primitiveTypeEnum, + out string typeString, + out Type type, + out bool isVariant) + { + isVariant = false; + primitiveTypeEnum = InternalPrimitiveTypeE.Invalid; + typeString = null; + type = null; + + switch (binaryTypeEnum) + { + case BinaryTypeEnum.Primitive: + primitiveTypeEnum = (InternalPrimitiveTypeE)typeInformation; + typeString = Converter.ToComType(primitiveTypeEnum); + type = Converter.ToType(primitiveTypeEnum); + break; + case BinaryTypeEnum.String: + type = Converter.s_typeofString; + break; + case BinaryTypeEnum.Object: + type = Converter.s_typeofObject; + isVariant = true; + break; + case BinaryTypeEnum.ObjectArray: + type = Converter.s_typeofObjectArray; + break; + case BinaryTypeEnum.StringArray: + type = Converter.s_typeofStringArray; + break; + case BinaryTypeEnum.PrimitiveArray: + primitiveTypeEnum = (InternalPrimitiveTypeE)typeInformation; + type = Converter.ToArrayType(primitiveTypeEnum); + break; + case BinaryTypeEnum.ObjectUser: + case BinaryTypeEnum.ObjectUrt: + if (typeInformation != null) + { + typeString = typeInformation.ToString(); + type = objectReader.GetType(assemblyInfo, typeString); + if (ReferenceEquals(type, Converter.s_typeofObject)) + { + isVariant = true; + } + } + break; + default: + throw new SerializationException(SR.Format(SR.Serialization_TypeRead, binaryTypeEnum.ToString())); + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryUtilClasses.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryUtilClasses.cs new file mode 100644 index 000000000000..5d82909b3b15 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryUtilClasses.cs @@ -0,0 +1,589 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Globalization; +using System.Reflection; +using System.Runtime.Remoting.Messaging; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class ParseRecord + { + // Enums + internal InternalParseTypeE _PRparseTypeEnum = InternalParseTypeE.Empty; + internal InternalObjectTypeE _PRobjectTypeEnum = InternalObjectTypeE.Empty; + internal InternalArrayTypeE _PRarrayTypeEnum = InternalArrayTypeE.Empty; + internal InternalMemberTypeE _PRmemberTypeEnum = InternalMemberTypeE.Empty; + internal InternalMemberValueE _PRmemberValueEnum = InternalMemberValueE.Empty; + internal InternalObjectPositionE _PRobjectPositionEnum = InternalObjectPositionE.Empty; + + // Object + internal string _PRname; + + // Value + internal string _PRvalue; + internal object _PRvarValue; + + // dt attribute + internal string _PRkeyDt; + internal Type _PRdtType; + internal InternalPrimitiveTypeE _PRdtTypeCode; + + // Object ID + internal long _PRobjectId; + + // Reference ID + internal long _PRidRef; + + // Array + + // Array Element Type + internal string _PRarrayElementTypeString; + internal Type _PRarrayElementType; + internal bool _PRisArrayVariant = false; + internal InternalPrimitiveTypeE _PRarrayElementTypeCode; + + // Parsed array information + internal int _PRrank; + internal int[] _PRlengthA; + internal int[] _PRlowerBoundA; + + // Array map for placing array elements in array + internal int[] _PRindexMap; + internal int _PRmemberIndex; + internal int _PRlinearlength; + internal int[] _PRrectangularMap; + internal bool _PRisLowerBound; + + // MemberInfo accumulated during parsing of members + + internal ReadObjectInfo _PRobjectInfo; + + // ValueType Fixup needed + internal bool _PRisValueTypeFixup = false; + + // Created object + internal object _PRnewObj; + internal object[] _PRobjectA; //optimization, will contain object[] + internal PrimitiveArray _PRprimitiveArray; // for Primitive Soap arrays, optimization + internal bool _PRisRegistered; // Used when registering nested classes + internal object[] _PRmemberData; // member data is collected here before populating + internal SerializationInfo _PRsi; + + internal int _consecutiveNullArrayEntryCount; + + internal ParseRecord() { } + + // Initialize ParseRecord. Called when reusing. + internal void Init() + { + // Enums + _PRparseTypeEnum = InternalParseTypeE.Empty; + _PRobjectTypeEnum = InternalObjectTypeE.Empty; + _PRarrayTypeEnum = InternalArrayTypeE.Empty; + _PRmemberTypeEnum = InternalMemberTypeE.Empty; + _PRmemberValueEnum = InternalMemberValueE.Empty; + _PRobjectPositionEnum = InternalObjectPositionE.Empty; + + // Object + _PRname = null; + + // Value + _PRvalue = null; + + // dt attribute + _PRkeyDt = null; + _PRdtType = null; + _PRdtTypeCode = InternalPrimitiveTypeE.Invalid; + + // Object ID + _PRobjectId = 0; + + // Reference ID + _PRidRef = 0; + + // Array + + // Array Element Type + _PRarrayElementTypeString = null; + _PRarrayElementType = null; + _PRisArrayVariant = false; + _PRarrayElementTypeCode = InternalPrimitiveTypeE.Invalid; + + // Parsed array information + _PRrank = 0; + _PRlengthA = null; + _PRlowerBoundA = null; + + // Array map for placing array elements in array + _PRindexMap = null; + _PRmemberIndex = 0; + _PRlinearlength = 0; + _PRrectangularMap = null; + _PRisLowerBound = false; + + // ValueType Fixup needed + _PRisValueTypeFixup = false; + + _PRnewObj = null; + _PRobjectA = null; + _PRprimitiveArray = null; + _PRobjectInfo = null; + _PRisRegistered = false; + _PRmemberData = null; + _PRsi = null; + + _consecutiveNullArrayEntryCount = 0; + } + } + + // Implements a stack used for parsing + internal sealed class SerStack + { + internal object[] _objects = new object[5]; + internal string _stackId; + internal int _top = -1; + + internal SerStack(string stackId) + { + _stackId = stackId; + } + + // Push the object onto the stack + internal void Push(object obj) + { + if (_top == (_objects.Length - 1)) + { + IncreaseCapacity(); + } + _objects[++_top] = obj; + } + + // Pop the object from the stack + internal object Pop() + { + if (_top < 0) + { + return null; + } + + object obj = _objects[_top]; + _objects[_top--] = null; + return obj; + } + + internal void IncreaseCapacity() + { + int size = _objects.Length * 2; + object[] newItems = new object[size]; + Array.Copy(_objects, 0, newItems, 0, _objects.Length); + _objects = newItems; + } + + // Gets the object on the top of the stack + internal object Peek() => _top < 0 ? null : _objects[_top]; + + // Gets the second entry in the stack. + internal object PeekPeek() => _top < 1 ? null : _objects[_top - 1]; + + // The number of entries in the stack + internal bool IsEmpty() => _top <= 0; + } + + // Implements a Growable array + [Serializable] + internal sealed class SizedArray : ICloneable + { + internal object[] _objects = null; + internal object[] _negObjects = null; + + internal SizedArray() + { + _objects = new object[16]; + _negObjects = new object[4]; + } + + internal SizedArray(int length) + { + _objects = new object[length]; + _negObjects = new object[length]; + } + + private SizedArray(SizedArray sizedArray) + { + _objects = new object[sizedArray._objects.Length]; + sizedArray._objects.CopyTo(_objects, 0); + _negObjects = new object[sizedArray._negObjects.Length]; + sizedArray._negObjects.CopyTo(_negObjects, 0); + } + + public object Clone() => new SizedArray(this); + + internal object this[int index] + { + get + { + if (index < 0) + { + return -index > _negObjects.Length - 1 ? null : _negObjects[-index]; + } + else + { + return index > _objects.Length - 1 ? null : _objects[index]; + } + } + set + { + if (index < 0) + { + if (-index > _negObjects.Length - 1) + { + IncreaseCapacity(index); + } + _negObjects[-index] = value; + } + else + { + if (index > _objects.Length - 1) + { + IncreaseCapacity(index); + } + _objects[index] = value; + } + } + } + + internal void IncreaseCapacity(int index) + { + try + { + if (index < 0) + { + int size = Math.Max(_negObjects.Length * 2, (-index) + 1); + object[] newItems = new object[size]; + Array.Copy(_negObjects, 0, newItems, 0, _negObjects.Length); + _negObjects = newItems; + } + else + { + int size = Math.Max(_objects.Length * 2, index + 1); + object[] newItems = new object[size]; + Array.Copy(_objects, 0, newItems, 0, _objects.Length); + _objects = newItems; + } + } + catch (Exception) + { + throw new SerializationException(SR.Serialization_CorruptedStream); + } + } + } + + [Serializable] + internal sealed class IntSizedArray : ICloneable + { + internal int[] objects = new int[16]; + internal int[] negObjects = new int[4]; + + public IntSizedArray() { } + + private IntSizedArray(IntSizedArray sizedArray) + { + objects = new int[sizedArray.objects.Length]; + sizedArray.objects.CopyTo(objects, 0); + negObjects = new int[sizedArray.negObjects.Length]; + sizedArray.negObjects.CopyTo(negObjects, 0); + } + + public object Clone() => new IntSizedArray(this); + + internal int this[int index] + { + get + { + if (index < 0) + { + return -index > negObjects.Length - 1 ? 0 : negObjects[-index]; + } + else + { + return index > objects.Length - 1 ? 0 : objects[index]; + } + } + set + { + if (index < 0) + { + if (-index > negObjects.Length - 1) + { + IncreaseCapacity(index); + } + negObjects[-index] = value; + } + else + { + if (index > objects.Length - 1) + { + IncreaseCapacity(index); + } + objects[index] = value; + } + } + } + + internal void IncreaseCapacity(int index) + { + try + { + if (index < 0) + { + int size = Math.Max(negObjects.Length * 2, (-index) + 1); + int[] newItems = new int[size]; + Array.Copy(negObjects, 0, newItems, 0, negObjects.Length); + negObjects = newItems; + } + else + { + int size = Math.Max(objects.Length * 2, index + 1); + int[] newItems = new int[size]; + Array.Copy(objects, 0, newItems, 0, objects.Length); + objects = newItems; + } + } + catch (Exception) + { + throw new SerializationException(SR.Serialization_CorruptedStream); + } + } + } + + internal sealed class NameCache + { + private static readonly ConcurrentDictionary s_ht = new ConcurrentDictionary(); + private string _name = null; + + internal object GetCachedValue(string name) + { + _name = name; + object value; + return s_ht.TryGetValue(name, out value) ? value : null; + } + + internal void SetCachedValue(object value) => s_ht[_name] = value; + } + + + // Used to fixup value types. Only currently used for valuetypes which are array items. + internal sealed class ValueFixup + { + internal ValueFixupEnum _valueFixupEnum = ValueFixupEnum.Empty; + internal Array _arrayObj; + internal int[] _indexMap; + internal object _header = null; + internal object _memberObject; + internal static volatile MemberInfo _valueInfo; + internal ReadObjectInfo _objectInfo; + internal string _memberName; + + internal ValueFixup(Array arrayObj, int[] indexMap) + { + _valueFixupEnum = ValueFixupEnum.Array; + _arrayObj = arrayObj; + _indexMap = indexMap; + } + + internal ValueFixup(object memberObject, string memberName, ReadObjectInfo objectInfo) + { + _valueFixupEnum = ValueFixupEnum.Member; + _memberObject = memberObject; + _memberName = memberName; + _objectInfo = objectInfo; + } + + internal void Fixup(ParseRecord record, ParseRecord parent) + { + object obj = record._PRnewObj; + switch (_valueFixupEnum) + { + case ValueFixupEnum.Array: + _arrayObj.SetValue(obj, _indexMap); + break; + case ValueFixupEnum.Header: + Type type = typeof(Header); + if (_valueInfo == null) + { + MemberInfo[] valueInfos = type.GetMember("Value"); + if (valueInfos.Length != 1) + { + throw new SerializationException(SR.Format(SR.Serialization_HeaderReflection, valueInfos.Length)); + } + _valueInfo = valueInfos[0]; + } + FormatterServices.SerializationSetValue(_valueInfo, _header, obj); + break; + case ValueFixupEnum.Member: + if (_objectInfo._isSi) + { + _objectInfo._objectManager.RecordDelayedFixup(parent._PRobjectId, _memberName, record._PRobjectId); + } + else + { + MemberInfo memberInfo = _objectInfo.GetMemberInfo(_memberName); + if (memberInfo != null) + { + _objectInfo._objectManager.RecordFixup(parent._PRobjectId, memberInfo, record._PRobjectId); + } + } + break; + } + } + } + + // Class used to transmit Enums from the XML and Binary Formatter class to the ObjectWriter and ObjectReader class + internal sealed class InternalFE + { + internal FormatterTypeStyle _FEtypeFormat; + internal FormatterAssemblyStyle _FEassemblyFormat; + internal TypeFilterLevel _FEsecurityLevel; + internal InternalSerializerTypeE _FEserializerTypeEnum; + } + + internal sealed class NameInfo + { + internal string _NIFullName; // Name from SerObjectInfo.GetType + internal long _NIobjectId; + internal long _NIassemId; + internal InternalPrimitiveTypeE _NIprimitiveTypeEnum = InternalPrimitiveTypeE.Invalid; + internal Type _NItype; + internal bool _NIisSealed; + internal bool _NIisArray; + internal bool _NIisArrayItem; + internal bool _NItransmitTypeOnObject; + internal bool _NItransmitTypeOnMember; + internal bool _NIisParentTypeOnObject; + internal InternalArrayTypeE _NIarrayEnum; + private bool _NIsealedStatusChecked = false; + + internal NameInfo() { } + + internal void Init() + { + _NIFullName = null; + _NIobjectId = 0; + _NIassemId = 0; + _NIprimitiveTypeEnum = InternalPrimitiveTypeE.Invalid; + _NItype = null; + _NIisSealed = false; + _NItransmitTypeOnObject = false; + _NItransmitTypeOnMember = false; + _NIisParentTypeOnObject = false; + _NIisArray = false; + _NIisArrayItem = false; + _NIarrayEnum = InternalArrayTypeE.Empty; + _NIsealedStatusChecked = false; + } + + public bool IsSealed + { + get + { + if (!_NIsealedStatusChecked) + { + _NIisSealed = _NItype.GetTypeInfo().IsSealed; + _NIsealedStatusChecked = true; + } + return _NIisSealed; + } + } + + public string NIname + { + get { return _NIFullName ?? (_NIFullName = _NItype.FullName); } + set { _NIFullName = value; } + } + } + + internal sealed class PrimitiveArray + { + private InternalPrimitiveTypeE _code; + private bool[] _booleanA = null; + private char[] _charA = null; + private double[] _doubleA = null; + private short[] _int16A = null; + private int[] _int32A = null; + private long[] _int64A = null; + private sbyte[] _sbyteA = null; + private float[] _singleA = null; + private ushort[] _uint16A = null; + private uint[] _uint32A = null; + private ulong[] _uint64A = null; + + internal PrimitiveArray(InternalPrimitiveTypeE code, Array array) + { + _code = code; + switch (code) + { + case InternalPrimitiveTypeE.Boolean: _booleanA = (bool[])array; break; + case InternalPrimitiveTypeE.Char: _charA = (char[])array; break; + case InternalPrimitiveTypeE.Double: _doubleA = (double[])array; break; + case InternalPrimitiveTypeE.Int16: _int16A = (short[])array; break; + case InternalPrimitiveTypeE.Int32: _int32A = (int[])array; break; + case InternalPrimitiveTypeE.Int64: _int64A = (long[])array; break; + case InternalPrimitiveTypeE.SByte: _sbyteA = (sbyte[])array; break; + case InternalPrimitiveTypeE.Single: _singleA = (float[])array; break; + case InternalPrimitiveTypeE.UInt16: _uint16A = (ushort[])array; break; + case InternalPrimitiveTypeE.UInt32: _uint32A = (uint[])array; break; + case InternalPrimitiveTypeE.UInt64: _uint64A = (ulong[])array; break; + } + } + + internal void SetValue(string value, int index) + { + switch (_code) + { + case InternalPrimitiveTypeE.Boolean: + _booleanA[index] = bool.Parse(value); + break; + case InternalPrimitiveTypeE.Char: + if ((value[0] == '_') && (value.Equals("_0x00_"))) + { + _charA[index] = char.MinValue; + } + else + { + _charA[index] = char.Parse(value); + } + break; + case InternalPrimitiveTypeE.Double: + _doubleA[index] = double.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.Int16: + _int16A[index] = short.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.Int32: + _int32A[index] = int.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.Int64: + _int64A[index] = long.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.SByte: + _sbyteA[index] = sbyte.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.Single: + _singleA[index] = float.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.UInt16: + _uint16A[index] = ushort.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.UInt32: + _uint32A[index] = uint.Parse(value, CultureInfo.InvariantCulture); + break; + case InternalPrimitiveTypeE.UInt64: + _uint64A[index] = ulong.Parse(value, CultureInfo.InvariantCulture); + break; + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/Converter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/Converter.cs new file mode 100644 index 000000000000..6dbdfe88d9ba --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/Converter.cs @@ -0,0 +1,391 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Globalization; +using System.Diagnostics; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal static class Converter + { + internal static readonly Type s_typeofISerializable = typeof(ISerializable); + internal static readonly Type s_typeofString = typeof(string); + internal static readonly Type s_typeofConverter = typeof(Converter); + internal static readonly Type s_typeofBoolean = typeof(bool); + internal static readonly Type s_typeofByte = typeof(byte); + internal static readonly Type s_typeofChar = typeof(char); + internal static readonly Type s_typeofDecimal = typeof(decimal); + internal static readonly Type s_typeofDouble = typeof(double); + internal static readonly Type s_typeofInt16 = typeof(short); + internal static readonly Type s_typeofInt32 = typeof(int); + internal static readonly Type s_typeofInt64 = typeof(long); + internal static readonly Type s_typeofSByte = typeof(sbyte); + internal static readonly Type s_typeofSingle = typeof(float); + internal static readonly Type s_typeofTimeSpan = typeof(TimeSpan); + internal static readonly Type s_typeofDateTime = typeof(DateTime); + internal static readonly Type s_typeofUInt16 = typeof(ushort); + internal static readonly Type s_typeofUInt32 = typeof(uint); + internal static readonly Type s_typeofUInt64 = typeof(ulong); + internal static readonly Type s_typeofObject = typeof(object); + internal static readonly Type s_typeofSystemVoid = typeof(void); + internal static readonly Assembly s_urtAssembly = s_typeofString.GetTypeInfo().Assembly; + internal static readonly string s_urtAssemblyString = s_urtAssembly.FullName; + + // Arrays + internal static readonly Type s_typeofTypeArray = typeof(Type[]); + internal static readonly Type s_typeofObjectArray = typeof(object[]); + internal static readonly Type s_typeofStringArray = typeof(string[]); + internal static readonly Type s_typeofBooleanArray = typeof(bool[]); + internal static readonly Type s_typeofByteArray = typeof(byte[]); + internal static readonly Type s_typeofCharArray = typeof(char[]); + internal static readonly Type s_typeofDecimalArray = typeof(decimal[]); + internal static readonly Type s_typeofDoubleArray = typeof(double[]); + internal static readonly Type s_typeofInt16Array = typeof(short[]); + internal static readonly Type s_typeofInt32Array = typeof(int[]); + internal static readonly Type s_typeofInt64Array = typeof(long[]); + internal static readonly Type s_typeofSByteArray = typeof(sbyte[]); + internal static readonly Type s_typeofSingleArray = typeof(float[]); + internal static readonly Type s_typeofTimeSpanArray = typeof(TimeSpan[]); + internal static readonly Type s_typeofDateTimeArray = typeof(DateTime[]); + internal static readonly Type s_typeofUInt16Array = typeof(ushort[]); + internal static readonly Type s_typeofUInt32Array = typeof(uint[]); + internal static readonly Type s_typeofUInt64Array = typeof(ulong[]); + internal static readonly Type s_typeofMarshalByRefObject = typeof(MarshalByRefObject); + + private const int PrimitiveTypeEnumLength = 17; //Number of PrimitiveTypeEnums + + private static volatile Type[] s_typeA; + private static volatile Type[] s_arrayTypeA; + private static volatile string[] s_valueA; + private static volatile TypeCode[] s_typeCodeA; + private static volatile InternalPrimitiveTypeE[] s_codeA; + + internal static InternalPrimitiveTypeE ToCode(Type type) => + type == null ? ToPrimitiveTypeEnum(TypeCode.Empty) : + type.GetTypeInfo().IsPrimitive ? ToPrimitiveTypeEnum(type.GetTypeCode()) : + ReferenceEquals(type, s_typeofDateTime) ? InternalPrimitiveTypeE.DateTime : + ReferenceEquals(type, s_typeofTimeSpan) ? InternalPrimitiveTypeE.TimeSpan : + ReferenceEquals(type, s_typeofDecimal) ? InternalPrimitiveTypeE.Decimal : + InternalPrimitiveTypeE.Invalid; + + internal static bool IsWriteAsByteArray(InternalPrimitiveTypeE code) + { + switch (code) + { + case InternalPrimitiveTypeE.Boolean: + case InternalPrimitiveTypeE.Char: + case InternalPrimitiveTypeE.Byte: + case InternalPrimitiveTypeE.Double: + case InternalPrimitiveTypeE.Int16: + case InternalPrimitiveTypeE.Int32: + case InternalPrimitiveTypeE.Int64: + case InternalPrimitiveTypeE.SByte: + case InternalPrimitiveTypeE.Single: + case InternalPrimitiveTypeE.UInt16: + case InternalPrimitiveTypeE.UInt32: + case InternalPrimitiveTypeE.UInt64: + return true; + default: + return false; + } + } + + internal static int TypeLength(InternalPrimitiveTypeE code) + { + switch (code) + { + case InternalPrimitiveTypeE.Boolean: return 1; + case InternalPrimitiveTypeE.Char: return 2; + case InternalPrimitiveTypeE.Byte: return 1; + case InternalPrimitiveTypeE.Double: return 8; + case InternalPrimitiveTypeE.Int16: return 2; + case InternalPrimitiveTypeE.Int32: return 4; + case InternalPrimitiveTypeE.Int64: return 8; + case InternalPrimitiveTypeE.SByte: return 1; + case InternalPrimitiveTypeE.Single: return 4; + case InternalPrimitiveTypeE.UInt16: return 2; + case InternalPrimitiveTypeE.UInt32: return 4; + case InternalPrimitiveTypeE.UInt64: return 8; + default: return 0; + } + } + + internal static InternalNameSpaceE GetNameSpaceEnum(InternalPrimitiveTypeE code, Type type, WriteObjectInfo objectInfo, out string typeName) + { + InternalNameSpaceE nameSpaceEnum = InternalNameSpaceE.None; + typeName = null; + + if (code != InternalPrimitiveTypeE.Invalid) + { + switch (code) + { + case InternalPrimitiveTypeE.Boolean: + case InternalPrimitiveTypeE.Char: + case InternalPrimitiveTypeE.Byte: + case InternalPrimitiveTypeE.Double: + case InternalPrimitiveTypeE.Int16: + case InternalPrimitiveTypeE.Int32: + case InternalPrimitiveTypeE.Int64: + case InternalPrimitiveTypeE.SByte: + case InternalPrimitiveTypeE.Single: + case InternalPrimitiveTypeE.UInt16: + case InternalPrimitiveTypeE.UInt32: + case InternalPrimitiveTypeE.UInt64: + case InternalPrimitiveTypeE.DateTime: + case InternalPrimitiveTypeE.TimeSpan: + nameSpaceEnum = InternalNameSpaceE.XdrPrimitive; + typeName = "System." + ToComType(code); + break; + + case InternalPrimitiveTypeE.Decimal: + nameSpaceEnum = InternalNameSpaceE.UrtSystem; + typeName = "System." + ToComType(code); + break; + } + } + + if ((nameSpaceEnum == InternalNameSpaceE.None) && type != null) + { + if (ReferenceEquals(type, s_typeofString)) + { + nameSpaceEnum = InternalNameSpaceE.XdrString; + } + else + { + if (objectInfo == null) + { + typeName = type.FullName; + nameSpaceEnum = type.GetTypeInfo().Assembly == s_urtAssembly ? InternalNameSpaceE.UrtSystem : InternalNameSpaceE.UrtUser; + } + else + { + typeName = objectInfo.GetTypeFullName(); + nameSpaceEnum = objectInfo.GetAssemblyString().Equals(s_urtAssemblyString) ? InternalNameSpaceE.UrtSystem : InternalNameSpaceE.UrtUser; + } + } + } + + return nameSpaceEnum; + } + + internal static Type ToArrayType(InternalPrimitiveTypeE code) + { + if (s_arrayTypeA == null) + { + InitArrayTypeA(); + } + return s_arrayTypeA[(int)code]; + } + + private static void InitTypeA() + { + var typeATemp = new Type[PrimitiveTypeEnumLength]; + typeATemp[(int)InternalPrimitiveTypeE.Invalid] = null; + typeATemp[(int)InternalPrimitiveTypeE.Boolean] = s_typeofBoolean; + typeATemp[(int)InternalPrimitiveTypeE.Byte] = s_typeofByte; + typeATemp[(int)InternalPrimitiveTypeE.Char] = s_typeofChar; + typeATemp[(int)InternalPrimitiveTypeE.Decimal] = s_typeofDecimal; + typeATemp[(int)InternalPrimitiveTypeE.Double] = s_typeofDouble; + typeATemp[(int)InternalPrimitiveTypeE.Int16] = s_typeofInt16; + typeATemp[(int)InternalPrimitiveTypeE.Int32] = s_typeofInt32; + typeATemp[(int)InternalPrimitiveTypeE.Int64] = s_typeofInt64; + typeATemp[(int)InternalPrimitiveTypeE.SByte] = s_typeofSByte; + typeATemp[(int)InternalPrimitiveTypeE.Single] = s_typeofSingle; + typeATemp[(int)InternalPrimitiveTypeE.TimeSpan] = s_typeofTimeSpan; + typeATemp[(int)InternalPrimitiveTypeE.DateTime] = s_typeofDateTime; + typeATemp[(int)InternalPrimitiveTypeE.UInt16] = s_typeofUInt16; + typeATemp[(int)InternalPrimitiveTypeE.UInt32] = s_typeofUInt32; + typeATemp[(int)InternalPrimitiveTypeE.UInt64] = s_typeofUInt64; + s_typeA = typeATemp; + } + + private static void InitArrayTypeA() + { + var arrayTypeATemp = new Type[PrimitiveTypeEnumLength]; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Invalid] = null; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Boolean] = s_typeofBooleanArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Byte] = s_typeofByteArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Char] = s_typeofCharArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Decimal] = s_typeofDecimalArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Double] = s_typeofDoubleArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Int16] = s_typeofInt16Array; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Int32] = s_typeofInt32Array; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Int64] = s_typeofInt64Array; + arrayTypeATemp[(int)InternalPrimitiveTypeE.SByte] = s_typeofSByteArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.Single] = s_typeofSingleArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.TimeSpan] = s_typeofTimeSpanArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.DateTime] = s_typeofDateTimeArray; + arrayTypeATemp[(int)InternalPrimitiveTypeE.UInt16] = s_typeofUInt16Array; + arrayTypeATemp[(int)InternalPrimitiveTypeE.UInt32] = s_typeofUInt32Array; + arrayTypeATemp[(int)InternalPrimitiveTypeE.UInt64] = s_typeofUInt64Array; + s_arrayTypeA = arrayTypeATemp; + } + + internal static Type ToType(InternalPrimitiveTypeE code) + { + if (s_typeA == null) + { + InitTypeA(); + } + return s_typeA[(int)code]; + } + + internal static Array CreatePrimitiveArray(InternalPrimitiveTypeE code, int length) + { + switch (code) + { + case InternalPrimitiveTypeE.Boolean: return new bool[length]; + case InternalPrimitiveTypeE.Byte: return new byte[length]; + case InternalPrimitiveTypeE.Char: return new char[length]; + case InternalPrimitiveTypeE.Decimal: return new decimal[length]; + case InternalPrimitiveTypeE.Double: return new double[length]; + case InternalPrimitiveTypeE.Int16: return new short[length]; + case InternalPrimitiveTypeE.Int32: return new int[length]; + case InternalPrimitiveTypeE.Int64: return new long[length]; + case InternalPrimitiveTypeE.SByte: return new sbyte[length]; + case InternalPrimitiveTypeE.Single: return new float[length]; + case InternalPrimitiveTypeE.TimeSpan: return new TimeSpan[length]; + case InternalPrimitiveTypeE.DateTime: return new DateTime[length]; + case InternalPrimitiveTypeE.UInt16: return new ushort[length]; + case InternalPrimitiveTypeE.UInt32: return new uint[length]; + case InternalPrimitiveTypeE.UInt64: return new ulong[length]; + default: return null; + } + } + + internal static bool IsPrimitiveArray(Type type, out object typeInformation) + { + bool bIsPrimitive = true; + + if (ReferenceEquals(type, s_typeofBooleanArray)) typeInformation = InternalPrimitiveTypeE.Boolean; + else if (ReferenceEquals(type, s_typeofByteArray)) typeInformation = InternalPrimitiveTypeE.Byte; + else if (ReferenceEquals(type, s_typeofCharArray)) typeInformation = InternalPrimitiveTypeE.Char; + else if (ReferenceEquals(type, s_typeofDoubleArray)) typeInformation = InternalPrimitiveTypeE.Double; + else if (ReferenceEquals(type, s_typeofInt16Array)) typeInformation = InternalPrimitiveTypeE.Int16; + else if (ReferenceEquals(type, s_typeofInt32Array)) typeInformation = InternalPrimitiveTypeE.Int32; + else if (ReferenceEquals(type, s_typeofInt64Array)) typeInformation = InternalPrimitiveTypeE.Int64; + else if (ReferenceEquals(type, s_typeofSByteArray)) typeInformation = InternalPrimitiveTypeE.SByte; + else if (ReferenceEquals(type, s_typeofSingleArray)) typeInformation = InternalPrimitiveTypeE.Single; + else if (ReferenceEquals(type, s_typeofUInt16Array)) typeInformation = InternalPrimitiveTypeE.UInt16; + else if (ReferenceEquals(type, s_typeofUInt32Array)) typeInformation = InternalPrimitiveTypeE.UInt32; + else if (ReferenceEquals(type, s_typeofUInt64Array)) typeInformation = InternalPrimitiveTypeE.UInt64; + else + { + typeInformation = null; + bIsPrimitive = false; + } + + return bIsPrimitive; + } + + private static void InitValueA() + { + var valueATemp = new string[PrimitiveTypeEnumLength]; + valueATemp[(int)InternalPrimitiveTypeE.Invalid] = null; + valueATemp[(int)InternalPrimitiveTypeE.Boolean] = "Boolean"; + valueATemp[(int)InternalPrimitiveTypeE.Byte] = "Byte"; + valueATemp[(int)InternalPrimitiveTypeE.Char] = "Char"; + valueATemp[(int)InternalPrimitiveTypeE.Decimal] = "Decimal"; + valueATemp[(int)InternalPrimitiveTypeE.Double] = "Double"; + valueATemp[(int)InternalPrimitiveTypeE.Int16] = "Int16"; + valueATemp[(int)InternalPrimitiveTypeE.Int32] = "Int32"; + valueATemp[(int)InternalPrimitiveTypeE.Int64] = "Int64"; + valueATemp[(int)InternalPrimitiveTypeE.SByte] = "SByte"; + valueATemp[(int)InternalPrimitiveTypeE.Single] = "Single"; + valueATemp[(int)InternalPrimitiveTypeE.TimeSpan] = "TimeSpan"; + valueATemp[(int)InternalPrimitiveTypeE.DateTime] = "DateTime"; + valueATemp[(int)InternalPrimitiveTypeE.UInt16] = "UInt16"; + valueATemp[(int)InternalPrimitiveTypeE.UInt32] = "UInt32"; + valueATemp[(int)InternalPrimitiveTypeE.UInt64] = "UInt64"; + s_valueA = valueATemp; + } + + internal static string ToComType(InternalPrimitiveTypeE code) + { + if (s_valueA == null) + { + InitValueA(); + } + return s_valueA[(int)code]; + } + + private static void InitTypeCodeA() + { + var typeCodeATemp = new TypeCode[PrimitiveTypeEnumLength]; + typeCodeATemp[(int)InternalPrimitiveTypeE.Invalid] = TypeCode.Object; + typeCodeATemp[(int)InternalPrimitiveTypeE.Boolean] = TypeCode.Boolean; + typeCodeATemp[(int)InternalPrimitiveTypeE.Byte] = TypeCode.Byte; + typeCodeATemp[(int)InternalPrimitiveTypeE.Char] = TypeCode.Char; + typeCodeATemp[(int)InternalPrimitiveTypeE.Decimal] = TypeCode.Decimal; + typeCodeATemp[(int)InternalPrimitiveTypeE.Double] = TypeCode.Double; + typeCodeATemp[(int)InternalPrimitiveTypeE.Int16] = TypeCode.Int16; + typeCodeATemp[(int)InternalPrimitiveTypeE.Int32] = TypeCode.Int32; + typeCodeATemp[(int)InternalPrimitiveTypeE.Int64] = TypeCode.Int64; + typeCodeATemp[(int)InternalPrimitiveTypeE.SByte] = TypeCode.SByte; + typeCodeATemp[(int)InternalPrimitiveTypeE.Single] = TypeCode.Single; + typeCodeATemp[(int)InternalPrimitiveTypeE.TimeSpan] = TypeCode.Object; + typeCodeATemp[(int)InternalPrimitiveTypeE.DateTime] = TypeCode.DateTime; + typeCodeATemp[(int)InternalPrimitiveTypeE.UInt16] = TypeCode.UInt16; + typeCodeATemp[(int)InternalPrimitiveTypeE.UInt32] = TypeCode.UInt32; + typeCodeATemp[(int)InternalPrimitiveTypeE.UInt64] = TypeCode.UInt64; + s_typeCodeA = typeCodeATemp; + } + + // Returns a System.TypeCode from a InternalPrimitiveTypeE + internal static TypeCode ToTypeCode(InternalPrimitiveTypeE code) + { + if (s_typeCodeA == null) + { + InitTypeCodeA(); + } + return s_typeCodeA[(int)code]; + } + + private static void InitCodeA() + { + var codeATemp = new InternalPrimitiveTypeE[19]; + codeATemp[(int)TypeCode.Empty] = InternalPrimitiveTypeE.Invalid; + codeATemp[(int)TypeCode.Object] = InternalPrimitiveTypeE.Invalid; + codeATemp[2] = InternalPrimitiveTypeE.Invalid; // TODO: Change 2 to (int)TypeCode.DBNull when it's available + codeATemp[(int)TypeCode.Boolean] = InternalPrimitiveTypeE.Boolean; + codeATemp[(int)TypeCode.Char] = InternalPrimitiveTypeE.Char; + codeATemp[(int)TypeCode.SByte] = InternalPrimitiveTypeE.SByte; + codeATemp[(int)TypeCode.Byte] = InternalPrimitiveTypeE.Byte; + codeATemp[(int)TypeCode.Int16] = InternalPrimitiveTypeE.Int16; + codeATemp[(int)TypeCode.UInt16] = InternalPrimitiveTypeE.UInt16; + codeATemp[(int)TypeCode.Int32] = InternalPrimitiveTypeE.Int32; + codeATemp[(int)TypeCode.UInt32] = InternalPrimitiveTypeE.UInt32; + codeATemp[(int)TypeCode.Int64] = InternalPrimitiveTypeE.Int64; + codeATemp[(int)TypeCode.UInt64] = InternalPrimitiveTypeE.UInt64; + codeATemp[(int)TypeCode.Single] = InternalPrimitiveTypeE.Single; + codeATemp[(int)TypeCode.Double] = InternalPrimitiveTypeE.Double; + codeATemp[(int)TypeCode.Decimal] = InternalPrimitiveTypeE.Decimal; + codeATemp[(int)TypeCode.DateTime] = InternalPrimitiveTypeE.DateTime; + codeATemp[17] = InternalPrimitiveTypeE.Invalid; + codeATemp[(int)TypeCode.String] = InternalPrimitiveTypeE.Invalid; + s_codeA = codeATemp; + } + + // Returns a InternalPrimitiveTypeE from a System.TypeCode + internal static InternalPrimitiveTypeE ToPrimitiveTypeEnum(TypeCode typeCode) + { + if (s_codeA == null) + { + InitCodeA(); + } + return s_codeA[(int)typeCode]; + } + + // Translates a string into an Object + internal static object FromString(string value, InternalPrimitiveTypeE code) + { + // InternalPrimitiveTypeE needs to be a primitive type + Debug.Assert((code != InternalPrimitiveTypeE.Invalid), "[Converter.FromString]!InternalPrimitiveTypeE.Invalid "); + return code != InternalPrimitiveTypeE.Invalid ? + Convert.ChangeType(value, ToTypeCode(code), CultureInfo.InvariantCulture) : + value; + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/IStreamable.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/IStreamable.cs new file mode 100644 index 000000000000..7e234d8d2cbe --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/IStreamable.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // Interface for Binary Records. + internal interface IStreamable + { + void Write(BinaryFormatterWriter output); + void Read(BinaryParser input); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveTyped.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveTyped.cs new file mode 100644 index 000000000000..e2c5cc525757 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveTyped.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class MemberPrimitiveTyped : IStreamable + { + internal InternalPrimitiveTypeE _primitiveTypeEnum; + internal object _value; + + internal MemberPrimitiveTyped() { } + + internal void Set(InternalPrimitiveTypeE primitiveTypeEnum, object value) + { + _primitiveTypeEnum = primitiveTypeEnum; + _value = value; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.MemberPrimitiveTyped); + output.WriteByte((byte)_primitiveTypeEnum); + output.WriteValue(_primitiveTypeEnum, _value); + } + + public void Read(BinaryParser input) + { + _primitiveTypeEnum = (InternalPrimitiveTypeE)input.ReadByte(); //PDJ + _value = input.ReadValue(_primitiveTypeEnum); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveUntyped.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveUntyped.cs new file mode 100644 index 000000000000..4b63a814214e --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberPrimitiveUntyped.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class MemberPrimitiveUnTyped : IStreamable + { + // Used for members with primitive values and types are needed + internal InternalPrimitiveTypeE _typeInformation; + internal object _value; + + internal MemberPrimitiveUnTyped() { } + + internal void Set(InternalPrimitiveTypeE typeInformation, object value) + { + _typeInformation = typeInformation; + _value = value; + } + + internal void Set(InternalPrimitiveTypeE typeInformation) + { + _typeInformation = typeInformation; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteValue(_typeInformation, _value); + } + + public void Read(BinaryParser input) + { + //binaryHeaderEnum = input.ReadByte(); already read + _value = input.ReadValue(_typeInformation); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberReference.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberReference.cs new file mode 100644 index 000000000000..0eaaaf6c333f --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MemberReference.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class MemberReference : IStreamable + { + internal int _idRef; + + internal MemberReference() { } + + internal void Set(int idRef) + { + _idRef = idRef; + } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.MemberReference); + output.WriteInt32(_idRef); + } + + public void Read(BinaryParser input) + { + //binaryHeaderEnum = input.ReadByte(); already read + _idRef = input.ReadInt32(); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MessageEnd.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MessageEnd.cs new file mode 100644 index 000000000000..9db54145a163 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/MessageEnd.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class MessageEnd : IStreamable + { + internal MessageEnd() { } + + public void Write(BinaryFormatterWriter output) + { + output.WriteByte((byte)BinaryHeaderEnum.MessageEnd); + } + + public void Read(BinaryParser input) + { + //binaryHeaderEnum = input.ReadByte(); already read + } + } +} + + + + + diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs new file mode 100644 index 000000000000..f7276c98b38d --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // When an ObjectWithMap or an ObjectWithMapTyped is read off the stream, an ObjectMap class is created + // to remember the type information. + internal sealed class ObjectMap + { + internal string _objectName; + internal Type _objectType; + + internal BinaryTypeEnum[] _binaryTypeEnumA; + internal object[] _typeInformationA; + internal Type[] _memberTypes; + internal string[] _memberNames; + internal ReadObjectInfo _objectInfo; + internal bool _isInitObjectInfo = true; + internal ObjectReader _objectReader = null; + internal int _objectId; + internal BinaryAssemblyInfo _assemblyInfo; + + internal ObjectMap(string objectName, Type objectType, string[] memberNames, ObjectReader objectReader, int objectId, BinaryAssemblyInfo assemblyInfo) + { + _objectName = objectName; + _objectType = objectType; + _memberNames = memberNames; + _objectReader = objectReader; + _objectId = objectId; + _assemblyInfo = assemblyInfo; + + _objectInfo = objectReader.CreateReadObjectInfo(objectType); + _memberTypes = _objectInfo.GetMemberTypes(memberNames, objectType); + + _binaryTypeEnumA = new BinaryTypeEnum[_memberTypes.Length]; + _typeInformationA = new object[_memberTypes.Length]; + + for (int i = 0; i < _memberTypes.Length; i++) + { + object typeInformation = null; + BinaryTypeEnum binaryTypeEnum = BinaryTypeConverter.GetParserBinaryTypeInfo(_memberTypes[i], out typeInformation); + _binaryTypeEnumA[i] = binaryTypeEnum; + _typeInformationA[i] = typeInformation; + } + } + + internal ObjectMap(string objectName, string[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, object[] typeInformationA, int[] memberAssemIds, ObjectReader objectReader, int objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable) + { + _objectName = objectName; + _memberNames = memberNames; + _binaryTypeEnumA = binaryTypeEnumA; + _typeInformationA = typeInformationA; + _objectReader = objectReader; + _objectId = objectId; + _assemblyInfo = assemblyInfo; + + if (assemblyInfo == null) + { + throw new SerializationException(SR.Format(SR.Serialization_Assembly, objectName)); + } + + _objectType = objectReader.GetType(assemblyInfo, objectName); + _memberTypes = new Type[memberNames.Length]; + + for (int i = 0; i < memberNames.Length; i++) + { + InternalPrimitiveTypeE primitiveTypeEnum; + string typeString; + Type type; + bool isVariant; + + BinaryTypeConverter.TypeFromInfo( + binaryTypeEnumA[i], typeInformationA[i], objectReader, (BinaryAssemblyInfo)assemIdToAssemblyTable[memberAssemIds[i]], + out primitiveTypeEnum, out typeString, out type, out isVariant); + _memberTypes[i] = type; + } + + _objectInfo = objectReader.CreateReadObjectInfo(_objectType, memberNames, null); + if (!_objectInfo._isSi) + { + _objectInfo.GetMemberTypes(memberNames, _objectInfo._objectType); // Check version match + } + } + + internal ReadObjectInfo CreateObjectInfo(ref SerializationInfo si, ref object[] memberData) + { + if (_isInitObjectInfo) + { + _isInitObjectInfo = false; + _objectInfo.InitDataStore(ref si, ref memberData); + return _objectInfo; + } + else + { + _objectInfo.PrepareForReuse(); + _objectInfo.InitDataStore(ref si, ref memberData); + return _objectInfo; + } + } + + // No member type information + internal static ObjectMap Create( + string name, Type objectType, string[] memberNames, ObjectReader objectReader, + int objectId, BinaryAssemblyInfo assemblyInfo) => + new ObjectMap(name, objectType, memberNames, objectReader, objectId, assemblyInfo); + + // Member type information + internal static ObjectMap Create( + string name, string[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, object[] typeInformationA, + int[] memberAssemIds, ObjectReader objectReader, int objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable) => + new ObjectMap(name, memberNames, binaryTypeEnumA, typeInformationA, memberAssemIds, objectReader, objectId, assemblyInfo, assemIdToAssemblyTable); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectNull.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectNull.cs new file mode 100644 index 000000000000..18c6d03c1ecc --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectNull.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + internal sealed class ObjectNull : IStreamable + { + internal int _nullCount; + + internal ObjectNull() { } + + internal void SetNullCount(int nullCount) + { + _nullCount = nullCount; + } + + public void Write(BinaryFormatterWriter output) + { + if (_nullCount == 1) + { + output.WriteByte((byte)BinaryHeaderEnum.ObjectNull); + } + else if (_nullCount < 256) + { + output.WriteByte((byte)BinaryHeaderEnum.ObjectNullMultiple256); + output.WriteByte((byte)_nullCount); + } + else + { + output.WriteByte((byte)BinaryHeaderEnum.ObjectNullMultiple); + output.WriteInt32(_nullCount); + } + } + + public void Read(BinaryParser input) + { + Read(input, BinaryHeaderEnum.ObjectNull); + } + + public void Read(BinaryParser input, BinaryHeaderEnum binaryHeaderEnum) + { + //binaryHeaderEnum = input.ReadByte(); already read + switch (binaryHeaderEnum) + { + case BinaryHeaderEnum.ObjectNull: + _nullCount = 1; + break; + case BinaryHeaderEnum.ObjectNullMultiple256: + _nullCount = input.ReadByte(); + break; + case BinaryHeaderEnum.ObjectNullMultiple: + _nullCount = input.ReadInt32(); + break; + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectProgress.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectProgress.cs new file mode 100644 index 000000000000..41843550f8a2 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectProgress.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // For each object or array being read off the stream, an ObjectProgress object is created. This object + // keeps track of the progress of the parsing. When an object is being parsed, it keeps track of + // the object member being parsed. When an array is being parsed it keeps track of the position within the + // array. + internal sealed class ObjectProgress + { + // Control + internal bool _isInitial; + internal int _count; //Progress count + internal BinaryTypeEnum _expectedType = BinaryTypeEnum.ObjectUrt; + internal object _expectedTypeInformation = null; + + internal string _name; + internal InternalObjectTypeE _objectTypeEnum = InternalObjectTypeE.Empty; + internal InternalMemberTypeE _memberTypeEnum; + internal InternalMemberValueE _memberValueEnum; + internal Type _dtType; + + // Array Information + internal int _numItems; + internal BinaryTypeEnum _binaryTypeEnum; + internal object _typeInformation; + + // Member Information + internal int _memberLength; + internal BinaryTypeEnum[] _binaryTypeEnumA; + internal object[] _typeInformationA; + internal string[] _memberNames; + internal Type[] _memberTypes; + + // ParseRecord + internal ParseRecord _pr = new ParseRecord(); + + internal ObjectProgress() { } + + internal void Init() + { + _isInitial = false; + _count = 0; + _expectedType = BinaryTypeEnum.ObjectUrt; + _expectedTypeInformation = null; + + _name = null; + _objectTypeEnum = InternalObjectTypeE.Empty; + _memberTypeEnum = InternalMemberTypeE.Empty; + _memberValueEnum = InternalMemberValueE.Empty; + _dtType = null; + + // Array Information + _numItems = 0; + + //binaryTypeEnum + _typeInformation = null; + + // Member Information + _memberLength = 0; + _binaryTypeEnumA = null; + _typeInformationA = null; + _memberNames = null; + _memberTypes = null; + + _pr.Init(); + } + + //Array item entry of nulls has a count of nulls represented by that item. The first null has been + // incremented by GetNext, the rest of the null counts are incremented here + internal void ArrayCountIncrement(int value) => _count += value; + + // Specifies what is to parsed next from the wire. + internal bool GetNext(out BinaryTypeEnum outBinaryTypeEnum, out object outTypeInformation) + { + //Initialize the out params up here. + outBinaryTypeEnum = BinaryTypeEnum.Primitive; + outTypeInformation = null; + + if (_objectTypeEnum == InternalObjectTypeE.Array) + { + // Array + if (_count == _numItems) + { + return false; + } + else + { + outBinaryTypeEnum = _binaryTypeEnum; + outTypeInformation = _typeInformation; + if (_count == 0) + _isInitial = false; + _count++; + return true; + } + } + else + { + // Member + if ((_count == _memberLength) && (!_isInitial)) + { + return false; + } + else + { + outBinaryTypeEnum = _binaryTypeEnumA[_count]; + outTypeInformation = _typeInformationA[_count]; + if (_count == 0) + { + _isInitial = false; + } + _name = _memberNames[_count]; + _dtType = _memberTypes[_count]; + _count++; + return true; + } + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/SerializationHeaderRecord.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/SerializationHeaderRecord.cs new file mode 100644 index 000000000000..bde9064203a5 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/SerializationHeaderRecord.cs @@ -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. +// See the LICENSE file in the project root for more information. + +using System.IO; + +namespace System.Runtime.Serialization.Formatters.Binary +{ + // The Following classes read and write the binary records + internal sealed class SerializationHeaderRecord : IStreamable + { + internal const int BinaryFormatterMajorVersion = 1; + internal const int BinaryFormatterMinorVersion = 0; + + internal BinaryHeaderEnum _binaryHeaderEnum; + internal int _topId; + internal int _headerId; + internal int _majorVersion; + internal int _minorVersion; + + internal SerializationHeaderRecord() { } + + internal SerializationHeaderRecord(BinaryHeaderEnum binaryHeaderEnum, int topId, int headerId, int majorVersion, int minorVersion) + { + _binaryHeaderEnum = binaryHeaderEnum; + _topId = topId; + _headerId = headerId; + _majorVersion = majorVersion; + _minorVersion = minorVersion; + } + + public void Write(BinaryFormatterWriter output) + { + _majorVersion = BinaryFormatterMajorVersion; + _minorVersion = BinaryFormatterMinorVersion; + output.WriteByte((byte)_binaryHeaderEnum); + output.WriteInt32(_topId); + output.WriteInt32(_headerId); + output.WriteInt32(BinaryFormatterMajorVersion); + output.WriteInt32(BinaryFormatterMinorVersion); + } + + private static int GetInt32(byte[] buffer, int index) => + buffer[index] | buffer[index + 1] << 8 | buffer[index + 2] << 16 | buffer[index + 3] << 24; + + public void Read(BinaryParser input) + { + byte[] headerBytes = input.ReadBytes(17); + + // Throw if we couldnt read header bytes + if (headerBytes.Length < 17) + { + throw new EndOfStreamException(SR.IO_EOF_ReadBeyondEOF); + } + + _majorVersion = GetInt32(headerBytes, 9); + if (_majorVersion > BinaryFormatterMajorVersion) + { + throw new SerializationException(SR.Format(SR.Serialization_InvalidFormat, BitConverter.ToString(headerBytes))); + } + + // binaryHeaderEnum has already been read + _binaryHeaderEnum = (BinaryHeaderEnum)headerBytes[0]; + _topId = GetInt32(headerBytes, 1); + _headerId = GetInt32(headerBytes, 5); + _minorVersion = GetInt32(headerBytes, 13); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/CommonEnums.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/CommonEnums.cs new file mode 100644 index 000000000000..655593cbc20c --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/CommonEnums.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters +{ + [Serializable] + public enum FormatterTypeStyle + { + TypesWhenNeeded = 0, // Types are outputted only for Arrays of Objects, Object Members of type Object, and ISerializable non-primitive value types + TypesAlways = 0x1, // Types are outputted for all Object members and ISerialiable object members. + XsdString = 0x2 // Strings are outputed as xsd rather then SOAP-ENC strings. No string ID's are transmitted + } + + [Serializable] + public enum FormatterAssemblyStyle + { + Simple = 0, + Full = 1, + } + + public enum TypeFilterLevel + { + Low = 0x2, + Full = 0x3 + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/IFieldInfo.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/IFieldInfo.cs new file mode 100644 index 000000000000..ccc2d2b23565 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/IFieldInfo.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization.Formatters +{ + public interface IFieldInfo + { + string[] FieldNames { get; set; } + Type[] FieldTypes { get; set; } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IDeserializationCallback.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IDeserializationCallback.cs index e7e2bef456a3..c4b508978267 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IDeserializationCallback.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IDeserializationCallback.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; - namespace System.Runtime.Serialization { public interface IDeserializationCallback diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs new file mode 100644 index 000000000000..28878780871d --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; + +namespace System.Runtime.Serialization +{ + public interface IFormatter + { + object Deserialize(Stream serializationStream); + void Serialize(Stream serializationStream, object graph); + ISurrogateSelector SurrogateSelector { get; set; } + SerializationBinder Binder { get; set; } + StreamingContext Context { get; set; } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatterConverter.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatterConverter.cs index 664dcbb52f1f..ac9c3d825532 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatterConverter.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatterConverter.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; - namespace System.Runtime.Serialization { [CLSCompliant(false)] @@ -27,8 +25,8 @@ public interface IFormatterConverter ulong ToUInt64(object value); float ToSingle(object value); double ToDouble(object value); - Decimal ToDecimal(object value); + decimal ToDecimal(object value); DateTime ToDateTime(object value); - String ToString(object value); + string ToString(object value); } -} \ No newline at end of file +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IObjectReference.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IObjectReference.cs new file mode 100644 index 000000000000..d41bc50dde4e --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IObjectReference.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization +{ + public interface IObjectReference + { + object GetRealObject(StreamingContext context); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializable.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializable.cs index 29b1409182ab..383b3f07af35 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializable.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializable.cs @@ -2,12 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; - namespace System.Runtime.Serialization { public interface ISerializable { void GetObjectData(SerializationInfo info, StreamingContext context); } -} \ No newline at end of file +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializationSurrogate.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializationSurrogate.cs new file mode 100644 index 000000000000..c2bf6799fcec --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISerializationSurrogate.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization +{ + public interface ISerializationSurrogate + { + void GetObjectData(object obj, SerializationInfo info, StreamingContext context); + object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISurrogateSelector.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISurrogateSelector.cs new file mode 100644 index 000000000000..3ec071563efa --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ISurrogateSelector.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization +{ + public interface ISurrogateSelector + { + void ChainSelector(ISurrogateSelector selector); + ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector); + ISurrogateSelector GetNextSelector(); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs new file mode 100644 index 000000000000..bb2393f3936c --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs @@ -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. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System.Runtime.Serialization +{ + [Serializable] + internal sealed class MemberHolder + { + internal readonly MemberInfo[] _members = null; + internal readonly Type _memberType; + internal readonly StreamingContext _context; + + internal MemberHolder(Type type, StreamingContext ctx) + { + _memberType = type; + _context = ctx; + } + + public override int GetHashCode() => _memberType.GetHashCode(); + + public override bool Equals(object obj) + { + var mh = obj as MemberHolder; + return + mh != null && + ReferenceEquals(mh._memberType, _memberType) && + mh._context.State == _context.State; + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectIDGenerator.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectIDGenerator.cs new file mode 100644 index 000000000000..1169a8cb8242 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectIDGenerator.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +namespace System.Runtime.Serialization +{ + [Serializable] + public class ObjectIDGenerator + { + private const int NumBins = 4; + + // Table of prime numbers to use as hash table sizes. Each entry is the + // smallest prime number larger than twice the previous entry. + private static readonly int[] s_sizes = + { + 5, 11, 29, 47, 97, 197, 397, 797, 1597, 3203, 6421, 12853, 25717, 51437, + 102877, 205759, 411527, 823117, 1646237, 3292489, 6584983 + }; + + internal int _currentCount; + internal int _currentSize; + internal long[] _ids; + internal object[] _objs; + + // Constructs a new ObjectID generator, initializing all of the necessary variables. + public ObjectIDGenerator() + { + _currentCount = 1; + _currentSize = s_sizes[0]; + _ids = new long[_currentSize * NumBins]; + _objs = new object[_currentSize * NumBins]; + } + + // Determines where element obj lives, or should live, + // within the table. It calculates the hashcode and searches all of the + // bins where the given object could live. If it's not found within the bin, + // we rehash and go look for it in another bin. If we find the object, we + // set found to true and return it's position. If we can't find the object, + // we set found to false and return the position where the object should be inserted. + private int FindElement(object obj, out bool found) + { + int hashcode = RuntimeHelpers.GetHashCode(obj); + int hashIncrement = (1 + ((hashcode & 0x7FFFFFFF) % (_currentSize - 2))); + do + { + int pos = ((hashcode & 0x7FFFFFFF) % _currentSize) * NumBins; + for (int i = pos; i < pos + NumBins; i++) + { + if (_objs[i] == null) + { + found = false; + return i; + } + if (_objs[i] == obj) + { + found = true; + return i; + } + } + hashcode += hashIncrement; + //the seemingly infinite loop must be revisited later. Currently it is assumed that + //always the array will be expanded (Rehash) when it is half full + } while (true); + } + + // Gets the id for a particular object, generating one if needed. GetID calls + // FindElement to find out where the object lives or should live. If we didn't + // find the element, we generate an object id for it and insert the pair into the + // table. We return an Int64 for the object id. The out parameter firstTime + // is set to true if this is the first time that we have seen this object. + public virtual long GetId(object obj, out bool firstTime) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj), SR.ArgumentNull_Obj); + } + + bool found; + int pos = FindElement(obj, out found); + + //We pull out foundID so that rehashing doesn't cause us to lose track of the id that we just found. + long foundID; + if (!found) + { + //We didn't actually find the object, so we should need to insert it into + //the array and assign it an object id. + _objs[pos] = obj; + _ids[pos] = _currentCount++; + foundID = _ids[pos]; + if (_currentCount > (_currentSize * NumBins) / 2) + { + Rehash(); + } + } + else + { + foundID = _ids[pos]; + } + firstTime = !found; + + return foundID; + } + + // Checks to see if obj has already been assigned an id. If it has, + // we return that id, otherwise we return 0. + public virtual long HasId(object obj, out bool firstTime) + { + bool found; + + if (obj == null) + { + throw new ArgumentNullException(nameof(obj), SR.ArgumentNull_Obj); + } + + int pos = FindElement(obj, out found); + if (found) + { + firstTime = false; + return _ids[pos]; + } + + firstTime = true; + return 0; + } + + // Rehashes the table by finding the next larger size in the list provided, + // allocating two new arrays of that size and rehashing all of the elements in + // the old arrays into the new ones. Expensive but necessary. + private void Rehash() + { + int i = 0; + for (int currSize = _currentSize; i < s_sizes.Length && s_sizes[i] <= currSize; i++) ; + if (i == s_sizes.Length) + { + // We just walked off the end of the array. + throw new SerializationException(SR.Serialization_TooManyElements); + } + _currentSize = s_sizes[i]; + + long[] newIds = new long[_currentSize * NumBins]; + object[] newObjs = new object[_currentSize * NumBins]; + + long[] oldIds = _ids; + object[] oldObjs = _objs; + + _ids = newIds; + _objs = newObjs; + + for (int j = 0; j < oldObjs.Length; j++) + { + if (oldObjs[j] != null) + { + bool found; + int pos = FindElement(oldObjs[j], out found); + _objs[pos] = oldObjs[j]; + _ids[pos] = oldIds[j]; + } + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs new file mode 100644 index 000000000000..a5e8c53da275 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs @@ -0,0 +1,1627 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics; +using System.Reflection; +using System.Globalization; + +namespace System.Runtime.Serialization +{ + public class ObjectManager + { + private const int DefaultInitialSize = 16; + private const int MaxArraySize = 0x1000; //MUST BE A POWER OF 2! + private const int ArrayMask = MaxArraySize - 1; + private const int MaxReferenceDepth = 100; + + private DeserializationEventHandler _onDeserializationHandler; + private SerializationEventHandler _onDeserializedHandler; + + internal ObjectHolder[] _objects; + internal object _topObject = null; + internal ObjectHolderList _specialFixupObjects; //This is IObjectReference, ISerializable, or has a Surrogate. + internal long _fixupCount; + internal readonly ISurrogateSelector _selector; + internal readonly StreamingContext _context; + private readonly bool _isCrossAppDomain; + + public ObjectManager(ISurrogateSelector selector, StreamingContext context) : this(selector, context, true, false) + { + } + + internal ObjectManager(ISurrogateSelector selector, StreamingContext context, bool checkSecurity, bool isCrossAppDomain) + { + _objects = new ObjectHolder[DefaultInitialSize]; + _selector = selector; + _context = context; + _isCrossAppDomain = isCrossAppDomain; + } + + private bool CanCallGetType(object obj) => true; + + internal object TopObject + { + set { _topObject = value; } + get { return _topObject; } + } + + internal ObjectHolderList SpecialFixupObjects => + _specialFixupObjects ?? (_specialFixupObjects = new ObjectHolderList()); + + internal ObjectHolder FindObjectHolder(long objectID) + { + // The index of the bin in which we live is rightmost n bits of the objectID. + int index = (int)(objectID & ArrayMask); + if (index >= _objects.Length) + { + return null; + } + + // Find the bin in which we live. + ObjectHolder temp = _objects[index]; + + // Walk the chain in that bin. Return the ObjectHolder if we find it, otherwise + // return null. + while (temp != null) + { + if (temp._id == objectID) + { + return temp; + } + temp = temp._next; + } + + return temp; + } + + internal ObjectHolder FindOrCreateObjectHolder(long objectID) + { + ObjectHolder holder; + holder = FindObjectHolder(objectID); + if (holder == null) + { + holder = new ObjectHolder(objectID); + AddObjectHolder(holder); + } + return holder; + } + + private void AddObjectHolder(ObjectHolder holder) + { + Debug.Assert(holder != null, "holder!=null"); + Debug.Assert(holder._id >= 0, "holder.m_id>=0"); + + //If the id that we need to place is greater than our current length, and less + //than the maximum allowable size of the array. We need to double the size + //of the array. If the array has already reached it's maximum allowable size, + //we chain elements off of the buckets. + if (holder._id >= _objects.Length && _objects.Length != MaxArraySize) + { + int newSize = MaxArraySize; + + if (holder._id < (MaxArraySize / 2)) + { + newSize = (_objects.Length * 2); + + //Keep doubling until we're larger than our target size. + //We could also do this with log operations, but that would + //be slower than the brute force approach. + while (newSize <= holder._id && newSize < MaxArraySize) + { + newSize *= 2; + } + + if (newSize > MaxArraySize) + { + newSize = MaxArraySize; + } + } + + ObjectHolder[] temp = new ObjectHolder[newSize]; + Array.Copy(_objects, 0, temp, 0, _objects.Length); + _objects = temp; + } + + //Find the bin in which we live and make this new element the first element in the bin. + int index = (int)(holder._id & ArrayMask); + + ObjectHolder tempHolder = _objects[index]; + holder._next = tempHolder; + _objects[index] = holder; + } + + private bool GetCompletionInfo(FixupHolder fixup, out ObjectHolder holder, out object member, bool bThrowIfMissing) + { + //Set the member id (String or MemberInfo) for the member being fixed up. + member = fixup._fixupInfo; + + //Find the object required for the fixup. Throw if we can't find it. + holder = FindObjectHolder(fixup._id); + + // CompletelyFixed is our poorly named property which indicates if something requires a SerializationInfo fixup + // or is an incomplete object reference. We have this particular branch to handle valuetypes which implement + // ISerializable. In that case, we can't do any fixups on them later, so we need to delay the fixups further. + if (!holder.CompletelyFixed) + { + if (holder.ObjectValue != null && holder.ObjectValue is ValueType) + { + SpecialFixupObjects.Add(holder); + return false; + } + } + + if (holder == null || holder.CanObjectValueChange || holder.ObjectValue == null) + { + if (bThrowIfMissing) + { + if (holder == null) + { + throw new SerializationException(SR.Format(SR.Serialization_NeverSeen, fixup._id)); + } + if (holder.IsIncompleteObjectReference) + { + throw new SerializationException(SR.Format(SR.Serialization_IORIncomplete, fixup._id)); + } + throw new SerializationException(SR.Format(SR.Serialization_ObjectNotSupplied, fixup._id)); + } + return false; + } + return true; + } + + private void FixupSpecialObject(ObjectHolder holder) + { + ISurrogateSelector uselessSelector = null; + + Debug.Assert(holder.RequiresSerInfoFixup, "[ObjectManager.FixupSpecialObject]holder.HasSurrogate||holder.HasISerializable"); + if (holder.HasSurrogate) + { + ISerializationSurrogate surrogate = holder.Surrogate; + Debug.Assert(surrogate != null, "surrogate!=null"); + object returnValue = surrogate.SetObjectData(holder.ObjectValue, holder.SerializationInfo, _context, uselessSelector); + if (returnValue != null) + { + if (!holder.CanSurrogatedObjectValueChange && returnValue != holder.ObjectValue) + { + throw new SerializationException(string.Format(CultureInfo.CurrentCulture, SR.Serialization_NotCyclicallyReferenceableSurrogate, surrogate.GetType().FullName)); + } + holder.SetObjectValue(returnValue, this); + } + holder._surrogate = null; + holder.SetFlags(); + } + else + { + //Set the object data + Debug.Assert(holder.ObjectValue is ISerializable, "holder.m_object is ISerializable"); + CompleteISerializableObject(holder.ObjectValue, holder.SerializationInfo, _context); + } + //Clear anything that we know that we're not going to need. + holder.SerializationInfo = null; + holder.RequiresSerInfoFixup = false; + + // For value types, fixups would have been done. So the newly fixed object must be copied + // to its container. + if (holder.RequiresValueTypeFixup && holder.ValueTypeFixupPerformed) + { + DoValueTypeFixup(null, holder, holder.ObjectValue); + } + DoNewlyRegisteredObjectFixups(holder); + } + + /// + /// Unfortunately, an ObjectReference could actually be a reference to another + /// object reference and we don't know how far we have to tunnel until we can find the real object. While + /// we're still getting instances of IObjectReference back and we're still getting new objects, keep calling + /// GetRealObject. Once we've got the new object, take care of all of the fixups + /// that we can do now that we've got it. + /// + /// + private bool ResolveObjectReference(ObjectHolder holder) + { + object tempObject; + Debug.Assert(holder.IsIncompleteObjectReference, "holder.IsIncompleteObjectReference"); + + //In the pathological case, an Object implementing IObjectReference could return a reference + //to a different object which implements IObjectReference. This makes us vulnerable to a + //denial of service attack and stack overflow. If the depthCount becomes greater than + //MaxReferenceDepth, we'll throw a SerializationException. + int depthCount = 0; + + //We wrap this in a try/catch block to handle the case where we're trying to resolve a chained + //list of object reference (e.g. an IObjectReference can't resolve itself without some information + //that's currently missing from the graph). We'll catch the NullReferenceException and come back + //and try again later. The downside of this scheme is that if the object actually needed to throw + //a NullReferenceException, it's being caught and turned into a SerializationException with a + //fairly cryptic message. + try + { + do + { + tempObject = holder.ObjectValue; + holder.SetObjectValue(((IObjectReference)(holder.ObjectValue)).GetRealObject(_context), this); + //The object didn't yet have enough information to resolve the reference, so we'll + //return false and the graph walker should call us back again after more objects have + //been resolved. + if (holder.ObjectValue == null) + { + holder.SetObjectValue(tempObject, this); + return false; + } + if (depthCount++ == MaxReferenceDepth) + { + throw new SerializationException(SR.Serialization_TooManyReferences); + } + } while ((holder.ObjectValue is IObjectReference) && (tempObject != holder.ObjectValue)); + } + catch (NullReferenceException) + { + return false; + } + + holder.IsIncompleteObjectReference = false; + DoNewlyRegisteredObjectFixups(holder); + return true; + } + + /*===============================DoValueTypeFixup=============================== + **Arguments: + ** memberToFix -- the member in the object contained in holder being fixed up. + ** holder -- the ObjectHolder for the object (a value type in this case) being completed. + ** value -- the data to set into the field. + ==============================================================================*/ + private bool DoValueTypeFixup(FieldInfo memberToFix, ObjectHolder holder, object value) + { + var fieldsTemp = new FieldInfo[4]; + FieldInfo[] fields = null; + int currentFieldIndex = 0; + int[] arrayIndex = null; + ValueTypeFixupInfo currFixup = null; + object fixupObj = holder.ObjectValue; + ObjectHolder originalHolder = holder; + + Debug.Assert(holder != null, "[TypedReferenceBuilder.ctor]holder!=null"); + Debug.Assert(holder.RequiresValueTypeFixup, "[TypedReferenceBuilder.ctor]holder.RequiresValueTypeFixup"); + + //In order to get a TypedReference, we need to get a list of all of the FieldInfos to + //create the path from our outermost containing object down to the actual field which + //we'd like to set. This loop is used to build up that list. + while (holder.RequiresValueTypeFixup) + { + //Enlarge the array if required (this is actually fairly unlikely as it would require that we + //be nested more than 4 deep. + if ((currentFieldIndex + 1) >= fieldsTemp.Length) + { + var temp = new FieldInfo[fieldsTemp.Length * 2]; + Array.Copy(fieldsTemp, 0, temp, 0, fieldsTemp.Length); + fieldsTemp = temp; + } + + //Get the fixup information. If we have data for our parent field, add it to our list + //and continue the walk up to find the next outermost containing object. We cache the + //object that we have. In most cases, we could have just grabbed it after this loop finished. + //However, if the outermost containing object is an array, we need the object one further + //down the chain, so we have to do a lot of caching. + currFixup = holder.ValueFixup; + fixupObj = holder.ObjectValue; //Save the most derived + if (currFixup.ParentField != null) + { + FieldInfo parentField = currFixup.ParentField; + + ObjectHolder tempHolder = FindObjectHolder(currFixup.ContainerID); + if (tempHolder.ObjectValue == null) + { + break; + } + if (Nullable.GetUnderlyingType(parentField.FieldType) != null) + { + fieldsTemp[currentFieldIndex] = parentField.FieldType.GetField("value", BindingFlags.NonPublic | BindingFlags.Instance); + currentFieldIndex++; + } + + fieldsTemp[currentFieldIndex] = parentField; + holder = tempHolder; + currentFieldIndex++; + } + else + { + //If we find an index into an array, save that information. + Debug.Assert(currFixup.ParentIndex != null, "[ObjectManager.DoValueTypeFixup]currFixup.ParentIndex!=null"); + holder = FindObjectHolder(currFixup.ContainerID); //find the array to fix. + arrayIndex = currFixup.ParentIndex; + break; + } + } + + //If the outermost container isn't an array, we need to grab it. Otherwise, we just need to hang onto + //the boxed object that we already grabbed. We'll assign the boxed object back into the array as the + //last step. + if (!(holder.ObjectValue is Array) && holder.ObjectValue != null) + { + fixupObj = holder.ObjectValue; + Debug.Assert(fixupObj != null, "[ObjectManager.DoValueTypeFixup]FixupObj!=null"); + } + + if (currentFieldIndex != 0) + { + //MakeTypedReference requires an array of exactly the correct size that goes from the outermost object + //in to the innermost field. We currently have an array of arbitrary size that goes from the innermost + //object outwards. We create an array of the right size and do the copy. + fields = new FieldInfo[currentFieldIndex]; + for (int i = 0; i < currentFieldIndex; i++) + { + FieldInfo fieldInfo = fieldsTemp[(currentFieldIndex - 1 - i)]; + fields[i] = fieldInfo; + } + + Debug.Assert(fixupObj != null, "[ObjectManager.DoValueTypeFixup]fixupObj!=null"); + //Make the TypedReference and use it to set the value. + TypedReference typedRef = TypedReference.MakeTypedReference(fixupObj, fields); + if (memberToFix != null) + { + memberToFix.SetValueDirect(typedRef, value); + } + else + { + TypedReference.SetTypedReference(typedRef, value); + } + } + else if (memberToFix != null) + { + FormatterServices.SerializationSetValue(memberToFix, fixupObj, value); + } + + //If we have an array index, it means that our outermost container was an array. We don't have + //any way to build a TypedReference into an array, so we'll use the array functions to set the value. + if (arrayIndex != null && holder.ObjectValue != null) + { + ((Array)(holder.ObjectValue)).SetValue(fixupObj, arrayIndex); + } + + return true; + } + + internal void CompleteObject(ObjectHolder holder, bool bObjectFullyComplete) + { + FixupHolderList fixups = holder._missingElements; + FixupHolder currentFixup; + SerializationInfo si; + object fixupInfo = null; + ObjectHolder tempObjectHolder = null; + int fixupsPerformed = 0; + + Debug.Assert(holder != null, "[ObjectManager.CompleteObject]holder.m_object!=null"); + if (holder.ObjectValue == null) + { + throw new SerializationException(SR.Format(SR.Serialization_MissingObject, holder._id)); + } + + if (fixups == null) + { + return; + } + //If either one of these conditions is true, we need to update the data in the + //SerializationInfo before calling SetObjectData. + if (holder.HasSurrogate || holder.HasISerializable) + { + si = holder._serInfo; + if (si == null) + { + throw new SerializationException(SR.Serialization_InvalidFixupDiscovered); + } + + //Walk each of the fixups and complete the name-value pair in the SerializationInfo. + if (fixups != null) + { + for (int i = 0; i < fixups._count; i++) + { + if (fixups._values[i] == null) + { + continue; + } + Debug.Assert(fixups._values[i]._fixupType == FixupHolder.DelayedFixup, "fixups.m_values[i].m_fixupType==FixupHolder.DelayedFixup"); + if (GetCompletionInfo(fixups._values[i], out tempObjectHolder, out fixupInfo, bObjectFullyComplete)) + { + //Walk the SerializationInfo and find the member needing completion. All we have to do + //at this point is set the member into the Object + object holderValue = tempObjectHolder.ObjectValue; + if (CanCallGetType(holderValue)) + { + si.UpdateValue((string)fixupInfo, holderValue, holderValue.GetType()); + } + else + { + si.UpdateValue((string)fixupInfo, holderValue, typeof(MarshalByRefObject)); + } + //Decrement our total number of fixups left to do. + fixupsPerformed++; + fixups._values[i] = null; + if (!bObjectFullyComplete) + { + holder.DecrementFixupsRemaining(this); + tempObjectHolder.RemoveDependency(holder._id); + } + } + } + } + } + else + { + for (int i = 0; i < fixups._count; i++) + { + currentFixup = fixups._values[i]; + if (currentFixup == null) + { + continue; + } + if (GetCompletionInfo(currentFixup, out tempObjectHolder, out fixupInfo, bObjectFullyComplete)) + { + // Check to make sure we are not both reachable from the topObject + // and there was a typeloadexception + if (tempObjectHolder.TypeLoadExceptionReachable) + { + holder.TypeLoadException = tempObjectHolder.TypeLoadException; + // If the holder is both reachable and typeloadexceptionreachable + // throw an exception with the type name + if (holder.Reachable) + { + throw new SerializationException(SR.Format(SR.Serialization_TypeLoadFailure, holder.TypeLoadException.TypeName)); + } + } + + // If the current holder is reachable, mark the dependant reachable as well + if (holder.Reachable) + { + tempObjectHolder.Reachable = true; + } + + //There are two types of fixups that we could be doing: array or member. + //Delayed Fixups should be handled by the above branch. + switch (currentFixup._fixupType) + { + case FixupHolder.ArrayFixup: + Debug.Assert(holder.ObjectValue is Array, "holder.ObjectValue is Array"); + if (holder.RequiresValueTypeFixup) + { + throw new SerializationException(SR.Serialization_ValueTypeFixup); + } + else + { + ((Array)(holder.ObjectValue)).SetValue(tempObjectHolder.ObjectValue, ((int[])fixupInfo)); + } + break; + case FixupHolder.MemberFixup: + Debug.Assert(fixupInfo is MemberInfo, "fixupInfo is MemberInfo"); + //Fixup the member directly. + MemberInfo tempMember = (MemberInfo)fixupInfo; + if (tempMember is FieldInfo) + { + // If we have a valuetype that's been boxed to an object and requires a fixup, + // there are two possible states: + // (a)The valuetype has never been fixed up into it's container. In this case, we should + // just fix up the boxed valuetype. The task of pushing that valuetype into it's container + // will be handled later. This case is handled by the else clause of the following statement. + // (b)The valuetype has already been inserted into it's container. In that case, we need + // to go through the more complicated path laid out in DoValueTypeFixup. We can tell that the + // valuetype has already been inserted into it's container because we set ValueTypeFixupPerformed + // to true when we do this. + if (holder.RequiresValueTypeFixup && holder.ValueTypeFixupPerformed) + { + if (!DoValueTypeFixup((FieldInfo)tempMember, holder, tempObjectHolder.ObjectValue)) + { + throw new SerializationException(SR.Serialization_PartialValueTypeFixup); + } + } + else + { + FormatterServices.SerializationSetValue(tempMember, holder.ObjectValue, tempObjectHolder.ObjectValue); + } + if (tempObjectHolder.RequiresValueTypeFixup) + { + tempObjectHolder.ValueTypeFixupPerformed = true; + } + } + else + { + throw new SerializationException(SR.Serialization_UnableToFixup); + } + break; + default: + throw new SerializationException(SR.Serialization_UnableToFixup); + } + //Decrement our total number of fixups left to do. + fixupsPerformed++; + fixups._values[i] = null; + if (!bObjectFullyComplete) + { + holder.DecrementFixupsRemaining(this); + tempObjectHolder.RemoveDependency(holder._id); + } + } + } + } + + _fixupCount -= fixupsPerformed; + + if (fixups._count == fixupsPerformed) + { + holder._missingElements = null; + } + } + + /// + /// This is called immediately after we register a new object. Walk that objects + /// dependency list (if it has one) and decrement the counters on each object for + /// the number of unsatisfiable references. If the count reaches 0, go ahead + /// and process the object. + /// + /// dependencies The list of dependent objects + private void DoNewlyRegisteredObjectFixups(ObjectHolder holder) + { + if (holder.CanObjectValueChange) + { + return; + } + + //If we don't have any dependencies, we're done. + LongList dependencies = holder.DependentObjects; + if (dependencies == null) + { + return; + } + + //Walk all of the dependencies and decrement the counter on each of uncompleted objects. + //If one of the counters reaches 0, all of it's fields have been completed and we should + //go take care of its fixups. + dependencies.StartEnumeration(); + while (dependencies.MoveNext()) + { + ObjectHolder temp = FindObjectHolder(dependencies.Current); + Debug.Assert(temp.DirectlyDependentObjects > 0, "temp.m_missingElementsRemaining>0"); + temp.DecrementFixupsRemaining(this); + if (((temp.DirectlyDependentObjects)) == 0) + { + // If this is null, we have the case where a fixup was registered for a child, the object + // required by the fixup was provided, and the object to be fixed hasn't yet been seen. + if (temp.ObjectValue != null) + { + CompleteObject(temp, true); + } + else + { + temp.MarkForCompletionWhenAvailable(); + } + } + } + } + + public virtual object GetObject(long objectID) + { + if (objectID <= 0) + { + throw new ArgumentOutOfRangeException(nameof(objectID), SR.ArgumentOutOfRange_ObjectID); + } + + //Find the bin in which we're interested. IObjectReference's shouldn't be returned -- the graph + //needs to link to the objects to which they refer, not to the references themselves. + ObjectHolder holder = FindObjectHolder(objectID); + if (holder == null || holder.CanObjectValueChange) + { + return null; + } + + return holder.ObjectValue; + } + + public virtual void RegisterObject(object obj, long objectID) + { + RegisterObject(obj, objectID, null, 0, null); + } + + public void RegisterObject(object obj, long objectID, SerializationInfo info) + { + RegisterObject(obj, objectID, info, 0, null); + } + + public void RegisterObject(object obj, long objectID, SerializationInfo info, long idOfContainingObj, MemberInfo member) + { + RegisterObject(obj, objectID, info, idOfContainingObj, member, null); + } + + internal void RegisterString(string obj, long objectID, SerializationInfo info, long idOfContainingObj, MemberInfo member) + { + ObjectHolder temp; + Debug.Assert(member == null || member is FieldInfo, "RegisterString - member is FieldInfo"); + + temp = new ObjectHolder(obj, objectID, info, null, idOfContainingObj, (FieldInfo)member, null); + AddObjectHolder(temp); + return; + } + + public void RegisterObject(object obj, long objectID, SerializationInfo info, long idOfContainingObj, MemberInfo member, int[] arrayIndex) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + if (objectID <= 0) + { + throw new ArgumentOutOfRangeException(nameof(objectID), SR.ArgumentOutOfRange_ObjectID); + } + if (member != null && !(member is FieldInfo)) + { + throw new SerializationException(SR.Serialization_UnknownMemberInfo); + } + + ObjectHolder temp; + ISerializationSurrogate surrogate = null; + ISurrogateSelector useless; + + if (_selector != null) + { + Type selectorType = CanCallGetType(obj) ? + obj.GetType() : + typeof(MarshalByRefObject); + + //If we need a surrogate for this object, lets find it now. + surrogate = _selector.GetSurrogate(selectorType, _context, out useless); + } + + //The object is interested in DeserializationEvents so lets register it. + if (obj is IDeserializationCallback) + { + DeserializationEventHandler d = new DeserializationEventHandler(((IDeserializationCallback)obj).OnDeserialization); + AddOnDeserialization(d); + } + + //Formatter developers may cache and reuse arrayIndex in their code. + //So that we don't get bitten by this, take a copy up front. + if (arrayIndex != null) + { + arrayIndex = (int[])arrayIndex.Clone(); + } + + //This is the first time which we've seen the object, we need to create a new holder. + temp = FindObjectHolder(objectID); + if (temp == null) + { + temp = new ObjectHolder(obj, objectID, info, surrogate, idOfContainingObj, (FieldInfo)member, arrayIndex); + AddObjectHolder(temp); + if (temp.RequiresDelayedFixup) + { + SpecialFixupObjects.Add(temp); + } + + // We cannot compute whether this has any fixups required or not + AddOnDeserialized(obj); + return; + } + + //If the object isn't null, we've registered this before. Not good. + if (temp.ObjectValue != null) + { + throw new SerializationException(SR.Serialization_RegisterTwice); + } + + //Complete the data in the ObjectHolder + temp.UpdateData(obj, info, surrogate, idOfContainingObj, (FieldInfo)member, arrayIndex, this); + + // The following case will only be true when somebody has registered a fixup on an object before + // registering the object itself. I don't believe that most well-behaved formatters will do this, + // but we need to allow it anyway. We will walk the list of fixups which have been recorded on + // the new object and fix those that we can. Because the user could still register later fixups + // on this object, we won't call any implementations of ISerializable now. If that's required, + // it will have to be handled by the code in DoFixups. + // README README: We have to do the UpdateData before + if (temp.DirectlyDependentObjects > 0) + { + CompleteObject(temp, false); + } + + if (temp.RequiresDelayedFixup) + { + SpecialFixupObjects.Add(temp); + } + + if (temp.CompletelyFixed) + { + //Here's where things get tricky. If this isn't an instance of IObjectReference, we need to walk it's fixup + //chain and decrement the counters on anything that has reached 0. Once we've notified all of the dependencies, + //we can simply clear the list of dependent objects. + DoNewlyRegisteredObjectFixups(temp); + temp.DependentObjects = null; + } + + //Register the OnDeserialized methods to be invoked after deserialization is complete + if (temp.TotalDependentObjects > 0) + { + AddOnDeserialized(obj); + } + else + { + RaiseOnDeserializedEvent(obj); + } + } + + /// + /// Completes an object implementing ISerializable. This will involve calling that + /// objects constructor which takes an instance of ISerializable and a StreamingContext. + /// + /// The object to be completed. + /// The SerializationInfo containing all info for obj. + /// The streaming context in which the serialization is taking place. + internal void CompleteISerializableObject(object obj, SerializationInfo info, StreamingContext context) + { + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + if (!(obj is ISerializable)) + { + throw new ArgumentException(SR.Serialization_NotISer); + } + + ConstructorInfo constInfo = null; + Type t = obj.GetType(); + try + { + constInfo = GetDeserializationConstructor(t); + } + catch (Exception e) + { + throw new SerializationException(SR.Format(SR.Serialization_ConstructorNotFound, t), e); + } + + constInfo.Invoke(obj, new object[] { info, context }); + } + + internal static ConstructorInfo GetDeserializationConstructor(Type t) + { + ConstructorInfo ci = t.GetConstructor(new[] { typeof(SerializationInfo), typeof(StreamingContext) }); + if (ci == null) + { + throw new SerializationException(SR.Format(SR.Serialization_ConstructorNotFound, t.FullName)); + } + return ci; + } + + public virtual void DoFixups() + { + ObjectHolder temp; + int fixupCount = -1; + + //The first thing that we need to do is fixup all of the objects which implement + //IObjectReference. This is complicated by the fact that we need to deal with IReferenceObjects + //objects that have a reference to an object implementing IObjectReference. We continually + //walk over the list of objects until we've completed all of the object references or until + //we can't resolve any more (which may happen if we have two objects implementing IObjectReference + //which have a circular dependency on each other). We don't explicitly catch the later case here, + //it will be caught when we try to do the rest of the fixups and discover that we have some that + //can't be completed. + while (fixupCount != 0) + { + fixupCount = 0; + //Walk all of the IObjectReferences and ensure that they've been properly completed. + ObjectHolderListEnumerator fixupObjectsEnum = SpecialFixupObjects.GetFixupEnumerator(); + while (fixupObjectsEnum.MoveNext()) + { + temp = fixupObjectsEnum.Current; + if (temp.ObjectValue == null) + { + throw new SerializationException(SR.Format(SR.Serialization_ObjectNotSupplied, temp._id)); + } + if (temp.TotalDependentObjects == 0) + { + if (temp.RequiresSerInfoFixup) + { + FixupSpecialObject(temp); + fixupCount++; + } + else if (!temp.IsIncompleteObjectReference) + { + CompleteObject(temp, true); + } + + if (temp.IsIncompleteObjectReference && ResolveObjectReference(temp)) + { + fixupCount++; + } + } + } + } + + Debug.Assert(_fixupCount >= 0, "[ObjectManager.DoFixups]m_fixupCount>=0"); + + //If our count is 0, we're done and should just return + if (_fixupCount == 0) + { + if (TopObject is TypeLoadExceptionHolder) + { + throw new SerializationException(SR.Format(SR.Serialization_TypeLoadFailure, ((TypeLoadExceptionHolder)TopObject).TypeName)); + } + return; + } + + //If our count isn't 0, we had at least one case where an object referenced another object twice. + //Walk the entire list until the count is 0 or until we find an object which we can't complete. + for (int i = 0; i < _objects.Length; i++) + { + temp = _objects[i]; + while (temp != null) + { + if (temp.TotalDependentObjects > 0 /*|| temp.m_missingElements!=null*/) + { + CompleteObject(temp, true); + } + temp = temp._next; + } + if (_fixupCount == 0) + { + return; + } + } + + // this assert can be trigered by user code that manages fixups manually + throw new SerializationException(SR.Serialization_IncorrectNumberOfFixups); + } + + /// + /// Do the actual grunt work of recording a fixup and registering the dependency. + /// Create the necessary ObjectHolders and use them to do the addition. + /// + /// The FixupHolder to be added. + /// The id of the object required to do the fixup. + /// The id of the object requiring the fixup. + private void RegisterFixup(FixupHolder fixup, long objectToBeFixed, long objectRequired) + { + //Record the fixup with the object that needs it. + ObjectHolder ohToBeFixed = FindOrCreateObjectHolder(objectToBeFixed); + ObjectHolder ohRequired; + + if (ohToBeFixed.RequiresSerInfoFixup && fixup._fixupType == FixupHolder.MemberFixup) + { + throw new SerializationException(SR.Serialization_InvalidFixupType); + } + + //Add the fixup to the list. + ohToBeFixed.AddFixup(fixup, this); + + //Find the object on which we're dependent and note the dependency. + //These dependencies will be processed when the object is supplied. + ohRequired = FindOrCreateObjectHolder(objectRequired); + + ohRequired.AddDependency(objectToBeFixed); + + _fixupCount++; + } + + public virtual void RecordFixup(long objectToBeFixed, MemberInfo member, long objectRequired) + { + //Verify our arguments + if (objectToBeFixed <= 0 || objectRequired <= 0) + { + throw new ArgumentOutOfRangeException(objectToBeFixed <= 0 ? nameof(objectToBeFixed) : nameof(objectRequired), SR.Serialization_IdTooSmall); + } + if (member == null) + { + throw new ArgumentNullException(nameof(member)); + } + if (!(member is FieldInfo)) + { + throw new SerializationException(SR.Format(SR.Serialization_InvalidType, member.GetType().ToString())); + } + + //Create a new fixup holder + FixupHolder fixup = new FixupHolder(objectRequired, member, FixupHolder.MemberFixup); + RegisterFixup(fixup, objectToBeFixed, objectRequired); + } + + public virtual void RecordDelayedFixup(long objectToBeFixed, string memberName, long objectRequired) + { + //Verify our arguments + if (objectToBeFixed <= 0 || objectRequired <= 0) + { + throw new ArgumentOutOfRangeException(objectToBeFixed <= 0 ? nameof(objectToBeFixed) : nameof(objectRequired), SR.Serialization_IdTooSmall); + } + if (memberName == null) + { + throw new ArgumentNullException(nameof(memberName)); + } + + //Create a new fixup holder + FixupHolder fixup = new FixupHolder(objectRequired, memberName, FixupHolder.DelayedFixup); + RegisterFixup(fixup, objectToBeFixed, objectRequired); + } + + public virtual void RecordArrayElementFixup(long arrayToBeFixed, int index, long objectRequired) + { + int[] indexArray = new int[1]; + indexArray[0] = index; + RecordArrayElementFixup(arrayToBeFixed, indexArray, objectRequired); + } + + public virtual void RecordArrayElementFixup(long arrayToBeFixed, int[] indices, long objectRequired) + { + //Verify our arguments + if (arrayToBeFixed <= 0 || objectRequired <= 0) + { + throw new ArgumentOutOfRangeException(arrayToBeFixed <= 0 ? nameof(arrayToBeFixed) : nameof(objectRequired), SR.Serialization_IdTooSmall); + } + if (indices == null) + { + throw new ArgumentNullException(nameof(indices)); + } + + FixupHolder fixup = new FixupHolder(objectRequired, indices, FixupHolder.ArrayFixup); + RegisterFixup(fixup, arrayToBeFixed, objectRequired); + } + + public virtual void RaiseDeserializationEvent() + { + // Invoke OnDerserialized event if applicable + _onDeserializedHandler?.Invoke(_context); + _onDeserializationHandler?.Invoke(null); + } + + internal virtual void AddOnDeserialization(DeserializationEventHandler handler) + { + _onDeserializationHandler = (DeserializationEventHandler)Delegate.Combine(_onDeserializationHandler, handler); + } + + internal virtual void RemoveOnDeserialization(DeserializationEventHandler handler) + { + _onDeserializationHandler = (DeserializationEventHandler)Delegate.Remove(_onDeserializationHandler, handler); + } + + internal virtual void AddOnDeserialized(object obj) + { + SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); + _onDeserializedHandler = cache.AddOnDeserialized(obj, _onDeserializedHandler); + } + + internal virtual void RaiseOnDeserializedEvent(object obj) + { + SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); + cache.InvokeOnDeserialized(obj, _context); + } + + public void RaiseOnDeserializingEvent(object obj) + { + // Run the OnDeserializing methods + SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); + cache.InvokeOnDeserializing(obj, _context); + } + } + + internal sealed class ObjectHolder + { + internal const int IncompleteObjectReference = 0x0001; + internal const int HAS_ISERIALIZABLE = 0x0002; + internal const int HAS_SURROGATE = 0x0004; + internal const int REQUIRES_VALUETYPE_FIXUP = 0x0008; + internal const int REQUIRES_DELAYED_FIXUP = HAS_ISERIALIZABLE | HAS_SURROGATE | IncompleteObjectReference; + internal const int SER_INFO_FIXED = 0x4000; + internal const int VALUETYPE_FIXUP_PERFORMED = 0x8000; + + private object _object; + internal readonly long _id; + private int _missingElementsRemaining; + private int _missingDecendents; + internal SerializationInfo _serInfo; + internal ISerializationSurrogate _surrogate; + internal FixupHolderList _missingElements; + internal LongList _dependentObjects; + internal ObjectHolder _next; + internal int _flags; + private bool _markForFixupWhenAvailable; + private ValueTypeFixupInfo _valueFixup; + private TypeLoadExceptionHolder _typeLoad = null; + private bool _reachable = false; + + internal ObjectHolder(long objID) : this(null, objID, null, null, 0, null, null) + { + } + + internal ObjectHolder( + object obj, long objID, SerializationInfo info, ISerializationSurrogate surrogate, + long idOfContainingObj, FieldInfo field, int[] arrayIndex) + { + Debug.Assert(objID >= 0, "objID>=0"); + + _object = obj; //May be null; + _id = objID; + + _flags = 0; + _missingElementsRemaining = 0; + _missingDecendents = 0; + _dependentObjects = null; + _next = null; + + _serInfo = info; + _surrogate = surrogate; + _markForFixupWhenAvailable = false; + + if (obj is TypeLoadExceptionHolder) + { + _typeLoad = (TypeLoadExceptionHolder)obj; + } + + if (idOfContainingObj != 0 && ((field != null && field.FieldType.GetTypeInfo().IsValueType) || arrayIndex != null)) + { + if (idOfContainingObj == objID) + { + throw new SerializationException(SR.Serialization_ParentChildIdentical); + } + + _valueFixup = new ValueTypeFixupInfo(idOfContainingObj, field, arrayIndex); + } + + SetFlags(); + } + + internal ObjectHolder( + string obj, long objID, SerializationInfo info, ISerializationSurrogate surrogate, + long idOfContainingObj, FieldInfo field, int[] arrayIndex) + { + Debug.Assert(objID >= 0, "objID>=0"); + + _object = obj; //May be null; + _id = objID; + + _flags = 0; + _missingElementsRemaining = 0; + _missingDecendents = 0; + _dependentObjects = null; + _next = null; + + _serInfo = info; + _surrogate = surrogate; + _markForFixupWhenAvailable = false; + + if (idOfContainingObj != 0 && arrayIndex != null) + { + _valueFixup = new ValueTypeFixupInfo(idOfContainingObj, field, arrayIndex); + } + + if (_valueFixup != null) + { + _flags |= REQUIRES_VALUETYPE_FIXUP; + } + } + + private void IncrementDescendentFixups(int amount) => _missingDecendents += amount; + + internal void DecrementFixupsRemaining(ObjectManager manager) + { + _missingElementsRemaining--; + if (RequiresValueTypeFixup) + { + UpdateDescendentDependencyChain(-1, manager); + } + } + + /// + /// Removes a dependency of the object represented in this holder. + /// This is normally the result of the dependency having been filled when + /// the object is going to be only partially completed. If we plan to fully + /// update the object, we do not take the work to do this. + /// + /// The id of the object for which to remove the dependency. + internal void RemoveDependency(long id) + { + Debug.Assert(_dependentObjects != null, "[ObjectHolder.RemoveDependency]m_dependentObjects!=null"); + Debug.Assert(id >= 0, "[ObjectHolder.RemoveDependency]id>=0"); + _dependentObjects.RemoveElement(id); + } + + /// + /// Note a fixup that has to be done before this object can be completed. + /// Fixups are things that need to happen when other objects in the graph + /// are added. Dependencies are things that need to happen when this object + /// is added. + /// + /// The fixup holder containing enough information to complete the fixup. + internal void AddFixup(FixupHolder fixup, ObjectManager manager) + { + if (_missingElements == null) + { + _missingElements = new FixupHolderList(); + } + _missingElements.Add(fixup); + _missingElementsRemaining++; + + if (RequiresValueTypeFixup) + { + UpdateDescendentDependencyChain(1, manager); + } + } + + /// + /// Updates the total list of dependencies to account for a fixup being added + /// or completed in a child value class. This will update all value classes + /// containing that child and the object which contains all of them. + /// + /// the amount by which to increment (or decrement) the dependency chain. + /// The ObjectManager used to lookup other objects up the chain. + private void UpdateDescendentDependencyChain(int amount, ObjectManager manager) + { + ObjectHolder holder = this; + + //This loop walks one more object up the chain than there are valuetypes. This + //is because we need to increment the TotalFixups in the holders as well. + do + { + holder = manager.FindOrCreateObjectHolder(holder.ContainerID); + Debug.Assert(holder != null, "[ObjectHolder.UpdateTotalDependencyChain]holder!=null"); + holder.IncrementDescendentFixups(amount); + } while (holder.RequiresValueTypeFixup); + } + + /// + /// Note an object which is dependent on the one which will be contained in + /// this ObjectHolder. Dependencies should only be added if the object hasn't + /// yet been added. NB: An incomplete object counts as having no object. + /// + /// the id of the object which is dependent on this object being provided. + internal void AddDependency(long dependentObject) + { + if (_dependentObjects == null) + { + _dependentObjects = new LongList(); + } + _dependentObjects.Add(dependentObject); + } + + /// + /// Update the data in the object holder. This should be called when the object + /// is finally registered. Presumably the ObjectHolder was created to track + /// some dependencies or preregistered fixups and we now need to actually record the + /// object and other associated data. We take this opportunity to set the flags + /// so that we can do some faster processing in the future. + /// + /// The object being held by this object holder. (This should no longer be null). + /// The SerializationInfo associated with this object, only required if we're doing delayed fixups. + /// the ObjectManager being used to track these ObjectHolders. + /// The surrogate handling this object. May be null. + /// The id of the object containing this one if this is a valuetype. + internal void UpdateData( + object obj, SerializationInfo info, ISerializationSurrogate surrogate, long idOfContainer, + FieldInfo field, int[] arrayIndex, ObjectManager manager) + { + Debug.Assert(obj != null, "obj!=null"); + Debug.Assert(_id > 0, "m_id>0"); + + //Record the fields that we can. + SetObjectValue(obj, manager); + _serInfo = info; + _surrogate = surrogate; + + if (idOfContainer != 0 && ((field != null && field.FieldType.GetTypeInfo().IsValueType) || arrayIndex != null)) + { + if (idOfContainer == _id) + { + throw new SerializationException(SR.Serialization_ParentChildIdentical); + } + _valueFixup = new ValueTypeFixupInfo(idOfContainer, field, arrayIndex); + } + + SetFlags(); + + if (RequiresValueTypeFixup) + { + UpdateDescendentDependencyChain(_missingElementsRemaining, manager); + } + } + + internal void MarkForCompletionWhenAvailable() => _markForFixupWhenAvailable = true; + + /// + /// An internal-only routine to set the flags based upon the data contained in + /// the ObjectHolder + /// + internal void SetFlags() + { + if (_object is IObjectReference) + { + _flags |= IncompleteObjectReference; + } + + _flags &= ~(HAS_ISERIALIZABLE | HAS_SURROGATE); + if (_surrogate != null) + { + _flags |= HAS_SURROGATE; + } + else if (_object is ISerializable) + { + _flags |= HAS_ISERIALIZABLE; + } + + if (_valueFixup != null) + { + _flags |= REQUIRES_VALUETYPE_FIXUP; + } + } + + internal bool IsIncompleteObjectReference + { + get { return (_flags & IncompleteObjectReference) != 0; } + set + { + if (value) + { + _flags |= IncompleteObjectReference; + } + else + { + _flags &= ~IncompleteObjectReference; + } + } + } + + internal bool RequiresDelayedFixup => (_flags & REQUIRES_DELAYED_FIXUP) != 0; + + internal bool RequiresValueTypeFixup => (_flags & REQUIRES_VALUETYPE_FIXUP) != 0; + + // ValueTypes which require fixups are initially handed to the ObjectManager + // as boxed objects. When they're still boxed objects, we should just do fixups + // on them like we would any other object. As soon as they're pushed into their + // containing object we set ValueTypeFixupPerformed to true and have to go through + // a more complicated path to set fixed up valuetype objects. + // We check whether or not there are any dependent objects. + internal bool ValueTypeFixupPerformed + { + get + { + return (((_flags & VALUETYPE_FIXUP_PERFORMED) != 0) || + (_object != null && ((_dependentObjects == null) || _dependentObjects.Count == 0))); + } + set + { + if (value) + { + _flags |= VALUETYPE_FIXUP_PERFORMED; + } + } + } + + internal bool HasISerializable => (_flags & HAS_ISERIALIZABLE) != 0; + + internal bool HasSurrogate => (_flags & HAS_SURROGATE) != 0; + + internal bool CanSurrogatedObjectValueChange => + (_surrogate == null || _surrogate.GetType() != typeof(SurrogateForCyclicalReference)); + + internal bool CanObjectValueChange => + IsIncompleteObjectReference ? true : + HasSurrogate ? CanSurrogatedObjectValueChange : + false; + + internal int DirectlyDependentObjects => _missingElementsRemaining; + + internal int TotalDependentObjects => _missingElementsRemaining + _missingDecendents; + + internal bool Reachable { get { return _reachable; } set { _reachable = value; } } + + internal bool TypeLoadExceptionReachable => _typeLoad != null; + + internal TypeLoadExceptionHolder TypeLoadException { get { return _typeLoad; } set { _typeLoad = value; } } + + internal object ObjectValue => _object; + + internal void SetObjectValue(object obj, ObjectManager manager) + { + _object = obj; + if (obj == manager.TopObject) + { + _reachable = true; + } + if (obj is TypeLoadExceptionHolder) + { + _typeLoad = (TypeLoadExceptionHolder)obj; + } + + if (_markForFixupWhenAvailable) + { + manager.CompleteObject(this, true); + } + } + + internal SerializationInfo SerializationInfo { get { return _serInfo; } set { _serInfo = value; } } + + internal ISerializationSurrogate Surrogate => _surrogate; + + internal LongList DependentObjects { get { return _dependentObjects; } set { _dependentObjects = value; } } + + internal bool RequiresSerInfoFixup + { + get + { + if (((_flags & HAS_SURROGATE) == 0) && ((_flags & HAS_ISERIALIZABLE) == 0)) + { + return false; + } + + return (_flags & SER_INFO_FIXED) == 0; + } + set + { + if (!value) + { + _flags |= SER_INFO_FIXED; + } + else + { + _flags &= ~SER_INFO_FIXED; + } + } + } + + internal ValueTypeFixupInfo ValueFixup => _valueFixup; + + internal bool CompletelyFixed => !RequiresSerInfoFixup && !IsIncompleteObjectReference; + + internal long ContainerID => _valueFixup != null ? _valueFixup.ContainerID : 0; + } + + [Serializable] + internal sealed class FixupHolder + { + internal const int ArrayFixup = 0x1; + internal const int MemberFixup = 0x2; + internal const int DelayedFixup = 0x4; + + internal long _id; + internal object _fixupInfo; //This is either an array index, a String, or a MemberInfo + internal readonly int _fixupType; + + internal FixupHolder(long id, object fixupInfo, int fixupType) + { + Debug.Assert(id > 0, "id>0"); + Debug.Assert(fixupInfo != null, "fixupInfo!=null"); + Debug.Assert(fixupType == ArrayFixup || fixupType == MemberFixup || fixupType == DelayedFixup, "fixupType==ArrayFixup || fixupType == MemberFixup || fixupType==DelayedFixup"); + + _id = id; + _fixupInfo = fixupInfo; + _fixupType = fixupType; + } + } + + [Serializable] + internal sealed class FixupHolderList + { + internal const int InitialSize = 2; + + internal FixupHolder[] _values; + internal int _count; + + internal FixupHolderList() : this(InitialSize) + { + } + + internal FixupHolderList(int startingSize) + { + _count = 0; + _values = new FixupHolder[startingSize]; + } + + internal void Add(FixupHolder fixup) + { + if (_count == _values.Length) + { + EnlargeArray(); + } + _values[_count++] = fixup; + } + + private void EnlargeArray() + { + int newLength = _values.Length * 2; + if (newLength < 0) + { + if (newLength == int.MaxValue) + { + throw new SerializationException(SR.Serialization_TooManyElements); + } + newLength = int.MaxValue; + } + + FixupHolder[] temp = new FixupHolder[newLength]; + Array.Copy(_values, 0, temp, 0, _count); + _values = temp; + } + } + + [Serializable] + internal sealed class LongList + { + private const int InitialSize = 2; + + private long[] _values; + private int _count; //The total number of valid items still in the list; + private int _totalItems; //The total number of allocated entries. This includes space for items which have been marked as deleted. + private int _currentItem; //Used when doing an enumeration over the list. + + // An m_currentItem of -1 indicates that the enumeration hasn't been started. + // An m_values[xx] of -1 indicates that the item has been deleted. + internal LongList() : this(InitialSize) + { + } + + internal LongList(int startingSize) + { + _count = 0; + _totalItems = 0; + _values = new long[startingSize]; + } + + internal void Add(long value) + { + if (_totalItems == _values.Length) + { + EnlargeArray(); + } + _values[_totalItems++] = value; + _count++; + } + + internal int Count => _count; + + internal void StartEnumeration() => _currentItem = -1; + + internal bool MoveNext() + { + while (++_currentItem < _totalItems && _values[_currentItem] == -1) ; + return _currentItem != _totalItems; + } + + internal long Current + { + get + { + Debug.Assert(_currentItem != -1, "[LongList.Current]m_currentItem!=-1"); + Debug.Assert(_values[_currentItem] != -1, "[LongList.Current]m_values[m_currentItem]!=-1"); + return _values[_currentItem]; + } + } + + internal bool RemoveElement(long value) + { + int i; + for (i = 0; i < _totalItems; i++) + { + if (_values[i] == value) + { + break; + } + } + if (i == _totalItems) + { + return false; + } + _values[i] = -1; + return true; + } + + private void EnlargeArray() + { + int newLength = _values.Length * 2; + if (newLength < 0) + { + if (newLength == int.MaxValue) + { + throw new SerializationException(SR.Serialization_TooManyElements); + } + newLength = int.MaxValue; + } + + long[] temp = new long[newLength]; + Array.Copy(_values, 0, temp, 0, _count); + _values = temp; + } + } + + internal sealed class ObjectHolderList + { + internal const int DefaultInitialSize = 8; + + internal ObjectHolder[] _values; + internal int _count; + + internal ObjectHolderList() : this(DefaultInitialSize) + { + } + + internal ObjectHolderList(int startingSize) + { + Debug.Assert(startingSize > 0 && startingSize < 0x1000, "startingSize>0 && startingSize<0x1000"); + _count = 0; + _values = new ObjectHolder[startingSize]; + } + + internal void Add(ObjectHolder value) + { + if (_count == _values.Length) + { + EnlargeArray(); + } + _values[_count++] = value; + } + + internal ObjectHolderListEnumerator GetFixupEnumerator() => new ObjectHolderListEnumerator(this, true); + + private void EnlargeArray() + { + int newLength = _values.Length * 2; + if (newLength < 0) + { + if (newLength == int.MaxValue) + { + throw new SerializationException(SR.Serialization_TooManyElements); + } + newLength = int.MaxValue; + } + + ObjectHolder[] temp = new ObjectHolder[newLength]; + Array.Copy(_values, 0, temp, 0, _count); + _values = temp; + } + + internal int Version => _count; + + internal int Count => _count; + } + + internal sealed class ObjectHolderListEnumerator + { + private readonly bool _isFixupEnumerator; + private readonly ObjectHolderList _list; + private readonly int _startingVersion; + private int _currPos; + + internal ObjectHolderListEnumerator(ObjectHolderList list, bool isFixupEnumerator) + { + Debug.Assert(list != null, "[ObjectHolderListEnumerator.ctor]list!=null"); + _list = list; + _startingVersion = _list.Version; + _currPos = -1; + _isFixupEnumerator = isFixupEnumerator; + } + + internal bool MoveNext() + { + Debug.Assert(_startingVersion == _list.Version, "[ObjectHolderListEnumerator.MoveNext]m_startingVersion==m_list.Version"); + if (_isFixupEnumerator) + { + while (++_currPos < _list.Count && _list._values[_currPos].CompletelyFixed) ; + return _currPos != _list.Count; + } + else + { + _currPos++; + return _currPos != _list.Count; + } + } + + internal ObjectHolder Current + { + get + { + Debug.Assert(_currPos != -1, "[ObjectHolderListEnumerator.Current]m_currPos!=-1"); + Debug.Assert(_currPos < _list.Count, "[ObjectHolderListEnumerator.Current]m_currPos _streamingContext; + } + + public interface ISafeSerializationData + { + // CompleteDeserialization is called when the object to which the extra serialized data was attached + // has completed its deserialization, and now needs to be populated with the extra data stored in + // this object. + void CompleteDeserialization(object deserialized); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationBinder.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationBinder.cs new file mode 100644 index 000000000000..50524064f195 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationBinder.cs @@ -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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.Serialization +{ + [Serializable] + public abstract class SerializationBinder + { + public virtual void BindToName(Type serializedType, out string assemblyName, out string typeName) + { + assemblyName = null; + typeName = null; + } + + public abstract Type BindToType(string assemblyName, string typeName); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs new file mode 100644 index 000000000000..6b094ce6efa4 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace System.Runtime.Serialization +{ + internal sealed class SerializationEvents + { + private readonly List _onSerializingMethods; + private readonly List _onSerializedMethods; + private readonly List _onDeserializingMethods; + private readonly List _onDeserializedMethods; + + internal SerializationEvents(Type t) + { + _onSerializingMethods = GetMethodsWithAttribute(typeof(OnSerializingAttribute), t); + _onSerializedMethods = GetMethodsWithAttribute(typeof(OnSerializedAttribute), t); + _onDeserializingMethods = GetMethodsWithAttribute(typeof(OnDeserializingAttribute), t); + _onDeserializedMethods = GetMethodsWithAttribute(typeof(OnDeserializedAttribute), t); + } + + private List GetMethodsWithAttribute(Type attribute, Type t) + { + List mi = null; + + // Traverse the hierarchy to find all methods with the particular attribute + Type baseType = t; + while (baseType != null && baseType != typeof(object)) + { + // Get all methods which are declared on this type, instance and public or nonpublic + MethodInfo[] mis = baseType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + foreach (MethodInfo m in mis) + { + // For each method find if attribute is present, the return type is void and the method is not virtual + if (m.IsDefined(attribute, false)) + { + if (mi == null) mi = new List(); + mi.Add(m); + } + } + baseType = baseType.GetTypeInfo().BaseType; + } + mi?.Reverse(); // We should invoke the methods starting from base + + return mi; + } + + internal bool HasOnSerializingEvents => + _onSerializingMethods != null || _onSerializedMethods != null; + + internal void InvokeOnSerializing(object obj, StreamingContext context) => + InvokeOnDelegate(obj, context, _onSerializingMethods); + + internal void InvokeOnDeserializing(object obj, StreamingContext context) => + InvokeOnDelegate(obj, context, _onDeserializingMethods); + + internal void InvokeOnDeserialized(object obj, StreamingContext context) => + InvokeOnDelegate(obj, context, _onDeserializedMethods); + + internal SerializationEventHandler AddOnSerialized(object obj, SerializationEventHandler handler) => + AddOnDelegate(obj, handler, _onSerializedMethods); + + internal SerializationEventHandler AddOnDeserialized(object obj, SerializationEventHandler handler) => + AddOnDelegate(obj, handler, _onDeserializedMethods); + + /// Invoke all methods. + private static void InvokeOnDelegate(object obj, StreamingContext context, List methods) + { + Debug.Assert(obj != null, "object should have been initialized"); + AddOnDelegate(obj, null, methods)?.Invoke(context); + } + + /// Add all methods to a delegate. + private static SerializationEventHandler AddOnDelegate(object obj, SerializationEventHandler handler, List methods) + { + if (methods != null) + { + foreach (MethodInfo m in methods) + { + SerializationEventHandler onDeserialized = (SerializationEventHandler)m.CreateDelegate(typeof(SerializationEventHandler), obj); + handler = (SerializationEventHandler)Delegate.Combine(handler, onDeserialized); + } + } + return handler; + } + } + + internal static class SerializationEventsCache + { + private static readonly ConcurrentDictionary s_cache = new ConcurrentDictionary(); + + internal static SerializationEvents GetSerializationEventsForType(Type t) => + s_cache.GetOrAdd(t, type => new SerializationEvents(type)); + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationInfo.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationInfo.cs index 516fa8a97daa..35facef50f7d 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationInfo.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationInfo.cs @@ -10,7 +10,7 @@ namespace System.Runtime.Serialization { public sealed class SerializationInfo { - private const int defaultSize = 4; + private const int DefaultSize = 4; // Even though we have a dictionary, we're still keeping all the arrays around for back-compat. // Otherwise we may run into potentially breaking behaviors like GetEnumerator() not returning entries in the same order they were added. @@ -30,11 +30,10 @@ public sealed class SerializationInfo [CLSCompliant(false)] public SerializationInfo(Type type, IFormatterConverter converter) { - if ((object)type == null) + if (type == null) { throw new ArgumentNullException(nameof(type)); } - if (converter == null) { throw new ArgumentNullException(nameof(converter)); @@ -44,21 +43,21 @@ public SerializationInfo(Type type, IFormatterConverter converter) _rootTypeName = type.FullName; _rootTypeAssemblyName = type.GetTypeInfo().Module.Assembly.FullName; - _names = new String[defaultSize]; - _values = new object[defaultSize]; - _types = new Type[defaultSize]; - + _names = new string[DefaultSize]; + _values = new object[DefaultSize]; + _types = new Type[DefaultSize]; _nameToIndex = new Dictionary(); - _converter = converter; } + internal SerializationInfo(Type type, IFormatterConverter converter, bool requireSameTokenInPartialTrust) : + this(type, converter) + { + } + public string FullTypeName { - get - { - return _rootTypeName; - } + get { return _rootTypeName; } set { if (null == value) @@ -73,10 +72,7 @@ public string FullTypeName public string AssemblyName { - get - { - return _rootTypeAssemblyName; - } + get { return _rootTypeAssemblyName; } set { if (null == value) @@ -95,6 +91,7 @@ public void SetType(Type type) { throw new ArgumentNullException(nameof(type)); } + if (!ReferenceEquals(_rootType, type)) { _rootType = type; @@ -105,26 +102,11 @@ public void SetType(Type type) } } - public int MemberCount - { - get - { - return _count; - } - } + public int MemberCount => _count; - public Type ObjectType - { - get - { - return _rootType; - } - } + public Type ObjectType => _rootType; - public SerializationInfoEnumerator GetEnumerator() - { - return new SerializationInfoEnumerator(_names, _values, _types, _count); - } + public SerializationInfoEnumerator GetEnumerator() => new SerializationInfoEnumerator(_names, _values, _types, _count); private void ExpandArrays() { @@ -133,9 +115,7 @@ private void ExpandArrays() newSize = (_count * 2); - // // In the pathological case, we may wrap - // if (newSize < _count) { if (int.MaxValue > _count) @@ -144,26 +124,39 @@ private void ExpandArrays() } } - // // Allocate more space and copy the data - // + string[] newMembers = new string[newSize]; + Array.Copy(_names, 0, newMembers, 0, _count); + _names = newMembers; + object[] newData = new object[newSize]; + Array.Copy(_values, 0, newData, 0, _count); + _values = newData; + Type[] newTypes = new Type[newSize]; + Array.Copy(_types, 0, newTypes, 0, _count); + _types = newTypes; + } - Array.Copy(_names, newMembers, _count); - Array.Copy(_values, newData, _count); - Array.Copy(_types, newTypes, _count); + internal void UpdateValue(string name, object value, Type type) + { + Debug.Assert(null != name, "[SerializationInfo.UpdateValue]name!=null"); + Debug.Assert(null != value, "[SerializationInfo.UpdateValue]value!=null"); + Debug.Assert(null != type, "[SerializationInfo.UpdateValue]type!=null"); - // - // Assign the new arrys back to the member vars. - // - _names = newMembers; - _values = newData; - _types = newTypes; + int index = FindElement(name); + if (index < 0) + { + AddValueInternal(name, value, type); + } + else + { + _values[index] = value; + _types[index] = type; + } } - #region AddValue public void AddValue(string name, object value, Type type) { if (null == name) @@ -264,7 +257,6 @@ public void AddValue(string name, DateTime value) { AddValue(name, value, typeof(DateTime)); } - #endregion internal void AddValueInternal(string name, object value, Type type) { @@ -274,17 +266,13 @@ internal void AddValueInternal(string name, object value, Type type) } _nameToIndex.Add(name, _count); - // // If we need to expand the arrays, do so. - // if (_count >= _names.Length) { ExpandArrays(); } - // // Add the data and then advance the counter. - // _names[_count] = name; _values[_count] = value; _types[_count] = type; @@ -297,24 +285,11 @@ private int FindElement(string name) { throw new ArgumentNullException(nameof(name)); } + int index; - if (_nameToIndex.TryGetValue(name, out index)) - { - return index; - } - return -1; - } - - /*==================================GetElement================================== - **Action: Use FindElement to get the location of a particular member and then return - ** the value of the element at that location. The type of the member is - ** returned in the foundType field. - **Returns: The value of the element at the position associated with name. - **Arguments: name -- the name of the element to find. - ** foundType -- the type of the element associated with the given name. - **Exceptions: None. FindElement does null checking and throws for elements not - ** found. - ==============================================================================*/ + return _nameToIndex.TryGetValue(name, out index) ? index : -1; + } + private object GetElement(string name, out Type foundType) { int index = FindElement(name); @@ -331,7 +306,23 @@ private object GetElement(string name, out Type foundType) return _values[index]; } - #region GetValue + private object GetElementNoThrow(string name, out Type foundType) + { + int index = FindElement(name); + if (index == -1) + { + foundType = null; + return null; + } + + Debug.Assert(index < _values.Length, "[SerializationInfo.GetElement]index _value; - public string Name => _name; - public Type ObjectType => _type; + private string _name; + private object _value; + private Type _type; internal SerializationEntry(string entryName, object entryValue, Type entryType) { - _value = entryValue; _name = entryName; + _value = entryValue; _type = entryType; } + + public object Value => _value; + public string Name => _name; + public Type ObjectType => _type; } - // - // A simple enumerator over the values stored in the SerializationInfo. - // This does not snapshot the values, it just keeps pointers to the - // member variables of the SerializationInfo that created it. - // public sealed class SerializationInfoEnumerator : IEnumerator { - private readonly string[] _names; - private readonly object[] _values; + private readonly string[] _members; + private readonly object[] _data; private readonly Type[] _types; - private readonly int _count; - private int _current; - private bool _hasCurrent; + private readonly int _numItems; + private int _currItem; + private bool _current; - internal SerializationInfoEnumerator(string[] names, object[] values, Type[] types, int count) + internal SerializationInfoEnumerator(string[] members, object[] info, Type[] types, int numItems) { - Debug.Assert(names != null); - Debug.Assert(values != null); - Debug.Assert(types != null); - Debug.Assert(count >= 0); - Debug.Assert(names.Length >= count); - Debug.Assert(values.Length >= count); - Debug.Assert(types.Length >= count); + Debug.Assert(members != null, "[SerializationInfoEnumerator.ctor]members!=null"); + Debug.Assert(info != null, "[SerializationInfoEnumerator.ctor]info!=null"); + Debug.Assert(types != null, "[SerializationInfoEnumerator.ctor]types!=null"); + Debug.Assert(numItems >= 0, "[SerializationInfoEnumerator.ctor]numItems>=0"); + Debug.Assert(members.Length >= numItems, "[SerializationInfoEnumerator.ctor]members.Length>=numItems"); + Debug.Assert(info.Length >= numItems, "[SerializationInfoEnumerator.ctor]info.Length>=numItems"); + Debug.Assert(types.Length >= numItems, "[SerializationInfoEnumerator.ctor]types.Length>=numItems"); - _names = names; - _values = values; + _members = members; + _data = info; _types = types; + //The MoveNext semantic is much easier if we enforce that [0..m_numItems] are valid entries //in the enumerator, hence we subtract 1. - _count = count - 1; - _current = -1; - _hasCurrent = false; + _numItems = numItems - 1; + _currItem = -1; + _current = false; } public bool MoveNext() { - if (_current < _count) + if (_currItem < _numItems) { - _current++; - _hasCurrent = true; + _currItem++; + _current = true; } else { - _hasCurrent = false; + _current = false; } - return _hasCurrent; - } - /// - object IEnumerator.Current - { - get - { - if (_hasCurrent == false) - { - throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); - } - return new SerializationEntry(_names[_current], _values[_current], _types[_current]); - } + return _current; } + object IEnumerator.Current => Current; + public SerializationEntry Current { get { - if (_hasCurrent == false) + if (_current == false) { throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } - return (new SerializationEntry(_names[_current], _values[_current], _types[_current])); + return new SerializationEntry(_members[_currItem], _data[_currItem], _types[_currItem]); } } public void Reset() { - _current = -1; - _hasCurrent = false; + _currItem = -1; + _current = false; } public string Name { get { - if (_hasCurrent == false) + if (_current == false) { throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } - return _names[_current]; + return _members[_currItem]; } } public object Value { get { - if (_hasCurrent == false) + if (_current == false) { throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } - return _values[_current]; + return _data[_currItem]; } } public Type ObjectType { get { - if (_hasCurrent == false) + if (_current == false) { throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); } - return _types[_current]; + return _types[_currItem]; } } } -} \ No newline at end of file +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs new file mode 100644 index 000000000000..d043af7d71a5 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs @@ -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. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Runtime.Serialization +{ + public sealed class SerializationObjectManager + { + private readonly Dictionary _objectSeenTable; // Table to keep track of objects [OnSerializing] has been called on + private readonly StreamingContext _context; + private SerializationEventHandler _onSerializedHandler; + + public SerializationObjectManager(StreamingContext context) + { + _context = context; + _objectSeenTable = new Dictionary(); + } + + public void RegisterObject(object obj) + { + // Invoke OnSerializing for this object + SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); + + // Check to make sure type has serializing events + if (cache.HasOnSerializingEvents) + { + // Check to see if we have invoked the events on the object + if (!_objectSeenTable.ContainsKey(obj)) + { + _objectSeenTable[obj] = true; + // Invoke the events + cache.InvokeOnSerializing(obj, _context); + // Register for OnSerialized event + AddOnSerialized(obj); + } + } + } + + public void RaiseOnSerializedEvent() => _onSerializedHandler?.Invoke(_context); + + private void AddOnSerialized(object obj) + { + SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); + _onSerializedHandler = cache.AddOnSerialized(obj, _onSerializedHandler); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SurrogateSelector.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SurrogateSelector.cs new file mode 100644 index 000000000000..82078ef303dc --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SurrogateSelector.cs @@ -0,0 +1,225 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Diagnostics; + +namespace System.Runtime.Serialization +{ + public class SurrogateSelector : ISurrogateSelector + { + internal readonly SurrogateHashtable _surrogates = new SurrogateHashtable(32); + internal ISurrogateSelector _nextSelector; + + public virtual void AddSurrogate(Type type, StreamingContext context, ISerializationSurrogate surrogate) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + if (surrogate == null) + { + throw new ArgumentNullException(nameof(surrogate)); + } + + var key = new SurrogateKey(type, context); + _surrogates.Add(key, surrogate); // Hashtable does duplicate checking. + } + + private static bool HasCycle(ISurrogateSelector selector) + { + Debug.Assert(selector != null, "[HasCycle]selector!=null"); + + ISurrogateSelector head = selector, tail = selector; + while (head != null) + { + head = head.GetNextSelector(); + if (head == null) + { + return true; + } + if (head == tail) + { + return false; + } + head = head.GetNextSelector(); + tail = tail.GetNextSelector(); + + if (head == tail) + { + return false; + } + } + + return true; + } + + // Adds another selector to check if we don't have match within this selector. + // The logic is:"Add this onto the list as the first thing that you check after yourself." + public virtual void ChainSelector(ISurrogateSelector selector) + { + if (selector == null) + { + throw new ArgumentNullException(nameof(selector)); + } + + // Verify that we don't try and add ourself twice. + if (selector == this) + { + throw new SerializationException(SR.Serialization_DuplicateSelector); + } + + // Verify that the argument doesn't contain a cycle. + if (!HasCycle(selector)) + { + throw new ArgumentException(SR.Serialization_SurrogateCycleInArgument, nameof(selector)); + } + + // Check for a cycle that would lead back to this. We find the end of the list that we're being asked to + // insert for use later. + ISurrogateSelector tempCurr = selector.GetNextSelector(); + ISurrogateSelector tempEnd = selector; + while (tempCurr != null && tempCurr != this) + { + tempEnd = tempCurr; + tempCurr = tempCurr.GetNextSelector(); + } + if (tempCurr == this) + { + throw new ArgumentException(SR.Serialization_SurrogateCycle, nameof(selector)); + } + + // Check for a cycle later in the list which would be introduced by this insertion. + tempCurr = selector; + ISurrogateSelector tempPrev = selector; + while (tempCurr != null) + { + if (tempCurr == tempEnd) + { + tempCurr = GetNextSelector(); + } + else + { + tempCurr = tempCurr.GetNextSelector(); + } + if (tempCurr == null) + { + break; + } + if (tempCurr == tempPrev) + { + throw new ArgumentException(SR.Serialization_SurrogateCycle, nameof(selector)); + } + + if (tempCurr == tempEnd) + { + tempCurr = GetNextSelector(); + } + else + { + tempCurr = tempCurr.GetNextSelector(); + } + + + if (tempPrev == tempEnd) + { + tempPrev = GetNextSelector(); + } + else + { + tempPrev = tempPrev.GetNextSelector(); + } + if (tempCurr == tempPrev) + { + throw new ArgumentException(SR.Serialization_SurrogateCycle, nameof(selector)); + } + } + + // Add the new selector and it's entire chain of selectors as the next thing that we check. + ISurrogateSelector temp = _nextSelector; + _nextSelector = selector; + if (temp != null) + { + tempEnd.ChainSelector(temp); + } + } + + // Get the next selector on the chain of selectors. + public virtual ISurrogateSelector GetNextSelector() => _nextSelector; + + // Gets the surrogate for a particular type. If this selector can't + // provide a surrogate, it checks with all of it's children before returning null. + public virtual ISerializationSurrogate GetSurrogate(Type type, StreamingContext context, out ISurrogateSelector selector) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + selector = this; + + SurrogateKey key = new SurrogateKey(type, context); + ISerializationSurrogate temp = (ISerializationSurrogate)_surrogates[key]; + if (temp != null) + { + return temp; + } + if (_nextSelector != null) + { + return _nextSelector.GetSurrogate(type, context, out selector); + } + return null; + } + + // Removes the surrogate associated with a given type. Does not + // check chained surrogates. + public virtual void RemoveSurrogate(Type type, StreamingContext context) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + SurrogateKey key = new SurrogateKey(type, context); + _surrogates.Remove(key); + } + } + + [Serializable] + internal sealed class SurrogateKey + { + internal readonly Type _type; + internal readonly StreamingContext _context; + + internal SurrogateKey(Type type, StreamingContext context) + { + Debug.Assert(type != null); + _type = type; + _context = context; + } + + public override int GetHashCode() => _type.GetHashCode(); + } + + // Subclass to override KeyEquals. + internal sealed class SurrogateHashtable : Hashtable + { + internal SurrogateHashtable(int size) : base(size) + { + } + + // Must return true if the context to serialize for (givenContext) + // is a subset of the context for which the serialization selector is provided (presentContext) + // Note: This is done by overriding KeyEquals rather than overriding Equals() in the SurrogateKey + // class because Equals() method must be commutative. + protected override bool KeyEquals(object key, object item) + { + SurrogateKey givenValue = (SurrogateKey)item; + SurrogateKey presentValue = (SurrogateKey)key; + return presentValue._type == givenValue._type && + (presentValue._context.State & givenValue._context.State) == givenValue._context.State && + presentValue._context.Context == givenValue._context.Context; + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ValueTypeFixupInfo.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ValueTypeFixupInfo.cs new file mode 100644 index 000000000000..9437b59f58e2 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ValueTypeFixupInfo.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System.Runtime.Serialization +{ + internal sealed class ValueTypeFixupInfo + { + /// + /// The id of the containing body. This could be a regular + /// object, another value type, or an array. For obvious reasons, + /// the containing body can never have both a FieldInfo and + /// an array index. + /// + private readonly long _containerID; + + /// + /// The FieldInfo into the containing body. This will only + /// apply if the containing body is a field info or another + /// value type. + /// + private readonly FieldInfo _parentField; + + /// + /// The array index of the index into the parent. This will only + /// apply if the containing body is an array. + /// + private readonly int[] _parentIndex; + + public ValueTypeFixupInfo(long containerID, FieldInfo member, int[] parentIndex) + { + // If both member and arrayIndex are null, we don't have enough information to create + // a tunnel to do the fixup. + if (member == null && parentIndex == null) + { + throw new ArgumentException(SR.Argument_MustSupplyParent); + } + + if (containerID == 0 && member == null) + { + _containerID = containerID; + _parentField = member; + _parentIndex = parentIndex; + } + + // If the member isn't null, we know that they supplied a MemberInfo as the parent. This means + // that the arrayIndex must be null because we can't have a FieldInfo into an array. + if (member != null) + { + if (parentIndex != null) + { + throw new ArgumentException(SR.Argument_MemberAndArray); + } + + if (member.FieldType.GetTypeInfo().IsValueType && containerID == 0) + { + throw new ArgumentException(SR.Argument_MustSupplyContainer); + } + } + + _containerID = containerID; + _parentField = member; + _parentIndex = parentIndex; + } + + public long ContainerID => _containerID; + + public FieldInfo ParentField => _parentField; + + public int[] ParentIndex => _parentIndex; + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/SerializableAttribute.cs b/src/System.Runtime.Serialization.Formatters/src/System/SerializableAttribute.cs index 0900b4393fd3..c25693137301 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/SerializableAttribute.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/SerializableAttribute.cs @@ -2,15 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; - namespace System { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)] public sealed class SerializableAttribute : Attribute { - public SerializableAttribute() - { - } + public SerializableAttribute() { } } -} \ No newline at end of file +} diff --git a/src/System.Runtime.Serialization.Formatters/src/System/TemporaryStubs.cs b/src/System.Runtime.Serialization.Formatters/src/System/TemporaryStubs.cs new file mode 100644 index 000000000000..186b4c7d6d04 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/src/System/TemporaryStubs.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System +{ + internal interface ICloneable // TODO: Replace with real ICloneable when it's available + { + object Clone(); + } + + internal class MarshalByRefObject { } // TODO: Replace with real MarshalByRefObject when it's available + + internal sealed class TypedReference // TODO: Replace with System.TypedReference when available and functional + { + internal object _target; + internal FieldInfo[] _fields; + + internal static TypedReference MakeTypedReference(object target, FieldInfo[] flds) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + if (flds == null) + { + throw new ArgumentNullException(nameof(flds)); + } + if (flds.Length == 0) + { + throw new ArgumentException(SR.Arg_ArrayZeroError); + } + + return new TypedReference { _target = target, _fields = flds }; + } + + internal static void SetTypedReference(object target, object value) + { + throw new NotSupportedException(); + } + } +} + +namespace System.Reflection +{ + internal static class CustomReflectionExtensions + { + internal static void SetValueDirect(this FieldInfo field, TypedReference obj, object value) // TODO: Replace with FieldInfo.SetValueDirect when available + { + if (field == null) + { + throw new NullReferenceException(); + } + if (obj == null) + { + throw new ArgumentNullException(nameof(obj)); + } + + // This is extremely inefficient, but without runtime support, we can't do much better. + + object[] values = new object[obj._fields.Length + 1]; + + values[0] = obj._target; + for (int i = 0; i < obj._fields.Length; i++) + { + values[i + 1] = obj._fields[i].GetValue(values[i]); + } + + field.SetValue(values[values.Length - 1], value); + + for (int i = values.Length - 2; i >= 0; i--) + { + obj._fields[i].SetValue(values[i], values[i + 1]); + } + } + + internal static TypeCode GetTypeCode(this Type type) // TODO: Replace with Type.TypeCode when it's available + { + if (type == null) return TypeCode.Empty; + else if (type == typeof(bool)) return TypeCode.Boolean; + else if (type == typeof(char)) return TypeCode.Char; + else if (type == typeof(sbyte)) return TypeCode.SByte; + else if (type == typeof(byte)) return TypeCode.Byte; + else if (type == typeof(short)) return TypeCode.Int16; + else if (type == typeof(ushort)) return TypeCode.UInt16; + else if (type == typeof(int)) return TypeCode.Int32; + else if (type == typeof(uint)) return TypeCode.UInt32; + else if (type == typeof(long)) return TypeCode.Int64; + else if (type == typeof(ulong)) return TypeCode.UInt64; + else if (type == typeof(float)) return TypeCode.Single; + else if (type == typeof(double)) return TypeCode.Double; + else if (type == typeof(decimal)) return TypeCode.Decimal; + else if (type == typeof(System.DateTime)) return TypeCode.DateTime; + else if (type == typeof(string)) return TypeCode.String; + else if (type.GetTypeInfo().IsEnum) return GetTypeCode(Enum.GetUnderlyingType(type)); + return TypeCode.Object; + } + } +} + +namespace System.Runtime.Remoting.Messaging // TODO: Use the implementation from remoting stubs when available +{ + public delegate object HeaderHandler(Header[] headers); + + [Serializable] + public class Header + { + public string HeaderNamespace; + public bool MustUnderstand; + public string Name; + public object Value; + + public Header(string _Name, object _Value) : this(_Name, _Value, true) + { + } + + public Header(string _Name, object _Value, bool _MustUnderstand) + { + Name = _Name; + Value = _Value; + MustUnderstand = _MustUnderstand; + } + + public Header(string _Name, object _Value, bool _MustUnderstand, string _HeaderNamespace) + { + Name = _Name; + Value = _Value; + MustUnderstand = _MustUnderstand; + HeaderNamespace = _HeaderNamespace; + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/src/project.json b/src/System.Runtime.Serialization.Formatters/src/project.json index 8d875807f412..c0f01e240754 100644 --- a/src/System.Runtime.Serialization.Formatters/src/project.json +++ b/src/System.Runtime.Serialization.Formatters/src/project.json @@ -4,11 +4,18 @@ "dependencies": { "Microsoft.NETCore.Platforms": "1.0.1", "System.Collections": "4.0.10", + "System.Collections.NonGeneric": "4.0.0", + "System.Collections.Concurrent": "4.0.0", "System.Diagnostics.Debug": "4.0.10", + "System.Reflection": "4.1.0", + "System.Reflection.Extensions": "4.0.0", + "System.Reflection.TypeExtensions": "4.1.0", "System.Resources.ResourceManager": "4.0.0", "System.Runtime": "4.0.20", "System.Runtime.Extensions": "4.0.10", - "System.Runtime.Serialization.Primitives": "4.1.1" + "System.Runtime.Serialization.Primitives": "4.2.0-beta-24314-02", + "System.Text.Encoding.Extensions": "4.0.10", + "System.Threading": "4.0.10" }, "imports": [ "dotnet5.5" diff --git a/src/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs b/src/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs new file mode 100644 index 000000000000..1e223818b71b --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/BinaryFormatterTests.cs @@ -0,0 +1,598 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Remoting.Messaging; +using System.Runtime.Serialization.Formatters.Binary; +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public class BinaryFormatterTests + { + public static IEnumerable SerializableObjects() + { + // Primitive types + yield return byte.MinValue; + yield return byte.MaxValue; + yield return sbyte.MinValue; + yield return sbyte.MaxValue; + yield return short.MinValue; + yield return short.MaxValue; + yield return int.MinValue; + yield return int.MaxValue; + yield return uint.MinValue; + yield return uint.MaxValue; + yield return long.MinValue; + yield return long.MaxValue; + yield return ulong.MinValue; + yield return ulong.MaxValue; + yield return char.MinValue; + yield return char.MaxValue; + yield return float.MinValue; + yield return float.MaxValue; + yield return double.MinValue; + yield return double.MaxValue; + yield return true; + yield return false; + yield return ""; + yield return "c"; + yield return "\u4F60\u597D"; + yield return "some\0data\0with\0null\0chars"; + yield return "<>&\"\'"; + yield return " < "; + yield return "minchar" + char.MinValue + "minchar"; + + // Enum values + yield return DayOfWeek.Monday; + yield return DateTimeKind.Local; + + // Nullables + yield return (int?)1; + yield return (StructWithIntField?)new StructWithIntField() { X = 42 }; + + // Other core serializable types + yield return TimeSpan.FromDays(7); + yield return new Version(1, 2, 3, 4); + yield return Tuple.Create(1, "2", Tuple.Create(3.4)); + yield return new Guid("0CACAA4D-C6BD-420A-B660-2F557337CA89"); + yield return new AttributeUsageAttribute(AttributeTargets.Class); + yield return new List(); + yield return new List() { 1, 2, 3, 4, 5 }; + + // Arrays of primitive types + yield return Enumerable.Range(0, 256).Select(i => (byte)i).ToArray(); + yield return new int[] { }; + yield return new int[] { 1 }; + yield return new int[] { 1, 2, 3, 4, 5 }; + yield return new char[] { 'a', 'b', 'c', 'd', 'e' }; + yield return new string[] { }; + yield return new string[] { "hello", "world" }; + yield return new short[] { short.MaxValue }; + yield return new long[] { long.MaxValue }; + yield return new ushort[] { ushort.MaxValue }; + yield return new uint[] { uint.MaxValue }; + yield return new ulong[] { ulong.MaxValue }; + yield return new bool[] { true, false }; + yield return new double[] { 1.2 }; + yield return new float[] { 1.2f, 3.4f }; + + // Arrays of other types + yield return new object[] { }; + yield return new Guid[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() }; + yield return new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }; + yield return new Point[] { new Point(1, 2), new Point(3, 4) }; + yield return new ObjectWithArrays + { + IntArray = new int[0], + StringArray = new string[] { "hello", "world" }, + ByteArray = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }, + JaggedArray = new int[][] { new int[] { 1, 2, 3 }, new int[] { 4, 5, 6, 7 } }, + MultiDimensionalArray = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } }, + TreeArray = new Tree[] { new Tree(1, new Tree(2, null, null), new Tree(3, null, null)) } + }; + yield return new object[] { new int[,] { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 11, 12, 13, 14, 15 } } }; + yield return new object[] { new int[,,] { { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 11, 12, 13, 14, 15 } } } }; + yield return new object[] { new int[,,,,] { { { { { 1 } } } } } }; + yield return new ArraySegment(new int[] { 1, 2, 3, 4, 5 }, 1, 2); + yield return Enumerable.Range(0, 10000).Select(i => (object)i).ToArray(); + yield return new object[200]; // fewer than 256 nulls + yield return new object[300]; // more than 256 nulls + + // Non-vector arrays + yield return Array.CreateInstance(typeof(uint), new[] { 5 }, new[] { 1 }); + yield return Array.CreateInstance(typeof(int), new[] { 0, 0, 0 }, new[] { 0, 0, 0 }); + var arr = Array.CreateInstance(typeof(string), new[] { 1, 2 }, new[] { 3, 4 }); + arr.SetValue("hello", new[] { 3, 5 }); + yield return arr; + + // Custom object + var sealedObjectWithIntStringFields = new SealedObjectWithIntStringFields(); + yield return sealedObjectWithIntStringFields; + yield return new SealedObjectWithIntStringFields() { Member1 = 42, Member2 = null, Member3 = "84" }; + + // Custom object with fields pointing to the same object + yield return new ObjectWithIntStringUShortUIntULongAndCustomObjectFields + { + Member1 = 10, + Member2 = "hello", + _member3 = "hello", + Member4 = sealedObjectWithIntStringFields, + Member4shared = sealedObjectWithIntStringFields, + Member5 = new SealedObjectWithIntStringFields(), + Member6 = "Hello World", + str1 = "hello < world", + str2 = "<", + str3 = "< world", + str4 = "hello < world", + u16 = ushort.MaxValue, + u32 = uint.MaxValue, + u64 = ulong.MaxValue, + }; + + // Simple type without a default ctor + var point = new Point(1, 2); + yield return point; + + // Graph without cycles + yield return new Tree(42, null, null); + yield return new Tree(1, new Tree(2, new Tree(3, null, null), null), null); + yield return new Tree(Colors.Red, null, new Tree(Colors.Blue, null, new Tree(Colors.Green, null, null))); + yield return new Tree(1, new Tree(2, new Tree(3, null, null), new Tree(4, null, null)), new Tree(5, null, null)); + + // Graph with cycles + Graph a = new Graph { Value = 1 }; + yield return a; + Graph b = new Graph { Value = 2, Links = new[] { a } }; + yield return b; + Graph c = new Graph { Value = 3, Links = new[] { a, b } }; + yield return c; + Graph d = new Graph { Value = 3, Links = new[] { a, b, c } }; + yield return d; + a.Links = new[] { b, c, d }; // complete the cycle + yield return a; + + // Structs + yield return new EmptyStruct(); + yield return new StructWithIntField { X = 42 }; + yield return new StructWithStringFields { String1 = "hello", String2 = "world" }; + yield return new StructContainingOtherStructs { Nested1 = new StructWithStringFields { String1 = "a", String2 = "b" }, Nested2 = new StructWithStringFields { String1 = "3", String2 = "4" } }; + yield return new StructContainingArraysOfOtherStructs(); + yield return new StructContainingArraysOfOtherStructs { Nested = new StructContainingOtherStructs[0] }; + var s = new StructContainingArraysOfOtherStructs + { + Nested = new[] + { + new StructContainingOtherStructs { Nested1 = new StructWithStringFields { String1 = "a", String2 = "b" }, Nested2 = new StructWithStringFields { String1 = "3", String2 = "4" } }, + new StructContainingOtherStructs { Nested1 = new StructWithStringFields { String1 = "e", String2 = "f" }, Nested2 = new StructWithStringFields { String1 = "7", String2 = "8" } }, + } + }; + yield return s; + yield return new object[] { s, new StructContainingArraysOfOtherStructs?(s) }; + + // ISerializable + yield return new BasicISerializableObject(1, "2"); + + // Various other special cases + yield return new TypeWithoutNamespace(); + } + + public static IEnumerable NonSerializableObjects() + { + yield return new NonSerializableStruct(); + yield return new NonSerializableClass(); + yield return new SerializableClassWithBadField(); + yield return new object[] { 1, 2, 3, new NonSerializableClass() }; + + // TODO: Move these to the serializable category when enabled in the runtime + yield return DateTime.Now; + yield return DateTimeOffset.Now; + var e = new OperationCanceledException(); + yield return e; + try { throw e; } catch { } yield return e; + yield return new Dictionary() { { 1, "test" }, { 2, "another test" } }; + yield return IntPtr.Zero; + yield return UIntPtr.Zero; + } + + public static IEnumerable ValidateBasicObjectsRoundtrip_MemberData() + { + foreach (object obj in SerializableObjects()) + { + foreach (FormatterAssemblyStyle assemblyFormat in new[] { FormatterAssemblyStyle.Full, FormatterAssemblyStyle.Simple }) + { + foreach (TypeFilterLevel filterLevel in new[] { TypeFilterLevel.Full, TypeFilterLevel.Low }) + { + foreach (FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString }) + { + foreach (bool unsafeDeserialize in new[] { true, false }) + { + yield return new object[] { obj, assemblyFormat, filterLevel, typeFormat, unsafeDeserialize }; + } + } + } + } + } + } + + [Theory] + [MemberData(nameof(ValidateBasicObjectsRoundtrip_MemberData))] + public void ValidateBasicObjectsRoundtrip(object obj, FormatterAssemblyStyle assemblyFormat, TypeFilterLevel filterLevel, FormatterTypeStyle typeFormat, bool unsafeDeserialize) + { + object result = FormatterClone(obj, null, assemblyFormat, filterLevel, typeFormat, unsafeDeserialize); + if (!ReferenceEquals(obj, string.Empty)) // "" is interned and will roundtrip as the same object + { + Assert.NotSame(obj, result); + } + Assert.Equal(obj, result); + } + + public static IEnumerable RoundtripWithHeaders_MemberData() + { + foreach (object obj in SerializableObjects()) + { + // Fails with strings as the root of the graph, both in core and on desktop: + // "The object with ID 1 was referenced in a fixup but does not exist" + if (obj is string) continue; + + yield return new[] { obj }; + } + } + + [Theory] + [MemberData(nameof(RoundtripWithHeaders_MemberData))] + public void RoundtripWithHeaders(object obj) + { + Assert.Equal(obj, FormatterClone(obj, headers: new Header[0])); + Assert.Equal(obj, FormatterClone(obj, headers: new[] { new Header("SomeHeader", "some value") })); + Assert.Equal(obj, FormatterClone(obj, headers: new[] { new Header("SomeHeader", "some value", true, "some namespace") })); + Assert.Equal(obj, FormatterClone(obj, headers: new[] { new Header("SomeHeader", 42), new Header("SomeOtherHeader", obj) })); ; + } + + [Fact] + public void RoundtripManyObjectsInOneStream() + { + object[] objects = SerializableObjects().ToArray(); + var s = new MemoryStream(); + var f = new BinaryFormatter(); + + foreach (object obj in objects) + { + f.Serialize(s, obj); + } + s.Position = 0; + foreach (object obj in objects) + { + object result = f.Deserialize(s); + Assert.Equal(obj, result); + } + } + + [Fact] + public void SameObjectRepeatedInArray() + { + object o = new object(); + object[] arr = new[] { o, o, o, o, o }; + object[] result = FormatterClone(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]); + } + } + + public static IEnumerable ValidateNonSerializableTypes_MemberData() + { + foreach (object obj in NonSerializableObjects()) + { + foreach (FormatterAssemblyStyle assemblyFormat in new[] { FormatterAssemblyStyle.Full, FormatterAssemblyStyle.Simple }) + { + foreach (TypeFilterLevel filterLevel in new[] { TypeFilterLevel.Full, TypeFilterLevel.Low }) + { + foreach (FormatterTypeStyle typeFormat in new[] { FormatterTypeStyle.TypesAlways, FormatterTypeStyle.TypesWhenNeeded, FormatterTypeStyle.XsdString }) + { + yield return new object[] { obj, assemblyFormat, filterLevel, typeFormat }; + } + } + } + } + } + + [Theory] + [MemberData(nameof(ValidateNonSerializableTypes_MemberData))] + public void ValidateNonSerializableTypes(object obj, FormatterAssemblyStyle assemblyFormat, TypeFilterLevel filterLevel, FormatterTypeStyle typeFormat) + { + var f = new BinaryFormatter() + { + AssemblyFormat = assemblyFormat, + FilterLevel = filterLevel, + TypeFormat = typeFormat + }; + using (var s = new MemoryStream()) + { + Assert.Throws(() => f.Serialize(s, obj)); + } + } + + [Fact] + public void SerializeNonSerializableTypeWithSurrogate() + { + var p = new NonSerializablePair() { Value1 = 1, Value2 = "2" }; + Assert.False(p.GetType().GetTypeInfo().IsSerializable); + Assert.Throws(() => FormatterClone(p)); + + NonSerializablePair result = FormatterClone(p, new NonSerializablePairSurrogate()); + Assert.NotSame(p, result); + Assert.Equal(p.Value1, result.Value1); + Assert.Equal(p.Value2, result.Value2); + } + + [Fact] + public void SerializationEvents_FireAsExpected() + { + var f = new BinaryFormatter(); + + var obj = new IncrementCountsDuringRoundtrip(null); + + Assert.Equal(0, obj.IncrementedDuringOnSerializingMethod); + Assert.Equal(0, obj.IncrementedDuringOnSerializedMethod); + Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod); + Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod); + + using (var s = new MemoryStream()) + { + f.Serialize(s, obj); + s.Position = 0; + + Assert.Equal(1, obj.IncrementedDuringOnSerializingMethod); + Assert.Equal(1, obj.IncrementedDuringOnSerializedMethod); + Assert.Equal(0, obj.IncrementedDuringOnDeserializingMethod); + Assert.Equal(0, obj.IncrementedDuringOnDeserializedMethod); + + var result = (IncrementCountsDuringRoundtrip)f.Deserialize(s); + + 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() + { + var f = new BinaryFormatter(); + + var obj = new DerivedIncrementCountsDuringRoundtrip(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); + + using (var s = new MemoryStream()) + { + f.Serialize(s, obj); + s.Position = 0; + + 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)f.Deserialize(s); + + 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); + } + } + + [Fact] + public void Properties_Roundtrip() + { + var f = new BinaryFormatter(); + + Assert.Null(f.Binder); + var binder = new DelegateBinder(); + f.Binder = binder; + Assert.Same(binder, f.Binder); + + Assert.NotNull(f.Context); + Assert.Null(f.Context.Context); + Assert.Equal(StreamingContextStates.All, f.Context.State); + var context = new StreamingContext(StreamingContextStates.Clone); + f.Context = context; + Assert.Equal(StreamingContextStates.Clone, f.Context.State); + + Assert.Null(f.SurrogateSelector); + var selector = new SurrogateSelector(); + f.SurrogateSelector = selector; + Assert.Same(selector, f.SurrogateSelector); + + Assert.Equal(FormatterAssemblyStyle.Simple, f.AssemblyFormat); + f.AssemblyFormat = FormatterAssemblyStyle.Full; + Assert.Equal(FormatterAssemblyStyle.Full, f.AssemblyFormat); + + Assert.Equal(TypeFilterLevel.Full, f.FilterLevel); + f.FilterLevel = TypeFilterLevel.Low; + Assert.Equal(TypeFilterLevel.Low, f.FilterLevel); + + Assert.Equal(FormatterTypeStyle.TypesAlways, f.TypeFormat); + f.TypeFormat = FormatterTypeStyle.XsdString; + Assert.Equal(FormatterTypeStyle.XsdString, f.TypeFormat); + } + + [Fact] + public void SerializeDeserialize_InvalidArguments_ThrowsException() + { + var f = new BinaryFormatter(); + Assert.Throws("serializationStream", () => f.Serialize(null, new object())); + Assert.Throws("serializationStream", () => f.Deserialize(null)); + Assert.Throws(() => f.Deserialize(new MemoryStream())); // seekable, 0-length + } + + [Theory] + [InlineData(FormatterAssemblyStyle.Simple, false)] + [InlineData(FormatterAssemblyStyle.Full, true)] + public void MissingField_FailsWithAppropriateStyle(FormatterAssemblyStyle style, bool exceptionExpected) + { + var f = new BinaryFormatter(); + var s = new MemoryStream(); + f.Serialize(s, new Version1ClassWithoutField()); + s.Position = 0; + + f = new BinaryFormatter() { AssemblyFormat = style }; + f.Binder = new DelegateBinder { BindToTypeDelegate = (_, __) => typeof(Version2ClassWithoutOptionalField) }; + if (exceptionExpected) + { + Assert.Throws(() => f.Deserialize(s)); + } + else + { + var result = (Version2ClassWithoutOptionalField)f.Deserialize(s); + Assert.NotNull(result); + Assert.Equal(null, result.Value); + } + } + + [Theory] + [InlineData(FormatterAssemblyStyle.Simple)] + [InlineData(FormatterAssemblyStyle.Full)] + public void OptionalField_Missing_Success(FormatterAssemblyStyle style) + { + var f = new BinaryFormatter(); + var s = new MemoryStream(); + f.Serialize(s, new Version1ClassWithoutField()); + s.Position = 0; + + f = new BinaryFormatter() { AssemblyFormat = style }; + f.Binder = new DelegateBinder { BindToTypeDelegate = (_, __) => typeof(Version2ClassWithOptionalField) }; + var result = (Version2ClassWithOptionalField)f.Deserialize(s); + Assert.NotNull(result); + Assert.Equal(null, result.Value); + } + + public static IEnumerable Deserialize_FuzzInput_MemberData() + { + var rand = new Random(42); + for (int i = 0; i < 100; i++) + { + byte[] data = new byte[rand.Next(100, 200)]; + rand.NextBytes(data); + yield return new object[] { data }; + } + } + + [Theory] + public void Deserialize_FuzzInput(byte[] data) + { + var f = new BinaryFormatter(); + Assert.Throws(() => f.Deserialize(new MemoryStream(data))); + } + + [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; + var data = new byte[i]; + Assert.Equal(data.Length, s.Read(data, 0, data.Length)); + Assert.Throws(() => f.Deserialize(new MemoryStream(data))); + } + } + + [ActiveIssue("Fails on desktop and core: 'The object with ID 1 was referenced in a fixup but does not exist'")] + [Fact] + public void RoundtripWithHeaders_StringAsGraphRootAndInHeader() + { + RoundtripWithHeaders("any string"); + } + + [ActiveIssue("Fails on desktop and core: 'Unable to cast object of type 'System.UInt32[][*]' to type 'System.Object[]'")] + [Fact] + public void Roundtrip_ArrayContainingArrayAtNonZeroLowerBound() + { + FormatterClone(Array.CreateInstance(typeof(uint[]), new[] { 5 }, new[] { 1 })); + } + + private class DelegateBinder : SerializationBinder + { + public Func BindToTypeDelegate = null; + public override Type BindToType(string assemblyName, string typeName) => BindToTypeDelegate?.Invoke(assemblyName, typeName); + } + + private static T FormatterClone( + T obj, + ISerializationSurrogate surrogate = null, + FormatterAssemblyStyle assemblyFormat = FormatterAssemblyStyle.Full, + TypeFilterLevel filterLevel = TypeFilterLevel.Full, + FormatterTypeStyle typeFormat = FormatterTypeStyle.TypesAlways, + bool unsafeDeserialize = false, + Header[] headers = null) + { + BinaryFormatter f; + if (surrogate == 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.FilterLevel = filterLevel; + f.TypeFormat = typeFormat; + + using (var s = new MemoryStream()) + { + f.Serialize(s, obj, headers); + Assert.NotEqual(0, s.Position); + s.Position = 0; + return (T)(unsafeDeserialize ? f.UnsafeDeserialize(s, handler: null) : f.Deserialize(s)); + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/FormatterConverterTests.cs b/src/System.Runtime.Serialization.Formatters/tests/FormatterConverterTests.cs new file mode 100644 index 000000000000..43ddc36302be --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/FormatterConverterTests.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public class FormatterConverterTests + { + [Fact] + public void InvalidArguments_ThrowExceptions() + { + var f = new FormatterConverter(); + Assert.Throws("value", () => f.Convert(null, typeof(int))); + Assert.Throws("value", () => f.Convert(null, TypeCode.Char)); + Assert.Throws("value", () => f.ToBoolean(null)); + Assert.Throws("value", () => f.ToByte(null)); + Assert.Throws("value", () => f.ToChar(null)); + Assert.Throws("value", () => f.ToDateTime(null)); + Assert.Throws("value", () => f.ToDecimal(null)); + Assert.Throws("value", () => f.ToDouble(null)); + Assert.Throws("value", () => f.ToInt16(null)); + Assert.Throws("value", () => f.ToInt32(null)); + Assert.Throws("value", () => f.ToInt64(null)); + Assert.Throws("value", () => f.ToSByte(null)); + Assert.Throws("value", () => f.ToSingle(null)); + Assert.Throws("value", () => f.ToString(null)); + Assert.Throws("value", () => f.ToUInt16(null)); + Assert.Throws("value", () => f.ToUInt32(null)); + Assert.Throws("value", () => f.ToUInt64(null)); + } + + [Fact] + public void ToMethods_ExpectedValue() + { + Assert.True(new FormatterConverter().ToBoolean("true")); + Assert.Equal((byte)42, new FormatterConverter().ToByte("42")); + Assert.Equal('c', new FormatterConverter().ToChar("c")); + Assert.Equal(new DateTime(2000, 1, 1), new FormatterConverter().ToDateTime(new DateTime(2000, 1, 1).ToString())); + Assert.Equal(1.2m, new FormatterConverter().ToDecimal("1.2")); + Assert.Equal(1.2, new FormatterConverter().ToDouble("1.2")); + Assert.Equal((short)42, new FormatterConverter().ToInt16("42")); + Assert.Equal(42, new FormatterConverter().ToInt32("42")); + Assert.Equal(42, new FormatterConverter().ToInt64("42")); + Assert.Equal((sbyte)42, new FormatterConverter().ToSByte("42")); + Assert.Equal(1.2f, new FormatterConverter().ToSingle("1.2")); + Assert.Equal("1.2", new FormatterConverter().ToString("1.2")); + Assert.Equal((ushort)42, new FormatterConverter().ToUInt16("42")); + Assert.Equal((uint)42, new FormatterConverter().ToUInt32("42")); + Assert.Equal((ulong)42, new FormatterConverter().ToUInt64("42")); + Assert.Equal(42, new FormatterConverter().Convert("42", TypeCode.Int32)); + } + + [Theory] + [InlineData("true", true)] + [InlineData("false", false)] + [InlineData("42", (byte)42)] + [InlineData("c", 'c')] + [InlineData("1.2", 1.2)] + [InlineData("42", (short)42)] + [InlineData("42", 42)] + [InlineData("42", (long)42)] + [InlineData("42", (sbyte)42)] + [InlineData("1.2", (float)1.2)] + [InlineData("1.2", "1.2")] + [InlineData("42", (ushort)42)] + [InlineData("42", (uint)42)] + [InlineData("42", (ulong)42)] + public void Convert_ExpectedValue(string input, object expected) + { + Assert.Equal(expected, new FormatterConverter().Convert(input, expected.GetType())); + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/FormatterServicesTests.cs b/src/System.Runtime.Serialization.Formatters/tests/FormatterServicesTests.cs new file mode 100644 index 000000000000..8bf332206779 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/FormatterServicesTests.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public class FormatterServicesTests + { + [Fact] + public void CheckTypeSecurity_Nop() + { + FormatterServices.CheckTypeSecurity(typeof(int), TypeFilterLevel.Full); + FormatterServices.CheckTypeSecurity(typeof(int), TypeFilterLevel.Low); + } + + [Fact] + public void GetSerializableMembers_InvalidArguments_ThrowsException() + { + Assert.Throws("type", () => FormatterServices.GetSerializableMembers(null)); + } + + [Fact] + public void GetSerializableMembers_Interface() + { + Assert.Equal(new MemberInfo[0], FormatterServices.GetSerializableMembers(typeof(IDisposable))); + } + + [Fact] + public void GetUninitializedObject_InvalidArguments_ThrowsException() + { + Assert.Throws("type", () => FormatterServices.GetUninitializedObject(null)); + Assert.Throws("type", () => FormatterServices.GetSafeUninitializedObject(null)); + } + + [Fact] + public void GetUninitializedObject_DoesNotRunConstructor() + { + Assert.Equal(42, new ObjectWithDefaultCtor().Value); + Assert.Equal(0, ((ObjectWithDefaultCtor)FormatterServices.GetUninitializedObject(typeof(ObjectWithDefaultCtor))).Value); + Assert.Equal(0, ((ObjectWithDefaultCtor)FormatterServices.GetSafeUninitializedObject(typeof(ObjectWithDefaultCtor))).Value); + } + + private class ObjectWithDefaultCtor + { + public int Value = 42; + } + + [Fact] + public void PopulateObjectMembers_InvalidArguments_ThrowsException() + { + Assert.Throws("obj", () => FormatterServices.PopulateObjectMembers(null, new MemberInfo[0], new object[0])); + Assert.Throws("members", () => FormatterServices.PopulateObjectMembers(new object(), null, new object[0])); + Assert.Throws("data", () => FormatterServices.PopulateObjectMembers(new object(), new MemberInfo[0], null)); + Assert.Throws(() => FormatterServices.PopulateObjectMembers(new object(), new MemberInfo[1], new object[2])); + Assert.Throws("members", () => FormatterServices.PopulateObjectMembers(new object(), new MemberInfo[1], new object[1])); + Assert.Throws(() => FormatterServices.PopulateObjectMembers(new object(), new MemberInfo[] { typeof(object).GetMethod("GetHashCode") }, new object[] { new object() })); + } + + [Fact] + public void GetObjectData_InvalidArguments_ThrowsException() + { + Assert.Throws("obj", () => FormatterServices.GetObjectData(null, new MemberInfo[0])); + Assert.Throws("members", () => FormatterServices.GetObjectData(new object(), null)); + Assert.Throws("members", () => FormatterServices.GetObjectData(new object(), new MemberInfo[1])); + Assert.Throws(() => FormatterServices.GetObjectData(new object(), new MethodInfo[] { typeof(object).GetMethod("GetHashCode") })); + } + + [Fact] + public void GetSurrogateForCyclicalReference_InvalidArguments_ThrowsException() + { + Assert.Throws("innerSurrogate", () => FormatterServices.GetSurrogateForCyclicalReference(null)); + } + + [Fact] + public void GetSurrogateForCyclicalReference_ValidSurrogate_GetsObject() + { + var surrogate = new NonSerializablePairSurrogate(); + ISerializationSurrogate newSurrogate = FormatterServices.GetSurrogateForCyclicalReference(surrogate); + Assert.NotNull(newSurrogate); + Assert.NotSame(surrogate, newSurrogate); + } + + [Fact] + public void GetTypeFromAssembly_InvalidArguments_ThrowsException() + { + Assert.Throws("assem", () => FormatterServices.GetTypeFromAssembly(null, "name")); + Assert.Null(FormatterServices.GetTypeFromAssembly(GetType().GetTypeInfo().Assembly, Guid.NewGuid().ToString("N"))); // non-existing type doesn't throw + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/FormatterTests.cs b/src/System.Runtime.Serialization.Formatters/tests/FormatterTests.cs new file mode 100644 index 000000000000..41acf660d1be --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/FormatterTests.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public class FormatterTests + { + [Fact] + public void DefaultCtor_ObjectsInitialized() + { + var f = new TestFormatter(); + + Assert.NotNull(f.m_idGenerator); + Assert.NotNull(f.m_objectQueue); + Assert.Equal(0, f.m_objectQueue.Count); + + bool firstTime; + Assert.Throws("obj", () => f.m_idGenerator.GetId(null, out firstTime)); + Assert.Throws("obj", () => f.m_idGenerator.HasId(null, out firstTime)); + } + + [Fact] + public void ScheduleAndGetObjects_ExpectedIDsAndOrder() + { + var f = new TestFormatter(); + + // null values don't count + long actualId; + Assert.Equal(0, f.Schedule(null)); + Assert.Equal(0, f.Schedule(null)); + Assert.Null(f.GetNext(out actualId)); + Assert.Equal(0, actualId); + + var objects = new object[] { new object(), new object(), new object() }; + + // Add each object for the first time + long nextExpectedId = 1; + foreach (var obj in objects) + { + Assert.Equal(nextExpectedId++, f.Schedule(obj)); + } + + // Adding them again should produce the same IDs + nextExpectedId = 1; + foreach (var obj in objects) + { + Assert.Equal(nextExpectedId++, f.Schedule(obj)); + } + + // Now retrieve them all + nextExpectedId = 1; + foreach (var obj in objects) + { + var actualObj = f.GetNext(out actualId); + Assert.Same(obj, actualObj); + Assert.Equal(nextExpectedId++, actualId); + } + } + + [Fact] + public void GetNext_UnexpectedObject_ThrowsException() + { + var f = new TestFormatter(); + f.m_objectQueue.Enqueue(new object()); + long id; + Assert.Throws(() => f.GetNext(out id)); + } + + [Fact] + public void WriteMember_InvokesProperMethod() + { + string calledMethod = null; + object result = null; + var f = new TestFormatter { WriteCallback = (name, val) => { calledMethod = name; result = val; } }; + + Action verify = (expectedMember, expectedValue) => + { + f.WriteMember("Member", expectedValue); + Assert.Equal(expectedMember, calledMethod); + Assert.Equal(expectedValue, result); + }; + verify("WriteBoolean", true); + verify("WriteByte", (byte)42); + verify("WriteChar", 'c'); + verify("WriteDateTime", DateTime.Now); + verify("WriteDecimal", 42m); + verify("WriteDouble", 1.2); + verify("WriteInt16", (short)42); + verify("WriteInt32", 42); + verify("WriteInt64", (long)42); + verify("WriteSByte", (sbyte)42); + verify("WriteSingle", 1.2f); + verify("WriteUInt16", (ushort)42); + verify("WriteUInt32", (uint)42); + verify("WriteUInt64", (ulong)42); + verify("WriteValueType", new KeyValuePair(1, 2)); + // verify("WriteTimeSpan", TimeSpan.FromSeconds(42)); // Fails on both desktop and core, getting routed as a normal ValueType: + verify("WriteValueType", TimeSpan.FromSeconds(42)); + verify("WriteObjectRef", new ObjectWithIntStringUShortUIntULongAndCustomObjectFields()); + verify("WriteObjectRef", null); + + f.WriteMember("Member", new[] { 1, 2, 3, 4, 5 }); + Assert.Equal("WriteArray", calledMethod); + Assert.Equal(new[] { 1, 2, 3, 4, 5 }, (int[])result); + } + + private sealed class TestFormatter : Formatter + { + public new object GetNext(out long objID) => base.GetNext(out objID); + public new long Schedule(object obj) => base.Schedule(obj); + public new void WriteMember(string memberName, object data) => base.WriteMember(memberName, data); + public new ObjectIDGenerator m_idGenerator => base.m_idGenerator; + public new Queue m_objectQueue => base.m_objectQueue; + public Action WriteCallback; + + protected override void WriteArray(object obj, string name, Type memberType) => WriteCallback?.Invoke("WriteArray", obj); + protected override void WriteBoolean(bool val, string name) => WriteCallback?.Invoke("WriteBoolean", val); + protected override void WriteByte(byte val, string name) => WriteCallback?.Invoke("WriteByte", val); + protected override void WriteChar(char val, string name) => WriteCallback?.Invoke("WriteChar", val); + protected override void WriteDateTime(DateTime val, string name) => WriteCallback?.Invoke("WriteDateTime", val); + protected override void WriteDecimal(decimal val, string name) => WriteCallback?.Invoke("WriteDecimal", val); + protected override void WriteDouble(double val, string name) => WriteCallback?.Invoke("WriteDouble", val); + protected override void WriteInt16(short val, string name) => WriteCallback?.Invoke("WriteInt16", val); + protected override void WriteInt32(int val, string name) => WriteCallback?.Invoke("WriteInt32", val); + protected override void WriteInt64(long val, string name) => WriteCallback?.Invoke("WriteInt64", val); + protected override void WriteObjectRef(object obj, string name, Type memberType) => WriteCallback?.Invoke("WriteObjectRef", obj); + protected override void WriteSByte(sbyte val, string name) => WriteCallback?.Invoke("WriteSByte", val); + protected override void WriteSingle(float val, string name) => WriteCallback?.Invoke("WriteSingle", val); + protected override void WriteTimeSpan(TimeSpan val, string name) => WriteCallback?.Invoke("WriteTimeSpan", val); + protected override void WriteUInt16(ushort val, string name) => WriteCallback?.Invoke("WriteUInt16", val); + protected override void WriteUInt32(uint val, string name) => WriteCallback?.Invoke("WriteUInt32", val); + protected override void WriteUInt64(ulong val, string name) => WriteCallback?.Invoke("WriteUInt64", val); + protected override void WriteValueType(object obj, string name, Type memberType) => WriteCallback?.Invoke("WriteValueType", obj); + + public override SerializationBinder Binder { get; set; } + public override StreamingContext Context { get; set; } + public override ISurrogateSelector SurrogateSelector { get; set; } + public override object Deserialize(Stream serializationStream) => null; + public override void Serialize(Stream serializationStream, object graph) { } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs b/src/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs new file mode 100644 index 000000000000..9e27718c9969 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/SerializationBinderTests.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public class SerializationBinderTests + { + [Fact] + public void BindToName_NullDefaults() + { + var b = new TrackAllBindToTypes(); + string assemblyName, typeName; + b.BindToName(typeof(string), out assemblyName, out typeName); + Assert.Null(assemblyName); + Assert.Null(typeName); + } + + [Fact] + public void BindToType_AllValuesTracked() + { + var s = new MemoryStream(); + var f = new BinaryFormatter(); + + f.Serialize(s, DayOfWeek.Monday); + s.Position = 0; + + var t = new TrackAllBindToTypes(); + f.Binder = t; + f.Deserialize(s); + + Assert.Contains(t.Binds, kvp => kvp.Value.Contains("System.DayOfWeek")); + } + + private class TrackAllBindToTypes : SerializationBinder + { + public readonly List> Binds = new List>(); + + public override Type BindToType(string assemblyName, string typeName) + { + Binds.Add(new KeyValuePair(assemblyName, typeName)); + return Assembly.Load(new AssemblyName(assemblyName)).GetType(typeName); + } + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/BasicTests.cs b/src/System.Runtime.Serialization.Formatters/tests/SerializationInfoTests.cs similarity index 67% rename from src/System.Runtime.Serialization.Formatters/tests/BasicTests.cs rename to src/System.Runtime.Serialization.Formatters/tests/SerializationInfoTests.cs index 48db8937e47a..e19c7257ed6f 100644 --- a/src/System.Runtime.Serialization.Formatters/tests/BasicTests.cs +++ b/src/System.Runtime.Serialization.Formatters/tests/SerializationInfoTests.cs @@ -7,13 +7,13 @@ namespace System.Runtime.Serialization.Formatters.Tests { - public class BinaryFormatterTests + public class SerializationInfoTests { [Fact] public void SerializationInfoAddGet() { var value = new Serializable(); - var si = new SerializationInfo(typeof(Serializable), FormatterConverter.Default); + var si = new SerializationInfo(typeof(Serializable), new FormatterConverter()); var sc = new StreamingContext(); value.GetObjectData(si, sc); @@ -46,7 +46,7 @@ public void SerializationInfoAddGet() public void SerializationInfoEnumerate() { var value = new Serializable(); - var si = new SerializationInfo(typeof(Serializable), FormatterConverter.Default); + var si = new SerializationInfo(typeof(Serializable), new FormatterConverter()); var sc = new StreamingContext(); value.GetObjectData(si, sc); @@ -77,7 +77,7 @@ public void SerializationInfoEnumerate() [Fact] public void NegativeAddValueTwice() { - var si = new SerializationInfo(typeof(Serializable), FormatterConverter.Default); + var si = new SerializationInfo(typeof(Serializable), new FormatterConverter()); Assert.Throws(() => { si.AddValue("bool", true); @@ -97,14 +97,14 @@ public void NegativeAddValueTwice() [Fact] public void NegativeValueNotFound() { - var si = new SerializationInfo(typeof(Serializable), FormatterConverter.Default); + var si = new SerializationInfo(typeof(Serializable), new FormatterConverter()); Assert.Throws(() => { si.AddValue("a", 1); si.GetInt32("b"); }); - si = new SerializationInfo(typeof(Serializable), FormatterConverter.Default); + si = new SerializationInfo(typeof(Serializable), new FormatterConverter()); try { si.AddValue("a", 1); @@ -140,94 +140,4 @@ public void GetObjectData(SerializationInfo info, StreamingContext context) info.AddValue("datetime", DateTime.MaxValue); } } - - internal class FormatterConverter : IFormatterConverter - { - public static readonly FormatterConverter Default = new FormatterConverter(); - - public object Convert(object value, TypeCode typeCode) - { - throw new NotImplementedException(); - } - - public object Convert(object value, Type type) - { - 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(); - } - } } diff --git a/src/System.Runtime.Serialization.Formatters/tests/SerializationTypes.cs b/src/System.Runtime.Serialization.Formatters/tests/SerializationTypes.cs new file mode 100644 index 000000000000..c7304830cb2e --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/SerializationTypes.cs @@ -0,0 +1,459 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +[Serializable] +public struct TypeWithoutNamespace { } + +namespace System.Runtime.Serialization.Formatters.Tests +{ + [Serializable] + public sealed class SealedObjectWithIntStringFields + { + public int Member1; + public string Member2; + public string Member3; + + public override bool Equals(object obj) + { + var o = obj as SealedObjectWithIntStringFields; + if (o == null) return false; + return + EqualityComparer.Default.Equals(Member1, o.Member1) && + EqualityComparer.Default.Equals(Member2, o.Member2) && + EqualityComparer.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) + { + var o = obj as ObjectWithIntStringUShortUIntULongAndCustomObjectFields; + if (o == null) return false; + + return + EqualityComparer.Default.Equals(Member1, o.Member1) && + EqualityComparer.Default.Equals(Member2, o.Member2) && + EqualityComparer.Default.Equals(_member3, o._member3) && + EqualityComparer.Default.Equals(Member4, o.Member4) && + EqualityComparer.Default.Equals(Member4shared, o.Member4shared) && + EqualityComparer.Default.Equals(Member5, o.Member5) && + EqualityComparer.Default.Equals(Member6, o.Member6) && + EqualityComparer.Default.Equals(str1, o.str1) && + EqualityComparer.Default.Equals(str2, o.str2) && + EqualityComparer.Default.Equals(str3, o.str3) && + EqualityComparer.Default.Equals(str4, o.str4) && + EqualityComparer.Default.Equals(u16, o.u16) && + EqualityComparer.Default.Equals(u16, o.u16) && + EqualityComparer.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 + { + public int X; + public int Y; + + public Point(int x, int y) + { + X = x; + Y = y; + } + + public override bool Equals(object obj) + { + var o = obj as Point; + if (o == null) return false; + return X == o.X && Y == o.Y; + } + public override int GetHashCode() => 1; + } + + [Serializable] + public class Tree + { + public Tree(T value, Tree left, Tree right) + { + Value = value; + Left = left; + Right = right; + } + + public T Value { get; } + public Tree Left { get; } + public Tree Right { get; } + + public override bool Equals(object obj) + { + Tree o = obj as Tree; + if (o == null) return false; + + return + EqualityComparer.Default.Equals(Value, o.Value) && + EqualityComparer>.Default.Equals(Left, o.Left) && + EqualityComparer>.Default.Equals(Right, o.Right) && + // make sure the branches aren't actually the exact same object + (Left == null || !ReferenceEquals(Left, o.Left)) && + (Right == null || !ReferenceEquals(Right, o.Right)); + } + + public override int GetHashCode() => 1; + } + + [Serializable] + public class Graph + { + public T Value; + public Graph[] Links; + + public override bool Equals(object obj) + { + Graph o = obj as Graph; + if (o == null) return false; + + var toExplore = new Stack, Graph>>(); + toExplore.Push(new KeyValuePair, Graph>(this, o)); + var seen1 = new HashSet>(new ObjectReferenceEqualityComparer()); + while (toExplore.Count > 0) + { + var cur = toExplore.Pop(); + if (!seen1.Add(cur.Key)) + { + continue; + } + + if (!EqualityComparer.Default.Equals(cur.Key.Value, cur.Value.Value)) + { + return false; + } + + if (Links == null || o.Links == 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>(Links[i], o.Links[i])); + } + } + + return true; + } + + public override int GetHashCode() => 1; + } + + [Serializable] + internal sealed class ObjectWithArrays + { + public int[] IntArray; + public string[] StringArray; + public Tree[] TreeArray; + public byte[] ByteArray; + public int[][] JaggedArray; + public int[,] MultiDimensionalArray; + + public override bool Equals(object obj) + { + ObjectWithArrays o = obj as ObjectWithArrays; + if (o == null) 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 + } + + public struct NonSerializableStruct + { + public int Value; + } + + public class NonSerializableClass + { + public int Value; + } + + [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 bool Equals(object obj) + { + if (!(obj is StructContainingArraysOfOtherStructs)) return false; + return EqualityHelpers.ArraysAreEqual(Nested, ((StructContainingArraysOfOtherStructs)obj).Nested); + } + + public override int GetHashCode() => 1; + } + + [Serializable] + public sealed class BasicISerializableObject : ISerializable + { + private NonSerializablePair _data; + + public BasicISerializableObject(int value1, string value2) + { + _data = new NonSerializablePair { Value1 = value1, Value2 = value2 }; + } + + public BasicISerializableObject(SerializationInfo info, StreamingContext context) + { + _data = new NonSerializablePair { 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) + { + var o = obj as BasicISerializableObject; + if (o == null) return false; + if (_data == null || o._data == null) return _data == o._data; + return _data.Value1 == o._data.Value1 && _data.Value2 == o._data.Value2; + } + + public override int GetHashCode() => 1; + } + + [Serializable] + public class IncrementCountsDuringRoundtrip + { + public int IncrementedDuringOnSerializingMethod; + public int IncrementedDuringOnSerializedMethod; + [NonSerialized] public int IncrementedDuringOnDeserializingMethod; + public int IncrementedDuringOnDeserializedMethod; + + public IncrementCountsDuringRoundtrip(string ignored) { } // non-default ctor so that we can observe changes from OnDeserializing + + [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++; + } + + internal sealed class NonSerializablePair + { + public T1 Value1; + public T2 Value2; + } + + internal sealed class NonSerializablePairSurrogate : ISerializationSurrogate + { + public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) + { + var pair = (NonSerializablePair)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)obj; + pair.Value1 = info.GetInt32("Value1"); + pair.Value2 = info.GetString("Value2"); + return pair; + } + } + + [Serializable] + public class Version1ClassWithoutField + { + } + + [Serializable] + public class Version2ClassWithoutOptionalField + { + public object Value; + } + + [Serializable] + public class Version2ClassWithOptionalField + { + [OptionalField(VersionAdded = 2)] + public object Value; + } + + internal sealed class ObjectReferenceEqualityComparer : IEqualityComparer + { + public new bool Equals(object x, object y) => ReferenceEquals(x, y); + public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); + } + + internal static class EqualityHelpers + { + public static bool ArraysAreEqual(T[] array1, T[] array2) + { + if (array1 == null || array2 == null) return array1 == array2; + if (array1.Length != array2.Length) return false; + for (int i = 0; i < array1.Length; i++) + { + if (!EqualityComparer.Default.Equals(array1[i], array2[i])) + { + return false; + } + } + return true; + } + + public static bool ArraysAreEqual(Array array1, Array array2) + { + if (array1 == null || array2 == 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.Default.Equals(e1.Current, e2.Current)) + { + return false; + } + } + return true; + } + + public static bool ArraysAreEqual(T[][] array1, T[][] array2) + { + if (array1 == null || array2 == 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 == null || sub2 == null && (sub1 != sub2)) return false; + if (sub1.Length != sub2.Length) return false; + for (int j = 0; j < sub1.Length; j++) + { + if (!EqualityComparer.Default.Equals(sub1[j], sub2[j])) + { + return false; + } + } + } + return true; + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/SurrogateSelectorTests.cs b/src/System.Runtime.Serialization.Formatters/tests/SurrogateSelectorTests.cs new file mode 100644 index 000000000000..1f3c1a07e993 --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/SurrogateSelectorTests.cs @@ -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. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public class SurrogateSelectorTests + { + [Fact] + public void AddSurrogate_InvalidArguments_ThrowExceptions() + { + var s = new SurrogateSelector(); + Assert.Throws("type", () => s.AddSurrogate(null, new StreamingContext(), new NonSerializablePairSurrogate())); + Assert.Throws("surrogate", () => s.AddSurrogate(typeof(NonSerializablePair), new StreamingContext(), null)); + } + + [Fact] + public void ChainSelector_InvalidArguments_ThrowExceptions() + { + var s1 = new SurrogateSelector(); + Assert.Throws("selector", () => s1.ChainSelector(null)); + Assert.Throws(() => s1.ChainSelector(s1)); + + var s2 = new SurrogateSelector(); + s2.ChainSelector(s1); + Assert.Throws("selector", () => s1.ChainSelector(s2)); + + var s3 = new SurrogateSelector(); + s3.ChainSelector(s2); + Assert.Throws("selector", () => s1.ChainSelector(s3)); + } + + [Fact] + public void GetNextSelector_ReturnsCorrectSelector() + { + var s1 = new SurrogateSelector(); + var s2 = new SurrogateSelector(); + s2.ChainSelector(s1); + Assert.Null(s1.GetNextSelector()); + Assert.Same(s1, s2.GetNextSelector()); + } + + [Fact] + public void GetSurrogate_InvalidArguments_ThrowExceptions() + { + var s = new SurrogateSelector(); + var c = new StreamingContext(); + ISurrogateSelector selector; + Assert.Throws("type", () => s.GetSurrogate(null, c, out selector)); + } + + [Fact] + public void GetSurrogate_ChainsToNextSelector() + { + var c = new StreamingContext(); + var s1 = new SurrogateSelector(); + var s2 = new SurrogateSelector(); + s2.ChainSelector(s1); + + ISurrogateSelector selector; + Assert.Null(s1.GetSurrogate(typeof(NonSerializablePair), c, out selector)); + Assert.Same(s1, selector); + + s1.AddSurrogate(typeof(NonSerializablePair), c, new NonSerializablePairSurrogate()); + Assert.NotNull(s1.GetSurrogate(typeof(NonSerializablePair), c, out selector)); + Assert.Same(s1, selector); + + Assert.NotNull(s2.GetSurrogate(typeof(NonSerializablePair), c, out selector)); + Assert.Same(s1, selector); + + s2.AddSurrogate(typeof(NonSerializablePair), c, new NonSerializablePairSurrogate()); + Assert.NotNull(s2.GetSurrogate(typeof(NonSerializablePair), c, out selector)); + Assert.Same(s2, selector); + + s2.RemoveSurrogate(typeof(NonSerializablePair), c); + Assert.NotNull(s2.GetSurrogate(typeof(NonSerializablePair), c, out selector)); + Assert.Same(s1, selector); + + s1.RemoveSurrogate(typeof(NonSerializablePair), c); + Assert.Null(s2.GetSurrogate(typeof(NonSerializablePair), c, out selector)); + Assert.Same(s1, selector); + } + + [Fact] + public void RemoveSurrogate_InvalidArguments_ThrowExceptions() + { + var s = new SurrogateSelector(); + var c = new StreamingContext(); + Assert.Throws("type", () => s.RemoveSurrogate(null, c)); + s.RemoveSurrogate(typeof(string), c); // no exception even if removal fails + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj b/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj index 223d2a3a48a8..191bc4316d9e 100644 --- a/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj +++ b/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj @@ -1,4 +1,4 @@ - + @@ -15,7 +15,14 @@ - + + + + + + + + diff --git a/src/System.Runtime.Serialization.Formatters/tests/project.json b/src/System.Runtime.Serialization.Formatters/tests/project.json index 4b390a289c7c..0499de9b7435 100644 --- a/src/System.Runtime.Serialization.Formatters/tests/project.json +++ b/src/System.Runtime.Serialization.Formatters/tests/project.json @@ -2,6 +2,7 @@ "dependencies": { "Microsoft.NETCore.Platforms": "1.0.2-beta-24315-03", "System.Collections": "4.0.12-beta-24315-03", + "System.Collections.NonGeneric": "4.0.2-beta-24315-03", "System.Diagnostics.Debug": "4.0.12-beta-24315-03", "System.ObjectModel": "4.0.13-beta-24315-03", "System.Reflection.TypeExtensions": "4.1.1-beta-24315-03",