// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class: FormatterServices
**
**
** Purpose: Provides some static methods to aid with the implementation
**          of a Formatter for Serialization.
**
**
============================================================*/
namespace System.Runtime.Serialization {
    
    using System;
    using System.Reflection;
    using System.Collections;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Security;    
    using System.Security.Permissions;
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.Remoting;
    using System.Runtime.CompilerServices;
    using System.Runtime.Versioning;
    using System.Threading;
    using System.IO;
    using System.Text;
    using System.Globalization;
    using System.Diagnostics.Contracts;

    [System.Runtime.InteropServices.ComVisible(true)]
    public static class FormatterServices {
#if FEATURE_SERIALIZATION        
        internal static ConcurrentDictionary<MemberHolder, MemberInfo[]> m_MemberInfoTable = new ConcurrentDictionary<MemberHolder, MemberInfo[]>();
        [System.Security.SecurityCritical]
        private static bool unsafeTypeForwardersIsEnabled = false;

        [System.Security.SecurityCritical]
        private static volatile bool unsafeTypeForwardersIsEnabledInitialized = false;

        [SecuritySafeCritical]
        static FormatterServices()
        {
            // Static initialization touches security critical types, so we need an
            // explicit static constructor to allow us to mark it safe critical.
        }

        private static MemberInfo[] GetSerializableMembers(RuntimeType type) {
            // get the list of all fields
            FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            int countProper = 0;
            for (int i = 0;  i < fields.Length; i++) {
                if ((fields[i].Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized)
                    continue;
                countProper++;
            }
            if (countProper != fields.Length) {
                FieldInfo[] properFields = new FieldInfo[countProper];
                countProper = 0;
                for (int i = 0;  i < fields.Length; i++) {
                    if ((fields[i].Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized)
                        continue;
                    properFields[countProper] = fields[i];
                    countProper++;
                }
                return properFields;
            }
            else
                return fields;
        }

        private static bool CheckSerializable(RuntimeType type) {
            if (type.IsSerializable) {
                return true;
            }
            return false;
        }

        private static MemberInfo[] InternalGetSerializableMembers(RuntimeType type) {
            List<SerializationFieldInfo> allMembers = null;
            MemberInfo[] typeMembers;
            FieldInfo [] typeFields;
            RuntimeType parentType;

            Contract.Assert((object)type != null, "[GetAllSerializableMembers]type!=null");
            
            //<

            if (type.IsInterface) {
                return new MemberInfo[0];
            }

            if (!(CheckSerializable(type))) {
                    throw new SerializationException(Environment.GetResourceString("Serialization_NonSerType", type.FullName, type.Module.Assembly.FullName));
            }
          
            //Get all of the serializable members in the class to be serialized.
            typeMembers = GetSerializableMembers(type);

            //If this class doesn't extend directly from object, walk its hierarchy and 
            //get all of the private and assembly-access fields (e.g. all fields that aren't
            //virtual) and include them in the list of things to be serialized.  
            parentType = (RuntimeType)(type.BaseType);
            if (parentType != null && parentType != (RuntimeType)typeof(Object)) {
                RuntimeType[] parentTypes = null;
                int parentTypeCount = 0;
                bool classNamesUnique = GetParentTypes(parentType, out parentTypes, out parentTypeCount);
                if (parentTypeCount > 0){
                    allMembers = new List<SerializationFieldInfo>();
                    for (int i = 0; i < parentTypeCount;i++){
                        parentType = parentTypes[i];
                        if (!CheckSerializable(parentType)) {
                                throw new SerializationException(Environment.GetResourceString("Serialization_NonSerType", parentType.FullName, parentType.Module.Assembly.FullName));
                        }

                        typeFields = parentType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
                        String typeName = classNamesUnique ? parentType.Name : parentType.FullName;
                        foreach (FieldInfo field in typeFields) {
                            // Family and Assembly fields will be gathered by the type itself.
                            if (!field.IsNotSerialized) {
                                allMembers.Add(new SerializationFieldInfo((RuntimeFieldInfo)field, typeName));
                            }
                        }
                    }
                    //If we actually found any new MemberInfo's, we need to create a new MemberInfo array and
                    //copy all of the members which we've found so far into that.
                    if (allMembers!=null && allMembers.Count>0) {
                        MemberInfo[] membersTemp = new MemberInfo[allMembers.Count + typeMembers.Length];
                        Array.Copy(typeMembers, membersTemp, typeMembers.Length);
                        ((ICollection)allMembers).CopyTo(membersTemp, typeMembers.Length);
                        typeMembers = membersTemp;
                    }
                }
            }
            return typeMembers;
        }

        private static bool GetParentTypes(RuntimeType parentType, out RuntimeType[] parentTypes, out int parentTypeCount){
            //Check if there are any dup class names. Then we need to include as part of
            //typeName to prefix the Field names in SerializationFieldInfo
            /*out*/ parentTypes = null;
            /*out*/ parentTypeCount = 0;
            bool unique = true;
            RuntimeType objectType = (RuntimeType)typeof(object);
            for (RuntimeType t1 = parentType; t1 != objectType; t1 = (RuntimeType)t1.BaseType)
            {
#if MONO
                // This should never happen
                if (t1 == null) {
                    throw new InvalidOperationException($"Type '{parentType}' of type '{parentType?.GetType()}' does not derive from System.Object");
                }
#endif

                if (t1.IsInterface) continue;
                string t1Name = t1.Name;
                for(int i=0;unique && i<parentTypeCount;i++){
                    string t2Name = parentTypes[i].Name;
                    if (t2Name.Length == t1Name.Length && t2Name[0] == t1Name[0] && t1Name == t2Name){
                        unique = false;
                        break;
                    }
                }
                //expand array if needed
                if (parentTypes == null || parentTypeCount == parentTypes.Length){
                    RuntimeType[] tempParentTypes = new RuntimeType[Math.Max(parentTypeCount*2, 12)];
                    if (parentTypes != null)
                        Array.Copy(parentTypes, 0, tempParentTypes, 0, parentTypeCount);
                    parentTypes = tempParentTypes;
                }
                parentTypes[parentTypeCount++] = t1;
            }
            return unique;
        }

        // Get all of the Serializable members for a particular class.  For all practical intents and
        // purposes, this is the non-transient, non-static members (fields and properties).  In order to
        // be included, properties must have both a getter and a setter.  N.B.: A class
        // which implements ISerializable or has a serialization surrogate may not use all of these members
        // (or may have additional members).
        [System.Security.SecurityCritical]  // auto-generated_required
        public static MemberInfo[] GetSerializableMembers(Type type) {
            return GetSerializableMembers(type, new StreamingContext(StreamingContextStates.All));
        }

        // Get all of the Serializable Members for a particular class.  If we're not cloning, this is all
        // non-transient, non-static fields.  If we are cloning, include the transient fields as well since
        // we know that we're going to live inside of the same context.
        [System.Security.SecurityCritical]  // auto-generated_required
        public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context) {    
            if ((object)type==null) {
                throw new ArgumentNullException("type");
            }
            Contract.EndContractBlock();

            if (!(type is RuntimeType)) {
                throw new SerializationException(Environment.GetResourceString("Serialization_InvalidType", type.ToString()));
            }
    
            MemberHolder mh = new MemberHolder(type, context);
            
            //If we've already gathered the members for this type, just return them.
            MemberInfo[] members = m_MemberInfoTable.GetOrAdd(mh, 
                _ => InternalGetSerializableMembers((RuntimeType)type));
                
            return members;
        }      
      
        static readonly Type[] advancedTypes = new Type[]{
            typeof(System.DelegateSerializationHolder),
#if FEATURE_REMOTING                        
            typeof(System.Runtime.Remoting.ObjRef),
            typeof(System.Runtime.Remoting.IEnvoyInfo),
            typeof(System.Runtime.Remoting.Lifetime.ISponsor),
#endif            
        };
  
        public static void CheckTypeSecurity(Type t,  TypeFilterLevel securityLevel) {            
            if (securityLevel == TypeFilterLevel.Low){
                for(int i=0;i<advancedTypes.Length;i++){
                    if (advancedTypes[i].IsAssignableFrom(t))
                        throw new SecurityException(Environment.GetResourceString("Serialization_TypeSecurity", advancedTypes[i].FullName, t.FullName));
                }                  
            }
        }    
#endif // FEATURE_SERIALIZATION
    
        // Gets a new instance of the object.  The entire object is initalized to 0 and no 
        // constructors have been run. **THIS MEANS THAT THE OBJECT MAY NOT BE IN A STATE
        // CONSISTENT WITH ITS INTERNAL REQUIREMENTS** This method should only be used for
        // deserialization when the user intends to immediately populate all fields.  This method
        // will not create an unitialized string because it is non-sensical to create an empty
        // instance of an immutable type.
        //
        [System.Security.SecurityCritical]  // auto-generated_required
        public static Object GetUninitializedObject(Type type) {
            if ((object)type == null) {
                throw new ArgumentNullException("type");
            }
            Contract.EndContractBlock();
    
            if (!(type is RuntimeType)) {
                throw new SerializationException(Environment.GetResourceString("Serialization_InvalidType", type.ToString()));
            }

            return nativeGetUninitializedObject((RuntimeType)type);
        }
    
        [System.Security.SecurityCritical]  // auto-generated_required
        public static Object GetSafeUninitializedObject(Type type) {
             if ((object)type == null) {
                throw new ArgumentNullException("type");
            }
             Contract.EndContractBlock();
    
            if (!(type is RuntimeType)) {
                throw new SerializationException(Environment.GetResourceString("Serialization_InvalidType", type.ToString()));
            }
#if FEATURE_REMOTING            
            if (Object.ReferenceEquals(type, typeof(System.Runtime.Remoting.Messaging.ConstructionCall)) || 
                Object.ReferenceEquals(type, typeof(System.Runtime.Remoting.Messaging.LogicalCallContext)) ||
                Object.ReferenceEquals(type, typeof(System.Runtime.Remoting.Contexts.SynchronizationAttribute)))
                 return nativeGetUninitializedObject((RuntimeType)type);                                    
#endif

            try {                            
                return nativeGetSafeUninitializedObject((RuntimeType)type);                    
            }
            catch(SecurityException e) {                
                throw new SerializationException(Environment.GetResourceString("Serialization_Security",  type.FullName), e);
            }                                        
        }

#if MONO
        private static Object nativeGetUninitializedObject(RuntimeType type)
        {
            return System.Runtime.Remoting.Activation.ActivationServices.AllocateUninitializedClassInstance (type);
        }

        private static Object nativeGetSafeUninitializedObject(RuntimeType type)
        {
            return System.Runtime.Remoting.Activation.ActivationServices.AllocateUninitializedClassInstance (type);
        }
#else
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        private static extern Object nativeGetSafeUninitializedObject(RuntimeType type);
    
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        private static extern Object nativeGetUninitializedObject(RuntimeType type);
#endif
#if FEATURE_SERIALIZATION
#if MONO
        static bool GetEnableUnsafeTypeForwarders ()
        {
            return false;
        }
#else
        [System.Security.SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        private static extern bool GetEnableUnsafeTypeForwarders();
#endif

        [SecuritySafeCritical]
        internal static bool UnsafeTypeForwardersIsEnabled()
        {
            if (!unsafeTypeForwardersIsEnabledInitialized)
            {
                unsafeTypeForwardersIsEnabled = GetEnableUnsafeTypeForwarders();
                unsafeTypeForwardersIsEnabledInitialized = true;
            }

            return unsafeTypeForwardersIsEnabled;
        }
#endif
        private static Binder s_binder = Type.DefaultBinder;
        [System.Security.SecurityCritical]
        internal static void SerializationSetValue(MemberInfo fi, Object target, Object value)
        {
            Contract.Requires(fi != null);

            RtFieldInfo rtField = fi as RtFieldInfo;

            if (rtField != null)
            {
                rtField.CheckConsistency(target);
                rtField.UnsafeSetValue(target, value, BindingFlags.Default, s_binder, null);
                return;
            }

            SerializationFieldInfo serField = fi as SerializationFieldInfo;
            if (serField != null)
            {
                serField.InternalSetValue(target, value, BindingFlags.Default, s_binder, null);
                return;
            }

            throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFieldInfo"));
        }

        // Fill in the members of obj with the data contained in data.
        // Returns the number of members populated.
        //
        [System.Security.SecurityCritical]  // auto-generated_required
        public static Object PopulateObjectMembers(Object obj, MemberInfo[] members, Object[] data) {
            if (obj==null) {
                throw new ArgumentNullException("obj");
            }

            if (members==null) {
                throw new ArgumentNullException("members");
            }

            if (data==null) {
                throw new ArgumentNullException("data");
            }

            if (members.Length!=data.Length) {
                throw new ArgumentException(Environment.GetResourceString("Argument_DataLengthDifferent"));
            }
            Contract.EndContractBlock();

            MemberInfo mi;

            BCLDebug.Trace("SER", "[PopulateObjectMembers]Enter.");

            for (int i=0; i<members.Length; i++) {
                mi = members[i];
    
                if (mi==null) {
                    throw new ArgumentNullException("members", Environment.GetResourceString("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.
                if (data[i]!=null) {
                    if (mi.MemberType==MemberTypes.Field) {
                        SerializationSetValue(mi, obj, data[i]);
                    } else {
                        throw new SerializationException(Environment.GetResourceString("Serialization_UnknownMemberInfo"));
                    }

                    BCLDebug.Trace("SER", "[PopulateObjectMembers]\tType:", obj.GetType(), "\tMember:", 
                                   members[i].Name, " with member type: ", ((FieldInfo)members[i]).FieldType);
                }
                //Console.WriteLine("X");
            }
            
            BCLDebug.Trace("SER", "[PopulateObjectMembers]Leave.");

            return obj;
        }
    
        // Extracts the data from obj.  members is the array of members which we wish to
        // extract (must be FieldInfos or PropertyInfos).  For each supplied member, extract the matching value and
        // return it in a Object[] of the same size.
        //
        [System.Security.SecurityCritical]  // auto-generated_required
        public static Object[] GetObjectData(Object obj, MemberInfo[] members) {
    
            if (obj==null) {
                throw new ArgumentNullException("obj");
            }
    
            if (members==null) {
                throw new ArgumentNullException("members");
            }
            Contract.EndContractBlock();
            
            int numberOfMembers = members.Length;
    
            Object[] data = new Object[numberOfMembers];
            MemberInfo mi;
    
            for (int i=0; i<numberOfMembers; i++) {
                mi=members[i];
    
                if (mi==null) {
                    throw new ArgumentNullException("members", Environment.GetResourceString("ArgumentNull_NullMember", i));
                }
    
                if (mi.MemberType==MemberTypes.Field) {
                    Contract.Assert(mi is RuntimeFieldInfo || mi is SerializationFieldInfo,
                                    "[FormatterServices.GetObjectData]mi is RuntimeFieldInfo || mi is SerializationFieldInfo.");

                    RtFieldInfo rfi = mi as RtFieldInfo;
                    if (rfi != null) {
                        rfi.CheckConsistency(obj);
                        data[i] = rfi.UnsafeGetValue(obj);
                    } else {
                        data[i] = ((SerializationFieldInfo)mi).InternalGetValue(obj);
                    }
                } else {
                    throw new SerializationException(Environment.GetResourceString("Serialization_UnknownMemberInfo"));
                }
            }
    
            return data;
        }

        [System.Security.SecurityCritical]  // auto-generated_required
        [System.Runtime.InteropServices.ComVisible(false)]
        public static ISerializationSurrogate GetSurrogateForCyclicalReference(ISerializationSurrogate innerSurrogate)
        {
            if (innerSurrogate == null)
                throw new ArgumentNullException("innerSurrogate");
            Contract.EndContractBlock();
            return new SurrogateForCyclicalReference(innerSurrogate);
        }

        /*=============================GetTypeFromAssembly==============================
        **Action:
        **Returns:
        **Arguments:
        **Exceptions:
        ==============================================================================*/
        [System.Security.SecurityCritical]  // auto-generated_required
        public static Type GetTypeFromAssembly(Assembly assem, String name) {
            if (assem==null)
                throw new ArgumentNullException("assem");
            Contract.EndContractBlock();
            return assem.GetType(name, false, false);
        }
    
        /*============================LoadAssemblyFromString============================
        **Action: Loads an assembly from a given string.  The current assembly loading story
        **        is quite confusing.  If the assembly is in the fusion cache, we can load it
        **        using the stringized-name which we transmitted over the wire.  If that fails,
        **        we try for a lookup of the assembly using the simple name which is the first
        **        part of the assembly name.  If we can't find it that way, we'll return null
        **        as our failure result.
        **Returns: The loaded assembly or null if it can't be found.
        **Arguments: assemblyName -- The stringized assembly name.
        **Exceptions: None
        ==============================================================================*/
        internal static Assembly LoadAssemblyFromString(String assemblyName) {
            //
            // Try using the stringized assembly name to load from the fusion cache.
            //
            BCLDebug.Trace("SER", "[LoadAssemblyFromString]Looking for assembly: ", assemblyName);
            Assembly found = Assembly.Load(assemblyName);
            return found;
        }

        internal static Assembly LoadAssemblyFromStringNoThrow(String assemblyName) {
            try {
                return LoadAssemblyFromString(assemblyName);
            }
            catch (Exception e){
                BCLDebug.Trace("SER", "[LoadAssemblyFromString]", e.ToString());
            }
            return null;
        }

        internal static string GetClrAssemblyName(Type type, out bool hasTypeForwardedFrom) {
            if ((object)type == null) {
                throw new ArgumentNullException("type");
            }

            object[] typeAttributes = type.GetCustomAttributes(typeof(TypeForwardedFromAttribute), false);
            if (typeAttributes != null && typeAttributes.Length > 0) {
                hasTypeForwardedFrom = true;
                TypeForwardedFromAttribute typeForwardedFromAttribute = (TypeForwardedFromAttribute)typeAttributes[0];
                return typeForwardedFromAttribute.AssemblyFullName;
            }
            else {
                hasTypeForwardedFrom = false;
                return type.Assembly.FullName;
            }
        }

        internal static string GetClrTypeFullName(Type type) {
            if (type.IsArray) {
                return GetClrTypeFullNameForArray(type);
            }
            else {
                return GetClrTypeFullNameForNonArrayTypes(type);
            }
        }

        static string GetClrTypeFullNameForArray(Type type) {
            int rank = type.GetArrayRank();
            if (rank == 1)
            {
                return String.Format(CultureInfo.InvariantCulture, "{0}{1}", GetClrTypeFullName(type.GetElementType()), "[]");
            }
            else
            {
                StringBuilder builder = new StringBuilder(GetClrTypeFullName(type.GetElementType())).Append("[");
                for (int commaIndex = 1; commaIndex < rank; commaIndex++)
                {
                    builder.Append(",");
                }
                builder.Append("]");
                return builder.ToString();
            }
        }

        static string GetClrTypeFullNameForNonArrayTypes(Type type) {
            if (!type.IsGenericType) {
                return type.FullName;
            }

            Type[] genericArguments = type.GetGenericArguments();
            StringBuilder builder = new StringBuilder(type.GetGenericTypeDefinition().FullName).Append("[");
            bool hasTypeForwardedFrom;

            foreach (Type genericArgument in genericArguments) {
                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
    {
        ISerializationSurrogate innerSurrogate;
        internal SurrogateForCyclicalReference(ISerializationSurrogate innerSurrogate)
        {
            if (innerSurrogate == null)
                throw new ArgumentNullException("innerSurrogate");
            this.innerSurrogate = innerSurrogate;
        }

        [System.Security.SecurityCritical]  // auto-generated        
        public void GetObjectData(Object obj, SerializationInfo info, StreamingContext context)
        {
            innerSurrogate.GetObjectData(obj, info, context);
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        public Object SetObjectData(Object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
        {
            return innerSurrogate.SetObjectData(obj, info, context, selector);
        }
    }
}





