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",