From c019a797492791093bccfb8404c90bf83761b3a4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 18 Jan 2023 23:39:57 -0800 Subject: [PATCH] Fix .NET Native AOT warnings in Protobuf reflection (#11128) The fixes from https://github.com/protocolbuffers/protobuf/pull/10978. This PR can be merged without updating the SDK. Will allow people to use Protobuf + AOT sooner rather than later. When the repo is updated to .NET 7 or .NET 8, the original PR can be rebased on latest to add AOT analysis and provide some AOT smoke tests. cc @jskeet Closes #11128 COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/11128 from JamesNK:jamesnk/enable-aot-analysis-2 51ed1bd9109278c21a954181b5dbe4030fe5c4bc PiperOrigin-RevId: 503077675 --- .../Reflection/ReflectionUtil.cs | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index 2a6afdbfcea5..cfe062ade5f3 100644 --- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs +++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs @@ -30,10 +30,11 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion -using Google.Protobuf.Compatibility; using System; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.CompilerServices; +using Google.Protobuf.Compatibility; namespace Google.Protobuf.Reflection { @@ -117,6 +118,7 @@ internal static Func CreateFuncIMessageBool(MethodInfo method) = GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method); [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", Justification = "Type definition is explicitly specified and type argument is always a message type.")] internal static Func CreateIsInitializedCaller([DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)]Type msg) => ((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller(); @@ -125,8 +127,22 @@ internal static Func CreateIsInitializedCaller([DynamicallyAcces /// the type that declares the method, and the second argument to the first parameter type of the method. /// [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] - internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) => - (IExtensionReflectionHelper)Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(extension.TargetType, extension.GetType().GenericTypeArguments[1]), extension); + internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) + { +#if NET5_0_OR_GREATER + if (!RuntimeFeature.IsDynamicCodeSupported) + { + // Using extensions with reflection is not supported with AOT. + // This helper is created when descriptors are populated. Delay throwing error until an app + // uses IFieldAccessor with an extension field. + return new AotExtensionReflectionHelper(); + } +#endif + + var t1 = extension.TargetType; + var t3 = extension.GetType().GenericTypeArguments[1]; + return (IExtensionReflectionHelper) Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(t1, t3), extension); + } /// /// Creates a reflection helper for the given type arguments. Currently these are created on demand @@ -135,8 +151,17 @@ internal static IExtensionReflectionHelper CreateExtensionHelper(Extension exten /// an object is pretty cheap. /// [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] - private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) => - (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); + private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) + { +#if NET5_0_OR_GREATER + if (!RuntimeFeature.IsDynamicCodeSupported) + { + return new AotReflectionHelper(); + } +#endif + + return (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); + } // Non-generic interface allowing us to use an instance of ReflectionHelper without statically // knowing the types involved. @@ -162,9 +187,8 @@ private interface IExtensionSetReflector Func CreateIsInitializedCaller(); } - private class ReflectionHelper : IReflectionHelper + private sealed class ReflectionHelper : IReflectionHelper { - public Func CreateFuncIMessageInt32(MethodInfo method) { // On pleasant runtimes, we can create a Func from a method returning @@ -209,7 +233,7 @@ public Func CreateFuncIMessageBool(MethodInfo method) } } - private class ExtensionReflectionHelper : IExtensionReflectionHelper + private sealed class ExtensionReflectionHelper : IExtensionReflectionHelper where T1 : IExtendableMessage { private readonly Extension extension; @@ -304,7 +328,39 @@ public void ClearExtension(IMessage message) } } - private class ExtensionSetReflector< +#if NET5_0_OR_GREATER + /// + /// This helper is compatible with .NET Native AOT. + /// MakeGenericType doesn't work when a type argument is a value type in AOT. + /// MethodInfo.Invoke is used instead of compiled expressions because it's faster in AOT. + /// + private sealed class AotReflectionHelper : IReflectionHelper + { + private static readonly object[] EmptyObjectArray = new object[0]; + public Action CreateActionIMessage(MethodInfo method) => message => method.Invoke(message, EmptyObjectArray); + public Action CreateActionIMessageObject(MethodInfo method) => (message, arg) => method.Invoke(message, new object[] { arg }); + public Func CreateFuncIMessageBool(MethodInfo method) => message => (bool) method.Invoke(message, EmptyObjectArray); + public Func CreateFuncIMessageInt32(MethodInfo method) => message => (int) method.Invoke(message, EmptyObjectArray); + public Func CreateFuncIMessageObject(MethodInfo method) => message => method.Invoke(message, EmptyObjectArray); + } + + /// + /// Reflection with extensions isn't supported because IExtendableMessage members are used to get values. + /// Can't use reflection to invoke those methods because they have a generic argument. + /// MakeGenericMethod can't be used because it will break whenever the extension type is a value type. + /// This could be made to work if there were non-generic methods available for getting and setting extension values. + /// + private sealed class AotExtensionReflectionHelper : IExtensionReflectionHelper + { + private const string Message = "Extensions reflection is not supported with AOT."; + public object GetExtension(IMessage message) => throw new NotSupportedException(Message); + public bool HasExtension(IMessage message) => throw new NotSupportedException(Message); + public void SetExtension(IMessage message, object value) => throw new NotSupportedException(Message); + public void ClearExtension(IMessage message) => throw new NotSupportedException(Message); + } +#endif + + private sealed class ExtensionSetReflector< [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T1> : IExtensionSetReflector where T1 : IExtendableMessage {