File: System\Runtime\Serialization\XmlDataContract.cs
Web Access
Project: src\src\libraries\System.Private.DataContractSerialization\src\System.Private.DataContractSerialization.csproj (System.Private.DataContractSerialization)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Threading;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
 
using DataContractDictionary = System.Collections.Generic.Dictionary<System.Xml.XmlQualifiedName, System.Runtime.Serialization.DataContracts.DataContract>;
 
namespace System.Runtime.Serialization.DataContracts
{
    internal delegate IXmlSerializable CreateXmlSerializableDelegate();
    public sealed class XmlDataContract : DataContract
    {
        internal const string ContractTypeString = nameof(XmlDataContract);
        public override string? ContractType => ContractTypeString;
 
        private readonly XmlDataContractCriticalHelper _helper;
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        internal XmlDataContract(Type type) : base(new XmlDataContractCriticalHelper(type))
        {
            _helper = (base.Helper as XmlDataContractCriticalHelper)!;
        }
 
        public override DataContractDictionary? KnownDataContracts
        {
            [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
            [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
            get => _helper.KnownDataContracts;
            internal set => _helper.KnownDataContracts = value;
        }
 
        public XmlSchemaType? XsdType
        {
            get => _helper.XsdType;
            internal set => _helper.XsdType = value;
        }
 
        public bool IsAnonymous
        {
            get => _helper.IsAnonymous;
        }
 
        public new bool IsValueType
        {
            get => _helper.IsValueType;
            set => _helper.IsValueType = value;
        }
 
        public new bool HasRoot
        {
            get => _helper.HasRoot;
            internal set => _helper.HasRoot = value;
        }
 
        public override XmlDictionaryString? TopLevelElementName
        {
            get => _helper.TopLevelElementName;
            internal set => _helper.TopLevelElementName = value;
        }
 
        public override XmlDictionaryString? TopLevelElementNamespace
        {
            get => _helper.TopLevelElementNamespace;
            internal set => _helper.TopLevelElementNamespace = value;
        }
 
        public bool IsTopLevelElementNullable
        {
            get => _helper.IsTopLevelElementNullable;
            internal set => _helper.IsTopLevelElementNullable = value;
        }
 
        public bool IsTypeDefinedOnImport
        {
            get => _helper.IsTypeDefinedOnImport;
            set => _helper.IsTypeDefinedOnImport = value;
        }
 
        internal CreateXmlSerializableDelegate CreateXmlSerializableDelegate
        {
            [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
            [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
            get
            {
                // We create XmlSerializableDelegate via CodeGen when CodeGen is enabled;
                // otherwise, we would create the delegate via reflection.
                if (DataContractSerializer.Option == SerializationOption.CodeGenOnly || DataContractSerializer.Option == SerializationOption.ReflectionAsBackup)
                {
                    if (_helper.CreateXmlSerializableDelegate == null)
                    {
                        lock (this)
                        {
                            if (_helper.CreateXmlSerializableDelegate == null)
                            {
                                CreateXmlSerializableDelegate tempCreateXmlSerializable = GenerateCreateXmlSerializableDelegate();
                                Interlocked.MemoryBarrier();
                                _helper.CreateXmlSerializableDelegate = tempCreateXmlSerializable;
                            }
                        }
                    }
                    return _helper.CreateXmlSerializableDelegate;
                }
 
                return () => ReflectionCreateXmlSerializable(this.UnderlyingType);
            }
        }
 
        internal override bool CanContainReferences => false;
 
        public override bool IsBuiltInDataContract => UnderlyingType == Globals.TypeOfXmlElement || UnderlyingType == Globals.TypeOfXmlNodeArray;
 
        private sealed class XmlDataContractCriticalHelper : DataContract.DataContractCriticalHelper
        {
            private DataContractDictionary? _knownDataContracts;
            private bool _isKnownTypeAttributeChecked;
            private XmlDictionaryString? _topLevelElementName;
            private XmlDictionaryString? _topLevelElementNamespace;
            private bool _isTopLevelElementNullable;
            private bool _isTypeDefinedOnImport;
            private CreateXmlSerializableDelegate? _createXmlSerializable;
            private XmlSchemaType? _xsdType;
 
            [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
            [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
            internal XmlDataContractCriticalHelper(
                [DynamicallyAccessedMembers(ClassDataContract.DataContractPreserveMemberTypes)]
                Type type) : base(type)
            {
                if (type.IsDefined(Globals.TypeOfDataContractAttribute, false))
                    throw new InvalidDataContractException(SR.Format(SR.IXmlSerializableCannotHaveDataContract, DataContract.GetClrTypeFullName(type)));
                if (type.IsDefined(Globals.TypeOfCollectionDataContractAttribute, false))
                    throw new InvalidDataContractException(SR.Format(SR.IXmlSerializableCannotHaveCollectionDataContract, DataContract.GetClrTypeFullName(type)));
                bool hasRoot;
                XmlSchemaType? xsdType;
                XmlQualifiedName xmlName;
                SchemaExporter.GetXmlTypeInfo(type, out xmlName, out xsdType, out hasRoot);
                XmlName = xmlName;
                XsdType = xsdType;
                HasRoot = hasRoot;
                XmlDictionary dictionary = new XmlDictionary();
                Name = dictionary.Add(XmlName.Name);
                Namespace = dictionary.Add(XmlName.Namespace);
                object[]? xmlRootAttributes = UnderlyingType?.GetCustomAttributes(Globals.TypeOfXmlRootAttribute, false).ToArray();
                if (xmlRootAttributes == null || xmlRootAttributes.Length == 0)
                {
                    if (hasRoot)
                    {
                        _topLevelElementName = Name;
                        _topLevelElementNamespace = (this.XmlName.Namespace == Globals.SchemaNamespace) ? DictionaryGlobals.EmptyString : Namespace;
                        _isTopLevelElementNullable = true;
                    }
                }
                else
                {
                    if (hasRoot)
                    {
                        XmlRootAttribute xmlRootAttribute = (XmlRootAttribute)xmlRootAttributes[0];
                        _isTopLevelElementNullable = xmlRootAttribute.IsNullable;
                        string elementName = xmlRootAttribute.ElementName;
                        _topLevelElementName = string.IsNullOrEmpty(elementName) ? Name : dictionary.Add(DataContract.EncodeLocalName(elementName));
                        string? elementNs = xmlRootAttribute.Namespace;
                        _topLevelElementNamespace = string.IsNullOrEmpty(elementNs) ? DictionaryGlobals.EmptyString : dictionary.Add(elementNs);
                    }
                    else
                    {
                        throw new InvalidDataContractException(SR.Format(SR.IsAnyCannotHaveXmlRoot, DataContract.GetClrTypeFullName(UnderlyingType!)));
                    }
                }
            }
 
            internal override DataContractDictionary? KnownDataContracts
            {
                [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
                [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
                get
                {
                    if (!_isKnownTypeAttributeChecked && UnderlyingType != null)
                    {
                        lock (this)
                        {
                            if (!_isKnownTypeAttributeChecked)
                            {
                                _knownDataContracts = DataContract.ImportKnownTypeAttributes(this.UnderlyingType);
                                Interlocked.MemoryBarrier();
                                _isKnownTypeAttributeChecked = true;
                            }
                            _knownDataContracts ??= new DataContractDictionary();
                        }
                    }
                    return _knownDataContracts;
                }
 
                set
                { _knownDataContracts = value; }
            }
 
            internal XmlSchemaType? XsdType
            {
                get => _xsdType;
                set => _xsdType = value;
            }
 
            internal bool IsAnonymous => _xsdType != null;
 
            internal override XmlDictionaryString? TopLevelElementName
            {
                get => _topLevelElementName;
                set => _topLevelElementName = value;
            }
 
            internal override XmlDictionaryString? TopLevelElementNamespace
            {
                get => _topLevelElementNamespace;
                set => _topLevelElementNamespace = value;
            }
 
            internal bool IsTopLevelElementNullable
            {
                get => _isTopLevelElementNullable;
                set => _isTopLevelElementNullable = value;
            }
 
            internal bool IsTypeDefinedOnImport
            {
                get => _isTypeDefinedOnImport;
                set => _isTypeDefinedOnImport = value;
            }
 
            internal CreateXmlSerializableDelegate? CreateXmlSerializableDelegate
            {
                get => _createXmlSerializable;
                set => _createXmlSerializable = value;
            }
        }
 
        private ConstructorInfo? GetConstructor()
        {
            if (UnderlyingType.IsValueType)
                return null;
 
            ConstructorInfo? ctor = UnderlyingType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, Type.EmptyTypes);
            if (ctor == null)
                throw new InvalidDataContractException(SR.Format(SR.IXmlSerializableMustHaveDefaultConstructor, DataContract.GetClrTypeFullName(UnderlyingType)));
 
            return ctor;
        }
 
        internal void SetTopLevelElementName(XmlQualifiedName? elementName)
        {
            if (elementName != null)
            {
                XmlDictionary dictionary = new XmlDictionary();
                TopLevelElementName = dictionary.Add(elementName.Name);
                TopLevelElementNamespace = dictionary.Add(elementName.Namespace);
            }
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        internal CreateXmlSerializableDelegate GenerateCreateXmlSerializableDelegate()
        {
            Type type = this.UnderlyingType;
            CodeGenerator ilg = new CodeGenerator();
            bool memberAccessFlag = RequiresMemberAccessForCreate(null) && !(type.FullName == "System.Xml.Linq.XElement");
            try
            {
                ilg.BeginMethod("Create" + DataContract.GetClrTypeFullName(type), typeof(CreateXmlSerializableDelegate), memberAccessFlag);
            }
            catch (SecurityException securityException)
            {
                if (memberAccessFlag)
                {
                    RequiresMemberAccessForCreate(securityException);
                }
                else
                {
                    throw;
                }
            }
            if (type.IsValueType)
            {
                System.Reflection.Emit.LocalBuilder local = ilg.DeclareLocal(type);
                ilg.Ldloca(local);
                ilg.InitObj(type);
                ilg.Ldloc(local);
            }
            else
            {
                // Special case XElement
                // codegen the same as 'internal XElement : this("default") { }'
                ConstructorInfo ctor = GetConstructor()!;
                if (!ctor.IsPublic && type.FullName == "System.Xml.Linq.XElement")
                {
                    Type? xName = type.Assembly.GetType("System.Xml.Linq.XName");
                    if (xName != null)
                    {
                        MethodInfo? XName_op_Implicit = xName.GetMethod(
                            "op_Implicit",
                            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
                            new Type[] { typeof(string) }
                            );
                        ConstructorInfo? XElement_ctor = type.GetConstructor(
                            BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
                            new Type[] { xName }
                            );
                        if (XName_op_Implicit != null && XElement_ctor != null)
                        {
                            ilg.Ldstr("default");
                            ilg.Call(XName_op_Implicit);
                            ctor = XElement_ctor;
                        }
                    }
                }
                ilg.New(ctor);
            }
            ilg.ConvertValue(this.UnderlyingType, Globals.TypeOfIXmlSerializable);
            ilg.Ret();
            return (CreateXmlSerializableDelegate)ilg.EndMethod();
        }
 
        /// <SecurityNote>
        /// Review - calculates whether this Xml type 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>
        private bool RequiresMemberAccessForCreate(SecurityException? securityException)
        {
            if (!IsTypeVisible(UnderlyingType))
            {
                if (securityException != null)
                {
                    throw new SecurityException(SR.Format(SR.PartialTrustIXmlSerializableTypeNotPublic, DataContract.GetClrTypeFullName(UnderlyingType)),
                        securityException);
                }
                return true;
            }
 
            if (ConstructorRequiresMemberAccess(GetConstructor()))
            {
                if (securityException != null)
                {
                    throw new SecurityException(SR.Format(SR.PartialTrustIXmlSerialzableNoPublicConstructor, DataContract.GetClrTypeFullName(UnderlyingType)),
                        securityException);
                }
                return true;
            }
 
            return false;
        }
 
        internal IXmlSerializable ReflectionCreateXmlSerializable(Type type)
        {
            if (type.IsValueType)
            {
                throw new NotImplementedException("ReflectionCreateXmlSerializable - value type");
            }
            else
            {
                object? o;
                if (type == typeof(System.Xml.Linq.XElement))
                {
                    o = new System.Xml.Linq.XElement("default");
                }
                else
                {
                    ConstructorInfo ctor = GetConstructor()!;
                    o = ctor.Invoke(Array.Empty<object>());
                }
 
                return (IXmlSerializable)o;
            }
        }
 
        internal override bool Equals(object? other, HashSet<DataContractPairKey>? checkedContracts)
        {
            if (IsEqualOrChecked(other, checkedContracts))
                return true;
 
            if (other is XmlDataContract dataContract)
            {
                if (this.HasRoot != dataContract.HasRoot)
                    return false;
 
                if (this.IsAnonymous)
                {
                    return dataContract.IsAnonymous;
                }
                else
                {
                    return (XmlName.Name == dataContract.XmlName.Name && XmlName.Namespace == dataContract.XmlName.Namespace);
                }
            }
            return false;
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        internal override void WriteXmlValue(XmlWriterDelegator xmlWriter, object obj, XmlObjectSerializerWriteContext? context)
        {
            if (context == null)
                XmlObjectSerializerWriteContext.WriteRootIXmlSerializable(xmlWriter, obj);
            else
                context.WriteIXmlSerializable(xmlWriter, obj);
        }
 
        [RequiresDynamicCode(DataContract.SerializerAOTWarning)]
        [RequiresUnreferencedCode(DataContract.SerializerTrimmerWarning)]
        internal override object? ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext? context)
        {
            object? o;
            if (context == null)
            {
                o = XmlObjectSerializerReadContext.ReadRootIXmlSerializable(xmlReader, this, true /*isMemberType*/);
            }
            else
            {
                o = context.ReadIXmlSerializable(xmlReader, this, true /*isMemberType*/);
                context.AddNewObject(o);
            }
            xmlReader.ReadEndElement();
            return o;
        }
    }
}