File: RdXmlRootProvider.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler\ILCompiler_inbuild.csproj (ilc)
// 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.Linq;
using System.Reflection.Metadata;
using System.Xml.Linq;

using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

namespace ILCompiler
{
    /// <summary>
    /// Compilation root provider that provides roots based on the RD.XML file format.
    /// Only supports a subset of the Runtime Directives configuration file format.
    /// </summary>
    /// <remarks>https://msdn.microsoft.com/en-us/library/dn600639(v=vs.110).aspx</remarks>
    internal sealed class RdXmlRootProvider : ICompilationRootProvider
    {
        private XElement _documentRoot;
        private TypeSystemContext _context;

        public RdXmlRootProvider(TypeSystemContext context, string rdXmlFileName)
        {
            _context = context;
            _documentRoot = XElement.Load(rdXmlFileName);
        }

        public void AddCompilationRoots(IRootingServiceProvider rootProvider)
        {
            var libraryOrApplication = _documentRoot.Elements().Single();

            if (libraryOrApplication.Name.LocalName != "Library" && libraryOrApplication.Name.LocalName != "Application")
                throw new NotSupportedException($"{libraryOrApplication.Name.LocalName} is not a supported top level Runtime Directive. Supported top level Runtime Directives are \"Library\" and \"Application\".");

            if (libraryOrApplication.Attributes().Any())
                throw new NotSupportedException($"The {libraryOrApplication.Name.LocalName} Runtime Directive does not support any attributes");

            foreach (var element in libraryOrApplication.Elements())
            {
                switch (element.Name.LocalName)
                {
                    case "Assembly":
                        ProcessAssemblyDirective(rootProvider, element);
                        break;

                    default:
                        throw new NotSupportedException($"\"{element.Name.LocalName}\" is not a supported Runtime Directive.");
                }
            }
        }

        private void ProcessAssemblyDirective(IRootingServiceProvider rootProvider, XElement assemblyElement)
        {
            var assemblyNameAttribute = assemblyElement.Attribute("Name");
            if (assemblyNameAttribute == null)
                throw new Exception("The \"Name\" attribute is required on the \"Assembly\" Runtime Directive.");

            ModuleDesc assembly = _context.ResolveAssembly(new AssemblyNameInfo(assemblyNameAttribute.Value));

            rootProvider.RootModuleMetadata(assembly, "RD.XML root");

            var dynamicDegreeAttribute = assemblyElement.Attribute("Dynamic");
            if (dynamicDegreeAttribute != null)
            {
                if (dynamicDegreeAttribute.Value != "Required All")
                    throw new NotSupportedException($"\"{dynamicDegreeAttribute.Value}\" is not a supported value for the \"Dynamic\" attribute of the \"Assembly\" Runtime Directive. Supported values are \"Required All\".");

                foreach (TypeDesc type in ((EcmaModule)assembly).GetAllTypes())
                {
                    RootingHelpers.TryRootType(rootProvider, type, rootBaseTypes: true, "RD.XML root");
                }
            }

            foreach (var element in assemblyElement.Elements())
            {
                switch (element.Name.LocalName)
                {
                    case "Type":
                        ProcessTypeDirective(rootProvider, assembly, element);
                        break;
                    default:
                        throw new NotSupportedException($"\"{element.Name.LocalName}\" is not a supported Runtime Directive.");
                }
            }
        }

        private static void ProcessTypeDirective(IRootingServiceProvider rootProvider, ModuleDesc containingModule, XElement typeElement)
        {
            var typeNameAttribute = typeElement.Attribute("Name");
            if (typeNameAttribute == null)
                throw new Exception("The \"Name\" attribute is required on the \"Type\" Runtime Directive.");

            string typeName = typeNameAttribute.Value;
            TypeDesc type = containingModule.GetTypeByCustomAttributeTypeName(typeName);

            var dynamicDegreeAttribute = typeElement.Attribute("Dynamic");
            if (dynamicDegreeAttribute != null)
            {
                if (dynamicDegreeAttribute.Value != "Required All")
                    throw new NotSupportedException($"\"{dynamicDegreeAttribute.Value}\" is not a supported value for the \"Dynamic\" attribute of the \"Type\" Runtime Directive. Supported values are \"Required All\".");

                RootingHelpers.RootType(rootProvider, type, rootBaseTypes: true, "RD.XML root");
            }

            var marshalStructureDegreeAttribute = typeElement.Attribute("MarshalStructure");
            if (marshalStructureDegreeAttribute != null && type is DefType defType)
            {
                if (marshalStructureDegreeAttribute.Value != "Required All")
                    throw new NotSupportedException($"\"{marshalStructureDegreeAttribute.Value}\" is not a supported value for the \"MarshalStructure\" attribute of the \"Type\" Runtime Directive. Supported values are \"Required All\".");

                rootProvider.RootStructMarshallingData(defType, "RD.XML root");
            }

            var marshalDelegateDegreeAttribute = typeElement.Attribute("MarshalDelegate");
            if (marshalDelegateDegreeAttribute != null && type.IsDelegate)
            {
                if (marshalDelegateDegreeAttribute.Value != "Required All")
                    throw new NotSupportedException($"\"{marshalDelegateDegreeAttribute.Value}\" is not a supported value for the \"MarshalDelegate\" attribute of the \"Type\" Runtime Directive. Supported values are \"Required All\".");

                rootProvider.RootDelegateMarshallingData((DefType)type, "RD.XML root");
            }

            foreach (var element in typeElement.Elements())
            {
                switch (element.Name.LocalName)
                {
                    case "Method":
                        ProcessMethodDirective(rootProvider, containingModule, type, element);
                        break;
                    default:
                        throw new NotSupportedException($"\"{element.Name.LocalName}\" is not a supported Runtime Directive.");
                }
            }
        }

        private static void ProcessMethodDirective(IRootingServiceProvider rootProvider, ModuleDesc containingModule, TypeDesc containingType, XElement methodElement)
        {
            var methodNameAttribute = methodElement.Attribute("Name");
            if (methodNameAttribute == null)
                throw new Exception("The \"Name\" attribute is required on the \"Method\" Runtime Directive.");

            var parameter = new List<TypeDesc>();
            var instArgs = new List<TypeDesc>();
            foreach (var element in methodElement.Elements())
            {
                switch (element.Name.LocalName)
                {
                    case "Parameter":
                        string paramName = element.Attribute("Name").Value;
                        parameter.Add(containingModule.GetTypeByCustomAttributeTypeName(paramName));
                        break;
                    case "GenericArgument":
                        string instArgName = element.Attribute("Name").Value;
                        instArgs.Add(containingModule.GetTypeByCustomAttributeTypeName(instArgName));
                        break;
                    default:
                        throw new NotSupportedException($"\"{element.Name.LocalName}\" is not a supported Runtime Directive.");
                }
            }

            static bool SignatureMatches(MethodDesc method, List<TypeDesc> parameter)
            {
                if (parameter.Count != method.Signature.Length)
                    return false;

                for (int i = 0; i < method.Signature.Length; i++)
                {
                    if (method.Signature[i] != parameter[i])
                        return false;
                }

                return true;
            }

            bool rootedAnyMethod = false;
            string methodName = methodNameAttribute.Value;
            foreach (var m in containingType.GetMethods())
            {
                var method = m;
                if (method.GetName() != methodName)
                    continue;

                if (instArgs.Count != method.Instantiation.Length)
                    continue;

                if (instArgs.Count > 0)
                {
                    var methodInst = new Instantiation(instArgs.ToArray());
                    method = method.MakeInstantiatedMethod(methodInst);
                }

                if (parameter.Count > 0 && !SignatureMatches(method, parameter))
                    continue;

                RootingHelpers.RootMethod(rootProvider, method, "RD.XML root");
                rootedAnyMethod = true;
            }

            if (!rootedAnyMethod)
            {
                string parameterString = parameter.Count > 0 ? "(" + string.Join(", ", parameter) + ")" : null;
                string instantiationString = instArgs.Count > 0 ? "<" + string.Join(", ", instArgs) + ">" : null;
                throw new Exception($"Could not find Method(s) {containingType}.{methodName}{instantiationString}{parameterString} specified by a Runtime Directive.");
            }
        }
    }
}