File: RequiresDynamicCodeAnalyzer.cs
Web Access
Project: src\src\tools\illink\src\ILLink.RoslynAnalyzer\ILLink.RoslynAnalyzer.csproj (ILLink.RoslynAnalyzer)
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
using System;
using System.Collections.Immutable;
using System.Linq;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;
 
namespace ILLink.RoslynAnalyzer
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public sealed class RequiresDynamicCodeAnalyzer : RequiresAnalyzerBase
    {
        private const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute);
        public const string FullyQualifiedRequiresDynamicCodeAttribute = "System.Diagnostics.CodeAnalysis." + RequiresDynamicCodeAttribute;
 
        private static readonly DiagnosticDescriptor s_requiresDynamicCodeOnStaticCtor = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCodeOnStaticConstructor);
        private static readonly DiagnosticDescriptor s_requiresDynamicCodeOnEntryPoint = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCodeOnEntryPoint);
        private static readonly DiagnosticDescriptor s_requiresDynamicCodeRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCode);
        private static readonly DiagnosticDescriptor s_requiresDynamicCodeAttributeMismatch = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCodeAttributeMismatch);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
            ImmutableArray.Create(s_requiresDynamicCodeRule, s_requiresDynamicCodeAttributeMismatch, s_requiresDynamicCodeOnStaticCtor, s_requiresDynamicCodeOnEntryPoint);
 
        private protected override string RequiresAttributeName => RequiresDynamicCodeAttribute;
 
        internal override string RequiresAttributeFullyQualifiedName => FullyQualifiedRequiresDynamicCodeAttribute;
 
        private protected override DiagnosticTargets AnalyzerDiagnosticTargets => DiagnosticTargets.MethodOrConstructor | DiagnosticTargets.Class;
 
        private protected override DiagnosticDescriptor RequiresDiagnosticRule => s_requiresDynamicCodeRule;
 
        private protected override DiagnosticId RequiresDiagnosticId => DiagnosticId.RequiresDynamicCode;
 
        private protected override DiagnosticDescriptor RequiresAttributeMismatch => s_requiresDynamicCodeAttributeMismatch;
 
        private protected override DiagnosticDescriptor RequiresOnStaticCtor => s_requiresDynamicCodeOnStaticCtor;
 
        private protected override DiagnosticDescriptor RequiresOnEntryPoint => s_requiresDynamicCodeOnEntryPoint;
 
        internal override bool IsAnalyzerEnabled(AnalyzerOptions options) =>
            options.IsMSBuildPropertyValueTrue(MSBuildPropertyOptionNames.EnableAotAnalyzer);
 
        internal override bool IsIntrinsicallyHandled(IMethodSymbol calledMethod, MultiValue instance, ImmutableArray<MultiValue> arguments)
        {
            MethodProxy method = new(calledMethod);
            var intrinsicId = Intrinsics.GetIntrinsicIdForMethod(method);
 
            switch (intrinsicId)
            {
                case IntrinsicId.Type_MakeGenericType:
                {
                    if (!instance.IsEmpty())
                    {
                        foreach (var value in instance.AsEnumerable())
                        {
                            if (value is SystemTypeValue typeValue)
                            {
                                if (!IsKnownInstantiation(arguments[0])
                                    && !IsConstrainedToBeReferenceTypes(typeValue.RepresentedType.GetGenericParameters()))
                                {
                                    return false;
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
                    return true;
                }
                case IntrinsicId.MethodInfo_MakeGenericMethod:
                {
                    if (!instance.IsEmpty())
                    {
                        foreach (var methodValue in instance.AsEnumerable())
                        {
                            if (methodValue is SystemReflectionMethodBaseValue methodBaseValue)
                            {
                                if (!IsKnownInstantiation(arguments[0])
                                    && !IsConstrainedToBeReferenceTypes(methodBaseValue.RepresentedMethod.GetGenericParameters()))
                                {
                                    return false;
                                }
                            }
                            else
                            {
                                return false;
                            }
                        }
                    }
                    return true;
                }
            }
 
            return false;
 
            static bool IsKnownInstantiation(MultiValue genericParametersArray)
            {
                var typesValue = genericParametersArray.AsSingleValue();
                if (typesValue is NullValue)
                {
                    // This will fail at runtime but no warning needed
                    return true;
                }
 
                // Is this an array we model?
                if (typesValue is not ArrayValue array)
                {
                    return false;
                }
 
                int? size = array.Size.AsConstInt();
                if (size == null)
                {
                    return false;
                }
 
                for (int i = 0; i < size.Value; i++)
                {
                    // Go over each element of the array. If the value is unknown, bail.
                    if (!array.TryGetValueByIndex(i, out MultiValue value))
                    {
                        return false;
                    }
 
                    var singleValue = value.AsSingleValue();
 
                    if (singleValue is not SystemTypeValue and not GenericParameterValue and not NullableSystemTypeValue)
                    {
                        return false;
                    }
                }
 
                return true;
            }
 
            static bool IsConstrainedToBeReferenceTypes(ImmutableArray<GenericParameterProxy> parameters)
            {
                foreach (GenericParameterProxy param in parameters)
                    if (!param.TypeParameterSymbol.HasReferenceTypeConstraint)
                        return false;
                return true;
            }
        }
 
        private protected override bool IsRequiresCheck(IPropertySymbol propertySymbol, Compilation compilation)
        {
            var runtimeFeaturesType = compilation.GetTypeByMetadataName("System.Runtime.CompilerServices.RuntimeFeature");
            if (runtimeFeaturesType == null)
                return false;
 
            var isDynamicCodeSupportedProperty = runtimeFeaturesType.GetMembers("IsDynamicCodeSupported").OfType<IPropertySymbol>().FirstOrDefault();
            if (isDynamicCodeSupportedProperty == null)
                return false;
 
            return SymbolEqualityComparer.Default.Equals(propertySymbol, isDynamicCodeSupportedProperty);
        }
 
        protected override bool VerifyAttributeArguments(AttributeData attribute) =>
            attribute.ConstructorArguments.Length >= 1 && attribute.ConstructorArguments is [{ Type.SpecialType: SpecialType.System_String }, ..];
 
        protected override string GetMessageFromAttribute(AttributeData? requiresAttribute)
        {
            var message = (string)requiresAttribute!.ConstructorArguments[0].Value!;
            return MessageFormat.FormatRequiresAttributeMessageArg(message);
        }
    }
}