From 230a11c33008c23aa5121caceff4d449f6f1eecb Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 19 Jul 2023 11:27:14 +0200 Subject: [PATCH 1/4] Annotate the library with trimming attributes --- .../DynamicDependencyAttribute.cs | 136 ++++++++++++++++++ .../DynamicallyAccessedMemberTypes.cs | 100 +++++++++++++ .../DynamicallyAccessedMembersAttribute.cs | 57 ++++++++ .../RequiresUnreferencedCodeAttribute.cs | 47 ++++++ ...edCircuitBreakerStrategyOptions.TResult.cs | 3 + .../CircuitBreakerStrategyOptions.cs | 2 + src/Polly.Core/Polly.Core.csproj | 4 + .../ResilienceStrategyBuilderBase.cs | 3 +- .../Retry/RetryStrategyOptions.TResult.cs | 2 + .../Telemetry/ResilienceStrategyTelemetry.cs | 8 ++ .../Timeout/TimeoutStrategyOptions.cs | 2 + src/Polly.Core/Utils/Constants.cs | 11 ++ src/Polly.Core/Utils/ValidationHelper.cs | 2 + .../AddResilienceStrategyContext.cs | 6 +- src/Polly.Extensions/Polly.Extensions.csproj | 4 + .../ConfigureBuilderContextExtensions.cs | 8 +- .../ResilienceTelemetryDiagnosticSource.cs | 6 +- .../Utils/OptionsReloadHelper.cs | 3 +- .../Polly.RateLimiting.csproj | 4 + 19 files changed, 401 insertions(+), 7 deletions(-) create mode 100644 src/LegacySupport/DynamicDependencyAttribute.cs create mode 100644 src/LegacySupport/DynamicallyAccessedMemberTypes.cs create mode 100644 src/LegacySupport/DynamicallyAccessedMembersAttribute.cs create mode 100644 src/LegacySupport/RequiresUnreferencedCodeAttribute.cs create mode 100644 src/Polly.Core/Utils/Constants.cs diff --git a/src/LegacySupport/DynamicDependencyAttribute.cs b/src/LegacySupport/DynamicDependencyAttribute.cs new file mode 100644 index 00000000000..e021a12de96 --- /dev/null +++ b/src/LegacySupport/DynamicDependencyAttribute.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable IDE0079 +#pragma warning disable SA1101 +#pragma warning disable SA1116 +#pragma warning disable SA1117 +#pragma warning disable SA1512 +#pragma warning disable SA1623 +#pragma warning disable SA1642 +#pragma warning disable S3903 + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// States a dependency that one member has on another. +/// +/// +/// This can be used to inform tooling of a dependency that is otherwise not evident purely from +/// metadata and IL, for example a member relied on via reflection. +/// +[AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method, + AllowMultiple = true, Inherited = false)] +[ExcludeFromCodeCoverage] +internal sealed class DynamicDependencyAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on the same type as the consumer. + /// + /// The signature of the member depended on. + public DynamicDependencyAttribute(string memberSignature) => MemberSignature = memberSignature; + + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on a . + /// + /// The signature of the member depended on. + /// The containing . + public DynamicDependencyAttribute(string memberSignature, Type type) + { + MemberSignature = memberSignature; + Type = type; + } + + /// + /// Initializes a new instance of the class + /// with the specified signature of a member on a type in an assembly. + /// + /// The signature of the member depended on. + /// The full name of the type containing the specified member. + /// The assembly name of the type containing the specified member. + public DynamicDependencyAttribute(string memberSignature, string typeName, string assemblyName) + { + MemberSignature = memberSignature; + TypeName = typeName; + AssemblyName = assemblyName; + } + + /// + /// Initializes a new instance of the class + /// with the specified types of members on a . + /// + /// The types of members depended on. + /// The containing the specified members. + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, Type type) + { + MemberTypes = memberTypes; + Type = type; + } + + /// + /// Initializes a new instance of the class + /// with the specified types of members on a type in an assembly. + /// + /// The types of members depended on. + /// The full name of the type containing the specified members. + /// The assembly name of the type containing the specified members. + public DynamicDependencyAttribute(DynamicallyAccessedMemberTypes memberTypes, string typeName, string assemblyName) + { + MemberTypes = memberTypes; + TypeName = typeName; + AssemblyName = assemblyName; + } + + /// + /// Gets the signature of the member depended on. + /// + /// + /// Either must be a valid string or + /// must not equal , but not both. + /// + public string? MemberSignature { get; } + + /// + /// Gets the which specifies the type + /// of members depended on. + /// + /// + /// Either must be a valid string or + /// must not equal , but not both. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + + /// + /// Gets the containing the specified member. + /// + /// + /// If neither nor are specified, + /// the type of the consumer is assumed. + /// + public Type? Type { get; } + + /// + /// Gets the full name of the type containing the specified member. + /// + /// + /// If neither nor are specified, + /// the type of the consumer is assumed. + /// + public string? TypeName { get; } + + /// + /// Gets the assembly name of the specified type. + /// + /// + /// is only valid when is specified. + /// + public string? AssemblyName { get; } + + /// + /// Gets or sets the condition in which the dependency is applicable, e.g. "DEBUG". + /// + public string? Condition { get; set; } +} diff --git a/src/LegacySupport/DynamicallyAccessedMemberTypes.cs b/src/LegacySupport/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 00000000000..eb6326d5a3c --- /dev/null +++ b/src/LegacySupport/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable IDE0079 +#pragma warning disable CA2217 +#pragma warning disable SA1413 +#pragma warning disable SA1512 +#pragma warning disable S4070 + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Specifies the types of members that are dynamically accessed. +/// +/// This enumeration has a attribute that allows a +/// bitwise combination of its member values. +/// +[Flags] +internal enum DynamicallyAccessedMemberTypes +{ + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None +} diff --git a/src/LegacySupport/DynamicallyAccessedMembersAttribute.cs b/src/LegacySupport/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 00000000000..10a900a9601 --- /dev/null +++ b/src/LegacySupport/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable IDE0079 +#pragma warning disable SA1101 +#pragma warning disable SA1116 +#pragma warning disable SA1117 +#pragma warning disable SA1512 +#pragma warning disable SA1623 +#pragma warning disable SA1642 +#pragma warning disable S3903 + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// Indicates that certain members on a specified are accessed dynamically, +/// for example through . +/// +/// +/// This allows tools to understand which members are being accessed during the execution +/// of a program. +/// +/// This attribute is valid on members whose type is or . +/// +/// When this attribute is applied to a location of type , the assumption is +/// that the string represents a fully qualified type name. +/// +/// When this attribute is applied to a class, interface, or struct, the members specified +/// can be accessed dynamically on instances returned from calling +/// on instances of that class, interface, or struct. +/// +/// If the attribute is applied to a method it's treated as a special case and it implies +/// the attribute should be applied to the "this" parameter of the method. As such the attribute +/// should only be used on instance methods of types assignable to System.Type (or string, but no methods +/// will use it there). +/// +[AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] +[ExcludeFromCodeCoverage] +internal sealed class DynamicallyAccessedMembersAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) => MemberTypes = memberTypes; + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } +} diff --git a/src/LegacySupport/RequiresUnreferencedCodeAttribute.cs b/src/LegacySupport/RequiresUnreferencedCodeAttribute.cs new file mode 100644 index 00000000000..876d514812b --- /dev/null +++ b/src/LegacySupport/RequiresUnreferencedCodeAttribute.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable IDE0079 +#pragma warning disable SA1101 +#pragma warning disable SA1116 +#pragma warning disable SA1117 +#pragma warning disable SA1512 +#pragma warning disable SA1623 +#pragma warning disable SA1642 +#pragma warning disable S3903 +#pragma warning disable S3996 + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// /// Indicates that the specified method requires dynamic access to code that is not referenced +/// statically, for example through . +/// +/// +/// This allows tools to understand which methods are unsafe to call when removing unreferenced +/// code from an application. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +[ExcludeFromCodeCoverage] +internal sealed class RequiresUnreferencedCodeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of unreferenced code. + /// + public RequiresUnreferencedCodeAttribute(string message) => Message = message; + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } +} diff --git a/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs b/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs index 93ecd2b0c13..37ca06227dd 100644 --- a/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs +++ b/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs @@ -48,6 +48,9 @@ public class AdvancedCircuitBreakerStrategyOptions : CircuitBreakerStra /// /// The default value is 30 seconds. Value must be greater than 0.5 seconds. /// +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:00.500", "1.00:00:00")] public TimeSpan SamplingDuration { get; set; } = CircuitBreakerConstants.DefaultSamplingDuration; +#pragma warning restore IL2026 + } diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs index 5fb5fbc1243..f58dbf96ba8 100644 --- a/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs +++ b/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs @@ -29,8 +29,10 @@ public abstract class CircuitBreakerStrategyOptions : ResilienceStrateg /// /// The default value is 5 seconds. Value must be greater than 0.5 seconds. /// +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:00.500", "1.00:00:00")] public TimeSpan BreakDuration { get; set; } = CircuitBreakerConstants.DefaultBreakDuration; +#pragma warning restore /// /// Gets or sets the predicates for the circuit breaker. diff --git a/src/Polly.Core/Polly.Core.csproj b/src/Polly.Core/Polly.Core.csproj index c4a3fbde39c..9d9a3774897 100644 --- a/src/Polly.Core/Polly.Core.csproj +++ b/src/Polly.Core/Polly.Core.csproj @@ -10,6 +10,10 @@ 100 true true + true + true + true + true diff --git a/src/Polly.Core/ResilienceStrategyBuilderBase.cs b/src/Polly.Core/ResilienceStrategyBuilderBase.cs index a1259f55c97..570940cff3d 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderBase.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderBase.cs @@ -1,7 +1,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; - namespace Polly; /// @@ -16,7 +15,9 @@ public abstract class ResilienceStrategyBuilderBase { private readonly List _entries = new(); private bool _used; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code private Action _validator = ValidationHelper.ValidateObject; +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code private protected ResilienceStrategyBuilderBase() { diff --git a/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs b/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs index f8f11db0ea4..b5ff344609b 100644 --- a/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs +++ b/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs @@ -58,8 +58,10 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// /// The default value is 2 seconds. /// +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:00", "1.00:00:00")] public TimeSpan BaseDelay { get; set; } = RetryConstants.DefaultBaseDelay; +#pragma warning restore IL2026 /// /// Gets or sets an outcome predicate that is used to register the predicates to determine if a retry should be performed. diff --git a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs index 9062ac77c87..2c507ebf8a7 100644 --- a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs +++ b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs @@ -44,7 +44,11 @@ public void Report(ResilienceEvent resilienceEvent, ResilienceContext con var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, resilienceEvent, context, null, args!); +#pragma warning disable IL2026 // The consumer of this method is Polly.Extensions and it does not use reflection at all +#pragma warning disable IL3050 DiagnosticSource.Write(resilienceEvent.EventName, telemetryArgs); +#pragma warning restore IL3050 +#pragma warning restore IL2026 TelemetryEventArguments.Return(telemetryArgs); } @@ -67,7 +71,11 @@ public void Report(ResilienceEvent resilienceEvent, OutcomeArgum var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, resilienceEvent, args.Context, args.Outcome.AsOutcome(), args.Arguments!); +#pragma warning disable IL2026 // The consumer of this method is Polly.Extensions and it does not use reflection at all +#pragma warning disable IL3050 DiagnosticSource.Write(resilienceEvent.EventName, telemetryArgs); +#pragma warning restore IL3050 +#pragma warning restore IL2026 TelemetryEventArguments.Return(telemetryArgs); } diff --git a/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs b/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs index 8a5e74113ee..eed4d85a89a 100644 --- a/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs +++ b/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs @@ -19,8 +19,10 @@ public class TimeoutStrategyOptions : ResilienceStrategyOptions /// /// This value must be greater than 1 second and less than 24 hours. The default value is 30 seconds. /// +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:01", "1.00:00:00")] public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); +#pragma warning restore IL2026 /// /// Gets or sets the timeout generator that generates the timeout for a given execution. diff --git a/src/Polly.Core/Utils/Constants.cs b/src/Polly.Core/Utils/Constants.cs new file mode 100644 index 00000000000..283608bb5c1 --- /dev/null +++ b/src/Polly.Core/Utils/Constants.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations; + +namespace Polly.Utils; + +internal static class Constants +{ + public const string ValidationTrimmingMessage = "The default options validation requires reflection. Use custom 'ResilienceStrategyBuilder.Validator' callback for validation."; + + public const string BuilderTrimmingMessage = $"The default validator uses reflection for validator. " + + $"Assign custom '{nameof(Validator)}' validation delegate if your library needs to be AOT friendly."; +} diff --git a/src/Polly.Core/Utils/ValidationHelper.cs b/src/Polly.Core/Utils/ValidationHelper.cs index 5fb599c7f19..50cf69e3afe 100644 --- a/src/Polly.Core/Utils/ValidationHelper.cs +++ b/src/Polly.Core/Utils/ValidationHelper.cs @@ -7,6 +7,8 @@ namespace Polly.Utils; [ExcludeFromCodeCoverage] internal static class ValidationHelper { + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TimeSpan))] + [RequiresUnreferencedCode("Calls System.ComponentModel.DataAnnotations.ValidationContext.ValidationContext(Object)")] public static void ValidateObject(ResilienceValidationContext context) { Guard.NotNull(context); diff --git a/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs b/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs index 3792108b1ab..48072b72815 100644 --- a/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs +++ b/src/Polly.Extensions/DependencyInjection/AddResilienceStrategyContext.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Polly.Extensions.Registry; @@ -53,7 +54,8 @@ internal AddResilienceStrategyContext(ConfigureBuilderContext registryCont /// You can listen for changes only for single options. If you call this method multiple times, the preceding calls are ignored and only the last one wins. /// /// - public void EnableReloads(string? name = null) => RegistryContext.EnableReloads(ServiceProvider.GetRequiredService>(), name); + public void EnableReloads<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions>(string? name = null) + => RegistryContext.EnableReloads(ServiceProvider.GetRequiredService>(), name); /// /// Gets the options identified by . @@ -64,7 +66,7 @@ internal AddResilienceStrategyContext(ConfigureBuilderContext registryCont /// /// If is then the global options are returned. /// - public TOptions GetOptions(string? name = null) + public TOptions GetOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions>(string? name = null) { var monitor = ServiceProvider.GetRequiredService>(); diff --git a/src/Polly.Extensions/Polly.Extensions.csproj b/src/Polly.Extensions/Polly.Extensions.csproj index f81a4b6dfa4..6f87f9850ae 100644 --- a/src/Polly.Extensions/Polly.Extensions.csproj +++ b/src/Polly.Extensions/Polly.Extensions.csproj @@ -8,6 +8,10 @@ Library 100 true + true + true + true + true diff --git a/src/Polly.Extensions/Registry/ConfigureBuilderContextExtensions.cs b/src/Polly.Extensions/Registry/ConfigureBuilderContextExtensions.cs index bdb257c379b..e28ef6743d9 100644 --- a/src/Polly.Extensions/Registry/ConfigureBuilderContextExtensions.cs +++ b/src/Polly.Extensions/Registry/ConfigureBuilderContextExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; using Polly.Extensions.Utils; using Polly.Registry; using Polly.Utils; @@ -25,7 +26,10 @@ public static class ConfigureBuilderContextExtensions /// You can listen for changes only for single options. If you call this method multiple times, the preceding calls are ignored and only the last one wins. /// /// - public static void EnableReloads(this ConfigureBuilderContext context, IOptionsMonitor optionsMonitor, string? name = null) + public static void EnableReloads( + this ConfigureBuilderContext context, + IOptionsMonitor optionsMonitor, + string? name = null) where TKey : notnull { Guard.NotNull(context); diff --git a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs index a7a54fb6ce6..030ea31753f 100644 --- a/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs +++ b/src/Polly.Extensions/Telemetry/ResilienceTelemetryDiagnosticSource.cs @@ -4,7 +4,7 @@ namespace Polly.Extensions.Telemetry; -internal class ResilienceTelemetryDiagnosticSource : DiagnosticSource +internal sealed class ResilienceTelemetryDiagnosticSource : DiagnosticSource { internal static readonly Meter Meter = new(TelemetryUtil.PollyDiagnosticSource, "1.0"); @@ -36,7 +36,11 @@ public ResilienceTelemetryDiagnosticSource(TelemetryOptions options) public override bool IsEnabled(string name) => true; +#pragma warning disable IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides. +#pragma warning disable IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides. public override void Write(string name, object? value) +#pragma warning restore IL3051 // 'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides. +#pragma warning restore IL2046 // 'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides. { if (value is not TelemetryEventArguments args) { diff --git a/src/Polly.Extensions/Utils/OptionsReloadHelper.cs b/src/Polly.Extensions/Utils/OptionsReloadHelper.cs index c865f97b537..66e1ef9e9e9 100644 --- a/src/Polly.Extensions/Utils/OptionsReloadHelper.cs +++ b/src/Polly.Extensions/Utils/OptionsReloadHelper.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Options; namespace Polly.Extensions.Utils; @@ -5,7 +6,7 @@ namespace Polly.Extensions.Utils; #pragma warning disable CA1001 // we can get away of not disposing this class because it's active for a lifetime of app #pragma warning disable S2931 -internal sealed class OptionsReloadHelper +internal sealed class OptionsReloadHelper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T> { private CancellationTokenSource _cancellation = new(); diff --git a/src/Polly.RateLimiting/Polly.RateLimiting.csproj b/src/Polly.RateLimiting/Polly.RateLimiting.csproj index 9ebb23c6b01..119bf02df1d 100644 --- a/src/Polly.RateLimiting/Polly.RateLimiting.csproj +++ b/src/Polly.RateLimiting/Polly.RateLimiting.csproj @@ -8,6 +8,10 @@ Library 100 true + true + true + true + true From 0fa271d0901218ce19a8c723ad14255fe5232ee3 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 19 Jul 2023 20:22:21 +0200 Subject: [PATCH 2/4] Make sure options are not trimmed for validation --- ...reakerResilienceStrategyBuilderExtensions.cs | 17 ++++++++++++++--- ...llbackResilienceStrategyBuilderExtensions.cs | 11 ++++++++--- ...edgingResilienceStrategyBuilderExtensions.cs | 11 ++++++++--- src/Polly.Core/ResilienceStrategyBuilderBase.cs | 5 ++++- .../ResilienceStrategyBuilderExtensions.cs | 4 ++++ .../RetryResilienceStrategyBuilderExtensions.cs | 16 +++++++++++----- ...imeoutResilienceStrategyBuilderExtensions.cs | 4 ++++ src/Polly.Core/Utils/Constants.cs | 12 +++++------- src/Polly.Core/Utils/ValidationHelper.cs | 3 ++- ...emetryResilienceStrategyBuilderExtensions.cs | 2 ++ ...imiterResilienceStrategyBuilderExtensions.cs | 4 ++++ 11 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs index 67d0388888f..1236f532d1f 100644 --- a/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs @@ -1,9 +1,12 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using Polly.CircuitBreaker; using Polly.CircuitBreaker.Health; namespace Polly; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + /// /// Circuit breaker strategy extensions for . /// @@ -110,7 +113,7 @@ private static TBuilder AddAdvancedCircuitBreakerCore(this TB options.MinimumThroughput, HealthMetrics.Create(options.SamplingDuration, context.TimeProvider)); - return CreateStrategy(context, options, behavior); + return CreateStrategy>(context, options, behavior); }, options); } @@ -118,10 +121,18 @@ private static TBuilder AddAdvancedCircuitBreakerCore(this TB private static TBuilder AddSimpleCircuitBreakerCore(this TBuilder builder, SimpleCircuitBreakerStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { - return builder.AddStrategy(context => CreateStrategy(context, options, new ConsecutiveFailuresCircuitBehavior(options.FailureThreshold)), options); + return builder.AddStrategy( + context => + { + return CreateStrategy>( + context, + options, + new ConsecutiveFailuresCircuitBehavior(options.FailureThreshold)); + }, + options); } - internal static CircuitBreakerResilienceStrategy CreateStrategy( + internal static CircuitBreakerResilienceStrategy CreateStrategy( ResilienceStrategyBuilderContext context, CircuitBreakerStrategyOptions options, CircuitBehavior behavior) diff --git a/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs index 89ee15dbddc..a997a31722a 100644 --- a/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using Polly.Fallback; namespace Polly; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + /// /// Provides extension methods for configuring fallback resilience strategies for . /// @@ -22,7 +25,7 @@ public static ResilienceStrategyBuilder AddFallback(this Resil Guard.NotNull(builder); Guard.NotNull(options); - builder.AddFallbackCore(options); + builder.AddFallbackCore>(options); return builder; } @@ -39,11 +42,13 @@ internal static ResilienceStrategyBuilder AddFallback(this ResilienceStrategyBui Guard.NotNull(builder); Guard.NotNull(options); - builder.AddFallbackCore(options); + builder.AddFallbackCore(options); return builder; } - internal static void AddFallbackCore(this ResilienceStrategyBuilderBase builder, FallbackStrategyOptions options) + internal static void AddFallbackCore( + this ResilienceStrategyBuilderBase builder, + FallbackStrategyOptions options) { builder.AddStrategy(context => { diff --git a/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs index 59587546475..50a98b0c805 100644 --- a/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs @@ -1,9 +1,12 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using Polly.Hedging; using Polly.Hedging.Utils; namespace Polly; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + /// /// Provides extension methods for configuring hedging resilience strategies for . /// @@ -23,7 +26,7 @@ public static ResilienceStrategyBuilder AddHedging(this Resili Guard.NotNull(builder); Guard.NotNull(options); - builder.AddHedgingCore(options); + builder.AddHedgingCore>(options); return builder; } @@ -40,11 +43,13 @@ internal static ResilienceStrategyBuilder AddHedging(this ResilienceStrategyBuil Guard.NotNull(builder); Guard.NotNull(options); - builder.AddHedgingCore(options); + builder.AddHedgingCore(options); return builder; } - internal static void AddHedgingCore(this ResilienceStrategyBuilderBase builder, HedgingStrategyOptions options) + internal static void AddHedgingCore( + this ResilienceStrategyBuilderBase builder, + HedgingStrategyOptions options) { builder.AddStrategy(context => { diff --git a/src/Polly.Core/ResilienceStrategyBuilderBase.cs b/src/Polly.Core/ResilienceStrategyBuilderBase.cs index 570940cff3d..04701e58979 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderBase.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderBase.cs @@ -1,6 +1,8 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + namespace Polly; /// @@ -122,11 +124,12 @@ private protected ResilienceStrategyBuilderBase(ResilienceStrategyBuilderBase ot public Action Validator { get => _validator; - set => _validator = Guard.NotNull(value); + internal set => _validator = Guard.NotNull(value); } internal abstract bool IsGenericBuilder { get; } + [RequiresUnreferencedCode(Constants.OptionsValidation)] internal void AddStrategyCore(Func factory, ResilienceStrategyOptions options) { Guard.NotNull(factory); diff --git a/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs index 89a96652ea1..771f7c0d0a4 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; namespace Polly; @@ -22,7 +23,9 @@ public static TBuilder AddStrategy(this TBuilder builder, ResilienceSt Guard.NotNull(builder); Guard.NotNull(strategy); +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code return builder.AddStrategy(_ => strategy, EmptyOptions.Instance); +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } /// @@ -53,6 +56,7 @@ public static ResilienceStrategyBuilder AddStrategy(this Resil /// Thrown when , or is . /// Thrown when this builder was already used to create a strategy. The builder cannot be modified after it has been used. /// Thrown when is invalid. + [RequiresUnreferencedCode(Constants.OptionsValidation)] public static TBuilder AddStrategy(this TBuilder builder, Func factory, ResilienceStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { diff --git a/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs index 088c3087050..c1f8b754bbc 100644 --- a/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using Polly.Retry; namespace Polly; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + /// /// Retry extension methods for the . /// @@ -21,7 +24,8 @@ public static ResilienceStrategyBuilder AddRetry(this ResilienceStrategyBuilder Guard.NotNull(builder); Guard.NotNull(options); - return builder.AddRetryCore(options); + builder.AddRetryCore(options); + return builder; } /// @@ -38,13 +42,15 @@ public static ResilienceStrategyBuilder AddRetry(this Resilien Guard.NotNull(builder); Guard.NotNull(options); - return builder.AddRetryCore(options); + builder.AddRetryCore>(options); + return builder; } - private static TBuilder AddRetryCore(this TBuilder builder, RetryStrategyOptions options) - where TBuilder : ResilienceStrategyBuilderBase + private static void AddRetryCore( + this ResilienceStrategyBuilderBase builder, + RetryStrategyOptions options) { - return builder.AddStrategy(context => + builder.AddStrategy(context => new RetryResilienceStrategy( options, context.IsGenericBuilder, diff --git a/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs index e2e81855635..806753d50f5 100644 --- a/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs @@ -1,8 +1,11 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using Polly.Timeout; namespace Polly; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + /// /// Extension methods for adding timeouts to a . /// @@ -37,6 +40,7 @@ public static TBuilder AddTimeout(this TBuilder builder, TimeSpan time /// The same builder instance. /// Thrown when or is . /// Thrown when are invalid. + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TimeoutStrategyOptions))] public static TBuilder AddTimeout(this TBuilder builder, TimeoutStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { diff --git a/src/Polly.Core/Utils/Constants.cs b/src/Polly.Core/Utils/Constants.cs index 283608bb5c1..7c7ee8cb97d 100644 --- a/src/Polly.Core/Utils/Constants.cs +++ b/src/Polly.Core/Utils/Constants.cs @@ -1,11 +1,9 @@ -using System.ComponentModel.DataAnnotations; - -namespace Polly.Utils; +namespace Polly.Utils; internal static class Constants { - public const string ValidationTrimmingMessage = "The default options validation requires reflection. Use custom 'ResilienceStrategyBuilder.Validator' callback for validation."; - - public const string BuilderTrimmingMessage = $"The default validator uses reflection for validator. " + - $"Assign custom '{nameof(Validator)}' validation delegate if your library needs to be AOT friendly."; + public const string OptionsValidation = """ + This call validates the options using the data annotations attributes. + Make sure that the options are included using the '[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(OptionsType))]' attribute on top of calling method. + """; } diff --git a/src/Polly.Core/Utils/ValidationHelper.cs b/src/Polly.Core/Utils/ValidationHelper.cs index 50cf69e3afe..2ecd374cdde 100644 --- a/src/Polly.Core/Utils/ValidationHelper.cs +++ b/src/Polly.Core/Utils/ValidationHelper.cs @@ -8,13 +8,13 @@ namespace Polly.Utils; internal static class ValidationHelper { [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TimeSpan))] - [RequiresUnreferencedCode("Calls System.ComponentModel.DataAnnotations.ValidationContext.ValidationContext(Object)")] public static void ValidateObject(ResilienceValidationContext context) { Guard.NotNull(context); var errors = new List(); +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code if (!Validator.TryValidateObject(context.Instance, new ValidationContext(context.Instance), errors, true)) { var stringBuilder = new StringBuilder(context.PrimaryMessage); @@ -28,5 +28,6 @@ public static void ValidateObject(ResilienceValidationContext context) throw new ValidationException(stringBuilder.ToString()); } +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } } diff --git a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategyBuilderExtensions.cs b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategyBuilderExtensions.cs index c3e2383eaaf..37ebaa95717 100644 --- a/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Extensions/Telemetry/TelemetryResilienceStrategyBuilderExtensions.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using Polly.Extensions.Telemetry; using Polly.Utils; @@ -42,6 +43,7 @@ public static TBuilder ConfigureTelemetry(this TBuilder builder, ILogg /// Additionally, the telemetry strategy that logs and meters the executions is added to the beginning of the strategy pipeline. /// /// Thrown when or is . + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TelemetryOptions))] public static TBuilder ConfigureTelemetry(this TBuilder builder, TelemetryOptions options) where TBuilder : ResilienceStrategyBuilderBase { diff --git a/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs b/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs index a51f12a8c17..b36027581c2 100644 --- a/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs @@ -1,9 +1,12 @@ using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using System.Threading.RateLimiting; using Polly.RateLimiting; namespace Polly; +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + /// /// The rate limiter extensions for . /// @@ -92,6 +95,7 @@ public static TBuilder AddRateLimiter( /// Thrown when or is . /// Thrown when are invalid. /// Thrown when for are invalid. + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(RateLimiterStrategyOptions))] public static TBuilder AddRateLimiter( this TBuilder builder, RateLimiterStrategyOptions options) From 4baa1359cd4795240bca7febbc4e0e393dcfadda Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 19 Jul 2023 20:36:38 +0200 Subject: [PATCH 3/4] fixes --- .../AdvancedCircuitBreakerStrategyOptions.TResult.cs | 3 +-- src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs | 2 +- src/Polly.Core/PublicAPI.Unshipped.txt | 1 - src/Polly.Core/ResilienceStrategyBuilderBase.cs | 2 +- src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs | 2 +- src/Polly.Core/Timeout/TimeoutStrategyOptions.cs | 2 +- 6 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs b/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs index 37ca06227dd..b2fccbcf247 100644 --- a/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs +++ b/src/Polly.Core/CircuitBreaker/AdvancedCircuitBreakerStrategyOptions.TResult.cs @@ -42,15 +42,14 @@ public class AdvancedCircuitBreakerStrategyOptions : CircuitBreakerStra [Range(CircuitBreakerConstants.MinimumValidThroughput, int.MaxValue)] public int MinimumThroughput { get; set; } = CircuitBreakerConstants.DefaultMinimumThroughput; +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method /// /// Gets or sets the duration of the sampling over which failure ratios are assessed. /// /// /// The default value is 30 seconds. Value must be greater than 0.5 seconds. /// -#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:00.500", "1.00:00:00")] public TimeSpan SamplingDuration { get; set; } = CircuitBreakerConstants.DefaultSamplingDuration; #pragma warning restore IL2026 - } diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs index f58dbf96ba8..da30948b6a2 100644 --- a/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs +++ b/src/Polly.Core/CircuitBreaker/CircuitBreakerStrategyOptions.cs @@ -23,13 +23,13 @@ public abstract class CircuitBreakerStrategyOptions : ResilienceStrateg /// Returns CircuitBreaker value. public sealed override string StrategyType => CircuitBreakerConstants.StrategyType; +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method /// /// Gets or sets the duration of break the circuit will stay open before resetting. /// /// /// The default value is 5 seconds. Value must be greater than 0.5 seconds. /// -#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:00.500", "1.00:00:00")] public TimeSpan BreakDuration { get; set; } = CircuitBreakerConstants.DefaultBreakDuration; #pragma warning restore diff --git a/src/Polly.Core/PublicAPI.Unshipped.txt b/src/Polly.Core/PublicAPI.Unshipped.txt index cd760667e30..31833b0d5b3 100644 --- a/src/Polly.Core/PublicAPI.Unshipped.txt +++ b/src/Polly.Core/PublicAPI.Unshipped.txt @@ -293,7 +293,6 @@ Polly.ResilienceStrategyBuilderBase.Properties.get -> Polly.ResilienceProperties Polly.ResilienceStrategyBuilderBase.Randomizer.get -> System.Func! Polly.ResilienceStrategyBuilderBase.Randomizer.set -> void Polly.ResilienceStrategyBuilderBase.Validator.get -> System.Action! -Polly.ResilienceStrategyBuilderBase.Validator.set -> void Polly.ResilienceStrategyBuilderContext Polly.ResilienceStrategyBuilderContext.BuilderInstanceName.get -> string? Polly.ResilienceStrategyBuilderContext.BuilderName.get -> string? diff --git a/src/Polly.Core/ResilienceStrategyBuilderBase.cs b/src/Polly.Core/ResilienceStrategyBuilderBase.cs index 04701e58979..cf86d5671a0 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderBase.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderBase.cs @@ -113,7 +113,7 @@ private protected ResilienceStrategyBuilderBase(ResilienceStrategyBuilderBase ot public Func Randomizer { get; set; } = RandomUtil.Instance.NextDouble; /// - /// Gets or sets the validator that is used for the validation. + /// Gets the validator that is used for the validation. /// /// The default value is a validation function that uses data annotations for validation. /// diff --git a/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs b/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs index b5ff344609b..d4ed56d267c 100644 --- a/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs +++ b/src/Polly.Core/Retry/RetryStrategyOptions.TResult.cs @@ -34,6 +34,7 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// public RetryBackoffType BackoffType { get; set; } = RetryConstants.DefaultBackoffType; +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method /// /// Gets or sets the base delay between retries. /// @@ -58,7 +59,6 @@ public class RetryStrategyOptions : ResilienceStrategyOptions /// /// The default value is 2 seconds. /// -#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:00", "1.00:00:00")] public TimeSpan BaseDelay { get; set; } = RetryConstants.DefaultBaseDelay; #pragma warning restore IL2026 diff --git a/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs b/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs index eed4d85a89a..9b6fc8f650e 100644 --- a/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs +++ b/src/Polly.Core/Timeout/TimeoutStrategyOptions.cs @@ -13,13 +13,13 @@ public class TimeoutStrategyOptions : ResilienceStrategyOptions /// Returns Timeout value. public sealed override string StrategyType => TimeoutConstants.StrategyType; +#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method /// /// Gets or sets the default timeout. /// /// /// This value must be greater than 1 second and less than 24 hours. The default value is 30 seconds. /// -#pragma warning disable IL2026 // Addressed with DynamicDependency on ValidationHelper.Validate method [Range(typeof(TimeSpan), "00:00:01", "1.00:00:00")] public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(30); #pragma warning restore IL2026 From d111b6bc8d300a182d730c45f4453bfd640f4221 Mon Sep 17 00:00:00 2001 From: Martin Tomka Date: Wed, 19 Jul 2023 21:05:36 +0200 Subject: [PATCH 4/4] fixes --- .../UnconditionalSuppressMessageAttribute.cs | 94 +++++++++++++++++++ ...akerResilienceStrategyBuilderExtensions.cs | 10 +- ...backResilienceStrategyBuilderExtensions.cs | 6 +- ...gingResilienceStrategyBuilderExtensions.cs | 6 +- .../ResilienceStrategyBuilderBase.cs | 9 +- .../ResilienceStrategyBuilderExtensions.cs | 6 +- ...etryResilienceStrategyBuilderExtensions.cs | 6 +- .../Telemetry/ResilienceStrategyTelemetry.cs | 26 +++-- ...eoutResilienceStrategyBuilderExtensions.cs | 6 +- src/Polly.Core/Utils/ValidationHelper.cs | 6 +- ...iterResilienceStrategyBuilderExtensions.cs | 6 +- .../ResilienceStrategyBuilderTests.cs | 9 -- 12 files changed, 149 insertions(+), 41 deletions(-) create mode 100644 src/LegacySupport/UnconditionalSuppressMessageAttribute.cs diff --git a/src/LegacySupport/UnconditionalSuppressMessageAttribute.cs b/src/LegacySupport/UnconditionalSuppressMessageAttribute.cs new file mode 100644 index 00000000000..50a66797c10 --- /dev/null +++ b/src/LegacySupport/UnconditionalSuppressMessageAttribute.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma warning disable IDE0079 +#pragma warning disable SA1101 +#pragma warning disable SA1116 +#pragma warning disable SA1117 +#pragma warning disable SA1512 +#pragma warning disable SA1623 +#pragma warning disable SA1642 +#pragma warning disable S3903 + +namespace System.Diagnostics.CodeAnalysis; + +/// +/// /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a +/// single code artifact. +/// +/// +/// is different than +/// in that it doesn't have a +/// . So it is always preserved in the compiled assembly. +/// +[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] +[ExcludeFromCodeCoverage] +internal sealed class UnconditionalSuppressMessageAttribute : Attribute +{ + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string? Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string? MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification { get; set; } +} diff --git a/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs index 1236f532d1f..fdb3ee02d74 100644 --- a/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/CircuitBreaker/CircuitBreakerResilienceStrategyBuilderExtensions.cs @@ -5,8 +5,6 @@ namespace Polly; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - /// /// Circuit breaker strategy extensions for . /// @@ -102,6 +100,10 @@ public static ResilienceStrategyBuilder AddSimpleCircuitBreaker(this ResilienceS return builder.AddSimpleCircuitBreakerCore(options); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members preserved.")] private static TBuilder AddAdvancedCircuitBreakerCore(this TBuilder builder, AdvancedCircuitBreakerStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { @@ -118,6 +120,10 @@ private static TBuilder AddAdvancedCircuitBreakerCore(this TB options); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members preserved.")] private static TBuilder AddSimpleCircuitBreakerCore(this TBuilder builder, SimpleCircuitBreakerStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { diff --git a/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs index a997a31722a..b361f5b5014 100644 --- a/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Fallback/FallbackResilienceStrategyBuilderExtensions.cs @@ -4,8 +4,6 @@ namespace Polly; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - /// /// Provides extension methods for configuring fallback resilience strategies for . /// @@ -46,6 +44,10 @@ internal static ResilienceStrategyBuilder AddFallback(this ResilienceStrategyBui return builder; } + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members preserved.")] internal static void AddFallbackCore( this ResilienceStrategyBuilderBase builder, FallbackStrategyOptions options) diff --git a/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs index 50a98b0c805..7d2ee22b75e 100644 --- a/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Hedging/HedgingResilienceStrategyBuilderExtensions.cs @@ -5,8 +5,6 @@ namespace Polly; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - /// /// Provides extension methods for configuring hedging resilience strategies for . /// @@ -47,6 +45,10 @@ internal static ResilienceStrategyBuilder AddHedging(this ResilienceStrategyBuil return builder; } + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members preserved.")] internal static void AddHedgingCore( this ResilienceStrategyBuilderBase builder, HedgingStrategyOptions options) diff --git a/src/Polly.Core/ResilienceStrategyBuilderBase.cs b/src/Polly.Core/ResilienceStrategyBuilderBase.cs index cf86d5671a0..92e4b273846 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderBase.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderBase.cs @@ -17,9 +17,6 @@ public abstract class ResilienceStrategyBuilderBase { private readonly List _entries = new(); private bool _used; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - private Action _validator = ValidationHelper.ValidateObject; -#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code private protected ResilienceStrategyBuilderBase() { @@ -121,11 +118,7 @@ private protected ResilienceStrategyBuilderBase(ResilienceStrategyBuilderBase ot /// /// Thrown when the attempting to assign to this property. [EditorBrowsable(EditorBrowsableState.Never)] - public Action Validator - { - get => _validator; - internal set => _validator = Guard.NotNull(value); - } + public Action Validator { get; private protected set; } = ValidationHelper.ValidateObject; internal abstract bool IsGenericBuilder { get; } diff --git a/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs index 771f7c0d0a4..da92795899e 100644 --- a/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/ResilienceStrategyBuilderExtensions.cs @@ -17,15 +17,17 @@ public static class ResilienceStrategyBuilderExtensions /// The same builder instance. /// Thrown when is null. /// Thrown when this builder was already used to create a strategy. The builder cannot be modified after it has been used. + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "The EmptyOptions have nothing to validate.")] public static TBuilder AddStrategy(this TBuilder builder, ResilienceStrategy strategy) where TBuilder : ResilienceStrategyBuilderBase { Guard.NotNull(builder); Guard.NotNull(strategy); -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code return builder.AddStrategy(_ => strategy, EmptyOptions.Instance); -#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } /// diff --git a/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs index c1f8b754bbc..5c68d1b0569 100644 --- a/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Retry/RetryResilienceStrategyBuilderExtensions.cs @@ -4,8 +4,6 @@ namespace Polly; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - /// /// Retry extension methods for the . /// @@ -46,6 +44,10 @@ public static ResilienceStrategyBuilder AddRetry(this Resilien return builder; } + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members preserved.")] private static void AddRetryCore( this ResilienceStrategyBuilderBase builder, RetryStrategyOptions options) diff --git a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs index 2c507ebf8a7..5ce68bad552 100644 --- a/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs +++ b/src/Polly.Core/Telemetry/ResilienceStrategyTelemetry.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace Polly.Telemetry; /// @@ -31,6 +33,14 @@ internal ResilienceStrategyTelemetry(ResilienceTelemetrySource source, Diagnosti /// The resilience context associated with this event. /// The event arguments. /// Thrown when is . + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "The reflection is not used when consuming the event.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", + Justification = "The reflection is not used when consuming the event.")] public void Report(ResilienceEvent resilienceEvent, ResilienceContext context, TArgs args) { Guard.NotNull(context); @@ -44,11 +54,7 @@ public void Report(ResilienceEvent resilienceEvent, ResilienceContext con var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, resilienceEvent, context, null, args!); -#pragma warning disable IL2026 // The consumer of this method is Polly.Extensions and it does not use reflection at all -#pragma warning disable IL3050 DiagnosticSource.Write(resilienceEvent.EventName, telemetryArgs); -#pragma warning restore IL3050 -#pragma warning restore IL2026 TelemetryEventArguments.Return(telemetryArgs); } @@ -60,6 +66,14 @@ public void Report(ResilienceEvent resilienceEvent, ResilienceContext con /// The type of the result. /// The reported resilience event. /// The event arguments. + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "The reflection is not used when consuming the event.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", + Justification = "The reflection is not used when consuming the event.")] public void Report(ResilienceEvent resilienceEvent, OutcomeArguments args) { args.Context.AddResilienceEvent(resilienceEvent); @@ -71,11 +85,7 @@ public void Report(ResilienceEvent resilienceEvent, OutcomeArgum var telemetryArgs = TelemetryEventArguments.Get(TelemetrySource, resilienceEvent, args.Context, args.Outcome.AsOutcome(), args.Arguments!); -#pragma warning disable IL2026 // The consumer of this method is Polly.Extensions and it does not use reflection at all -#pragma warning disable IL3050 DiagnosticSource.Write(resilienceEvent.EventName, telemetryArgs); -#pragma warning restore IL3050 -#pragma warning restore IL2026 TelemetryEventArguments.Return(telemetryArgs); } diff --git a/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs b/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs index 806753d50f5..1b928dcaf81 100644 --- a/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.Core/Timeout/TimeoutResilienceStrategyBuilderExtensions.cs @@ -4,8 +4,6 @@ namespace Polly; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - /// /// Extension methods for adding timeouts to a . /// @@ -41,6 +39,10 @@ public static TBuilder AddTimeout(this TBuilder builder, TimeSpan time /// Thrown when or is . /// Thrown when are invalid. [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TimeoutStrategyOptions))] + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members preserved.")] public static TBuilder AddTimeout(this TBuilder builder, TimeoutStrategyOptions options) where TBuilder : ResilienceStrategyBuilderBase { diff --git a/src/Polly.Core/Utils/ValidationHelper.cs b/src/Polly.Core/Utils/ValidationHelper.cs index 2ecd374cdde..f50e9644273 100644 --- a/src/Polly.Core/Utils/ValidationHelper.cs +++ b/src/Polly.Core/Utils/ValidationHelper.cs @@ -8,13 +8,16 @@ namespace Polly.Utils; internal static class ValidationHelper { [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TimeSpan))] + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "The member of options are preserved and no trimmed. See builder.AddStrategy() extension.")] public static void ValidateObject(ResilienceValidationContext context) { Guard.NotNull(context); var errors = new List(); -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code if (!Validator.TryValidateObject(context.Instance, new ValidationContext(context.Instance), errors, true)) { var stringBuilder = new StringBuilder(context.PrimaryMessage); @@ -28,6 +31,5 @@ public static void ValidateObject(ResilienceValidationContext context) throw new ValidationException(stringBuilder.ToString()); } -#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code } } diff --git a/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs b/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs index b36027581c2..31211a2eeea 100644 --- a/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs +++ b/src/Polly.RateLimiting/RateLimiterResilienceStrategyBuilderExtensions.cs @@ -5,8 +5,6 @@ namespace Polly; -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - /// /// The rate limiter extensions for . /// @@ -96,6 +94,10 @@ public static TBuilder AddRateLimiter( /// Thrown when are invalid. /// Thrown when for are invalid. [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(RateLimiterStrategyOptions))] + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", + Justification = "All options members are preserved.")] public static TBuilder AddRateLimiter( this TBuilder builder, RateLimiterStrategyOptions options) diff --git a/test/Polly.Core.Tests/ResilienceStrategyBuilderTests.cs b/test/Polly.Core.Tests/ResilienceStrategyBuilderTests.cs index 72bb3cf9cef..78eb9a0175b 100644 --- a/test/Polly.Core.Tests/ResilienceStrategyBuilderTests.cs +++ b/test/Polly.Core.Tests/ResilienceStrategyBuilderTests.cs @@ -143,15 +143,6 @@ The field RetryCount must be between -1 and 100. """); } - [Fact] - public void Validator_Null_Throws() - { - new ResilienceStrategyBuilder() - .Invoking(b => b.Validator = null!) - .Should() - .Throw(); - } - [Fact] public void AddStrategy_MultipleNonDelegating_Ok() {