Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix .NET Native AOT warnings in Protobuf reflection #11128

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 65 additions & 9 deletions csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -117,6 +118,7 @@ internal static Func<IMessage, bool> 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<IMessage, bool> CreateIsInitializedCaller([DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)]Type msg) =>
((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller();

Expand All @@ -125,8 +127,22 @@ internal static Func<IMessage, bool> CreateIsInitializedCaller([DynamicallyAcces
/// the type that declares the method, and the second argument to the first parameter type of the method.
/// </summary>
[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);
}

/// <summary>
/// Creates a reflection helper for the given type arguments. Currently these are created on demand
Expand All @@ -135,8 +151,17 @@ internal static IExtensionReflectionHelper CreateExtensionHelper(Extension exten
/// an object is pretty cheap.
/// </summary>
[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<T1, T2> without statically
// knowing the types involved.
Expand All @@ -162,9 +187,8 @@ private interface IExtensionSetReflector
Func<IMessage, bool> CreateIsInitializedCaller();
}

private class ReflectionHelper<T1, T2> : IReflectionHelper
private sealed class ReflectionHelper<T1, T2> : IReflectionHelper
{

public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method)
{
// On pleasant runtimes, we can create a Func<int> from a method returning
Expand Down Expand Up @@ -209,7 +233,7 @@ public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method)
}
}

private class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper
private sealed class ExtensionReflectionHelper<T1, T3> : IExtensionReflectionHelper
where T1 : IExtendableMessage<T1>
{
private readonly Extension extension;
Expand Down Expand Up @@ -304,7 +328,39 @@ public void ClearExtension(IMessage message)
}
}

private class ExtensionSetReflector<
#if NET5_0_OR_GREATER
/// <summary>
/// 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.
/// </summary>
private sealed class AotReflectionHelper : IReflectionHelper
{
private static readonly object[] EmptyObjectArray = new object[0];
public Action<IMessage> CreateActionIMessage(MethodInfo method) => message => method.Invoke(message, EmptyObjectArray);
public Action<IMessage, object> CreateActionIMessageObject(MethodInfo method) => (message, arg) => method.Invoke(message, new object[] { arg });
public Func<IMessage, bool> CreateFuncIMessageBool(MethodInfo method) => message => (bool) method.Invoke(message, EmptyObjectArray);
public Func<IMessage, int> CreateFuncIMessageInt32(MethodInfo method) => message => (int) method.Invoke(message, EmptyObjectArray);
public Func<IMessage, object> CreateFuncIMessageObject(MethodInfo method) => message => method.Invoke(message, EmptyObjectArray);
}

/// <summary>
/// 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.
/// </summary>
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<T1>
{
Expand Down