// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using EpicGames.Core; using Microsoft.Extensions.Logging; namespace UnrealBuildTool.Configuration.CompileWarnings { /// /// Flag used to communicate which context the should be applied. /// internal enum InitializationContext { /// /// Apply the in all contexts. /// Any, /// /// Apply the exclusively in the context. /// Target, /// /// Apply the exclusively in the context. /// Constructor } internal static class WarningLevelDefaultHelpers { public static IList? CastWarningLevelDefaultAttirbuteToTarget(IList uncastedAttributes, ILogger? logger) { List? castedAttributes = null; try { castedAttributes = uncastedAttributes.Cast().ToList(); } catch (Exception ex) { logger?.LogError("Exception occurred {ExMessage}. Received a set of attributes that weren't of the correct warning attribute type ({Type})", ex.Message, nameof(T)); } return castedAttributes; } } /// /// Abstract attribute used to specify the default to assign a member, and the interface, /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] internal abstract class WarningLevelDefaultAttribute : Attribute { protected static readonly ILogger? Logger = Log.Logger; /// /// Constructor a WarningLevelDefaultAttribute. /// /// The context in which to apply this default. public WarningLevelDefaultAttribute(InitializationContext context = InitializationContext.Any) { Context = context; } /// /// Gets the default to apply to the member. /// /// The build system context used to consider defaults and applicability. /// public abstract WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext); /// /// The context in which this attribute should be applied. /// public InitializationContext Context { get; } } /// /// Basic attribute used to set the default values of a under the context of context and context. /// /// When combined with any other , it will act as the fallback in resolution if no other attribute sets a non-. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] #pragma warning disable CA1813 // Avoid unsealed attributes internal class BasicWarningLevelDefaultAttribute : WarningLevelDefaultAttribute #pragma warning restore CA1813 // Avoid unsealed attributes { /// /// Constructor a BasicWarningLevelDefaultAttribute. /// /// The default warning level to apply for this bound. /// The context in which to apply this default. #pragma warning disable CA1019 // Define accessors for attribute arguments public BasicWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(context) #pragma warning restore CA1019 // Define accessors for attribute arguments { DefaultLevel = defaultLevel; } /// /// The default warning level. /// /// The build system context used to conditionally augment the by. public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null) { return DefaultLevel; } /// /// Resolver for . /// /// The set of attributes of which to consider for resolution. /// The build system context used to consider defaults and applicability. /// The corresponding of which to apply to the property with the default value attributes. [WarningLevelResolverDelegate(nameof(BasicWarningLevelDefaultAttribute))] internal static WarningLevel ResolveWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext) { if (unsortedDefaultValueAttributes.Count != 1) { Logger?.LogError("Invalid attribute definition for {Name}.", nameof(WarningLevelDefaultAttribute)); return WarningLevel.Default; } return unsortedDefaultValueAttributes[0].GetDefaultLevel(buildSystemContext); } protected readonly WarningLevel DefaultLevel; } #region -- Compiler Constrained Warning Level Defaults -- /// /// Compiler constrained Attribute used to set the default values of a under the context of context and context. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] internal abstract class CompilerWarningLevelDefaultAttribute : BasicWarningLevelDefaultAttribute { public WindowsCompiler Compiler { get; } /// /// Constructs a CompilerWarningLevelDefaultAttribute. /// /// The compiler which this attribute should be applied. /// The default warning level to apply for this bound. /// The context in which to apply this default. public CompilerWarningLevelDefaultAttribute(WindowsCompiler compiler, WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(defaultLevel, context) { Compiler = compiler; } /// /// Resolves the set of to a provided the current . /// /// The set of attributes of which to consider for resolution. /// The build system context used to consider defaults and applicability. /// The corresponding of which to apply to the property with the default value attributes. [WarningLevelResolverDelegate(nameof(CompilerWarningLevelDefaultAttribute))] #pragma warning disable IDE0060 // Remove unused parameter internal static WarningLevel ResolveVersionWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext) #pragma warning restore IDE0060 // Remove unused parameter { IList? castedAttributes = WarningLevelDefaultHelpers.CastWarningLevelDefaultAttirbuteToTarget(unsortedDefaultValueAttributes, Logger); if (castedAttributes == null) { Logger?.LogWarning("Unable to discern default warning level through resolution. Defaulting to WarningLevel.Default."); return WarningLevel.Default; } int nonDefaultCount = castedAttributes.Select(x => x.GetDefaultLevel(buildSystemContext) != WarningLevel.Default).Count(); if (nonDefaultCount > 1) { Logger?.LogWarning("Unable to disambiguate the warning level through resolution. Too many CompilerWarningLevelDefaultAttribute returned a resolved value. Defaulting to WarningLevel.Default."); return WarningLevel.Default; } if (nonDefaultCount == 1) { return castedAttributes.Where(x => x.GetDefaultLevel(buildSystemContext) != WarningLevel.Default).Select(x => x.GetDefaultLevel(buildSystemContext)).First(); } return WarningLevel.Default; } } /// /// Version constrained attribute used to set the default values of a under the context of context and context. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] internal sealed class IntelCompilerWarningLevelDefaultAttribute : CompilerWarningLevelDefaultAttribute { /// /// Constructs a IntelCompilerWarningLevelDefaultAttribute. /// /// The default warning level to apply for this bound. /// The context in which to apply this default. public IntelCompilerWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(WindowsCompiler.Intel, defaultLevel, context) { } public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null) { if (buildSystemContext == null) { return WarningLevel.Default; } ReadOnlyTargetRules? readOnlyTargetRules = buildSystemContext.GetReadOnlyTargetRules(); if (readOnlyTargetRules?.WindowsPlatform?.Compiler.IsIntel() == true) { return DefaultLevel; } return WarningLevel.Default; } } /// /// Version constrained attribute used to set the default values of a under the context of context and context. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] internal sealed class MSVCCompilerWarningLevelDefaultAttribute : CompilerWarningLevelDefaultAttribute { /// /// Constructs a MSVCCompilerWarningLevelDefaultAttribute. /// /// The default warning level to apply for this bound. /// The context in which to apply this default. public MSVCCompilerWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(WindowsCompiler.VisualStudio2022, defaultLevel, context) { } public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null) { if (buildSystemContext == null) { return WarningLevel.Default; } ReadOnlyTargetRules? readOnlyTargetRules = buildSystemContext.GetReadOnlyTargetRules(); if (readOnlyTargetRules?.WindowsPlatform?.Compiler.IsMSVC() == true) { return DefaultLevel; } return WarningLevel.Default; } } /// /// Version constrained attribute used to set the default values of a under the context of context and context. /// /// Although is considered clang, this specificaly removes that. [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] internal sealed class ClangNonIntelSpecializedCompilerWarningLevelDefaultAttribute : CompilerWarningLevelDefaultAttribute { /// /// Constructs a ClangNonIntelSpecializedCompilerWarningLevelDefaultAttribute. /// /// The default warning level to apply for this bound. /// The context in which to apply this default. public ClangNonIntelSpecializedCompilerWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(WindowsCompiler.Clang, defaultLevel, context) { } public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null) { if (buildSystemContext == null) { return WarningLevel.Default; } ReadOnlyTargetRules? readOnlyTargetRules = buildSystemContext.GetReadOnlyTargetRules(); if (readOnlyTargetRules?.WindowsPlatform?.Compiler.IsClang() == true && !readOnlyTargetRules.WindowsPlatform.Compiler.IsIntel()) { return DefaultLevel; } return WarningLevel.Default; } } #endregion -- Compiler Constrained Warning Level Defaults -- /// /// Version constrained attribute used to set the default values of a under the context of context and context. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] internal sealed class VersionWarningLevelDefaultAttribute : BasicWarningLevelDefaultAttribute { /// /// The lower bound (inclusive) of which to apply this to. /// public BuildSettingsVersion MinVersion { get; } /// /// The upper bound (inclusive) of which to apply this to. /// public BuildSettingsVersion MaxVersion { get; } /// /// Constructs a VersionWarningLevelDefaultAttribute. /// /// Minimum version of the bound to apply this setting to. Inclusive. /// Maximum version of the bound to apply this setting to. Inclusive. /// The default warning level to apply for this bound. /// The context in which to apply this default. #pragma warning disable CA1019 public VersionWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, BuildSettingsVersion minVersion = BuildSettingsVersion.V1, BuildSettingsVersion maxVersion = BuildSettingsVersion.Latest, InitializationContext context = InitializationContext.Any) : base(defaultLevel, context) #pragma warning restore CA1019 { MinVersion = minVersion; MaxVersion = maxVersion; } /// /// Resolves the set of to a provided the current . /// /// The set of attributes of which to consider for resolution. /// The build system context used to consider defaults and applicability. /// The corresponding of which to apply to the property with the default value attributes. /// Will invoke on the input . [WarningLevelResolverDelegate(nameof(VersionWarningLevelDefaultAttribute))] internal static WarningLevel ResolveVersionWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext) { IList? castedAttributes = WarningLevelDefaultHelpers.CastWarningLevelDefaultAttirbuteToTarget(unsortedDefaultValueAttributes, Logger); if (castedAttributes == null) { Logger?.LogWarning("Unable to discern default warning level through resolution. Defaulting to WarningLevel.Default."); return WarningLevel.Default; } BuildSettingsVersion activeVersion = buildSystemContext != null ? buildSystemContext._buildContext.GetBuildSettings() : BuildSettingsVersion.V1; List sortedAndMerged = EnsureWarningLevelDefaultBounds(castedAttributes); WarningLevel returnWarningLevel = WarningLevel.Default; foreach (VersionWarningLevelDefaultAttribute attr in sortedAndMerged) { // If we find our appropriate range, we early out. if (attr.MinVersion <= activeVersion && activeVersion <= attr.MaxVersion) { returnWarningLevel = attr.GetDefaultLevel(buildSystemContext); break; } } return returnWarningLevel; } /// /// Ensures the attributes collection represents a contiguous, non-overlapping range. /// /// The list of attributes to verify. /// A list of attributes that has no overlaps, and is contiguous. internal static List EnsureWarningLevelDefaultBounds(IList attributes) { List sorted = attributes.OrderBy(a => a.MinVersion).ToList(); List merged = new List(sorted.Count); for (int i = 0; i < sorted.Count - 1; i++) { VersionWarningLevelDefaultAttribute current = sorted[i]; VersionWarningLevelDefaultAttribute next = sorted[i + 1]; if (current.MaxVersion < next.MinVersion - 1) { Logger?.LogWarning("Malformed VersionWarningLevelDefaultAttribute collection; taking corrective action to address gap in range (CurrentMax: {CurrentMax} NextMin: {NextMin}-1).", next.MinVersion, current.MaxVersion); // Extend current range to cover the gap up to the next MinVersion,using the old build settings version standard. current = new VersionWarningLevelDefaultAttribute( current.DefaultLevel, current.MinVersion, next.MinVersion - 1 ); } else if (next.MinVersion <= current.MaxVersion) { Logger?.LogWarning("Malformed VersionWarningLevelDefaultAttribute collection; taking corrective action to address overlap in range (NextMin: {NextMin} <= CurrentMax: {CurrentMax}).", next.MinVersion, current.MaxVersion); // Reduce next range to be more constrained, leaving the larger current range at the old build settings version standard. next = new VersionWarningLevelDefaultAttribute( next.DefaultLevel, current.MaxVersion + 1, next.MaxVersion ); // Flatten the old value with the updated one. sorted[i + 1] = next; } merged.Add(current); } merged.Add(sorted.Last()); return merged; } } }