From 0e726b67ea85ee476c6b9a29eb69042945009f35 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 17 Oct 2024 11:51:23 -0700 Subject: [PATCH] Add trimming/AOT annotations (#266) --- src/AmqpEventSource.cs | 11 ++ src/Microsoft.Azure.Amqp.csproj | 6 +- src/Serialization/AmqpContractSerializer.cs | 24 +++ src/Serialization/MemberAccessor.cs | 6 + src/Serialization/MethodAccessor.cs | 3 + src/Serialization/ReflectionExtentions.cs | 4 + src/Serialization/SerializableType.cs | 11 ++ src/TrimmingAttributes.cs | 159 ++++++++++++++++++++ 8 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 src/TrimmingAttributes.cs diff --git a/src/AmqpEventSource.cs b/src/AmqpEventSource.cs index f4756255..8f329485 100644 --- a/src/AmqpEventSource.cs +++ b/src/AmqpEventSource.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Amqp { using System; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Tracing; [EventSource(Name = "Microsoft-Azure-Amqp")] @@ -15,6 +16,8 @@ sealed class AmqpEventSource : EventSource // 3. When changing the event definition, update callers in AmqpTrace // if needed. + private const string EventSourceSuppressMessage = "Parameters to this method are primitive and are trimmer safe."; + public static readonly AmqpEventSource Log; static AmqpEventSource() @@ -23,6 +26,7 @@ static AmqpEventSource() } [Event(1, Level = EventLevel.Informational, Message = "{0}: open connection {1}.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] public unsafe void AmqpOpenConnection(string source, string connection) { fixed (char* ptrSource = source) @@ -440,6 +444,7 @@ public unsafe void AmqpSentMessage(string source, uint deliveryId, long bytes) } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2) { EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; @@ -451,6 +456,7 @@ unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2) } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, IntPtr a3, int size3) { EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; @@ -464,6 +470,7 @@ unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, IntPtr a3, int size3, IntPtr a4, int size4) { @@ -480,6 +487,7 @@ unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, IntPtr a3, int size3, IntPtr a4, int size4, IntPtr a5, int size5) { @@ -498,6 +506,7 @@ unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, IntPtr a3, int size3, IntPtr a4, int size4, IntPtr a5, int size5, IntPtr a6, int size6) { @@ -518,6 +527,7 @@ unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, IntPtr a3, int size3, IntPtr a4, int size4, IntPtr a5, int size5, IntPtr a6, int size6, IntPtr a7, int size7) @@ -541,6 +551,7 @@ unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, } [NonEvent] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026", Justification = EventSourceSuppressMessage)] unsafe void WriteEvent(int eventId, IntPtr a1, int size1, IntPtr a2, int size2, IntPtr a3, int size3, IntPtr a4, int size4, IntPtr a5, int size5, IntPtr a6, int size6, IntPtr a7, int size7, IntPtr a8, int size8) diff --git a/src/Microsoft.Azure.Amqp.csproj b/src/Microsoft.Azure.Amqp.csproj index 93bd16e8..32a2c6f3 100644 --- a/src/Microsoft.Azure.Amqp.csproj +++ b/src/Microsoft.Azure.Amqp.csproj @@ -1,4 +1,4 @@ - + Microsoft.Azure.Amqp Class Library @@ -22,7 +22,9 @@ false Debug;Release;Signed false - 9 + 10 + true + true diff --git a/src/Serialization/AmqpContractSerializer.cs b/src/Serialization/AmqpContractSerializer.cs index acc3fbbd..6e4905d8 100644 --- a/src/Serialization/AmqpContractSerializer.cs +++ b/src/Serialization/AmqpContractSerializer.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Amqp.Serialization using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -18,6 +19,9 @@ namespace Microsoft.Azure.Amqp.Serialization /// public sealed class AmqpContractSerializer { + internal const string TrimWarning = "AmqpContractSerializer relies on reflection-based serialization. Required types may be removed when trimming."; + internal const string AotWarning = "AmqpContractSerializer relies on dynamically creating types that may not be available with Ahead of Time compilation."; + static readonly Dictionary builtInTypes = new Dictionary() { { typeof(bool), SerializableType.CreatePrimitiveType(typeof(bool)) }, @@ -91,6 +95,8 @@ public AmqpContractSerializer(Func compiler) /// /// The destination stream. /// The object to serialize. + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] public static void WriteObject(Stream stream, object graph) { if (graph == null) @@ -114,6 +120,8 @@ public static void WriteObject(Stream stream, object graph) /// The expected type. /// The source stream. /// An object of T. + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] public static T ReadObject(Stream stream) { return ReadObject(stream); @@ -128,6 +136,8 @@ public static T ReadObject(Stream stream) /// An object of TAs. /// The serializer uses T to resolve decoding /// types and returns the decoded object as TAs. + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] public static TAs ReadObject(Stream stream) { if (!stream.CanSeek) @@ -168,6 +178,8 @@ public static TAs ReadObject(Stream stream) /// /// /// + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] public void WriteObjectToBuffer(ByteBuffer buffer, object graph) { if (graph == null) @@ -187,6 +199,8 @@ public void WriteObjectToBuffer(ByteBuffer buffer, object graph) /// The expected type. /// The source buffer. /// An object of T. + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] public T ReadObjectFromBuffer(ByteBuffer buffer) { return this.ReadObjectFromBuffer(buffer); @@ -201,12 +215,16 @@ public T ReadObjectFromBuffer(ByteBuffer buffer) /// An object of TAs. /// The serializer uses T to resolve decoding /// types and returns the decoded object as TAs. + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] public TAs ReadObjectFromBuffer(ByteBuffer buffer) { SerializableType type = this.GetType(typeof(T)); return (TAs)type.ReadObject(buffer); } + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] internal SerializableType GetType(Type type) { return this.GetOrCompileType(type, false); @@ -227,6 +245,8 @@ bool TryGetSerializableType(Type type, out SerializableType serializableType) return false; } + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] SerializableType GetOrCompileType(Type type, bool describedOnly) { SerializableType serialiableType = null; @@ -247,6 +267,8 @@ SerializableType GetOrCompileType(Type type, bool describedOnly) return serialiableType; } + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] SerializableType CompileType(Type type, bool describedOnly) { if (this.externalCompilers != null) @@ -395,6 +417,8 @@ SerializableType CompileType(Type type, bool describedOnly) } } + [RequiresUnreferencedCode(TrimWarning)] + [RequiresDynamicCode(AotWarning)] SerializableType CompileNonContractTypes(Type type) { if (type.GetTypeInfo().IsGenericType && diff --git a/src/Serialization/MemberAccessor.cs b/src/Serialization/MemberAccessor.cs index 01aeead2..029a6cbe 100644 --- a/src/Serialization/MemberAccessor.cs +++ b/src/Serialization/MemberAccessor.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Amqp.Serialization { using System; + using System.Diagnostics.CodeAnalysis; using System.Reflection; abstract class MemberAccessor @@ -22,6 +23,8 @@ public Type Type get { return this.type; } } + [RequiresUnreferencedCode(AmqpContractSerializer.TrimWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] public static MemberAccessor Create(MemberInfo memberInfo, bool requiresSetter) { FieldInfo fieldInfo; @@ -48,6 +51,7 @@ public void Set(object container, object value) this.setter(container, value); } + [RequiresUnreferencedCode(AmqpContractSerializer.TrimWarning)] sealed class FieldMemberAccessor : MemberAccessor { public FieldMemberAccessor(FieldInfo fieldInfo) @@ -58,6 +62,8 @@ public FieldMemberAccessor(FieldInfo fieldInfo) } } + [RequiresUnreferencedCode(AmqpContractSerializer.TrimWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] sealed class PropertyMemberAccessor : MemberAccessor { public PropertyMemberAccessor(PropertyInfo propertyInfo, bool requiresSetter) diff --git a/src/Serialization/MethodAccessor.cs b/src/Serialization/MethodAccessor.cs index 4f543cfa..38e1fcdc 100644 --- a/src/Serialization/MethodAccessor.cs +++ b/src/Serialization/MethodAccessor.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Amqp.Serialization { using System; + using System.Diagnostics.CodeAnalysis; using System.Reflection; delegate object MethodDelegate(object container, object[] parameters); @@ -13,6 +14,7 @@ abstract class MethodAccessor bool isStatic; MethodDelegate methodDelegate; + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] public static MethodAccessor Create(MethodInfo methodInfo) { return new TypeMethodAccessor(methodInfo); @@ -54,6 +56,7 @@ public ConstructorAccessor(ConstructorInfo constructorInfo) sealed class TypeMethodAccessor : MethodAccessor { + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] public TypeMethodAccessor(MethodInfo methodInfo) { this.isStatic = methodInfo.IsStatic; diff --git a/src/Serialization/ReflectionExtentions.cs b/src/Serialization/ReflectionExtentions.cs index 03e23fb1..48b7254e 100644 --- a/src/Serialization/ReflectionExtentions.cs +++ b/src/Serialization/ReflectionExtentions.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.Amqp.Serialization { using System; + using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.Serialization; @@ -25,6 +26,7 @@ public static Action CreateSetter(this FieldInfo fieldInfo) return (obj, val) => fieldInfo.SetValue(obj, val); } + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] public static Func CreateGetter(this PropertyInfo propertyInfo) { MethodInfo getMethod = propertyInfo.GetGetMethod(true); @@ -33,6 +35,7 @@ public static Func CreateGetter(this PropertyInfo propertyInfo) return (Func)m2.Invoke(null, new object[] { getMethod }); } + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] public static Action CreateSetter(this PropertyInfo propertyInfo, bool requiresSetter) { if (requiresSetter && propertyInfo.DeclaringType.IsValueType) @@ -63,6 +66,7 @@ public static MethodDelegate CreateMethod(this ConstructorInfo constructorInfo) throw new NotImplementedException(); } + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] public static MethodDelegate CreateMethod(this MethodInfo methodInfo, bool isStatic) { var parameters = methodInfo.GetParameters(); diff --git a/src/Serialization/SerializableType.cs b/src/Serialization/SerializableType.cs index f15cb3a5..32dd0b79 100644 --- a/src/Serialization/SerializableType.cs +++ b/src/Serialization/SerializableType.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Amqp.Serialization using System; using System.Collections; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.Serialization; using Microsoft.Azure.Amqp.Encoding; @@ -299,6 +300,8 @@ public override object ReadObject(ByteBuffer buffer) } } + [RequiresUnreferencedCode(AmqpContractSerializer.TrimWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] internal sealed class List : Collection { readonly SerializableType itemType; @@ -376,6 +379,8 @@ public override void ReadMembers(ByteBuffer buffer, object container, ref int co } } + [RequiresUnreferencedCode(AmqpContractSerializer.TrimWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] internal sealed class Map : Collection { readonly SerializableType keyType; @@ -451,6 +456,8 @@ public override void ReadMembers(ByteBuffer buffer, object container, ref int co } } + [RequiresUnreferencedCode(AmqpContractSerializer.AotWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] internal abstract class Composite : Collection { readonly Composite baseType; @@ -625,6 +632,8 @@ bool AreEqual(ulong? code1, AmqpSymbol symbol1, ulong? code2, AmqpSymbol symbol2 } } + [RequiresUnreferencedCode(AmqpContractSerializer.AotWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] internal sealed class CompositeList : Composite { public CompositeList( @@ -682,6 +691,8 @@ public override void ReadMembers(ByteBuffer buffer, object container, ref int co } } + [RequiresUnreferencedCode(AmqpContractSerializer.AotWarning)] + [RequiresDynamicCode(AmqpContractSerializer.AotWarning)] internal sealed class CompositeMap : Composite { public CompositeMap( diff --git a/src/TrimmingAttributes.cs b/src/TrimmingAttributes.cs new file mode 100644 index 00000000..87e3b354 --- /dev/null +++ b/src/TrimmingAttributes.cs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable + +namespace System.Diagnostics.CodeAnalysis; + +#if !NET7_0_OR_GREATER +/// +/// Indicates that the specified method requires the ability to generate new code at runtime, +/// for example through . +/// +/// +/// This allows tools to understand which methods are unsafe to call when compiling ahead of time. +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +internal sealed class RequiresDynamicCodeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of dynamic code. + /// + public RequiresDynamicCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of dynamic code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires dynamic code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } +} +#endif + +#if !NET5_0_OR_GREATER +/// +/// 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)] +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; } +} + +/// +/// 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)] +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; } +} +#endif