File: Linker\LinkerAttributesInformation.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// 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.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.Shared;
using Mono.Cecil;
 
namespace Mono.Linker
{
	readonly struct LinkerAttributesInformation
	{
		readonly List<(Type Type, List<Attribute> Attributes)>? _linkerAttributes;
 
		private LinkerAttributesInformation (List<(Type Type, List<Attribute> Attributes)>? cache)
		{
			this._linkerAttributes = cache;
		}
 
		private static bool TryFindAttributeList (List<(Type Type, List<Attribute> Attributes)> list, Type type, [NotNullWhen (returnValue: true)] out List<Attribute>? foundAttributes)
		{
			foreach (var item in list) {
				if (item.Type == type) {
					foundAttributes = item.Attributes;
					return true;
				}
			}
			foundAttributes = null;
			return false;
		}
 
		public static LinkerAttributesInformation Create (LinkContext context, IMemberDefinition provider)
		{
			Debug.Assert (context.CustomAttributes.HasAny (provider));
 
			List<(Type Type, List<Attribute> Attributes)>? cache = null;
 
			foreach (var customAttribute in context.CustomAttributes.GetCustomAttributes (provider)) {
				var attributeType = customAttribute.AttributeType;
 
				Attribute? attributeValue;
				bool allowMultiple = false;
				switch (attributeType.Name) {
				case "RequiresUnreferencedCodeAttribute" when attributeType.Namespace == "System.Diagnostics.CodeAnalysis":
					attributeValue = ProcessRequiresUnreferencedCodeAttribute (context, provider, customAttribute);
					break;
				case "DynamicDependencyAttribute" when attributeType.Namespace == "System.Diagnostics.CodeAnalysis":
					attributeValue = DynamicDependency.ProcessAttribute (context, provider, customAttribute);
					allowMultiple = true;
					break;
				case "RemoveAttributeInstancesAttribute":
					if (provider is not TypeDefinition td)
						continue;
 
					// The attribute is never removed if it's explicitly preserved (e.g. via xml descriptor)
					if (context.Annotations.TryGetPreserve (td, out TypePreserve preserve) && preserve != TypePreserve.Nothing)
						continue;
 
					attributeValue = new RemoveAttributeInstancesAttribute (customAttribute.ConstructorArguments);
					allowMultiple = true;
					break;
				default:
					continue;
				}
 
				if (attributeValue == null)
					continue;
 
				cache ??= new List<(Type Type, List<Attribute> Attributes)> ();
 
				Type attributeValueType = attributeValue.GetType ();
 
				if (!TryFindAttributeList (cache, attributeValueType, out var attributeList)) {
					attributeList = new List<Attribute> ();
					cache.Add ((attributeValueType, attributeList));
				} else if (!allowMultiple) {
					context.LogWarning (provider, DiagnosticId.AttributeShouldOnlyBeUsedOnceOnMember, attributeValueType.FullName ?? "", (provider is MemberReference memberRef) ? memberRef.GetDisplayName () : provider.FullName);
					continue;
				}
 
				attributeList.Add (attributeValue);
			}
 
			return new LinkerAttributesInformation (cache);
		}
 
		public bool HasAttribute<T> () where T : Attribute
		{
			return _linkerAttributes != null && TryFindAttributeList (_linkerAttributes, typeof (T), out _);
		}
 
		public IEnumerable<T> GetAttributes<T> () where T : Attribute
		{
			if (_linkerAttributes == null)
				return Enumerable.Empty<T> ();
 
			if (!TryFindAttributeList (_linkerAttributes, typeof (T), out var attributeList))
				return Enumerable.Empty<T> ();
 
			if (attributeList.Count == 0)
				throw new InvalidOperationException ("Unexpected list of attributes.");
 
			return attributeList.Cast<T> ();
		}
 
		static RequiresUnreferencedCodeAttribute? ProcessRequiresUnreferencedCodeAttribute (LinkContext context, ICustomAttributeProvider provider, CustomAttribute customAttribute)
		{
			if (!(provider is MethodDefinition || provider is TypeDefinition))
				return null;
 
			if (customAttribute.HasConstructorArguments && customAttribute.ConstructorArguments[0].Value is string message) {
				var ruca = new RequiresUnreferencedCodeAttribute (message);
				if (customAttribute.HasProperties) {
					foreach (var prop in customAttribute.Properties) {
						if (prop.Name == "Url") {
							ruca.Url = prop.Argument.Value as string;
							break;
						}
					}
				}
 
				return ruca;
			}
 
			context.LogWarning ((IMemberDefinition) provider, DiagnosticId.AttributeDoesntHaveTheRequiredNumberOfParameters, typeof (RequiresUnreferencedCodeAttribute).FullName ?? "");
			return null;
		}
	}
}