File: Compiler\BodySubstitutionParser.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// 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;
using System.IO;
using System.Reflection.Metadata;
using Internal.TypeSystem;
using System.Xml;
using System.Xml.XPath;
using System.Globalization;
using System.Linq;
using ILLink.Shared;
using ILCompiler.Dataflow;

namespace ILCompiler
{
    public sealed class BodySubstitutionsParser : ProcessLinkerXmlBase
    {
        private readonly Dictionary<MethodDesc, BodySubstitution> _methodSubstitutions;
        private readonly Dictionary<FieldDesc, object> _fieldSubstitutions;


        private BodySubstitutionsParser(Logger logger, TypeSystemContext context, Stream documentStream, ManifestResource resource, ModuleDesc resourceAssembly, string xmlDocumentLocation, IReadOnlyDictionary<string, bool> featureSwitchValues)
                : base(logger, context, documentStream, resource, resourceAssembly, xmlDocumentLocation, featureSwitchValues)
        {
            _methodSubstitutions = new Dictionary<MethodDesc, BodySubstitution>();
            _fieldSubstitutions = new Dictionary<FieldDesc, object>();
        }

        private BodySubstitutionsParser(Logger logger, TypeSystemContext context, XmlReader document, string xmlDocumentLocation, IReadOnlyDictionary<string, bool> featureSwitchValues)
                : base(logger, context, document, xmlDocumentLocation, featureSwitchValues)
        {
            _methodSubstitutions = new Dictionary<MethodDesc, BodySubstitution>();
            _fieldSubstitutions = new Dictionary<FieldDesc, object>();
        }

        protected override void ProcessAssembly(ModuleDesc assembly, XPathNavigator nav, bool warnOnUnresolvedTypes)
        {
            ProcessTypes(assembly, nav, warnOnUnresolvedTypes);
        }

        protected override MetadataType ProcessExportedType(MetadataType exported, ModuleDesc assembly, XPathNavigator nav) => null;

        protected override bool ProcessTypePattern(string fullname, ModuleDesc assembly, XPathNavigator nav) => false;

        protected override void ProcessType(TypeDesc type, XPathNavigator nav)
        {
            Debug.Assert(ShouldProcessElement(nav));
            ProcessTypeChildren(type, nav);
        }

        protected override void ProcessMethod(TypeDesc type, XPathNavigator methodNav, object customData)
        {
            string signature = GetSignature(methodNav);
            if (string.IsNullOrEmpty(signature))
                return;

            MethodDesc method = FindMethod(type, signature);
            if (method == null)
            {
#if !READYTORUN
                LogWarning(methodNav, DiagnosticId.XmlCouldNotFindMethodOnType, signature, type.GetDisplayName());
#endif
                return;
            }

            string action = GetAttribute(methodNav, "body");
            switch (action)
            {
                case "remove":
                    _methodSubstitutions.Add(method, BodySubstitution.ThrowingBody);
                    break;
                case "stub":
                    BodySubstitution stubBody = null;
                    if (method.Signature.ReturnType.IsVoid)
                        stubBody = BodySubstitution.EmptyBody;
                    else
                    {
                        object substitution = TryCreateSubstitution(method.Signature.ReturnType, GetAttribute(methodNav, "value"));
                        if (substitution != null)
                            stubBody = BodySubstitution.Create(substitution);
                    }

                    if (stubBody != null)
                    {
                        _methodSubstitutions[method] = stubBody;
                    }
                    else
                    {
#if !READYTORUN
                        LogWarning(methodNav, DiagnosticId.XmlInvalidValueForStub, method.GetDisplayName());
#endif
                    }
                    break;
                default:
#if !READYTORUN
                    LogWarning(methodNav, DiagnosticId.XmlUnkownBodyModification, action, method.GetDisplayName());
#endif
                    break;
            }
        }

        protected override void ProcessField(TypeDesc type, XPathNavigator fieldNav)
        {
            string name = GetAttribute(fieldNav, "name");
            if (string.IsNullOrEmpty(name))
                return;

            var field = type.GetFields().FirstOrDefault(f => f.Name.StringEquals(name));
            if (field == null)
            {
#if !READYTORUN
                LogWarning(fieldNav, DiagnosticId.XmlCouldNotFindFieldOnType, name, type.GetDisplayName());
#endif
                return;
            }

            if (!field.IsStatic || field.IsLiteral)
            {
#if !READYTORUN
                LogWarning(fieldNav, DiagnosticId.XmlSubstitutedFieldNeedsToBeStatic, field.GetDisplayName());
#endif
                return;
            }

            string value = GetAttribute(fieldNav, "value");
            if (string.IsNullOrEmpty(value))
            {
#if !READYTORUN
                LogWarning(fieldNav, DiagnosticId.XmlMissingSubstitutionValueForField, field.GetDisplayName());
#endif
                return;
            }

            object substitution = TryCreateSubstitution(field.FieldType, value);
            if (substitution == null)
            {
#if !READYTORUN
                LogWarning(fieldNav, DiagnosticId.XmlInvalidSubstitutionValueForField, value, field.GetDisplayName());
#endif
                return;
            }

            if (string.Equals(GetAttribute(fieldNav, "initialize"), "true", StringComparison.InvariantCultureIgnoreCase))
            {
                // We would need to also mess with the cctor of the type to set the field to this value,
                // and doing so correctly is difficult.
                throw new NotSupportedException();
            }

            _fieldSubstitutions[field] = substitution;
        }

        private static MethodDesc FindMethod(TypeDesc type, string signature)
        {
            foreach (MethodDesc meth in type.GetMethods())
                if (signature == GetMethodSignature(meth, includeGenericParameters: true))
                    return meth;
            return null;
        }

        private static object TryCreateSubstitution(TypeDesc type, string value)
        {
            switch (type.UnderlyingType.Category)
            {
                case TypeFlags.Int32:
                    if (string.IsNullOrEmpty(value))
                        return 0;
                    else if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int iresult))
                        return iresult;
                    break;

                case TypeFlags.Boolean:
                    if (string.IsNullOrEmpty(value))
                        return 0;
                    else if (bool.TryParse(value, out bool bvalue))
                        return bvalue ? 1 : 0;
                    else
                        goto case TypeFlags.Int32;

                default:
                    throw new NotSupportedException(type.ToString());
            }

            return null;
        }

        public static BodyAndFieldSubstitutions GetSubstitutions(Logger logger, TypeSystemContext context, Stream documentStream, ManifestResource resource, ModuleDesc resourceAssembly, string xmlDocumentLocation, IReadOnlyDictionary<string, bool> featureSwitchValues)
        {
            var rdr = new BodySubstitutionsParser(logger, context, documentStream, resource, resourceAssembly, xmlDocumentLocation, featureSwitchValues);
            rdr.ProcessXml(false);
            return new BodyAndFieldSubstitutions(rdr._methodSubstitutions, rdr._fieldSubstitutions);
        }

        public static BodyAndFieldSubstitutions GetSubstitutions(Logger logger, TypeSystemContext context, XmlReader reader, string xmlDocumentLocation, IReadOnlyDictionary<string, bool> featureSwitchValues)
        {
            var rdr = new BodySubstitutionsParser(logger, context, reader, xmlDocumentLocation, featureSwitchValues);
            rdr.ProcessXml(false);
            return new BodyAndFieldSubstitutions(rdr._methodSubstitutions, rdr._fieldSubstitutions);
        }
    }

    public struct BodyAndFieldSubstitutions
    {
        private Dictionary<MethodDesc, BodySubstitution> _bodySubstitutions;
        private Dictionary<FieldDesc, object> _fieldSubstitutions;

        public IReadOnlyDictionary<MethodDesc, BodySubstitution> BodySubstitutions => _bodySubstitutions;
        public IReadOnlyDictionary<FieldDesc, object> FieldSubstitutions => _fieldSubstitutions;

        public BodyAndFieldSubstitutions(Dictionary<MethodDesc, BodySubstitution> bodySubstitutions, Dictionary<FieldDesc, object> fieldSubstitutions)
            => (_bodySubstitutions, _fieldSubstitutions) = (bodySubstitutions, fieldSubstitutions);

        public void AppendFrom(BodyAndFieldSubstitutions other)
        {
            if (_bodySubstitutions == null)
            {
                _bodySubstitutions = other._bodySubstitutions;
                _fieldSubstitutions = other._fieldSubstitutions;
            }
            else if (other._bodySubstitutions == null)
            {
                // Nothing to do
            }
            else
            {
                foreach (var kvp in other._bodySubstitutions)
                    _bodySubstitutions[kvp.Key] = kvp.Value;

                foreach (var kvp in other._fieldSubstitutions)
                    _fieldSubstitutions[kvp.Key] = kvp.Value;
            }
        }
    }
}