Skip to content

Commit

Permalink
Fix to allow AOT compilers to play nicely with reflection
Browse files Browse the repository at this point in the history
With this fix, Unity using IL2CPP should work with one of two
approaches:

- Call `FileDescriptor.ForceReflectionInitialization<T>` for every
  enum present in generated code (including oneof case enums)
- Ensure that IL2CPP uses the same code for int and any int-based
  enums

The former approach is likely to be simpler, unless IL2CPP changes
its default behavior. We *could* potentially generate the code
automatically, but that makes me slightly uncomfortable in terms of
generating code that's only relevant in one specific scenario. It
would be reasonably easy to write a tool (separate from protoc) to
generate the code required for any specific set of assemblies, so
that Unity users can include it in their application. We can always
decide to change to generate it automatically later.
  • Loading branch information
jskeet committed Apr 27, 2018
1 parent a21f225 commit 1b219a1
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 18 deletions.
24 changes: 24 additions & 0 deletions csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion

using Google.Protobuf.WellKnownTypes;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
Expand All @@ -43,6 +44,16 @@ namespace Google.Protobuf.Reflection
/// </summary>
public sealed class FileDescriptor : IDescriptor
{
// Prevent linker failures when using IL2CPP with the well-known types.
static FileDescriptor()
{
ForceReflectionInitialization<Syntax>();
ForceReflectionInitialization<NullValue>();
ForceReflectionInitialization<Field.Types.Cardinality>();
ForceReflectionInitialization<Field.Types.Kind>();
ForceReflectionInitialization<Value.KindOneofCase>();
}

private FileDescriptor(ByteString descriptorData, FileDescriptorProto proto, FileDescriptor[] dependencies, DescriptorPool pool, bool allowUnknownDependencies, GeneratedClrTypeInfo generatedCodeInfo)
{
SerializedData = descriptorData;
Expand Down Expand Up @@ -334,5 +345,18 @@ public override string ToString()
/// The (possibly empty) set of custom options for this file.
/// </summary>
public CustomOptions CustomOptions => Proto.Options?.CustomOptions ?? CustomOptions.Empty;

/// <summary>
/// Performs initialization for the given generic type argument.
/// </summary>
/// <remarks>
/// This method is present for the sake of AOT compilers. It allows code (whether handwritten or generated)
/// to make calls into the reflection machinery of this library to express an intention to use that type
/// reflectively (e.g. for JSON parsing and formatting). The call itself does almost nothing, but AOT compilers
/// attempting to determine which generic type arguments need to be handled will spot the code path and act
/// accordingly.
/// </remarks>
/// <typeparam name="T">The type to force initialization for.</typeparam>
public static void ForceReflectionInitialization<T>() => ReflectionUtil.ForceInitialize<T>();
}
}
41 changes: 23 additions & 18 deletions csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ namespace Google.Protobuf.Reflection
/// </summary>
internal static class ReflectionUtil
{
static ReflectionUtil()
{
ForceInitialize<string>(); // Handles all reference types
ForceInitialize<int>();
ForceInitialize<long>();
ForceInitialize<uint>();
ForceInitialize<ulong>();
ForceInitialize<float>();
ForceInitialize<double>();
ForceInitialize<bool>();
ForceInitialize<int?>();
ForceInitialize<long?>();
ForceInitialize<uint?>();
ForceInitialize<ulong?>();
ForceInitialize<float?>();
ForceInitialize<double?>();
ForceInitialize<bool?>();
ForceInitialize<SampleEnum>();
SampleEnumMethod();
}

internal static void ForceInitialize<T>() => new ReflectionHelper<IMessage, T>();

/// <summary>
/// Empty Type[] used when calling GetProperty to force property instead of indexer fetching.
/// </summary>
Expand Down Expand Up @@ -163,7 +186,6 @@ private static bool CheckCanConvertEnumFuncToInt32Func()
{
try
{
PreventLinkerFailures();
// Try to do the conversion using reflection, so we can see whether it's supported.
MethodInfo method = typeof(ReflectionUtil).GetMethod(nameof(SampleEnumMethod));
// If this passes, we're in a reasonable runtime.
Expand All @@ -176,23 +198,6 @@ private static bool CheckCanConvertEnumFuncToInt32Func()
}
}

/// <summary>
/// This method is effectively pointless, but only called once. It's present (and called)
/// to avoid the Unity linker from removing code that's only called via reflection.
/// </summary>
private static void PreventLinkerFailures()
{
// Exercise the method directly. This should avoid any pro-active linkers from stripping
// the method out.
SampleEnum x = SampleEnumMethod();
if (x != SampleEnum.X)
{
throw new InvalidOperationException("Failure in reflection utilities");
}
// Make sure the ReflectionHelper parameterless constructor isn't removed...
var helper = new ReflectionHelper<int, int>();
}

public enum SampleEnum
{
X
Expand Down

0 comments on commit 1b219a1

Please sign in to comment.