// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using EpicGames.Core; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; using UnrealBuildTool.Configuration.CompileWarnings; namespace UnrealBuildTool { /// /// Interface that participates in resolving a set of against a member. /// internal interface IWarningLevelResolver { /// /// Resolves a given a set of . /// /// The set of attributes to consider in resolution. /// The build system context to evaluate these under. /// The resolved warning level. WarningLevel ResolveWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext); } /// /// WarningLevelResolverRegistry is the container for all of the resolvers applicable within the and contexts. /// /// The primary entry point for extensibility is . internal class WarningLevelResolverRegistry { private readonly ILogger? _logger = Log.Logger; private static Lazy s_resolverRegistry = new(() => new()); private readonly Dictionary _attributeNameToBehaviour = []; /// /// The resolver name to resolver representation of the registry. /// internal static IReadOnlyDictionary Resolvers => s_resolverRegistry.Value._attributeNameToBehaviour; /// /// Requests a resolver by name. /// /// The name of the resolver to obtain. /// The IWarningLevelResolver if it has been registered, null if it was not found. internal static IWarningLevelResolver? RequestResolver(string resolverName) { IWarningLevelResolver? returnResolver; Resolvers.TryGetValue(resolverName, out returnResolver); return returnResolver; } private WarningLevelResolverRegistry() { // Process all of the static methods in the assembly that are annotated wtih WarningLevelResolverDelegateAttribute, and evaluate given they match // the execution delegate, which is effectively IWarningLevelResolver.ResolveWarningLevelDefault MethodInfo? reflectiveInterfaceMethodInfo = typeof(IWarningLevelResolver).GetMethod(nameof(IWarningLevelResolver.ResolveWarningLevelDefault)); if (reflectiveInterfaceMethodInfo != null) { Assembly assembly = Assembly.GetExecutingAssembly(); Type[] allTypes = assembly.GetTypes(); List delegatesForConsideration = new List(); foreach (Type type in allTypes) { IEnumerable methodsWithAttribute = type .GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) .Where(m => m.GetCustomAttributes(typeof(WarningLevelResolverDelegateAttribute), false).Length > 0); delegatesForConsideration.AddRange(methodsWithAttribute); } foreach (MethodInfo mi in delegatesForConsideration) { if (mi.ReturnType != reflectiveInterfaceMethodInfo.ReturnType) { _logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute as it doesn't return {ReturnType}.", mi.Name, reflectiveInterfaceMethodInfo.ReturnType.Name); continue; } if (mi.GetParameters().Length != reflectiveInterfaceMethodInfo.GetParameters().Length) { _logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute as it doesn't contain the same number of parameters as the base method interface ({Count})", mi.Name, reflectiveInterfaceMethodInfo.GetParameters().Length); continue; } if (!mi.GetParameters().SequenceEqual(reflectiveInterfaceMethodInfo.GetParameters(), EqualityComparer.Create((a, b) => a?.ParameterType == b?.ParameterType))) { _logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute it's parameters don't match the base method interface.", mi.Name); continue; } WarningLevelResolverDelegateAttribute? attributeInstance = mi.GetCustomAttribute(); if (attributeInstance == null) { _logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute as reflection has failed to obtain the attribute instance.", mi.Name); continue; } if (!_attributeNameToBehaviour.TryAdd(attributeInstance.ResolverName, new WarningLevelResolverWrapper(attributeInstance, mi))) { _logger?.LogWarning("Duplicate resolver names ({DuplicateName}) encountered when building the WarningLevelResolverRegistry.", attributeInstance.ResolverName); } } } else { _logger?.LogWarning("Base method interface was null. Ignoring all method reflective methods used for resolvers."); } } } }