// 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 { using System; using System.Collections; using System.Diagnostics; using System.Collections.Generic; using System.IO; using System.Globalization; using System.Reflection; using System.Threading; using Microsoft.Xml; using DataContractDictionary = System.Collections.Generic.Dictionary<Microsoft.Xml.XmlQualifiedName, DataContract>; using System.Security; using System.Linq; #if USE_REFEMIT || NET_NATIVE public sealed class ClassDataContract : DataContract #else internal sealed class ClassDataContract : DataContract #endif { /// <SecurityNote> /// Review - XmlDictionaryString(s) representing the XML namespaces for class members. /// statically cached and used from IL generated code. should ideally be Critical. /// marked SecurityRequiresReview to be callable from transparent IL generated code. /// not changed to property to avoid regressing performance; any changes to initalization should be reviewed. /// </SecurityNote> public XmlDictionaryString[] ContractNamespaces; /// <SecurityNote> /// Review - XmlDictionaryString(s) representing the XML element names for class members. /// statically cached and used from IL generated code. should ideally be Critical. /// marked SecurityRequiresReview to be callable from transparent IL generated code. /// not changed to property to avoid regressing performance; any changes to initalization should be reviewed. /// </SecurityNote> public XmlDictionaryString[] MemberNames; /// <SecurityNote> /// Review - XmlDictionaryString(s) representing the XML namespaces for class members. /// statically cached and used when calling IL generated code. should ideally be Critical. /// marked SecurityRequiresReview to be callable from transparent code. /// not changed to property to avoid regressing performance; any changes to initalization should be reviewed. /// </SecurityNote> public XmlDictionaryString[] MemberNamespaces; [SecurityCritical] /// <SecurityNote> /// Critical - XmlDictionaryString representing the XML namespaces for members of class. /// statically cached and used from IL generated code. /// </SecurityNote> private XmlDictionaryString[] _childElementNamespaces; [SecurityCritical] /// <SecurityNote> /// Critical - holds instance of CriticalHelper which keeps state that is cached statically for serialization. /// Static fields are marked SecurityCritical or readonly to prevent /// data from being modified or leaked to other components in appdomain. /// </SecurityNote> private ClassDataContractCriticalHelper _helper; private bool _isScriptObject; #if NET_NATIVE || !disabled public ClassDataContract() : base(new ClassDataContractCriticalHelper()) { InitClassDataContract(); } #endif /// <SecurityNote> /// Critical - initializes SecurityCritical field 'helper' /// Safe - doesn't leak anything /// </SecurityNote> [SecuritySafeCritical] internal ClassDataContract(Type type) : base(new ClassDataContractCriticalHelper(type)) { InitClassDataContract(); } [SecuritySafeCritical] /// <SecurityNote> /// Critical - initializes SecurityCritical field 'helper' /// Safe - doesn't leak anything /// </SecurityNote> private ClassDataContract(Type type, XmlDictionaryString ns, string[] memberNames) : base(new ClassDataContractCriticalHelper(type, ns, memberNames)) { InitClassDataContract(); } [SecurityCritical] /// <SecurityNote> /// Critical - initializes SecurityCritical fields; called from all constructors /// </SecurityNote> private void InitClassDataContract() { _helper = base.Helper as ClassDataContractCriticalHelper; this.ContractNamespaces = _helper.ContractNamespaces; this.MemberNames = _helper.MemberNames; this.MemberNamespaces = _helper.MemberNamespaces; _isScriptObject = _helper.IsScriptObject; } internal ClassDataContract BaseContract { /// <SecurityNote> /// Critical - fetches the critical baseContract property /// Safe - baseContract only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.BaseContract; } set { _helper.BaseContract = value; } } internal List<DataMember> Members { /// <SecurityNote> /// Critical - fetches the critical members property /// Safe - members only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.Members; } /// <SecurityNote> /// Critical - Sets the critical members property. /// Safe - Protected for write if contract has underlyingType. /// </SecurityNote> [SecurityCritical] set { _helper.Members = value; } } public XmlDictionaryString[] ChildElementNamespaces { /// <SecurityNote> /// Critical - fetches the critical childElementNamespaces property /// Safe - childElementNamespaces only needs to be protected for write; initialized in getter if null /// </SecurityNote> [SecuritySafeCritical] get { if (_childElementNamespaces == null) { lock (this) { if (_childElementNamespaces == null) { if (_helper.ChildElementNamespaces == null) { XmlDictionaryString[] tempChildElementamespaces = CreateChildElementNamespaces(); Interlocked.MemoryBarrier(); _helper.ChildElementNamespaces = tempChildElementamespaces; } _childElementNamespaces = _helper.ChildElementNamespaces; } } } return _childElementNamespaces; } #if NET_NATIVE set { _childElementNamespaces = value; } #endif } internal MethodInfo OnSerializing { /// <SecurityNote> /// Critical - fetches the critical onSerializing property /// Safe - onSerializing only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.OnSerializing; } } internal MethodInfo OnSerialized { /// <SecurityNote> /// Critical - fetches the critical onSerialized property /// Safe - onSerialized only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.OnSerialized; } } internal MethodInfo OnDeserializing { /// <SecurityNote> /// Critical - fetches the critical onDeserializing property /// Safe - onDeserializing only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.OnDeserializing; } } internal MethodInfo OnDeserialized { /// <SecurityNote> /// Critical - fetches the critical onDeserialized property /// Safe - onDeserialized only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.OnDeserialized; } } #if !NET_NATIVE public override DataContractDictionary KnownDataContracts { /// <SecurityNote> /// Critical - fetches the critical knownDataContracts property /// Safe - knownDataContracts only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.KnownDataContracts; } } #endif internal bool IsNonAttributedType { /// <SecurityNote> /// Critical - fetches the critical IsNonAttributedType property /// Safe - IsNonAttributedType only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] get { return _helper.IsNonAttributedType; } } #if NET_NATIVE public bool HasDataContract { [SecuritySafeCritical] get { return _helper.HasDataContract; } set { _helper.HasDataContract = value; } } public bool HasExtensionData { [SecuritySafeCritical] get { return _helper.HasExtensionData; } set { _helper.HasExtensionData = value; } } #endif internal bool IsKeyValuePairAdapter { [SecuritySafeCritical] get { return _helper.IsKeyValuePairAdapter; } } internal Type[] KeyValuePairGenericArguments { [SecuritySafeCritical] get { return _helper.KeyValuePairGenericArguments; } } internal ConstructorInfo KeyValuePairAdapterConstructorInfo { [SecuritySafeCritical] get { return _helper.KeyValuePairAdapterConstructorInfo; } } internal MethodInfo GetKeyValuePairMethodInfo { [SecuritySafeCritical] get { return _helper.GetKeyValuePairMethodInfo; } } /// <SecurityNote> /// Critical - fetches information about which constructor should be used to initialize non-attributed types that are valid for serialization /// Safe - only needs to be protected for write /// </SecurityNote> [SecuritySafeCritical] internal ConstructorInfo GetNonAttributedTypeConstructor() { return _helper.GetNonAttributedTypeConstructor(); } #if !NET_NATIVE internal XmlFormatClassWriterDelegate XmlFormatWriterDelegate { /// <SecurityNote> /// Critical - fetches the critical xmlFormatWriterDelegate property /// Safe - xmlFormatWriterDelegate only needs to be protected for write; initialized in getter if null /// </SecurityNote> [SecuritySafeCritical] get { if (_helper.XmlFormatWriterDelegate == null) { lock (this) { if (_helper.XmlFormatWriterDelegate == null) { XmlFormatClassWriterDelegate tempDelegate = new XmlFormatWriterGenerator().GenerateClassWriter(this); Interlocked.MemoryBarrier(); _helper.XmlFormatWriterDelegate = tempDelegate; } } } return _helper.XmlFormatWriterDelegate; } } #else public XmlFormatClassWriterDelegate XmlFormatWriterDelegate { get; set; } #endif #if !NET_NATIVE internal XmlFormatClassReaderDelegate XmlFormatReaderDelegate { /// <SecurityNote> /// Critical - fetches the critical xmlFormatReaderDelegate property /// Safe - xmlFormatReaderDelegate only needs to be protected for write; initialized in getter if null /// </SecurityNote> [SecuritySafeCritical] get { if (_helper.XmlFormatReaderDelegate == null) { lock (this) { if (_helper.XmlFormatReaderDelegate == null) { XmlFormatClassReaderDelegate tempDelegate = new XmlFormatReaderGenerator().GenerateClassReader(this); Interlocked.MemoryBarrier(); _helper.XmlFormatReaderDelegate = tempDelegate; } } } return _helper.XmlFormatReaderDelegate; } } #else public XmlFormatClassReaderDelegate XmlFormatReaderDelegate { get; set; } #endif internal static ClassDataContract CreateClassDataContractForKeyValue(Type type, XmlDictionaryString ns, string[] memberNames) { #if !NET_NATIVE return new ClassDataContract(type, ns, memberNames); #else ClassDataContract cdc = (ClassDataContract)DataContract.GetDataContractFromGeneratedAssembly(type); ClassDataContract cloned = cdc.Clone(); cloned.UpdateNamespaceAndMembers(type, ns, memberNames); return cloned; #endif } internal static void CheckAndAddMember(List<DataMember> members, DataMember memberContract, Dictionary<string, DataMember> memberNamesTable) { DataMember existingMemberContract; if (memberNamesTable.TryGetValue(memberContract.Name, out existingMemberContract)) { Type declaringType = memberContract.MemberInfo.DeclaringType; DataContract.ThrowInvalidDataContractException( string.Format((declaringType.GetTypeInfo().IsEnum ? SRSerialization.DupEnumMemberValue : SRSerialization.DupMemberName), existingMemberContract.MemberInfo.Name, memberContract.MemberInfo.Name, DataContract.GetClrTypeFullName(declaringType), memberContract.Name), declaringType); } memberNamesTable.Add(memberContract.Name, memberContract); members.Add(memberContract); } internal static XmlDictionaryString GetChildNamespaceToDeclare(DataContract dataContract, Type childType, XmlDictionary dictionary) { childType = DataContract.UnwrapNullableType(childType); if (!childType.GetTypeInfo().IsEnum && !Globals.TypeOfIXmlSerializable.IsAssignableFrom(childType) && DataContract.GetBuiltInDataContract(childType) == null && childType != Globals.TypeOfDBNull) { string ns = DataContract.GetStableName(childType).Namespace; if (ns.Length > 0 && ns != dataContract.Namespace.Value) return dictionary.Add(ns); } return null; } private static bool IsArraySegment(Type t) { return t.GetTypeInfo().IsGenericType && (t.GetGenericTypeDefinition() == typeof(ArraySegment<>)); } /// <SecurityNote> /// RequiresReview - callers may need to depend on isNonAttributedType for a security decision /// isNonAttributedType must be calculated correctly /// IsNonAttributedTypeValidForSerialization is used as part of the isNonAttributedType calculation and /// is therefore marked SRR /// Safe - does not let caller influence isNonAttributedType calculation; no harm in leaking value /// </SecurityNote> static internal bool IsNonAttributedTypeValidForSerialization(Type type) { if (type.IsArray) return false; if (type.GetTypeInfo().IsEnum) return false; if (type.IsGenericParameter) return false; if (Globals.TypeOfIXmlSerializable.IsAssignableFrom(type)) return false; if (type.IsPointer) return false; if (type.GetTypeInfo().IsAttributeDefined(Globals.TypeOfCollectionDataContractAttribute)) return false; Type[] interfaceTypes = type.GetInterfaces(); if (!IsArraySegment(type)) { foreach (Type interfaceType in interfaceTypes) { if (CollectionDataContract.IsCollectionInterface(interfaceType)) return false; } } if (type.GetTypeInfo().IsAttributeDefined(Globals.TypeOfDataContractAttribute)) return false; if (type.GetTypeInfo().IsValueType) { return type.GetTypeInfo().IsVisible; } else { return (type.GetTypeInfo().IsVisible && type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Array.Empty<Type>()) != null); } } private static string[] s_knownSerializableTypeNames = new string[] { "System.Collections.Queue", "System.Collections.Stack", "System.Globalization.CultureInfo", "System.Version", "System.Collections.Generic.KeyValuePair`2", "System.Collections.Generic.Queue`1", "System.Collections.Generic.Stack`1", "System.Collections.ObjectModel.ReadOnlyCollection`1", "System.Collections.ObjectModel.ReadOnlyDictionary`2", "System.Tuple`1", "System.Tuple`2", "System.Tuple`3", "System.Tuple`4", "System.Tuple`5", "System.Tuple`6", "System.Tuple`7", "System.Tuple`8", }; internal static bool IsKnownSerializableType(Type type) { // Applies to known types that DCS understands how to serialize/deserialize // // Ajdust for generic type if (type.GetTypeInfo().IsGenericType && !type.GetTypeInfo().IsGenericTypeDefinition) { type = type.GetGenericTypeDefinition(); } // Check for known types if (Enumerable.Contains(s_knownSerializableTypeNames, type.FullName)) { return true; } //Enable ClassDataContract to give support to Exceptions. if (Globals.TypeOfException.IsAssignableFrom(type)) return true; return false; } private XmlDictionaryString[] CreateChildElementNamespaces() { if (Members == null) return null; XmlDictionaryString[] baseChildElementNamespaces = null; if (this.BaseContract != null) baseChildElementNamespaces = this.BaseContract.ChildElementNamespaces; int baseChildElementNamespaceCount = (baseChildElementNamespaces != null) ? baseChildElementNamespaces.Length : 0; XmlDictionaryString[] childElementNamespaces = new XmlDictionaryString[Members.Count + baseChildElementNamespaceCount]; if (baseChildElementNamespaceCount > 0) Array.Copy(baseChildElementNamespaces, 0, childElementNamespaces, 0, baseChildElementNamespaces.Length); XmlDictionary dictionary = new XmlDictionary(); for (int i = 0; i < this.Members.Count; i++) { childElementNamespaces[i + baseChildElementNamespaceCount] = GetChildNamespaceToDeclare(this, this.Members[i].MemberType, dictionary); } return childElementNamespaces; } [SecuritySafeCritical] /// <SecurityNote> /// Critical - calls critical method on helper /// Safe - doesn't leak anything /// </SecurityNote> private void EnsureMethodsImported() { _helper.EnsureMethodsImported(); } public override void WriteXmlValue(XmlWriterDelegator xmlWriter, object obj, XmlObjectSerializerWriteContext context) { if (_isScriptObject) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidDataContractException(string.Format(SRSerialization.UnexpectedContractType, DataContract.GetClrTypeFullName(this.GetType()), DataContract.GetClrTypeFullName(UnderlyingType)))); } XmlFormatWriterDelegate(xmlWriter, obj, context, this); } public override object ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context) { if (_isScriptObject) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidDataContractException(string.Format(SRSerialization.UnexpectedContractType, DataContract.GetClrTypeFullName(this.GetType()), DataContract.GetClrTypeFullName(UnderlyingType)))); } xmlReader.Read(); object o = XmlFormatReaderDelegate(xmlReader, context, MemberNames, MemberNamespaces); xmlReader.ReadEndElement(); return o; } /// <SecurityNote> /// Review - calculates whether this class requires MemberAccessPermission for deserialization. /// since this information is used to determine whether to give the generated code access /// permissions to private members, any changes to the logic should be reviewed. /// </SecurityNote> internal bool RequiresMemberAccessForRead(SecurityException securityException) { EnsureMethodsImported(); if (!IsTypeVisible(UnderlyingType)) { if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractTypeNotPublic, DataContract.GetClrTypeFullName(UnderlyingType)), securityException)); } return true; } if (this.BaseContract != null && this.BaseContract.RequiresMemberAccessForRead(securityException)) return true; if (ConstructorRequiresMemberAccess(GetNonAttributedTypeConstructor())) { if (Globals.TypeOfScriptObject_IsAssignableFrom(UnderlyingType)) { return true; } if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustNonAttributedSerializableTypeNoPublicConstructor, DataContract.GetClrTypeFullName(UnderlyingType)), securityException)); } return true; } if (MethodRequiresMemberAccess(this.OnDeserializing)) { if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractOnDeserializingNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.OnDeserializing.Name), securityException)); } return true; } if (MethodRequiresMemberAccess(this.OnDeserialized)) { if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractOnDeserializedNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.OnDeserialized.Name), securityException)); } return true; } if (this.Members != null) { for (int i = 0; i < this.Members.Count; i++) { if (this.Members[i].RequiresMemberAccessForSet()) { if (securityException != null) { if (this.Members[i].MemberInfo is FieldInfo) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractFieldSetNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.Members[i].MemberInfo.Name), securityException)); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractPropertySetNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.Members[i].MemberInfo.Name), securityException)); } } return true; } } } return false; } /// <SecurityNote> /// Review - calculates whether this class requires MemberAccessPermission for serialization. /// since this information is used to determine whether to give the generated code access /// permissions to private members, any changes to the logic should be reviewed. /// </SecurityNote> internal bool RequiresMemberAccessForWrite(SecurityException securityException) { EnsureMethodsImported(); if (!IsTypeVisible(UnderlyingType)) { if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractTypeNotPublic, DataContract.GetClrTypeFullName(UnderlyingType)), securityException)); } return true; } if (this.BaseContract != null && this.BaseContract.RequiresMemberAccessForWrite(securityException)) return true; if (MethodRequiresMemberAccess(this.OnSerializing)) { if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractOnSerializingNotPublic, DataContract.GetClrTypeFullName(this.UnderlyingType), this.OnSerializing.Name), securityException)); } return true; } if (MethodRequiresMemberAccess(this.OnSerialized)) { if (securityException != null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractOnSerializedNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.OnSerialized.Name), securityException)); } return true; } if (this.Members != null) { for (int i = 0; i < this.Members.Count; i++) { if (this.Members[i].RequiresMemberAccessForGet()) { if (securityException != null) { if (this.Members[i].MemberInfo is FieldInfo) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractFieldGetNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.Members[i].MemberInfo.Name), securityException)); } else { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new SecurityException(string.Format( SRSerialization.PartialTrustDataContractPropertyGetNotPublic, DataContract.GetClrTypeFullName(UnderlyingType), this.Members[i].MemberInfo.Name), securityException)); } } return true; } } } return false; } [SecurityCritical] /// <SecurityNote> /// Critical - holds all state used for (de)serializing classes. /// since the data is cached statically, we lock down access to it. /// </SecurityNote> private class ClassDataContractCriticalHelper : DataContract.DataContractCriticalHelper { private ClassDataContract _baseContract; private List<DataMember> _members; private MethodInfo _onSerializing, _onSerialized; private MethodInfo _onDeserializing, _onDeserialized; #if !NET_NATIVE private DataContractDictionary _knownDataContracts; private bool _isKnownTypeAttributeChecked; #endif private bool _isMethodChecked; /// <SecurityNote> /// in serialization/deserialization we base the decision whether to Demand SerializationFormatter permission on this value and hasDataContract /// </SecurityNote> private bool _isNonAttributedType; /// <SecurityNote> /// in serialization/deserialization we base the decision whether to Demand SerializationFormatter permission on this value and isNonAttributedType /// </SecurityNote> private bool _hasDataContract; #if NET_NATIVE private bool _hasExtensionData; #endif private bool _isScriptObject; private XmlDictionaryString[] _childElementNamespaces; private XmlFormatClassReaderDelegate _xmlFormatReaderDelegate; private XmlFormatClassWriterDelegate _xmlFormatWriterDelegate; public XmlDictionaryString[] ContractNamespaces; public XmlDictionaryString[] MemberNames; public XmlDictionaryString[] MemberNamespaces; internal ClassDataContractCriticalHelper() : base() { } internal ClassDataContractCriticalHelper(Type type) : base(type) { XmlQualifiedName stableName = GetStableNameAndSetHasDataContract(type); if (type == Globals.TypeOfDBNull) { this.StableName = stableName; _members = new List<DataMember>(); XmlDictionary dictionary = new XmlDictionary(2); this.Name = dictionary.Add(StableName.Name); this.Namespace = dictionary.Add(StableName.Namespace); this.ContractNamespaces = this.MemberNames = this.MemberNamespaces = Array.Empty<XmlDictionaryString>(); EnsureMethodsImported(); return; } Type baseType = type.GetTypeInfo().BaseType; SetIsNonAttributedType(type); SetKeyValuePairAdapterFlags(type); this.IsValueType = type.GetTypeInfo().IsValueType; if (baseType != null && baseType != Globals.TypeOfObject && baseType != Globals.TypeOfValueType && baseType != Globals.TypeOfUri) { DataContract baseContract = DataContract.GetDataContract(baseType); if (baseContract is CollectionDataContract) this.BaseContract = ((CollectionDataContract)baseContract).SharedTypeContract as ClassDataContract; else this.BaseContract = baseContract as ClassDataContract; if (this.BaseContract != null && this.BaseContract.IsNonAttributedType && !_isNonAttributedType) { throw /*System.Runtime.Serialization.*/DiagnosticUtility.ExceptionUtility.ThrowHelperError (new InvalidDataContractException(string.Format(SRSerialization.AttributedTypesCannotInheritFromNonAttributedSerializableTypes, DataContract.GetClrTypeFullName(type), DataContract.GetClrTypeFullName(baseType)))); } } else this.BaseContract = null; { this.StableName = stableName; ImportDataMembers(); XmlDictionary dictionary = new XmlDictionary(2 + Members.Count); Name = dictionary.Add(StableName.Name); Namespace = dictionary.Add(StableName.Namespace); int baseMemberCount = 0; int baseContractCount = 0; if (BaseContract == null) { MemberNames = new XmlDictionaryString[Members.Count]; MemberNamespaces = new XmlDictionaryString[Members.Count]; ContractNamespaces = new XmlDictionaryString[1]; } else { baseMemberCount = BaseContract.MemberNames.Length; MemberNames = new XmlDictionaryString[Members.Count + baseMemberCount]; Array.Copy(BaseContract.MemberNames, 0, MemberNames, 0, baseMemberCount); MemberNamespaces = new XmlDictionaryString[Members.Count + baseMemberCount]; Array.Copy(BaseContract.MemberNamespaces, 0, MemberNamespaces, 0, baseMemberCount); baseContractCount = BaseContract.ContractNamespaces.Length; ContractNamespaces = new XmlDictionaryString[1 + baseContractCount]; Array.Copy(BaseContract.ContractNamespaces, 0, ContractNamespaces, 0, baseContractCount); } ContractNamespaces[baseContractCount] = Namespace; for (int i = 0; i < Members.Count; i++) { MemberNames[i + baseMemberCount] = dictionary.Add(Members[i].Name); MemberNamespaces[i + baseMemberCount] = Namespace; } } EnsureMethodsImported(); _isScriptObject = this.IsNonAttributedType && Globals.TypeOfScriptObject_IsAssignableFrom(this.UnderlyingType); } internal ClassDataContractCriticalHelper(Type type, XmlDictionaryString ns, string[] memberNames) : base(type) { this.StableName = new XmlQualifiedName(GetStableNameAndSetHasDataContract(type).Name, ns.Value); ImportDataMembers(); XmlDictionary dictionary = new XmlDictionary(1 + Members.Count); Name = dictionary.Add(StableName.Name); Namespace = ns; ContractNamespaces = new XmlDictionaryString[] { Namespace }; MemberNames = new XmlDictionaryString[Members.Count]; MemberNamespaces = new XmlDictionaryString[Members.Count]; for (int i = 0; i < Members.Count; i++) { Members[i].Name = memberNames[i]; MemberNames[i] = dictionary.Add(Members[i].Name); MemberNamespaces[i] = Namespace; } EnsureMethodsImported(); } private void EnsureIsReferenceImported(Type type) { DataContractAttribute dataContractAttribute; bool isReference = false; bool hasDataContractAttribute = TryGetDCAttribute(type, out dataContractAttribute); if (BaseContract != null) { if (hasDataContractAttribute && dataContractAttribute.IsReferenceSetExplicitly) { bool baseIsReference = this.BaseContract.IsReference; if ((baseIsReference && !dataContractAttribute.IsReference) || (!baseIsReference && dataContractAttribute.IsReference)) { DataContract.ThrowInvalidDataContractException( string.Format(SRSerialization.InconsistentIsReference, DataContract.GetClrTypeFullName(type), dataContractAttribute.IsReference, DataContract.GetClrTypeFullName(this.BaseContract.UnderlyingType), this.BaseContract.IsReference), type); } else { isReference = dataContractAttribute.IsReference; } } else { isReference = this.BaseContract.IsReference; } } else if (hasDataContractAttribute) { if (dataContractAttribute.IsReference) isReference = dataContractAttribute.IsReference; } if (isReference && type.GetTypeInfo().IsValueType) { DataContract.ThrowInvalidDataContractException( string.Format(SRSerialization.ValueTypeCannotHaveIsReference, DataContract.GetClrTypeFullName(type), true, false), type); return; } this.IsReference = isReference; } private void ImportDataMembers() { Type type = this.UnderlyingType; EnsureIsReferenceImported(type); List<DataMember> tempMembers = new List<DataMember>(); Dictionary<string, DataMember> memberNamesTable = new Dictionary<string, DataMember>(); MemberInfo[] memberInfos; bool isPodSerializable = !_isNonAttributedType || IsKnownSerializableType(type); if (!isPodSerializable) { memberInfos = type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public); } else { memberInfos = type.GetMembers(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } for (int i = 0; i < memberInfos.Length; i++) { MemberInfo member = memberInfos[i]; if (HasDataContract) { object[] memberAttributes = member.GetCustomAttributes(typeof(DataMemberAttribute), false).ToArray(); if (memberAttributes != null && memberAttributes.Length > 0) { if (memberAttributes.Length > 1) ThrowInvalidDataContractException(string.Format(SRSerialization.TooManyDataMembers, DataContract.GetClrTypeFullName(member.DeclaringType), member.Name)); DataMember memberContract = new DataMember(member); if (member is PropertyInfo) { PropertyInfo property = (PropertyInfo)member; MethodInfo getMethod = property.GetMethod; if (getMethod != null && IsMethodOverriding(getMethod)) continue; MethodInfo setMethod = property.SetMethod; if (setMethod != null && IsMethodOverriding(setMethod)) continue; if (getMethod == null) ThrowInvalidDataContractException(string.Format(SRSerialization.NoGetMethodForProperty, property.DeclaringType, property.Name)); if (setMethod == null) { if (!SetIfGetOnlyCollection(memberContract)) { ThrowInvalidDataContractException(string.Format(SRSerialization.NoSetMethodForProperty, property.DeclaringType, property.Name)); } } if (getMethod.GetParameters().Length > 0) ThrowInvalidDataContractException(string.Format(SRSerialization.IndexedPropertyCannotBeSerialized, property.DeclaringType, property.Name)); } else if (!(member is FieldInfo)) ThrowInvalidDataContractException(string.Format(SRSerialization.InvalidMember, DataContract.GetClrTypeFullName(type), member.Name)); DataMemberAttribute memberAttribute = (DataMemberAttribute)memberAttributes[0]; if (memberAttribute.IsNameSetExplicitly) { if (memberAttribute.Name == null || memberAttribute.Name.Length == 0) ThrowInvalidDataContractException(string.Format(SRSerialization.InvalidDataMemberName, member.Name, DataContract.GetClrTypeFullName(type))); memberContract.Name = memberAttribute.Name; } else memberContract.Name = member.Name; memberContract.Name = DataContract.EncodeLocalName(memberContract.Name); memberContract.IsNullable = DataContract.IsTypeNullable(memberContract.MemberType); memberContract.IsRequired = memberAttribute.IsRequired; if (memberAttribute.IsRequired && this.IsReference) { ThrowInvalidDataContractException( string.Format(SRSerialization.IsRequiredDataMemberOnIsReferenceDataContractType, DataContract.GetClrTypeFullName(member.DeclaringType), member.Name, true), type); } memberContract.EmitDefaultValue = memberAttribute.EmitDefaultValue; memberContract.Order = memberAttribute.Order; CheckAndAddMember(tempMembers, memberContract, memberNamesTable); } } else if (!isPodSerializable) { FieldInfo field = member as FieldInfo; PropertyInfo property = member as PropertyInfo; if ((field == null && property == null) || (field != null && field.IsInitOnly)) continue; object[] memberAttributes = member.GetCustomAttributes(typeof(IgnoreDataMemberAttribute), false).ToArray(); if (memberAttributes != null && memberAttributes.Length > 0) { if (memberAttributes.Length > 1) ThrowInvalidDataContractException(string.Format(SRSerialization.TooManyIgnoreDataMemberAttributes, DataContract.GetClrTypeFullName(member.DeclaringType), member.Name)); else continue; } DataMember memberContract = new DataMember(member); if (property != null) { MethodInfo getMethod = property.GetMethod; if (getMethod == null || IsMethodOverriding(getMethod) || getMethod.GetParameters().Length > 0) continue; MethodInfo setMethod = property.SetMethod; if (setMethod == null) { if (!SetIfGetOnlyCollection(memberContract)) continue; } else { if (!setMethod.IsPublic || IsMethodOverriding(setMethod)) continue; } } memberContract.Name = DataContract.EncodeLocalName(member.Name); memberContract.IsNullable = DataContract.IsTypeNullable(memberContract.MemberType); CheckAndAddMember(tempMembers, memberContract, memberNamesTable); } else { // [Serializible] and [NonSerialized] are deprecated on FxCore // Try to mimic the behavior by allowing certain known types to go through // POD types are fine also FieldInfo field = member as FieldInfo; if (CanSerializeMember(field)) { DataMember memberContract = new DataMember(member); memberContract.Name = DataContract.EncodeLocalName(member.Name); object[] optionalFields = null; if (optionalFields == null || optionalFields.Length == 0) { if (this.IsReference) { ThrowInvalidDataContractException( string.Format(SRSerialization.NonOptionalFieldMemberOnIsReferenceSerializableType, DataContract.GetClrTypeFullName(member.DeclaringType), member.Name, true), type); } memberContract.IsRequired = true; } memberContract.IsNullable = DataContract.IsTypeNullable(memberContract.MemberType); CheckAndAddMember(tempMembers, memberContract, memberNamesTable); } } } if (tempMembers.Count > 1) tempMembers.Sort(DataMemberComparer.Singleton); SetIfMembersHaveConflict(tempMembers); Interlocked.MemoryBarrier(); _members = tempMembers; } private static bool CanSerializeMember(FieldInfo field) { return field != null && field.FieldType != Globals.TypeOfObject; // Don't really know how to serialize plain System.Object instance; } private bool SetIfGetOnlyCollection(DataMember memberContract) { //OK to call IsCollection here since the use of surrogated collection types is not supported in get-only scenarios if (CollectionDataContract.IsCollection(memberContract.MemberType, false /*isConstructorRequired*/) && !memberContract.MemberType.GetTypeInfo().IsValueType) { memberContract.IsGetOnlyCollection = true; return true; } return false; } private void SetIfMembersHaveConflict(List<DataMember> members) { if (BaseContract == null) return; int baseTypeIndex = 0; List<Member> membersInHierarchy = new List<Member>(); foreach (DataMember member in members) { membersInHierarchy.Add(new Member(member, this.StableName.Namespace, baseTypeIndex)); } ClassDataContract currContract = BaseContract; while (currContract != null) { baseTypeIndex++; foreach (DataMember member in currContract.Members) { membersInHierarchy.Add(new Member(member, currContract.StableName.Namespace, baseTypeIndex)); } currContract = currContract.BaseContract; } IComparer<Member> comparer = DataMemberConflictComparer.Singleton; membersInHierarchy.Sort(comparer); for (int i = 0; i < membersInHierarchy.Count - 1; i++) { int startIndex = i; int endIndex = i; bool hasConflictingType = false; while (endIndex < membersInHierarchy.Count - 1 && String.CompareOrdinal(membersInHierarchy[endIndex].member.Name, membersInHierarchy[endIndex + 1].member.Name) == 0 && String.CompareOrdinal(membersInHierarchy[endIndex].ns, membersInHierarchy[endIndex + 1].ns) == 0) { membersInHierarchy[endIndex].member.ConflictingMember = membersInHierarchy[endIndex + 1].member; if (!hasConflictingType) { if (membersInHierarchy[endIndex + 1].member.HasConflictingNameAndType) { hasConflictingType = true; } else { hasConflictingType = (membersInHierarchy[endIndex].member.MemberType != membersInHierarchy[endIndex + 1].member.MemberType); } } endIndex++; } if (hasConflictingType) { for (int j = startIndex; j <= endIndex; j++) { membersInHierarchy[j].member.HasConflictingNameAndType = true; } } i = endIndex + 1; } } /// <SecurityNote> /// Critical - sets the critical hasDataContract field /// Safe - uses a trusted critical API (DataContract.GetStableName) to calculate the value /// does not accept the value from the caller /// </SecurityNote> //CSD16748 //[SecuritySafeCritical] private XmlQualifiedName GetStableNameAndSetHasDataContract(Type type) { return DataContract.GetStableName(type, out _hasDataContract); } /// <SecurityNote> /// RequiresReview - marked SRR because callers may need to depend on isNonAttributedType for a security decision /// isNonAttributedType must be calculated correctly /// SetIsNonAttributedType should not be called before GetStableNameAndSetHasDataContract since it /// is dependent on the correct calculation of hasDataContract /// Safe - does not let caller influence isNonAttributedType calculation; no harm in leaking value /// </SecurityNote> private void SetIsNonAttributedType(Type type) { _isNonAttributedType = !_hasDataContract && IsNonAttributedTypeValidForSerialization(type); } private static bool IsMethodOverriding(MethodInfo method) { return method.IsVirtual && ((method.Attributes & MethodAttributes.NewSlot) == 0); } internal void EnsureMethodsImported() { if (!_isMethodChecked && UnderlyingType != null) { lock (this) { if (!_isMethodChecked) { Type type = this.UnderlyingType; MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < methods.Length; i++) { MethodInfo method = methods[i]; Type prevAttributeType = null; ParameterInfo[] parameters = method.GetParameters(); //THese attributes are cut from mscorlib. if (IsValidCallback(method, parameters, Globals.TypeOfOnSerializingAttribute, _onSerializing, ref prevAttributeType)) _onSerializing = method; if (IsValidCallback(method, parameters, Globals.TypeOfOnSerializedAttribute, _onSerialized, ref prevAttributeType)) _onSerialized = method; if (IsValidCallback(method, parameters, Globals.TypeOfOnDeserializingAttribute, _onDeserializing, ref prevAttributeType)) _onDeserializing = method; if (IsValidCallback(method, parameters, Globals.TypeOfOnDeserializedAttribute, _onDeserialized, ref prevAttributeType)) _onDeserialized = method; } Interlocked.MemoryBarrier(); _isMethodChecked = true; } } } } private static bool IsValidCallback(MethodInfo method, ParameterInfo[] parameters, Type attributeType, MethodInfo currentCallback, ref Type prevAttributeType) { if (method.IsDefined(attributeType, false)) { if (currentCallback != null) DataContract.ThrowInvalidDataContractException(string.Format(SRSerialization.DuplicateCallback, method, currentCallback, DataContract.GetClrTypeFullName(method.DeclaringType), attributeType), method.DeclaringType); else if (prevAttributeType != null) DataContract.ThrowInvalidDataContractException(string.Format(SRSerialization.DuplicateAttribute, prevAttributeType, attributeType, DataContract.GetClrTypeFullName(method.DeclaringType), method), method.DeclaringType); else if (method.IsVirtual) DataContract.ThrowInvalidDataContractException(string.Format(SRSerialization.CallbacksCannotBeVirtualMethods, method, DataContract.GetClrTypeFullName(method.DeclaringType), attributeType), method.DeclaringType); else { if (method.ReturnType != Globals.TypeOfVoid) DataContract.ThrowInvalidDataContractException(string.Format(SRSerialization.CallbackMustReturnVoid, DataContract.GetClrTypeFullName(method.DeclaringType), method), method.DeclaringType); if (parameters == null || parameters.Length != 1 || parameters[0].ParameterType != Globals.TypeOfStreamingContext) DataContract.ThrowInvalidDataContractException(string.Format(SRSerialization.CallbackParameterInvalid, DataContract.GetClrTypeFullName(method.DeclaringType), method, Globals.TypeOfStreamingContext), method.DeclaringType); prevAttributeType = attributeType; } return true; } return false; } internal ClassDataContract BaseContract { get { return _baseContract; } set { _baseContract = value; if (_baseContract != null && IsValueType) ThrowInvalidDataContractException(string.Format(SRSerialization.ValueTypeCannotHaveBaseType, StableName.Name, StableName.Namespace, _baseContract.StableName.Name, _baseContract.StableName.Namespace)); } } internal List<DataMember> Members { get { return _members; } set { _members = value; } } internal MethodInfo OnSerializing { get { EnsureMethodsImported(); return _onSerializing; } } internal MethodInfo OnSerialized { get { EnsureMethodsImported(); return _onSerialized; } } internal MethodInfo OnDeserializing { get { EnsureMethodsImported(); return _onDeserializing; } } internal MethodInfo OnDeserialized { get { EnsureMethodsImported(); return _onDeserialized; } } #if !NET_NATIVE internal override DataContractDictionary KnownDataContracts { [SecurityCritical] get { if (!_isKnownTypeAttributeChecked && UnderlyingType != null) { lock (this) { if (!_isKnownTypeAttributeChecked) { _knownDataContracts = DataContract.ImportKnownTypeAttributes(this.UnderlyingType); Interlocked.MemoryBarrier(); _isKnownTypeAttributeChecked = true; } } } return _knownDataContracts; } [SecurityCritical] set { _knownDataContracts = value; } } #endif internal bool HasDataContract { get { return _hasDataContract; } #if NET_NATIVE set { _hasDataContract = value; } #endif } #if NET_NATIVE internal bool HasExtensionData { get { return _hasExtensionData; } set { _hasExtensionData = value; } } #endif internal bool IsNonAttributedType { get { return _isNonAttributedType; } } private void SetKeyValuePairAdapterFlags(Type type) { if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == Globals.TypeOfKeyValuePairAdapter) { _isKeyValuePairAdapter = true; _keyValuePairGenericArguments = type.GetGenericArguments(); _keyValuePairCtorInfo = type.GetConstructor(Globals.ScanAllMembers, new Type[] { Globals.TypeOfKeyValuePair.MakeGenericType(_keyValuePairGenericArguments) }); _getKeyValuePairMethodInfo = type.GetMethod("GetKeyValuePair", Globals.ScanAllMembers); } } private bool _isKeyValuePairAdapter; private Type[] _keyValuePairGenericArguments; private ConstructorInfo _keyValuePairCtorInfo; private MethodInfo _getKeyValuePairMethodInfo; internal bool IsKeyValuePairAdapter { get { return _isKeyValuePairAdapter; } } internal bool IsScriptObject { get { return _isScriptObject; } } internal Type[] KeyValuePairGenericArguments { get { return _keyValuePairGenericArguments; } } internal ConstructorInfo KeyValuePairAdapterConstructorInfo { get { return _keyValuePairCtorInfo; } } internal MethodInfo GetKeyValuePairMethodInfo { get { return _getKeyValuePairMethodInfo; } } internal ConstructorInfo GetNonAttributedTypeConstructor() { if (!this.IsNonAttributedType) return null; Type type = UnderlyingType; if (type.GetTypeInfo().IsValueType) return null; ConstructorInfo ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Array.Empty<Type>()); if (ctor == null) throw /*System.Runtime.Serialization.*/DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidDataContractException(string.Format(SRSerialization.NonAttributedSerializableTypesMustHaveDefaultConstructor, DataContract.GetClrTypeFullName(type)))); return ctor; } internal XmlFormatClassWriterDelegate XmlFormatWriterDelegate { get { return _xmlFormatWriterDelegate; } set { _xmlFormatWriterDelegate = value; } } internal XmlFormatClassReaderDelegate XmlFormatReaderDelegate { get { return _xmlFormatReaderDelegate; } set { _xmlFormatReaderDelegate = value; } } public XmlDictionaryString[] ChildElementNamespaces { get { return _childElementNamespaces; } set { _childElementNamespaces = value; } } internal struct Member { internal Member(DataMember member, string ns, int baseTypeIndex) { this.member = member; this.ns = ns; this.baseTypeIndex = baseTypeIndex; } internal DataMember member; internal string ns; internal int baseTypeIndex; } internal class DataMemberConflictComparer : IComparer<Member> { [SecuritySafeCritical] public int Compare(Member x, Member y) { int nsCompare = String.CompareOrdinal(x.ns, y.ns); if (nsCompare != 0) return nsCompare; int nameCompare = String.CompareOrdinal(x.member.Name, y.member.Name); if (nameCompare != 0) return nameCompare; return x.baseTypeIndex - y.baseTypeIndex; } internal static DataMemberConflictComparer Singleton = new DataMemberConflictComparer(); } #if NET_NATIVE internal ClassDataContractCriticalHelper Clone() { ClassDataContractCriticalHelper clonedHelper = new ClassDataContractCriticalHelper(this.UnderlyingType); clonedHelper._baseContract = this._baseContract; clonedHelper._childElementNamespaces = this._childElementNamespaces; clonedHelper.ContractNamespaces = this.ContractNamespaces; clonedHelper._hasDataContract = this._hasDataContract; clonedHelper._isMethodChecked = this._isMethodChecked; clonedHelper._isNonAttributedType = this._isNonAttributedType; clonedHelper.IsReference = this.IsReference; clonedHelper.IsValueType = this.IsValueType; clonedHelper.MemberNames = this.MemberNames; clonedHelper.MemberNamespaces = this.MemberNamespaces; clonedHelper._members = this._members; clonedHelper.Name = this.Name; clonedHelper.Namespace = this.Namespace; clonedHelper._onDeserialized = this._onDeserialized; clonedHelper._onDeserializing = this._onDeserializing; clonedHelper._onSerialized = this._onSerialized; clonedHelper._onSerializing = this._onSerializing; clonedHelper.StableName = this.StableName; clonedHelper.TopLevelElementName = this.TopLevelElementName; clonedHelper.TopLevelElementNamespace = this.TopLevelElementNamespace; clonedHelper._xmlFormatReaderDelegate = this._xmlFormatReaderDelegate; clonedHelper._xmlFormatWriterDelegate = this._xmlFormatWriterDelegate; return clonedHelper; } #endif } internal class DataMemberComparer : IComparer<DataMember> { public int Compare(DataMember x, DataMember y) { int orderCompare = x.Order - y.Order; if (orderCompare != 0) return orderCompare; return String.CompareOrdinal(x.Name, y.Name); } internal static DataMemberComparer Singleton = new DataMemberComparer(); } #if !NET_NATIVE /// <summary> /// Get object type for Xml/JsonFormmatReaderGenerator /// </summary> internal Type ObjectType { get { Type type = UnderlyingType; if (type.GetTypeInfo().IsValueType && !IsNonAttributedType) { type = Globals.TypeOfValueType; } return type; } } #endif #if NET_NATIVE internal ClassDataContract Clone() { ClassDataContract clonedDc = new ClassDataContract(this.UnderlyingType); clonedDc._helper = _helper.Clone(); clonedDc.ContractNamespaces = this.ContractNamespaces; clonedDc.ChildElementNamespaces = this.ChildElementNamespaces; clonedDc.MemberNames = this.MemberNames; clonedDc.MemberNamespaces = this.MemberNamespaces; clonedDc.XmlFormatWriterDelegate = this.XmlFormatWriterDelegate; clonedDc.XmlFormatReaderDelegate = this.XmlFormatReaderDelegate; return clonedDc; } internal void UpdateNamespaceAndMembers(Type type, XmlDictionaryString ns, string[] memberNames) { this.StableName = new XmlQualifiedName(GetStableName(type).Name, ns.Value); this.Namespace = ns; XmlDictionary dictionary = new XmlDictionary(1 + memberNames.Length); this.Name = dictionary.Add(StableName.Name); this.Namespace = ns; this.ContractNamespaces = new XmlDictionaryString[] { ns }; this.MemberNames = new XmlDictionaryString[memberNames.Length]; this.MemberNamespaces = new XmlDictionaryString[memberNames.Length]; for (int i = 0; i < memberNames.Length; i++) { this.MemberNames[i] = dictionary.Add(memberNames[i]); this.MemberNamespaces[i] = ns; } } #endif } }